diff --git a/components/codecs/component.mk b/components/codecs/component.mk index b08ea173..3c921edc 100644 --- a/components/codecs/component.mk +++ b/components/codecs/component.mk @@ -5,11 +5,12 @@ COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) \ $(COMPONENT_PATH)/lib/libmad.a \ $(COMPONENT_PATH)/lib/libesp-flac.a \ - $(COMPONENT_PATH)/lib/libfaad.a \ + $(COMPONENT_PATH)/lib/libhelix-aac.a \ $(COMPONENT_PATH)/lib/libvorbisidec.a \ $(COMPONENT_PATH)/lib/libogg.a \ $(COMPONENT_PATH)/lib/libalac.a + #$(COMPONENT_PATH)/lib/libfaad.a #$(COMPONENT_PATH)/lib/libvorbisidec.a #$(COMPONENT_PATH)/lib/libogg.a #$(COMPONENT_PATH)/lib/libesp-tremor.a diff --git a/components/codecs/inc/helix-aac/aacdec.h b/components/codecs/inc/helix-aac/aacdec.h new file mode 100644 index 00000000..a7823b82 --- /dev/null +++ b/components/codecs/inc/helix-aac/aacdec.h @@ -0,0 +1,173 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Source last modified: $Id: aacdec.h,v 1.8 2005/11/10 00:15:08 margotm Exp $ + * + * Portions Copyright (c) 1995-2005 RealNetworks, Inc. All Rights Reserved. + * + * The contents of this file, and the files included with this file, + * are subject to the current version of the RealNetworks Public + * Source License (the "RPSL") available at + * http://www.helixcommunity.org/content/rpsl unless you have licensed + * the file under the current version of the RealNetworks Community + * Source License (the "RCSL") available at + * http://www.helixcommunity.org/content/rcsl, in which case the RCSL + * will apply. You may also obtain the license terms directly from + * RealNetworks. You may not use this file except in compliance with + * the RPSL or, if you have a valid RCSL with RealNetworks applicable + * to this file, the RCSL. Please see the applicable RPSL or RCSL for + * the rights, obligations and limitations governing use of the + * contents of the file. + * + * This file is part of the Helix DNA Technology. RealNetworks is the + * developer of the Original Code and owns the copyrights in the + * portions it created. + * + * This file, and the files included with this file, is distributed + * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY + * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS + * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET + * ENJOYMENT OR NON-INFRINGEMENT. + * + * Technology Compatibility Kit Test Suite(s) Location: + * http://www.helixcommunity.org/content/tck + * + * Contributor(s): + * + * ***** END LICENSE BLOCK ***** */ + +/************************************************************************************** + * Fixed-point HE-AAC decoder + * Jon Recker (jrecker@real.com) + * February 2005 + * + * aacdec.h - public C API for AAC decoder + **************************************************************************************/ + +#ifndef _AACDEC_H +#define _AACDEC_H + +#if defined(_WIN32) && !defined(_WIN32_WCE) +# +#elif defined(_WIN32) && defined(_WIN32_WCE) && defined(ARM) +# +#elif defined(_WIN32) && defined(WINCE_EMULATOR) +# +#elif defined (__arm) && defined (__ARMCC_VERSION) +# +#elif defined(_SYMBIAN) && defined(__WINS__) +# +#elif defined(__GNUC__) && defined(__arm__) +# +#elif defined(__GNUC__) && defined(__i386__) +# +#elif defined(__GNUC__) && defined(__amd64__) +# +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__POWERPC__)) +# +#elif defined(_OPENWAVE_SIMULATOR) || defined(_OPENWAVE_ARMULATOR) +# +#elif defined(_SOLARIS) && !defined(__GNUC__) +# +#elif defined(__XTENSA__) +# +#else +#error No platform defined. See valid options in aacdec.h +#endif + +#ifndef USE_DEFAULT_STDLIB +#define USE_DEFAULT_STDLIB +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* according to spec (13818-7 section 8.2.2, 14496-3 section 4.5.3) + * max size of input buffer = + * 6144 bits = 768 bytes per SCE or CCE-I + * 12288 bits = 1536 bytes per CPE + * 0 bits = 0 bytes per CCE-D (uses bits from the SCE/CPE/CCE-I it is coupled to) + */ +#ifndef AAC_MAX_NCHANS /* if max channels isn't set in makefile, */ +#define AAC_MAX_NCHANS 2 /* set to default max number of channels */ +#endif +#define AAC_MAX_NSAMPS 1024 +#define AAC_MAINBUF_SIZE (768 * AAC_MAX_NCHANS) + +#define AAC_NUM_PROFILES 3 +#define AAC_PROFILE_MP 0 +#define AAC_PROFILE_LC 1 +#define AAC_PROFILE_SSR 2 + +/* define these to enable decoder features */ +#if defined(HELIX_FEATURE_AUDIO_CODEC_AAC_SBR) +#define AAC_ENABLE_SBR +#endif // HELIX_FEATURE_AUDIO_CODEC_AAC_SBR. +#define AAC_ENABLE_MPEG4 + +enum { + ERR_AAC_NONE = 0, + ERR_AAC_INDATA_UNDERFLOW = -1, + ERR_AAC_NULL_POINTER = -2, + ERR_AAC_INVALID_ADTS_HEADER = -3, + ERR_AAC_INVALID_ADIF_HEADER = -4, + ERR_AAC_INVALID_FRAME = -5, + ERR_AAC_MPEG4_UNSUPPORTED = -6, + ERR_AAC_CHANNEL_MAP = -7, + ERR_AAC_SYNTAX_ELEMENT = -8, + + ERR_AAC_DEQUANT = -9, + ERR_AAC_STEREO_PROCESS = -10, + ERR_AAC_PNS = -11, + ERR_AAC_SHORT_BLOCK_DEINT = -12, + ERR_AAC_TNS = -13, + ERR_AAC_IMDCT = -14, + ERR_AAC_NCHANS_TOO_HIGH = -15, + + ERR_AAC_SBR_INIT = -16, + ERR_AAC_SBR_BITSTREAM = -17, + ERR_AAC_SBR_DATA = -18, + ERR_AAC_SBR_PCM_FORMAT = -19, + ERR_AAC_SBR_NCHANS_TOO_HIGH = -20, + ERR_AAC_SBR_SINGLERATE_UNSUPPORTED = -21, + + ERR_AAC_RAWBLOCK_PARAMS = -22, + + ERR_AAC_UNKNOWN = -9999 +}; + +typedef struct _AACFrameInfo { + int bitRate; + int nChans; + int sampRateCore; + int sampRateOut; + int bitsPerSample; + int outputSamps; + int profile; + int tnsUsed; + int pnsUsed; +} AACFrameInfo; + +typedef void *HAACDecoder; + +/* public C API */ +HAACDecoder AACInitDecoder(void); +HAACDecoder AACInitDecoderPre(void *ptr, int sz); +void AACFreeDecoder(HAACDecoder hAACDecoder); +int AACDecode(HAACDecoder hAACDecoder, unsigned char **inbuf, int *bytesLeft, short *outbuf); + +int AACFindSyncWord(unsigned char *buf, int nBytes); +void AACGetLastFrameInfo(HAACDecoder hAACDecoder, AACFrameInfo *aacFrameInfo); +int AACSetRawBlockParams(HAACDecoder hAACDecoder, int copyLast, AACFrameInfo *aacFrameInfo); +int AACFlushCodec(HAACDecoder hAACDecoder); + +#ifdef HELIX_CONFIG_AAC_GENERATE_TRIGTABS_FLOAT +int AACInitTrigtabsFloat(void); +void AACFreeTrigtabsFloat(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _AACDEC_H */ diff --git a/components/codecs/lib/libhelix-aac.a b/components/codecs/lib/libhelix-aac.a new file mode 100644 index 00000000..24a78584 Binary files /dev/null and b/components/codecs/lib/libhelix-aac.a differ diff --git a/main/component.mk b/main/component.mk index fc27adef..7829568e 100644 --- a/main/component.mk +++ b/main/component.mk @@ -2,13 +2,15 @@ # "main" pseudo-component makefile. # # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) -CFLAGS += -O3 -DPOSIX -DLINKALL -DLOOPBACK -DDACAUDIO -DTREMOR_ONLY -DBYTES_PER_FRAME=4 \ - -I$(COMPONENT_PATH)/../components/codecs/inc \ - -I$(COMPONENT_PATH)/../components/codecs/inc/mad \ - -I$(COMPONENT_PATH)/../components/codecs/inc/faad2 \ - -I$(COMPONENT_PATH)/../components/codecs/inc/alac \ +CFLAGS += -O3 -DPOSIX -DLINKALL -DLOOPBACK -DDACAUDIO -DNO_FAAD -DTREMOR_ONLY -DBYTES_PER_FRAME=4 \ + -I$(COMPONENT_PATH)/../components/codecs/inc \ + -I$(COMPONENT_PATH)/../components/codecs/inc/mad \ + -I$(COMPONENT_PATH)/../components/codecs/inc/alac \ + -I$(COMPONENT_PATH)/../components/codecs/inc/helix-aac \ -I$(COMPONENT_PATH)/../components/codecs/inc/vorbis LDFLAGS += -s +# -I$(COMPONENT_PATH)/../components/codecs/inc/faad2 + diff --git a/main/decode.c b/main/decode.c index 0eb1c2d2..43e35c07 100644 --- a/main/decode.c +++ b/main/decode.c @@ -175,6 +175,9 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_faad()); #endif + if (!strstr(exclude_codecs, "aac") && (!include_codecs || (order_codecs = strstr(include_codecs, "aac")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_helixaac()); + if (!strstr(exclude_codecs, "ogg") && (!include_codecs || (order_codecs = strstr(include_codecs, "ogg")))) sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_vorbis()); diff --git a/main/faad.c b/main/faad.c.nocompile similarity index 99% rename from main/faad.c rename to main/faad.c.nocompile index 095c566d..839f53fb 100644 --- a/main/faad.c +++ b/main/faad.c.nocompile @@ -328,7 +328,7 @@ static decode_state faad_decode(void) { 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; diff --git a/main/helix-aac.c b/main/helix-aac.c new file mode 100644 index 00000000..1f71da6f --- /dev/null +++ b/main/helix-aac.c @@ -0,0 +1,654 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com + * Philippe, philippe_44@outlook.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 . + * + */ + +#include "squeezelite.h" + +#include + +// AAC_MAX_SAMPLES is the number of samples for one channel +#define FRAME_BUF (AAC_MAX_NSAMPS*2) + +#if BYTES_PER_FRAME == 4 +#define ALIGN(n) (n) +#else +#define ALIGN(n) (n << 8) +#endif + +#define WRAPBUF_LEN 2048 + +static unsigned rates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; + +struct chunk_table { + u32_t sample, offset; +}; + +struct helixaac { + HAACDecoder hAac; + u8_t type; + u8_t *write_buf; + // 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; +#if !LINKALL +#endif +}; + +static struct helixaac *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 HAAC(h, fn, ...) (AAC ## fn)(__VA_ARGS__) +#else +#define HAAC(h, fn, ...) (h)->AAC##fn(__VA_ARGS__) +#endif + +// minimal code for mp4 file parsing to extract audio config and find media data + +// adapted from faad2/common/mp4ff +u32_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 data +static 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) { + u8_t *ptr = streambuf->readp + 12; + AACFrameInfo info; + 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; + } + mp4_desc_length(&ptr); + info.profile = *ptr >> 3; + info.sampRateCore = (*ptr++ & 0x07) << 1; + info.sampRateCore |= (*ptr >> 7) & 0x01; + info.sampRateCore = rates[info.sampRateCore]; + info.nChans = *ptr >> 3; + *channels_p = info.nChans; + *samplerate_p = info.sampRateCore; + HAAC(a, SetRawBlockParams, a->hAac, 0, &info); + LOG_DEBUG("playable aac track: %u (p:%x, r:%d, c:%d)", trak, info.profile, info.sampRateCore, info.nChans); + 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 helixaac_decode(void) { + size_t bytes_total, bytes_wrap; + int res, bytes; + static AACFrameInfo info; + ISAMPLE_T *iptr; + u8_t *sptr; + 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 + long n = AACFindSyncWord(streambuf->readp, bytes_wrap); + + if (n > 0) { + u8_t *p = streambuf->readp + n; + int bytes = bytes_wrap - n; + + if (!HAAC(a, Decode, a->hAac, &p, &bytes, (short*) a->write_buf)) { + HAAC(a, GetLastFrameInfo, a->hAac, &info); + channels = info.nChans; + samplerate = info.sampRateOut; + found = 1; + } + + bytes_total -= n; + bytes_wrap -= n; + _buf_inc_readp(streambuf, n); + } else { + 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); + + sptr = buf; + bytes = bytes_wrap = WRAPBUF_LEN; + } else { + + sptr = streambuf->readp; + bytes = bytes_wrap; + } + + // decode function changes iptr, so can't use streambuf->readp (same for bytes) + res = HAAC(a, Decode, a->hAac, &sptr, &bytes, (short*) a->write_buf); + if (res < 0) { + LOG_WARN("AAC decode error %d", res); + } + + HAAC(a, GetLastFrameInfo, a->hAac, &info); + iptr = (ISAMPLE_T *) a->write_buf; + bytes = bytes_wrap - bytes; + 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 != bytes) { + LOG_DEBUG("skipping to next chunk pos: %u consumed: %u != skip: %u", a->pos, bytes, 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 (bytes > 0) { + + _buf_inc_readp(streambuf, bytes); + a->pos += bytes; + + // 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.outputSamps) { + a->empty = true; + return DECODE_RUNNING; + } + + frames = info.outputSamps / info.nChans; + + 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.nChans; + } + + 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.nChans == 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.nChans == 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 helixaac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { + 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) { + HAAC(a, FlushCodec, a->hAac); + } else { + a->hAac = HAAC(a, InitDecoder); + a->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME); + } +} + +static void helixaac_close(void) { + HAAC(a, FreeDecoder, a->hAac); + a->hAac = NULL; + if (a->chunkinfo) { + free(a->chunkinfo); + a->chunkinfo = NULL; + } + if (a->stsc) { + free(a->stsc); + a->stsc = NULL; + } + free(a->write_buf); +} + +static bool load_helixaac() { +#if !LINKALL + void *handle = dlopen(LIBHELIX-AAC, RTLD_NOW); + char *err; + + if (!handle) { + LOG_INFO("dlerror: %s", dlerror()); + return false; + } + + // load symbols here + + if ((err = dlerror()) != NULL) { + LOG_INFO("dlerror: %s", err); + return false; + } + + LOG_INFO("loaded "LIBHELIX-AAC""); +#endif + + return true; +} + +struct codec *register_helixaac(void) { + static struct codec ret = { + 'a', // id + "aac", // types + WRAPBUF_LEN, // min read + 20480, // min space + helixaac_open, // open + helixaac_close, // close + helixaac_decode, // decode + }; + + a = malloc(sizeof(struct helixaac)); + if (!a) { + return NULL; + } + + a->hAac = NULL; + a->chunkinfo = NULL; + a->stsc = NULL; + + if (!load_helixaac()) { + return NULL; + } + + LOG_INFO("using helix-aac to decode aac"); + return &ret; +} diff --git a/main/squeezelite.h b/main/squeezelite.h index 98c048b6..69c61c0e 100644 --- a/main/squeezelite.h +++ b/main/squeezelite.h @@ -767,6 +767,7 @@ struct codec *register_mad(void); struct codec *register_mpg(void); struct codec *register_vorbis(void); struct codec *register_faad(void); +struct codec *register_helixaac(void); struct codec *register_dsd(void); struct codec *register_alac(void); struct codec *register_ff(const char *codec);