mirror of
				https://git.code.sf.net/p/minidlna/git
				synced 2025-10-29 12:55:45 +00:00 
			
		
		
		
	Add Album Artist AAC metadata parsing contributed by SF user knono549. Then, change music metadata gathering code to use either Album Artist or Band as upnp:artist.
		
			
				
	
	
		
			414 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
//=========================================================================
 | 
						|
// FILENAME	: tagutils-aac.c
 | 
						|
// DESCRIPTION	: AAC metadata reader
 | 
						|
//=========================================================================
 | 
						|
// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
 | 
						|
//=========================================================================
 | 
						|
 | 
						|
/*
 | 
						|
 * This program is free software; you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU General Public License as published by
 | 
						|
 * the Free Software Foundation; either version 2 of the License, or
 | 
						|
 * (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 * GNU General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License
 | 
						|
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * This file is derived from mt-daap project.
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
// _aac_findatom:
 | 
						|
static long
 | 
						|
_aac_findatom(FILE *fin, long max_offset, char *which_atom, int *atom_size)
 | 
						|
{
 | 
						|
	long current_offset = 0;
 | 
						|
	int size;
 | 
						|
	char atom[4];
 | 
						|
 | 
						|
	while(current_offset < max_offset)
 | 
						|
	{
 | 
						|
		if(fread((void*)&size, 1, sizeof(int), fin) != sizeof(int))
 | 
						|
			return -1;
 | 
						|
 | 
						|
		size = ntohl(size);
 | 
						|
 | 
						|
		if(size <= 7)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		if(fread(atom, 1, 4, fin) != 4)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		if(strncasecmp(atom, which_atom, 4) == 0)
 | 
						|
		{
 | 
						|
			*atom_size = size;
 | 
						|
			return current_offset;
 | 
						|
		}
 | 
						|
 | 
						|
		fseek(fin, size - 8, SEEK_CUR);
 | 
						|
		current_offset += size;
 | 
						|
	}
 | 
						|
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
// _get_aactags
 | 
						|
static int
 | 
						|
_get_aactags(char *file, struct song_metadata *psong)
 | 
						|
{
 | 
						|
	FILE *fin;
 | 
						|
	long atom_offset;
 | 
						|
	unsigned int atom_length;
 | 
						|
 | 
						|
	long current_offset = 0;
 | 
						|
	int current_size;
 | 
						|
	char current_atom[4];
 | 
						|
	char *current_data = NULL;
 | 
						|
	int genre;
 | 
						|
	int len;
 | 
						|
 | 
						|
	if(!(fin = fopen(file, "rb")))
 | 
						|
	{
 | 
						|
		DPRINTF(E_ERROR, L_SCANNER, "Cannot open file %s for reading\n", file);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	fseek(fin, 0, SEEK_SET);
 | 
						|
 | 
						|
	atom_offset = _aac_lookforatom(fin, "moov:udta:meta:ilst", &atom_length);
 | 
						|
	if(atom_offset != -1)
 | 
						|
	{
 | 
						|
		while(current_offset < atom_length)
 | 
						|
		{
 | 
						|
			if(fread((void*)¤t_size, 1, sizeof(int), fin) != sizeof(int))
 | 
						|
				break;
 | 
						|
 | 
						|
			current_size = ntohl(current_size);
 | 
						|
 | 
						|
			if(current_size <= 7 || current_size > 1<<24)  // something not right
 | 
						|
				break;
 | 
						|
 | 
						|
			if(fread(current_atom, 1, 4, fin) != 4)
 | 
						|
				break;
 | 
						|
 | 
						|
			len = current_size - 7; // too short
 | 
						|
			if(len < 22)
 | 
						|
				len = 22;
 | 
						|
 | 
						|
			current_data = (char*)malloc(len); // extra byte
 | 
						|
 | 
						|
			if(fread(current_data, 1, current_size - 8, fin) != current_size - 8)
 | 
						|
				break;
 | 
						|
 | 
						|
			current_data[current_size - 8] = '\0';
 | 
						|
			if(!memcmp(current_atom, "\xA9" "nam", 4))
 | 
						|
				psong->title = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "ART", 4) ||
 | 
						|
				!memcmp(current_atom, "\xA9" "art", 4))
 | 
						|
				psong->contributor[ROLE_ARTIST] = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "alb", 4))
 | 
						|
				psong->album = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "cmt", 4))
 | 
						|
				psong->comment = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "aART", 4) ||
 | 
						|
				!memcmp(current_atom, "aart", 4))
 | 
						|
				psong->contributor[ROLE_ALBUMARTIST] = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "dir", 4))
 | 
						|
				psong->contributor[ROLE_CONDUCTOR] = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "wrt", 4))
 | 
						|
				psong->contributor[ROLE_COMPOSER] = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "grp", 4))
 | 
						|
				psong->grouping = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "gen", 4))
 | 
						|
				psong->genre = strdup((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "\xA9" "day", 4))
 | 
						|
				psong->year = atoi((char*)¤t_data[16]);
 | 
						|
			else if(!memcmp(current_atom, "tmpo", 4))
 | 
						|
				psong->bpm = (current_data[16] << 8) | current_data[17];
 | 
						|
			else if(!memcmp(current_atom, "trkn", 4))
 | 
						|
			{
 | 
						|
				psong->track = (current_data[18] << 8) | current_data[19];
 | 
						|
				psong->total_tracks = (current_data[20] << 8) | current_data[21];
 | 
						|
			}
 | 
						|
			else if(!memcmp(current_atom, "disk", 4))
 | 
						|
			{
 | 
						|
				psong->disc = (current_data[18] << 8) | current_data[19];
 | 
						|
				psong->total_discs = (current_data[20] << 8) | current_data[21];
 | 
						|
			}
 | 
						|
			else if(!memcmp(current_atom, "gnre", 4))
 | 
						|
			{
 | 
						|
				genre = current_data[17] - 1;
 | 
						|
				if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
 | 
						|
					genre = WINAMP_GENRE_UNKNOWN;
 | 
						|
				psong->genre = strdup(winamp_genre[genre]);
 | 
						|
			}
 | 
						|
			else if(!memcmp(current_atom, "cpil", 4))
 | 
						|
			{
 | 
						|
				psong->compilation = current_data[16];
 | 
						|
			}
 | 
						|
			else if(!memcmp(current_atom, "covr", 4))
 | 
						|
			{
 | 
						|
				psong->image_size = current_size - 8 - 16;
 | 
						|
				if((psong->image = malloc(psong->image_size)))
 | 
						|
					memcpy(psong->image, current_data+16, psong->image_size);
 | 
						|
				else
 | 
						|
					DPRINTF(E_ERROR, L_SCANNER, "Out of memory [%s]\n", file);
 | 
						|
			}
 | 
						|
 | 
						|
			free(current_data);
 | 
						|
			current_data = NULL;
 | 
						|
			current_offset += current_size;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	fclose(fin);
 | 
						|
	free(current_data);
 | 
						|
 | 
						|
	if(atom_offset == -1)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
// aac_lookforatom
 | 
						|
static off_t
 | 
						|
_aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length)
 | 
						|
{
 | 
						|
	long atom_offset;
 | 
						|
	off_t file_size;
 | 
						|
	char *cur_p, *end_p;
 | 
						|
	char atom_name[5];
 | 
						|
 | 
						|
	fseek(aac_fp, 0, SEEK_END);
 | 
						|
	file_size = ftell(aac_fp);
 | 
						|
	rewind(aac_fp);
 | 
						|
 | 
						|
	end_p = atom_path;
 | 
						|
	while(*end_p != '\0')
 | 
						|
	{
 | 
						|
		end_p++;
 | 
						|
	}
 | 
						|
	atom_name[4] = '\0';
 | 
						|
	cur_p = atom_path;
 | 
						|
 | 
						|
	while(cur_p)
 | 
						|
	{
 | 
						|
		if((end_p - cur_p) < 4)
 | 
						|
		{
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		strncpy(atom_name, cur_p, 4);
 | 
						|
		atom_offset = _aac_findatom(aac_fp, file_size, atom_name, (int*)atom_length);
 | 
						|
		if(atom_offset == -1)
 | 
						|
		{
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
		cur_p = strchr(cur_p, ':');
 | 
						|
		if(cur_p != NULL)
 | 
						|
		{
 | 
						|
			cur_p++;
 | 
						|
 | 
						|
			if(!strcmp(atom_name, "meta"))
 | 
						|
			{
 | 
						|
				fseek(aac_fp, 4, SEEK_CUR);
 | 
						|
			}
 | 
						|
			else if(!strcmp(atom_name, "stsd"))
 | 
						|
			{
 | 
						|
				fseek(aac_fp, 8, SEEK_CUR);
 | 
						|
			}
 | 
						|
			else if(!strcmp(atom_name, "mp4a"))
 | 
						|
			{
 | 
						|
				fseek(aac_fp, 28, SEEK_CUR);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// return position of 'size:atom'
 | 
						|
	return ftell(aac_fp) - 8;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
_aac_check_extended_descriptor(FILE *infile)
 | 
						|
{
 | 
						|
	short int i;
 | 
						|
	unsigned char buf[3];
 | 
						|
 | 
						|
	if( fread((void *)&buf, 1, 3, infile) < 3 )
 | 
						|
		return -1;
 | 
						|
	for( i=0; i<3; i++ )
 | 
						|
	{
 | 
						|
		if( (buf[i] != 0x80) &&
 | 
						|
		    (buf[i] != 0x81) &&
 | 
						|
		    (buf[i] != 0xFE) )
 | 
						|
		{
 | 
						|
			fseek(infile, -3, SEEK_CUR);
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
// _get_aacfileinfo
 | 
						|
int
 | 
						|
_get_aacfileinfo(char *file, struct song_metadata *psong)
 | 
						|
{
 | 
						|
	FILE *infile;
 | 
						|
	long atom_offset;
 | 
						|
	int atom_length;
 | 
						|
	int sample_size;
 | 
						|
	int samples;
 | 
						|
	unsigned int bitrate;
 | 
						|
	off_t file_size;
 | 
						|
	int ms;
 | 
						|
	unsigned char buffer[2];
 | 
						|
	aac_object_type_t profile_id = 0;
 | 
						|
 | 
						|
	psong->vbr_scale = -1;
 | 
						|
	psong->channels = 2; // A "normal" default in case we can't find this information
 | 
						|
 | 
						|
	infile = fopen(file, "rb");
 | 
						|
	if(!infile)
 | 
						|
	{
 | 
						|
		DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	fseek(infile, 0, SEEK_END);
 | 
						|
	file_size = ftell(infile);
 | 
						|
	fseek(infile, 0, SEEK_SET);
 | 
						|
 | 
						|
	// move to 'mvhd' atom
 | 
						|
	atom_offset = _aac_lookforatom(infile, "moov:mvhd", (unsigned int*)&atom_length);
 | 
						|
	if(atom_offset != -1)
 | 
						|
	{
 | 
						|
		fseek(infile, 12, SEEK_CUR);
 | 
						|
		if(fread((void*)&sample_size, 1, sizeof(int), infile) != sizeof(int) ||
 | 
						|
		   fread((void*)&samples, 1, sizeof(int), infile) != sizeof(int))
 | 
						|
		{
 | 
						|
			fclose(infile);
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
 | 
						|
		sample_size = ntohl(sample_size);
 | 
						|
		samples = ntohl(samples);
 | 
						|
 | 
						|
		// avoid overflowing on large sample_sizes (90000)
 | 
						|
		ms = 1000;
 | 
						|
		while((ms > 9) && (!(sample_size % 10)))
 | 
						|
		{
 | 
						|
			sample_size /= 10;
 | 
						|
			ms /= 10;
 | 
						|
		}
 | 
						|
 | 
						|
		// unit = ms
 | 
						|
		psong->song_length = (int)((samples * ms) / sample_size);
 | 
						|
	}
 | 
						|
 | 
						|
	psong->bitrate = 0;
 | 
						|
 | 
						|
	// see if it is aac or alac
 | 
						|
	atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:alac", (unsigned int*)&atom_length);
 | 
						|
	if(atom_offset != -1) {
 | 
						|
		fseek(infile, atom_offset + 32, SEEK_SET);
 | 
						|
		if (fread(buffer, sizeof(unsigned char), 2, infile) == 2)
 | 
						|
			psong->samplerate = (buffer[0] << 8) | (buffer[1]);
 | 
						|
		goto bad_esds;
 | 
						|
	}
 | 
						|
 | 
						|
	// get samplerate from 'mp4a' (not from 'mdhd')
 | 
						|
	atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length);
 | 
						|
	if(atom_offset != -1)
 | 
						|
	{
 | 
						|
		fseek(infile, atom_offset + 32, SEEK_SET);
 | 
						|
		if(fread(buffer, sizeof(unsigned char), 2, infile) == 2)
 | 
						|
			psong->samplerate = (buffer[0] << 8) | (buffer[1]);
 | 
						|
 | 
						|
		fseek(infile, 2, SEEK_CUR);
 | 
						|
 | 
						|
		// get bitrate from 'esds'
 | 
						|
		atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length);
 | 
						|
 | 
						|
		if(atom_offset != -1)
 | 
						|
		{
 | 
						|
			// skip the version number
 | 
						|
			fseek(infile, atom_offset + 4, SEEK_CUR);
 | 
						|
			// should be 0x03, to signify the descriptor type (section)
 | 
						|
			if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x03) || (_aac_check_extended_descriptor(infile) != 0) )
 | 
						|
				goto bad_esds;
 | 
						|
			fseek(infile, 4, SEEK_CUR);
 | 
						|
			if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x04) || (_aac_check_extended_descriptor(infile) != 0) )
 | 
						|
				goto bad_esds;
 | 
						|
			fseek(infile, 10, SEEK_CUR); // 10 bytes into section 4 should be average bitrate.  max bitrate is 6 bytes in.
 | 
						|
			if(fread((void *)&bitrate, sizeof(unsigned int), 1, infile))
 | 
						|
				psong->bitrate = ntohl(bitrate);
 | 
						|
			if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x05) || (_aac_check_extended_descriptor(infile) != 0) )
 | 
						|
				goto bad_esds;
 | 
						|
			fseek(infile, 1, SEEK_CUR); // 1 bytes into section 5 should be the setup data
 | 
						|
			if(fread((void *)&buffer, 2, 1, infile))
 | 
						|
			{
 | 
						|
				profile_id = (buffer[0] >> 3); // first 5 bits of setup data is the Audo Profile ID
 | 
						|
				/* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */
 | 
						|
				samples = ((buffer[1] >> 3) & 0xF);
 | 
						|
				psong->channels = (samples == 7 ? 8 : samples);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
