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