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*)&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
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