bad_esds:
 | 
						|
 | 
						|
	atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length);
 | 
						|
	psong->audio_size = atom_length - 8;
 | 
						|
	psong->audio_offset = atom_offset;
 | 
						|
 | 
						|
	if(!psong->bitrate)
 | 
						|
	{
 | 
						|
		/* Dont' scare people with this for now.  Could be Apple Lossless?
 | 
						|
		DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate. [%s]\n", basename(file)); */
 | 
						|
		if((atom_offset != -1) && (psong->song_length))
 | 
						|
		{
 | 
						|
			psong->bitrate = atom_length * 1000 / psong->song_length / 128;
 | 
						|
		}
 | 
						|
		/* If this is an obviously wrong bitrate, try something different */
 | 
						|
		if((psong->bitrate < 16000) && (psong->song_length > 1000))
 | 
						|
		{
 | 
						|
			psong->bitrate = (file_size * 8) / (psong->song_length / 1000);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	//DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id);
 | 
						|
	switch( profile_id )
 | 
						|
	{
 | 
						|
		case AAC_LC:
 | 
						|
		case AAC_LC_ER:
 | 
						|
			if( psong->samplerate < 8000 || psong->samplerate > 48000 )
 | 
						|
			{
 | 
						|
				DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
 | 
						|
				                              psong->samplerate);
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			/* AAC @ Level 1/2 */
 | 
						|
			if( psong->channels <= 2 && psong->bitrate <= 320000 )
 | 
						|
				xasprintf(&(psong->dlna_pn), "AAC_ISO_320");
 | 
						|
			else if( psong->channels <= 2 && psong->bitrate <= 576000 )
 | 
						|
				xasprintf(&(psong->dlna_pn), "AAC_ISO");
 | 
						|
			else if( psong->channels <= 6 && psong->bitrate <= 1440000 )
 | 
						|
				xasprintf(&(psong->dlna_pn), "AAC_MULT5_ISO");
 | 
						|
			else
 | 
						|
				DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n",
 | 
						|
				                             psong->channels, psong->bitrate);
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type %d [%s]\n", profile_id, basename(file));
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	fclose(infile);
 | 
						|
	return 0;
 | 
						|
}
 |