1//========================================================================= 2// FILENAME : tagutils-aac.c 3// DESCRIPTION : AAC metadata reader 4//========================================================================= 5// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved. 6//========================================================================= 7 8/* 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program; if not, write to the Free Software 21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 */ 23 24/* 25 * This file is derived from mt-daap project. 26 */ 27 28// _mac_to_unix_time 29static time_t 30_mac_to_unix_time(int t) 31{ 32 struct timeval tv; 33 struct timezone tz; 34 35 gettimeofday(&tv, &tz); 36 37 return (t - (365L * 66L * 24L * 60L * 60L + 17L * 60L * 60L * 24L) + 38 (tz.tz_minuteswest * 60)); 39} 40 41 42 43// _aac_findatom: 44static long 45_aac_findatom(FILE *fin, long max_offset, char *which_atom, int *atom_size) 46{ 47 long current_offset = 0; 48 int size; 49 char atom[4]; 50 51 while(current_offset < max_offset) 52 { 53 if(fread((void*)&size, 1, sizeof(int), fin) != sizeof(int)) 54 return -1; 55 56 size = ntohl(size); 57 58 if(size <= 7) 59 return -1; 60 61 if(fread(atom, 1, 4, fin) != 4) 62 return -1; 63 64 if(strncasecmp(atom, which_atom, 4) == 0) 65 { 66 *atom_size = size; 67 return current_offset; 68 } 69 70 fseek(fin, size - 8, SEEK_CUR); 71 current_offset += size; 72 } 73 74 return -1; 75} 76 77// _get_aactags 78static int 79_get_aactags(char *file, struct song_metadata *psong) 80{ 81 FILE *fin; 82 long atom_offset; 83 unsigned int atom_length; 84 85 long current_offset = 0; 86 int current_size; 87 char current_atom[4]; 88 char *current_data; 89 int genre; 90 int len; 91 92 if(!(fin = fopen(file, "rb"))) 93 { 94 DPRINTF(E_ERROR, L_SCANNER, "Cannot open file %s for reading\n", file); 95 return -1; 96 } 97 98 fseek(fin, 0, SEEK_SET); 99 100 atom_offset = _aac_lookforatom(fin, "moov:udta:meta:ilst", &atom_length); 101 if(atom_offset != -1) 102 { 103 while(current_offset < atom_length) 104 { 105 if(fread((void*)¤t_size, 1, sizeof(int), fin) != sizeof(int)) 106 break; 107 108 current_size = ntohl(current_size); 109 110 if(current_size <= 7 || current_size > 1<<24) // something not right 111 break; 112 113 if(fread(current_atom, 1, 4, fin) != 4) 114 break; 115 116 len = current_size - 7; // too short 117 if(len < 22) 118 len = 22; 119 120 current_data = (char*)malloc(len); // extra byte 121 memset(current_data, 0x00, len); 122 123 if(fread(current_data, 1, current_size - 8, fin) != current_size - 8) 124 break; 125 126 if(!memcmp(current_atom, "\xA9" "nam", 4)) 127 psong->title = strdup((char*)¤t_data[16]); 128 else if(!memcmp(current_atom, "\xA9" "ART", 4) || 129 !memcmp(current_atom, "\xA9" "art", 4)) 130 psong->contributor[ROLE_ARTIST] = strdup((char*)¤t_data[16]); 131 else if(!memcmp(current_atom, "\xA9" "alb", 4)) 132 psong->album = strdup((char*)¤t_data[16]); 133 else if(!memcmp(current_atom, "\xA9" "cmt", 4)) 134 psong->comment = strdup((char*)¤t_data[16]); 135 else if(!memcmp(current_atom, "\xA9" "dir", 4)) 136 psong->contributor[ROLE_CONDUCTOR] = strdup((char*)¤t_data[16]); 137 else if(!memcmp(current_atom, "\xA9" "wrt", 4)) 138 psong->contributor[ROLE_COMPOSER] = strdup((char*)¤t_data[16]); 139 else if(!memcmp(current_atom, "\xA9" "grp", 4)) 140 psong->grouping = strdup((char*)¤t_data[16]); 141 else if(!memcmp(current_atom, "\xA9" "gen", 4)) 142 psong->genre = strdup((char*)¤t_data[16]); 143 else if(!memcmp(current_atom, "\xA9" "day", 4)) 144 psong->year = atoi((char*)¤t_data[16]); 145 else if(!memcmp(current_atom, "tmpo", 4)) 146 psong->bpm = (current_data[16] << 8) | current_data[17]; 147 else if(!memcmp(current_atom, "trkn", 4)) 148 { 149 psong->track = (current_data[18] << 8) | current_data[19]; 150 psong->total_tracks = (current_data[20] << 8) | current_data[21]; 151 } 152 else if(!memcmp(current_atom, "disk", 4)) 153 { 154 psong->disc = (current_data[18] << 8) | current_data[19]; 155 psong->total_discs = (current_data[20] << 8) | current_data[21]; 156 } 157 else if(!memcmp(current_atom, "gnre", 4)) 158 { 159 genre = current_data[17] - 1; 160 if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN)) 161 genre = WINAMP_GENRE_UNKNOWN; 162 psong->genre = strdup(winamp_genre[genre]); 163 } 164 else if(!memcmp(current_atom, "cpil", 4)) 165 { 166 psong->compilation = current_data[16]; 167 } 168 else if(!memcmp(current_atom, "covr", 4)) 169 { 170 psong->image_size = current_size - 8 - 16; 171 if((psong->image = malloc(psong->image_size))) 172 memcpy(psong->image, current_data+16, psong->image_size); 173 else 174 DPRINTF(E_ERROR, L_SCANNER, "Out of memory [%s]\n", file); 175 } 176 177 free(current_data); 178 current_offset += current_size; 179 } 180 } 181 182 fclose(fin); 183 184 if(atom_offset == -1) 185 return -1; 186 187 return 0; 188} 189 190// aac_lookforatom 191static off_t 192_aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length) 193{ 194 long atom_offset; 195 off_t file_size; 196 char *cur_p, *end_p; 197 char atom_name[5]; 198 199 fseek(aac_fp, 0, SEEK_END); 200 file_size = ftell(aac_fp); 201 rewind(aac_fp); 202 203 end_p = atom_path; 204 while(*end_p != '\0') 205 { 206 end_p++; 207 } 208 atom_name[4] = '\0'; 209 cur_p = atom_path; 210 211 while(cur_p) 212 { 213 if((end_p - cur_p) < 4) 214 { 215 return -1; 216 } 217 strncpy(atom_name, cur_p, 4); 218 atom_offset = _aac_findatom(aac_fp, file_size, atom_name, (int*)atom_length); 219 if(atom_offset == -1) 220 { 221 return -1; 222 } 223 cur_p = strchr(cur_p, ':'); 224 if(cur_p != NULL) 225 { 226 cur_p++; 227 228 if(!strcmp(atom_name, "meta")) 229 { 230 fseek(aac_fp, 4, SEEK_CUR); 231 } 232 else if(!strcmp(atom_name, "stsd")) 233 { 234 fseek(aac_fp, 8, SEEK_CUR); 235 } 236 else if(!strcmp(atom_name, "mp4a")) 237 { 238 fseek(aac_fp, 28, SEEK_CUR); 239 } 240 } 241 } 242 243 // return position of 'size:atom' 244 return ftell(aac_fp) - 8; 245} 246 247int 248_aac_check_extended_descriptor(FILE *infile) 249{ 250 short int i; 251 unsigned char buf[3]; 252 253 if( !fread((void *)&buf, 3, 1, infile) ) 254 return -1; 255 for( i=0; i<3; i++ ) 256 { 257 if( (buf[i] != 0x80) && 258 (buf[i] != 0x81) && 259 (buf[i] != 0xFE) ) 260 { 261 fseek(infile, -3, SEEK_CUR); 262 return 0; 263 } 264 } 265 266 return 0; 267} 268 269// _get_aacfileinfo 270int 271_get_aacfileinfo(char *file, struct song_metadata *psong) 272{ 273 FILE *infile; 274 long atom_offset; 275 int atom_length; 276 int sample_size; 277 int samples; 278 unsigned int bitrate; 279 off_t file_size; 280 int ms; 281 unsigned char buffer[2]; 282 int time = 0; 283 aac_object_type_t profile_id = 0; 284 285 psong->vbr_scale = -1; 286 psong->channels = 2; // A "normal" default in case we can't find this information 287 288 if(!(infile = fopen(file, "rb"))) 289 { 290 DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file); 291 return -1; 292 } 293 294 fseek(infile, 0, SEEK_END); 295 file_size = ftell(infile); 296 fseek(infile, 0, SEEK_SET); 297 298 // move to 'mvhd' atom 299 atom_offset = _aac_lookforatom(infile, "moov:mvhd", (unsigned int*)&atom_length); 300 if(atom_offset != -1) 301 { 302 fseek(infile, 8, SEEK_CUR); 303 fread((void *)&time, sizeof(int), 1, infile); 304 time = ntohl(time); 305 // slimserver prefer to use filesystem time 306 //psong->time_modified = _mac_to_unix_time(time); 307 fread((void*)&sample_size, 1, sizeof(int), infile); 308 fread((void*)&samples, 1, sizeof(int), infile); 309 310 sample_size = ntohl(sample_size); 311 samples = ntohl(samples); 312 313 // avoid overflowing on large sample_sizes (90000) 314 ms = 1000; 315 while((ms > 9) && (!(sample_size % 10))) 316 { 317 sample_size /= 10; 318 ms /= 10; 319 } 320 321 // unit = ms 322 psong->song_length = (int)((samples * ms) / sample_size); 323 } 324 325 psong->bitrate = 0; 326 327 // see if it is aac or alac 328 atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:alac", (unsigned int*)&atom_length); 329 if(atom_offset != -1) { 330 fseek(infile, atom_offset + 32, SEEK_SET); 331 fread(buffer, sizeof(unsigned char), 2, infile); 332 333 psong->samplerate = (buffer[0] << 8) | (buffer[1]); 334 goto bad_esds; 335 } 336 337 // get samplerate from 'mp4a' (not from 'mdhd') 338 atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length); 339 if(atom_offset != -1) 340 { 341 fseek(infile, atom_offset + 32, SEEK_SET); 342 fread(buffer, sizeof(unsigned char), 2, infile); 343 344 psong->samplerate = (buffer[0] << 8) | (buffer[1]); 345 346 fseek(infile, 2, SEEK_CUR); 347 348 // get bitrate from 'esds' 349 atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length); 350 351 if(atom_offset != -1) 352 { 353 // skip the version number 354 fseek(infile, atom_offset + 4, SEEK_CUR); 355 // should be 0x03, to signify the descriptor type (section) 356 fread((void *)&buffer, 1, 1, infile); 357 if( (buffer[0] != 0x03) || (_aac_check_extended_descriptor(infile) != 0) ) 358 goto bad_esds; 359 fseek(infile, 4, SEEK_CUR); 360 fread((void *)&buffer, 1, 1, infile); 361 if( (buffer[0] != 0x04) || (_aac_check_extended_descriptor(infile) != 0) ) 362 goto bad_esds; 363 fseek(infile, 10, SEEK_CUR); // 10 bytes into section 4 should be average bitrate. max bitrate is 6 bytes in. 364 fread((void *)&bitrate, sizeof(unsigned int), 1, infile); 365 psong->bitrate = ntohl(bitrate); 366 fread((void *)&buffer, 1, 1, infile); 367 if( (buffer[0] != 0x05) || (_aac_check_extended_descriptor(infile) != 0) ) 368 goto bad_esds; 369 fseek(infile, 1, SEEK_CUR); // 1 bytes into section 5 should be the setup data 370 fread((void *)&buffer, 2, 1, infile); 371 profile_id = (buffer[0] >> 3); // first 5 bits of setup data is the Audo Profile ID 372 /* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */ 373 samples = ((buffer[1] >> 3) & 0xF); 374 psong->channels = (samples == 7 ? 8 : samples); 375 } 376 } 377bad_esds: 378 379 atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length); 380 psong->audio_size = atom_length - 8; 381 psong->audio_offset = atom_offset; 382 383 if(!psong->bitrate) 384 { 385 /* Dont' scare people with this for now. Could be Apple Lossless? 386 DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate. [%s]\n", basename(file)); */ 387 if((atom_offset != -1) && (psong->song_length)) 388 { 389 psong->bitrate = atom_length * 1000 / psong->song_length / 128; 390 } 391 /* If this is an obviously wrong bitrate, try something different */ 392 if((psong->bitrate < 16000) && (psong->song_length > 1000)) 393 { 394 psong->bitrate = (file_size * 8) / (psong->song_length / 1000); 395 } 396 } 397 398 //DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id); 399 switch( profile_id ) 400 { 401 case AAC_LC: 402 case AAC_LC_ER: 403 if( psong->samplerate < 8000 || psong->samplerate > 48000 ) 404 { 405 DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n", 406 psong->samplerate); 407 break; 408 } 409 /* AAC @ Level 1/2 */ 410 if( psong->channels <= 2 && psong->bitrate <= 320000 ) 411 asprintf(&(psong->dlna_pn), "AAC_ISO_320"); 412 else if( psong->channels <= 2 && psong->bitrate <= 576000 ) 413 asprintf(&(psong->dlna_pn), "AAC_ISO"); 414 else if( psong->channels <= 6 && psong->bitrate <= 1440000 ) 415 asprintf(&(psong->dlna_pn), "AAC_MULT5_ISO"); 416 else 417 DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n", 418 psong->channels, psong->bitrate); 419 break; 420 default: 421 DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type %d [%s]\n", profile_id, basename(file)); 422 break; 423 } 424 425 fclose(infile); 426 return 0; 427} 428