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*)&current_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*)&current_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*)&current_data[16]);
131			else if(!memcmp(current_atom, "\xA9" "alb", 4))
132				psong->album = strdup((char*)&current_data[16]);
133			else if(!memcmp(current_atom, "\xA9" "cmt", 4))
134				psong->comment = strdup((char*)&current_data[16]);
135			else if(!memcmp(current_atom, "\xA9" "dir", 4))
136				psong->contributor[ROLE_CONDUCTOR] = strdup((char*)&current_data[16]);
137			else if(!memcmp(current_atom, "\xA9" "wrt", 4))
138				psong->contributor[ROLE_COMPOSER] = strdup((char*)&current_data[16]);
139			else if(!memcmp(current_atom, "\xA9" "grp", 4))
140				psong->grouping = strdup((char*)&current_data[16]);
141			else if(!memcmp(current_atom, "\xA9" "gen", 4))
142				psong->genre = strdup((char*)&current_data[16]);
143			else if(!memcmp(current_atom, "\xA9" "day", 4))
144				psong->year = atoi((char*)&current_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