1 //=========================================================================
2 // FILENAME : tagutils-aac.c
3 // DESCRIPTION : AAC metadata reader
4 //=========================================================================
5 // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
6 //=========================================================================
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.
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.
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/>.
24 * This file is derived from mt-daap project.
29 _mac_to_unix_time(int t
)
34 gettimeofday(&tv
, &tz
);
36 return (t
- (365L * 66L * 24L * 60L * 60L + 17L * 60L * 60L * 24L) +
37 (tz
.tz_minuteswest
* 60));
44 _aac_findatom(FILE *fin
, long max_offset
, char *which_atom
, int *atom_size
)
46 long current_offset
= 0;
50 while(current_offset
< max_offset
)
52 if(fread((void*)&size
, 1, sizeof(int), fin
) != sizeof(int))
60 if(fread(atom
, 1, 4, fin
) != 4)
63 if(strncasecmp(atom
, which_atom
, 4) == 0)
66 return current_offset
;
69 fseek(fin
, size
- 8, SEEK_CUR
);
70 current_offset
+= size
;
78 _get_aactags(char *file
, struct song_metadata
*psong
)
82 unsigned int atom_length
;
84 long current_offset
= 0;
91 if(!(fin
= fopen(file
, "rb")))
93 DPRINTF(E_ERROR
, L_SCANNER
, "Cannot open file %s for reading\n", file
);
97 fseek(fin
, 0, SEEK_SET
);
99 atom_offset
= _aac_lookforatom(fin
, "moov:udta:meta:ilst", &atom_length
);
100 if(atom_offset
!= -1)
102 while(current_offset
< atom_length
)
104 if(fread((void*)¤t_size
, 1, sizeof(int), fin
) != sizeof(int))
107 current_size
= ntohl(current_size
);
109 if(current_size
<= 7 || current_size
> 1<<24) // something not right
112 if(fread(current_atom
, 1, 4, fin
) != 4)
115 len
= current_size
- 7; // too short
119 current_data
= (char*)malloc(len
); // extra byte
120 memset(current_data
, 0x00, len
);
122 if(fread(current_data
, 1, current_size
- 8, fin
) != current_size
- 8)
125 if(!memcmp(current_atom
, "\xA9" "nam", 4))
126 psong
->title
= strdup((char*)¤t_data
[16]);
127 else if(!memcmp(current_atom
, "\xA9" "ART", 4) ||
128 !memcmp(current_atom
, "\xA9" "art", 4))
129 psong
->contributor
[ROLE_ARTIST
] = strdup((char*)¤t_data
[16]);
130 else if(!memcmp(current_atom
, "\xA9" "alb", 4))
131 psong
->album
= strdup((char*)¤t_data
[16]);
132 else if(!memcmp(current_atom
, "\xA9" "cmt", 4))
133 psong
->comment
= strdup((char*)¤t_data
[16]);
134 else if(!memcmp(current_atom
, "\xA9" "dir", 4))
135 psong
->contributor
[ROLE_CONDUCTOR
] = strdup((char*)¤t_data
[16]);
136 else if(!memcmp(current_atom
, "\xA9" "wrt", 4))
137 psong
->contributor
[ROLE_COMPOSER
] = strdup((char*)¤t_data
[16]);
138 else if(!memcmp(current_atom
, "\xA9" "grp", 4))
139 psong
->grouping
= strdup((char*)¤t_data
[16]);
140 else if(!memcmp(current_atom
, "\xA9" "gen", 4))
141 psong
->genre
= strdup((char*)¤t_data
[16]);
142 else if(!memcmp(current_atom
, "\xA9" "day", 4))
143 psong
->year
= atoi((char*)¤t_data
[16]);
144 else if(!memcmp(current_atom
, "tmpo", 4))
145 psong
->bpm
= (current_data
[16] << 8) | current_data
[17];
146 else if(!memcmp(current_atom
, "trkn", 4))
148 psong
->track
= (current_data
[18] << 8) | current_data
[19];
149 psong
->total_tracks
= (current_data
[20] << 8) | current_data
[21];
151 else if(!memcmp(current_atom
, "disk", 4))
153 psong
->disc
= (current_data
[18] << 8) | current_data
[19];
154 psong
->total_discs
= (current_data
[20] << 8) | current_data
[21];
156 else if(!memcmp(current_atom
, "gnre", 4))
158 genre
= current_data
[17] - 1;
159 if((genre
< 0) || (genre
> WINAMP_GENRE_UNKNOWN
))
160 genre
= WINAMP_GENRE_UNKNOWN
;
161 psong
->genre
= strdup(winamp_genre
[genre
]);
163 else if(!memcmp(current_atom
, "cpil", 4))
165 psong
->compilation
= current_data
[16];
167 else if(!memcmp(current_atom
, "covr", 4))
169 psong
->image_size
= current_size
- 8 - 16;
170 if((psong
->image
= malloc(psong
->image_size
)))
171 memcpy(psong
->image
, current_data
+16, psong
->image_size
);
173 DPRINTF(E_ERROR
, L_SCANNER
, "Out of memory [%s]\n", file
);
177 current_offset
+= current_size
;
183 if(atom_offset
== -1)
191 _aac_lookforatom(FILE *aac_fp
, char *atom_path
, unsigned int *atom_length
)
198 fseek(aac_fp
, 0, SEEK_END
);
199 file_size
= ftell(aac_fp
);
203 while(*end_p
!= '\0')
212 if((end_p
- cur_p
) < 4)
216 strncpy(atom_name
, cur_p
, 4);
217 atom_offset
= _aac_findatom(aac_fp
, file_size
, atom_name
, (int*)atom_length
);
218 if(atom_offset
== -1)
222 cur_p
= strchr(cur_p
, ':');
227 if(!strcmp(atom_name
, "meta"))
229 fseek(aac_fp
, 4, SEEK_CUR
);
231 else if(!strcmp(atom_name
, "stsd"))
233 fseek(aac_fp
, 8, SEEK_CUR
);
235 else if(!strcmp(atom_name
, "mp4a"))
237 fseek(aac_fp
, 28, SEEK_CUR
);
242 // return position of 'size:atom'
243 return ftell(aac_fp
) - 8;
247 _aac_check_extended_descriptor(FILE *infile
)
250 unsigned char buf
[3];
252 if( !fread((void *)&buf
, 3, 1, infile
) )
256 if( (buf
[i
] != 0x80) &&
260 fseek(infile
, -3, SEEK_CUR
);
270 _get_aacfileinfo(char *file
, struct song_metadata
*psong
)
277 unsigned int bitrate
;
280 unsigned char buffer
[2];
282 aac_object_type_t profile_id
= 0;
284 psong
->vbr_scale
= -1;
285 psong
->channels
= 2; // A "normal" default in case we can't find this information
287 if(!(infile
= fopen(file
, "rb")))
289 DPRINTF(E_ERROR
, L_SCANNER
, "Could not open %s for reading\n", file
);
293 fseek(infile
, 0, SEEK_END
);
294 file_size
= ftell(infile
);
295 fseek(infile
, 0, SEEK_SET
);
297 // move to 'mvhd' atom
298 atom_offset
= _aac_lookforatom(infile
, "moov:mvhd", (unsigned int*)&atom_length
);
299 if(atom_offset
!= -1)
301 fseek(infile
, 8, SEEK_CUR
);
302 fread((void *)&time
, sizeof(int), 1, infile
);
304 // slimserver prefer to use filesystem time
305 //psong->time_modified = _mac_to_unix_time(time);
306 fread((void*)&sample_size
, 1, sizeof(int), infile
);
307 fread((void*)&samples
, 1, sizeof(int), infile
);
309 sample_size
= ntohl(sample_size
);
310 samples
= ntohl(samples
);
312 // avoid overflowing on large sample_sizes (90000)
314 while((ms
> 9) && (!(sample_size
% 10)))
321 psong
->song_length
= (int)((samples
* ms
) / sample_size
);
326 // see if it is aac or alac
327 atom_offset
= _aac_lookforatom(infile
, "moov:trak:mdia:minf:stbl:stsd:alac", (unsigned int*)&atom_length
);
328 if(atom_offset
!= -1) {
329 fseek(infile
, atom_offset
+ 32, SEEK_SET
);
330 fread(buffer
, sizeof(unsigned char), 2, infile
);
332 psong
->samplerate
= (buffer
[0] << 8) | (buffer
[1]);
336 // get samplerate from 'mp4a' (not from 'mdhd')
337 atom_offset
= _aac_lookforatom(infile
, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length
);
338 if(atom_offset
!= -1)
340 fseek(infile
, atom_offset
+ 32, SEEK_SET
);
341 fread(buffer
, sizeof(unsigned char), 2, infile
);
343 psong
->samplerate
= (buffer
[0] << 8) | (buffer
[1]);
345 fseek(infile
, 2, SEEK_CUR
);
347 // get bitrate from 'esds'
348 atom_offset
= _aac_findatom(infile
, atom_length
- (ftell(infile
) - atom_offset
), "esds", &atom_length
);
350 if(atom_offset
!= -1)
352 // skip the version number
353 fseek(infile
, atom_offset
+ 4, SEEK_CUR
);
354 // should be 0x03, to signify the descriptor type (section)
355 fread((void *)&buffer
, 1, 1, infile
);
356 if( (buffer
[0] != 0x03) || (_aac_check_extended_descriptor(infile
) != 0) )
358 fseek(infile
, 4, SEEK_CUR
);
359 fread((void *)&buffer
, 1, 1, infile
);
360 if( (buffer
[0] != 0x04) || (_aac_check_extended_descriptor(infile
) != 0) )
362 fseek(infile
, 10, SEEK_CUR
); // 10 bytes into section 4 should be average bitrate. max bitrate is 6 bytes in.
363 fread((void *)&bitrate
, sizeof(unsigned int), 1, infile
);
364 psong
->bitrate
= ntohl(bitrate
);
365 fread((void *)&buffer
, 1, 1, infile
);
366 if( (buffer
[0] != 0x05) || (_aac_check_extended_descriptor(infile
) != 0) )
368 fseek(infile
, 1, SEEK_CUR
); // 1 bytes into section 5 should be the setup data
369 fread((void *)&buffer
, 2, 1, infile
);
370 profile_id
= (buffer
[0] >> 3); // first 5 bits of setup data is the Audo Profile ID
371 /* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */
372 samples
= ((buffer
[1] >> 3) & 0xF);
373 psong
->channels
= (samples
== 7 ? 8 : samples
);
378 atom_offset
= _aac_lookforatom(infile
, "mdat", (unsigned int*)&atom_length
);
379 psong
->audio_size
= atom_length
- 8;
380 psong
->audio_offset
= atom_offset
;
384 /* Dont' scare people with this for now. Could be Apple Lossless?
385 DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate. [%s]\n", basename(file)); */
386 if((atom_offset
!= -1) && (psong
->song_length
))
388 psong
->bitrate
= atom_length
* 1000 / psong
->song_length
/ 128;
390 /* If this is an obviously wrong bitrate, try something different */
391 if((psong
->bitrate
< 16000) && (psong
->song_length
> 1000))
393 psong
->bitrate
= (file_size
* 8) / (psong
->song_length
/ 1000);
397 //DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id);
402 if( psong
->samplerate
< 8000 || psong
->samplerate
> 48000 )
404 DPRINTF(E_DEBUG
, L_METADATA
, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
408 /* AAC @ Level 1/2 */
409 if( psong
->channels
<= 2 && psong
->bitrate
<= 320000 )
410 asprintf(&(psong
->dlna_pn
), "AAC_ISO_320");
411 else if( psong
->channels
<= 2 && psong
->bitrate
<= 576000 )
412 asprintf(&(psong
->dlna_pn
), "AAC_ISO");
413 else if( psong
->channels
<= 6 && psong
->bitrate
<= 1440000 )
414 asprintf(&(psong
->dlna_pn
), "AAC_MULT5_ISO");
416 DPRINTF(E_DEBUG
, L_METADATA
, "Unhandled AAC: %d channels, %d bitrate\n",
417 psong
->channels
, psong
->bitrate
);
420 DPRINTF(E_DEBUG
, L_METADATA
, "Unhandled AAC type %d [%s]\n", profile_id
, basename(file
));