| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 | /*  *  Squeezelite - lightweight headless squeezebox emulator * *  (c) Adrian Smith 2012-2015, triode1@btinternet.com *      Ralph Irving 2015-2017, ralph_irving@hotmail.com *   * 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 3 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/>. * */#include "squeezelite.h"#include <neaacdec.h>#if BYTES_PER_FRAME == 4		#define ALIGN(n) 	(n)#else#define ALIGN(n) 	(n << 8)		#endif#define WRAPBUF_LEN 2048struct chunk_table {	u32_t sample, offset;};struct faad {	NeAACDecHandle hAac;	u8_t type;	// following used for mp4 only	u32_t consume;	u32_t pos;	u32_t sample;	u32_t nextchunk;	void *stsc;	u32_t skip;	u64_t samples;	u64_t sttssamples;	bool  empty;	struct chunk_table *chunkinfo;	// faad symbols to be dynamically loaded#if !LINKALL	NeAACDecConfigurationPtr (* NeAACDecGetCurrentConfiguration)(NeAACDecHandle);	unsigned char (* NeAACDecSetConfiguration)(NeAACDecHandle, NeAACDecConfigurationPtr);	NeAACDecHandle (* NeAACDecOpen)(void);	void (* NeAACDecClose)(NeAACDecHandle);	long (* NeAACDecInit)(NeAACDecHandle, unsigned char *, unsigned long, unsigned long *, unsigned char *);	char (* NeAACDecInit2)(NeAACDecHandle, unsigned char *pBuffer, unsigned long, unsigned long *, unsigned char *);	void *(* NeAACDecDecode)(NeAACDecHandle, NeAACDecFrameInfo *, unsigned char *, unsigned long);	char *(* NeAACDecGetErrorMessage)(unsigned char);#endif};static struct faad *a;extern log_level loglevel;extern struct buffer *streambuf;extern struct buffer *outputbuf;extern struct streamstate stream;extern struct outputstate output;extern struct decodestate decode;extern struct processstate process;#define LOCK_S   mutex_lock(streambuf->mutex)#define UNLOCK_S mutex_unlock(streambuf->mutex)#define LOCK_O   mutex_lock(outputbuf->mutex)#define UNLOCK_O mutex_unlock(outputbuf->mutex)#if PROCESS#define LOCK_O_direct   if (decode.direct) mutex_lock(outputbuf->mutex)#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)#define IF_DIRECT(x)    if (decode.direct) { x }#define IF_PROCESS(x)   if (!decode.direct) { x }#else#define LOCK_O_direct   mutex_lock(outputbuf->mutex)#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)#define IF_DIRECT(x)    { x }#define IF_PROCESS(x)#endif#if LINKALL#define NEAAC(h, fn, ...) (NeAACDec ## fn)(__VA_ARGS__)#else#define NEAAC(h, fn, ...) (h)->NeAACDec##fn(__VA_ARGS__)#endif// minimal code for mp4 file parsing to extract audio config and find media data// adapted from faad2/common/mp4ffu32_t mp4_desc_length(u8_t **buf) {	u8_t b;	u8_t num_bytes = 0;	u32_t length = 0;	do {		b = **buf;		*buf += 1;		num_bytes++;		length = (length << 7) | (b & 0x7f);	} while ((b & 0x80) && num_bytes < 4);	return length;}// read mp4 header to extract config datastatic int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_p) {	size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));	char type[5];	u32_t len;	while (bytes >= 8) {		// count trak to find the first playable one		static unsigned trak, play;		u32_t consume;		len = unpackN((u32_t *)streambuf->readp);		memcpy(type, streambuf->readp + 4, 4);		type[4] = '\0';		if (!strcmp(type, "moov")) {			trak = 0;			play = 0;		}		if (!strcmp(type, "trak")) {			trak++;		}		// extract audio config from within esds and pass to DecInit2		if (!strcmp(type, "esds") && bytes > len) {			unsigned config_len;			u8_t *ptr = streambuf->readp + 12;			if (*ptr++ == 0x03) {				mp4_desc_length(&ptr);				ptr += 4;			} else {				ptr += 3;			}			mp4_desc_length(&ptr);			ptr += 13;			if (*ptr++ != 0x05) {				LOG_WARN("error parsing esds");				return -1;			}			config_len = mp4_desc_length(&ptr);			if (NEAAC(a, Init2, a->hAac, ptr, config_len, samplerate_p, channels_p) == 0) {				LOG_DEBUG("playable aac track: %u", trak);				play = trak;			}		}		// extract the total number of samples from stts		if (!strcmp(type, "stts") && bytes > len) {			u32_t i;			u8_t *ptr = streambuf->readp + 12;			u32_t entries = unpackN((u32_t *)ptr);			ptr += 4;			for (i = 0; i < entries; ++i) {				u32_t count = unpackN((u32_t *)ptr);				u32_t size = unpackN((u32_t *)(ptr + 4));				a->sttssamples += count * size;				ptr += 8;			}			LOG_DEBUG("total number of samples contained in stts: " FMT_u64, a->sttssamples);		}		// stash sample to chunk info, assume it comes before stco		if (!strcmp(type, "stsc") && bytes > len && !a->chunkinfo) {			a->stsc = malloc(len - 12);			if (a->stsc == NULL) {				LOG_WARN("malloc fail");				return -1;			}			memcpy(a->stsc, streambuf->readp + 12, len - 12);		}		// build offsets table from stco and stored stsc		if (!strcmp(type, "stco") && bytes > len && play == trak) {			u32_t i;			// extract chunk offsets			u8_t *ptr = streambuf->readp + 12;			u32_t entries = unpackN((u32_t *)ptr);			ptr += 4;			a->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1));			if (a->chunkinfo == NULL) {				LOG_WARN("malloc fail");				return -1;			}			for (i = 0; i < entries; ++i) {				a->chunkinfo[i].offset = unpackN((u32_t *)ptr);				a->chunkinfo[i].sample = 0;				ptr += 4;			}			a->chunkinfo[i].sample = 0;			a->chunkinfo[i].offset = 0;			// fill in first sample id for each chunk from stored stsc			if (a->stsc) {				u32_t stsc_entries = unpackN((u32_t *)a->stsc);				u32_t sample = 0;				u32_t last = 0, last_samples = 0;				u8_t *ptr = (u8_t *)a->stsc + 4;				while (stsc_entries--) {					u32_t first = unpackN((u32_t *)ptr);					u32_t samples = unpackN((u32_t *)(ptr + 4));					if (last) {						for (i = last - 1; i < first - 1; ++i) {							a->chunkinfo[i].sample = sample;							sample += last_samples;						}					}					if (stsc_entries == 0) {						for (i = first - 1; i < entries; ++i) {							a->chunkinfo[i].sample = sample;							sample += samples;						}					}					last = first;					last_samples = samples;					ptr += 12;				}				free(a->stsc);				a->stsc = NULL;			}		}		// found media data, advance to start of first chunk and return		if (!strcmp(type, "mdat")) {			_buf_inc_readp(streambuf, 8);			a->pos += 8;			bytes  -= 8;			if (play) {				LOG_DEBUG("type: mdat len: %u pos: %u", len, a->pos);				if (a->chunkinfo && a->chunkinfo[0].offset > a->pos) {					u32_t skip = a->chunkinfo[0].offset - a->pos; 						LOG_DEBUG("skipping: %u", skip);					if (skip <= bytes) {						_buf_inc_readp(streambuf, skip);						a->pos += skip;					} else {						a->consume = skip;					}				}				a->sample = a->nextchunk = 1;				return 1;			} else {				LOG_DEBUG("type: mdat len: %u, no playable track found", len);				return -1;			}		}		// parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless		if (!strcmp(type, "----") && bytes > len) {			u8_t *ptr = streambuf->readp + 8;			u32_t remain = len - 8, size;			if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) {				ptr += size; remain -= size;			}			if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) {				ptr += size; remain -= size;			}			if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) {				// data is stored as hex strings: 0 start end samples				u32_t b, c; u64_t d;				if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) {					LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d);					if (a->sttssamples && a->sttssamples < b + c + d) {						LOG_DEBUG("reducing samples as stts count is less");						d = a->sttssamples - (b + c);					}					a->skip = b;					a->samples = d;				}			}		}		// default to consuming entire box		consume = len;		// read into these boxes so reduce consume		if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") ||			!strcmp(type, "udta") || !strcmp(type, "ilst")) {			consume = 8;		}		// special cases which mix mix data in the enclosing box which we want to read into		if (!strcmp(type, "stsd")) consume = 16;		if (!strcmp(type, "mp4a")) consume = 36;		if (!strcmp(type, "meta")) consume = 12;		// consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse		if (bytes >= consume) {			LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume);			_buf_inc_readp(streambuf, consume);			a->pos += consume;			bytes -= consume;		} else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") || 					 !strcmp(type, "stco") || !strcmp(type, "----")) ) {			LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes);			_buf_inc_readp(streambuf, bytes);			a->pos += bytes;			a->consume = consume - bytes;			break;		} else {			break;		}	}	return 0;}static decode_state faad_decode(void) {	size_t bytes_total;	size_t bytes_wrap;	static NeAACDecFrameInfo info;	ISAMPLE_T *iptr;	bool endstream;	frames_t frames;	LOCK_S;	bytes_total = _buf_used(streambuf);	bytes_wrap  = min(bytes_total, _buf_cont_read(streambuf));		if (stream.state <= DISCONNECT && !bytes_total) {		UNLOCK_S;		return DECODE_COMPLETE;	}	if (a->consume) {		u32_t consume = min(a->consume, bytes_wrap);		LOG_DEBUG("consume: %u of %u", consume, a->consume);		_buf_inc_readp(streambuf, consume);		a->pos += consume;		a->consume -= consume;		UNLOCK_S;		return DECODE_RUNNING;	}	if (decode.new_stream) {		int found = 0;		static unsigned char channels;		static unsigned long samplerate;		if (a->type == '2') {			// adts stream - seek for header			while (bytes_wrap >= 2 && (*(streambuf->readp) != 0xFF || (*(streambuf->readp + 1) & 0xF6) != 0xF0)) {				_buf_inc_readp(streambuf, 1);				bytes_total--;				bytes_wrap--;			}						if (bytes_wrap >= 2) {				long n = NEAAC(a, Init, a->hAac, streambuf->readp, bytes_wrap, &samplerate, &channels);				if (n < 0) {					found = -1;				} else {					_buf_inc_readp(streambuf, n);					found = 1;				}			}		} else {			// mp4 - read header			found = read_mp4_header(&samplerate, &channels);		}		if (found == 1) {			LOG_INFO("samplerate: %u channels: %u", samplerate, channels);			bytes_total = _buf_used(streambuf);			bytes_wrap  = min(bytes_total, _buf_cont_read(streambuf));			LOCK_O;			LOG_INFO("setting track_start");			output.next_sample_rate = decode_newstream(samplerate, output.supported_rates);			IF_DSD( output.next_fmt = PCM; )			output.track_start = outputbuf->writep;			if (output.fade_mode) _checkfade(true);			decode.new_stream = false;			UNLOCK_O;		} else if (found == -1) {			LOG_WARN("error reading stream header");			UNLOCK_S;			return DECODE_ERROR;		} else {			// not finished header parsing come back next time			UNLOCK_S;			return DECODE_RUNNING;		}	}	if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) {		// make a local copy of frames which may have wrapped round the end of streambuf		static u8_t buf[WRAPBUF_LEN];		memcpy(buf, streambuf->readp, bytes_wrap);		memcpy(buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap);		iptr = NEAAC(a, Decode, a->hAac, &info, buf, WRAPBUF_LEN);	} else {		iptr = NEAAC(a, Decode, a->hAac, &info, streambuf->readp, bytes_wrap);	}	if (info.error) {		LOG_WARN("error: %u %s", info.error, NEAAC(a, GetErrorMessage, info.error));	}	endstream = false;	// mp4 end of chunk - skip to next offset	if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) {		if (a->chunkinfo[a->nextchunk].offset > a->pos) {			u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos;			if (skip != info.bytesconsumed) {				LOG_DEBUG("skipping to next chunk pos: %u consumed: %u != skip: %u", a->pos, info.bytesconsumed, skip);			}			if (bytes_total >= skip) {				_buf_inc_readp(streambuf, skip);				a->pos += skip;			} else {				a->consume = skip;			}			a->nextchunk++;		} else {			LOG_ERROR("error: need to skip backwards!");			endstream = true;		}	// adts and mp4 when not at end of chunk 	} else if (info.bytesconsumed != 0) {		_buf_inc_readp(streambuf, info.bytesconsumed);		a->pos += info.bytesconsumed;	// error which doesn't advance streambuf - end	} else {		endstream = true;	}	UNLOCK_S;	if (endstream) {		LOG_WARN("unable to decode further");		return DECODE_ERROR;	}	if (!info.samples) {		a->empty = true;		return DECODE_RUNNING;	}	frames = info.samples / info.channels;	if (a->skip) {		u32_t skip;		if (a->empty) {			a->empty = false;			a->skip -= frames;			LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames);		}		skip = min(frames, a->skip);		LOG_DEBUG("gapless: skipping %u frames at start", skip);		frames -= skip;		a->skip -= skip;		iptr += skip * info.channels;	}	if (a->samples) {		if (a->samples < frames) {			LOG_DEBUG("gapless: trimming %u frames from end", frames - a->samples);			frames = (frames_t)a->samples;		}		a->samples -= frames;	}	LOG_SDEBUG("write %u frames", frames);	LOCK_O_direct;	while (frames > 0) {		frames_t f;		frames_t count;		ISAMPLE_T *optr;		IF_DIRECT(			f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME;			optr = (ISAMPLE_T *)outputbuf->writep;		);		IF_PROCESS(			f = process.max_in_frames;			optr = (ISAMPLE_T *)process.inbuf;		);		f = min(f, frames);		count = f;				if (info.channels == 2) {#if BYTES_PER_FRAME == 4						memcpy(optr, iptr, count * BYTES_PER_FRAME);			iptr += count * 2;#else 						while (count--) {				*optr++ = *iptr++ << 8;				*optr++ = *iptr++ << 8;			}#endif					} else if (info.channels == 1) {			while (count--) {				*optr++ = ALIGN(*iptr);				*optr++ = ALIGN(*iptr++);			}		} else {			LOG_WARN("unsupported number of channels");		}		frames -= f;		IF_DIRECT(			_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);		);		IF_PROCESS(			process.in_frames = f;			if (frames) LOG_ERROR("unhandled case");		);	}	UNLOCK_O_direct;	return DECODE_RUNNING;}static void faad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {	NeAACDecConfigurationPtr conf;	LOG_INFO("opening %s stream", size == '2' ? "adts" : "mp4");	a->type = size;	a->pos = a->consume = a->sample = a->nextchunk = 0;	if (a->chunkinfo) {		free(a->chunkinfo);	}	if (a->stsc) {		free(a->stsc);	}	a->chunkinfo = NULL;	a->stsc = NULL;	a->skip = 0;	a->samples = 0;	a->sttssamples = 0;	a->empty = false;	if (a->hAac) {		NEAAC(a, Close, a->hAac);	}	a->hAac = NEAAC(a, Open);	conf = NEAAC(a, GetCurrentConfiguration, a->hAac);#if BYTES_PER_FRAME == 4	conf->outputFormat = FAAD_FMT_16BIT;#else	conf->outputFormat = FAAD_FMT_24BIT;#endif    conf->defSampleRate = 44100;	conf->downMatrix = 1;	if (!NEAAC(a, SetConfiguration, a->hAac, conf)) {		LOG_WARN("error setting config");	};}static void faad_close(void) {	NEAAC(a, Close, a->hAac);	a->hAac = NULL;	if (a->chunkinfo) {		free(a->chunkinfo);		a->chunkinfo = NULL;	}	if (a->stsc) {		free(a->stsc);		a->stsc = NULL;	}}static bool load_faad() {#if !LINKALL	void *handle = dlopen(LIBFAAD, RTLD_NOW);	char *err;	if (!handle) {		LOG_INFO("dlerror: %s", dlerror());		return false;	}	a->NeAACDecGetCurrentConfiguration = dlsym(handle, "NeAACDecGetCurrentConfiguration");	a->NeAACDecSetConfiguration = dlsym(handle, "NeAACDecSetConfiguration");	a->NeAACDecOpen = dlsym(handle, "NeAACDecOpen");	a->NeAACDecClose = dlsym(handle, "NeAACDecClose");	a->NeAACDecInit = dlsym(handle, "NeAACDecInit");	a->NeAACDecInit2 = dlsym(handle, "NeAACDecInit2");	a->NeAACDecDecode = dlsym(handle, "NeAACDecDecode");	a->NeAACDecGetErrorMessage = dlsym(handle, "NeAACDecGetErrorMessage");	if ((err = dlerror()) != NULL) {		LOG_INFO("dlerror: %s", err);				return false;	}	LOG_INFO("loaded "LIBFAAD"");#endif	return true;}struct codec *register_faad(void) {	static struct codec ret = { 		'a',          // id		"aac",        // types		WRAPBUF_LEN,  // min read		20480,        // min space		faad_open,    // open		faad_close,   // close		faad_decode,  // decode	};	a = malloc(sizeof(struct faad));	if (!a) {		return NULL;	}	a->hAac = NULL;	a->chunkinfo = NULL;	a->stsc = NULL;	if (!load_faad()) {		return NULL;	}	LOG_INFO("using faad to decode aac");	return &ret;}
 |