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:
29 static long
_aac_findatom(FILE * fin,long max_offset,char * which_atom,int * atom_size)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
63 static int
_get_aactags(char * file,struct song_metadata * psong)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*)&current_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*)&current_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*)&current_data[16]);
116 			else if(!memcmp(current_atom, "\xA9" "alb", 4))
117 				psong->album = strdup((char*)&current_data[16]);
118 			else if(!memcmp(current_atom, "\xA9" "cmt", 4))
119 				psong->comment = strdup((char*)&current_data[16]);
120 			else if(!memcmp(current_atom, "aART", 4) ||
121 				!memcmp(current_atom, "aart", 4))
122 				psong->contributor[ROLE_ALBUMARTIST] = strdup((char*)&current_data[16]);
123 			else if(!memcmp(current_atom, "\xA9" "dir", 4))
124 				psong->contributor[ROLE_CONDUCTOR] = strdup((char*)&current_data[16]);
125 			else if(!memcmp(current_atom, "\xA9" "wrt", 4))
126 				psong->contributor[ROLE_COMPOSER] = strdup((char*)&current_data[16]);
127 			else if(!memcmp(current_atom, "\xA9" "grp", 4))
128 				psong->grouping = strdup((char*)&current_data[16]);
129 			else if(!memcmp(current_atom, "\xA9" "gen", 4))
130 				psong->genre = strdup((char*)&current_data[16]);
131 			else if(!memcmp(current_atom, "\xA9" "day", 4))
132 				psong->year = atoi((char*)&current_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
180 static off_t
_aac_lookforatom(FILE * aac_fp,char * atom_path,unsigned int * atom_length)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 
236 int
_aac_check_extended_descriptor(FILE * infile)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
259 int
_get_aacfileinfo(char * file,struct song_metadata * psong)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 	}
363 bad_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