diff --git a/.gitignore b/.gitignore index 253ad71d..9bf2ab72 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk sdkconfig +*.save +libs/ + 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/Kconfig.projbuild b/main/Kconfig.projbuild index 4aa9e55a..1201eee1 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -162,4 +162,4 @@ menu "Squeezelite-ESP32" endmenu -endmenu +endmenu \ No newline at end of file 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 fb17f203..43e35c07 100644 --- a/main/decode.c +++ b/main/decode.c @@ -54,7 +54,7 @@ static bool running = true; #endif static void *decode_thread() { - + while (running) { size_t bytes, space, min_space; bool toend; @@ -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..a9642b71 --- /dev/null +++ b/main/helix-aac.c @@ -0,0 +1,658 @@ +/* + * 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); + + LOG_DEBUG("Sync search in %d bytes %d", bytes_wrap, n); + + 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; + } + + HAAC(a, FlushCodec, a->hAac); + + 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/output_bt.c b/main/output_bt.c index 88a72255..869caccb 100644 --- a/main/output_bt.c +++ b/main/output_bt.c @@ -16,8 +16,10 @@ extern struct buffer *streambuf; #define FRAME_BLOCK MAX_SILENCE_FRAMES extern u8_t *silencebuf; + extern u8_t *bt_optr; void hal_bluetooth_init(log_level); + static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr); #if BTAUDIO @@ -26,9 +28,6 @@ void set_volume(unsigned left, unsigned right) { LOCK; output.gainL = left; output.gainR = right; - // TODO - output.gainL = FIXED_ONE; - output.gainR = FIXED_ONE; UNLOCK; } #endif @@ -70,23 +69,23 @@ void output_close_bt(void) { static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) { - + if (!silence ) { DEBUG_LOG_TIMED(200,"Not silence, Writing audio out."); - /* TODO need 16 bit fix + /* TODO need 16 bit fix + if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); } - + if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { _apply_gain(outputbuf, out_frames, gainL, gainR); } - */ -#if BYTES_PER_FRAME == 4 +#if BYTES_PER_FRAME == 4 memcpy(bt_optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); #else - { + { frames_t count = out_frames; s32_t *_iptr = (s32_t*) outputbuf->readp; s16_t *_optr = (s16_t*) bt_optr; @@ -94,8 +93,8 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g *_optr++ = *_iptr++ >> 16; *_optr++ = *_iptr++ >> 16; } - } -#endif + } +#endif } else { DEBUG_LOG_TIMED(200,"Silence flag true. Writing silence to audio out."); @@ -104,9 +103,8 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g memcpy(bt_optr, buf, out_frames * 4); } - + bt_optr += out_frames * 4; return (int)out_frames; } - diff --git a/main/squeezelite.h b/main/squeezelite.h index a8e8c06c..0bc65b3b 100644 --- a/main/squeezelite.h +++ b/main/squeezelite.h @@ -798,6 +798,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); diff --git a/main/vorbis.c b/main/vorbis.c index 930cac46..1326c441 100644 --- a/main/vorbis.c +++ b/main/vorbis.c @@ -21,7 +21,15 @@ #include "squeezelite.h" -#define MAX_FRAMES 4096 +/* +* with some low-end CPU, the decode call takes a fair bit of time and if the outputbuf is locked during that +* period, the output_thread (or equivalent) will be locked although there is plenty of samples available. +* Normally, with PRIO_INHERIT, that thread should increase decoder priority and get the lock quickly but it +* seems that when the streambuf has plenty of data, the decode thread grabs the CPU to much, even it the output +* thread has a higher priority. Using an interim buffer where vorbis decoder writes the output is not great from +* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long +*/ +#define FRAME_BUF 2048 #if BYTES_PER_FRAME == 4 #define ALIGN(n) (n) @@ -45,6 +53,9 @@ struct vorbis { OggVorbis_File *vf; bool opened; +#if FRAME_BUF + u8_t *write_buf; +#endif #if !LINKALL // vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library vorbis_info *(* ov_info)(OggVorbis_File *vf, int link); @@ -172,21 +183,26 @@ static decode_state vorbis_decode(void) { } } +#if !FRAME_BUF LOCK_O_direct; +#endif IF_DIRECT( frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; +#if FRAME_BUF + write_buf = v->write_buf; +#else write_buf = outputbuf->writep; +#endif ); IF_PROCESS( frames = process.max_in_frames; write_buf = process.inbuf; ); - // should be fine to unlock here. This is needed b/c other tasks need to tip intot the output buf - UNLOCK_O_direct; - - frames = min(frames, MAX_FRAMES); +#if FRAME_BUF + frames = min(frames, FRAME_BUF); +#endif bytes = frames * 2 * channels; // samples returned are 16 bits // write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them @@ -206,6 +222,10 @@ static decode_state vorbis_decode(void) { } #endif +#if FRAME_BUF + LOCK_O_direct; +#endif + if (n > 0) { frames_t count; s16_t *iptr; @@ -218,7 +238,9 @@ static decode_state vorbis_decode(void) { optr = (ISAMPLE_T *)write_buf + frames * 2; if (channels == 2) { -#if BYTES_PER_FRAME == 8 +#if BYTES_PER_FRAME == 4 + memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME); +#else while (count--) { *--optr = *--iptr << 16; } @@ -230,20 +252,19 @@ static decode_state vorbis_decode(void) { } } - LOCK_O_direct; IF_DIRECT( _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames = frames; ); - UNLOCK_O_direct; LOG_SDEBUG("wrote %u frames", frames); } else if (n == 0) { LOG_INFO("end of stream"); + UNLOCK_O_direct; return DECODE_COMPLETE; } else if (n == OV_HOLE) { @@ -254,9 +275,11 @@ static decode_state vorbis_decode(void) { } else { LOG_INFO("ov_read error: %d", n); + UNLOCK_O_direct; return DECODE_COMPLETE; } + UNLOCK_O_direct; return DECODE_RUNNING; } @@ -264,6 +287,9 @@ static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { if (!v->vf) { v->vf = malloc(sizeof(OggVorbis_File) + 128); // add some padding as struct size may be larger memset(v->vf, 0, sizeof(OggVorbis_File) + 128); +#if FRAME_BUF + v->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME); +#endif } else { if (v->opened) { OV(v, clear, v->vf); @@ -278,6 +304,10 @@ static void vorbis_close(void) { v->opened = false; } free(v->vf); +#if FRAME_BUF + free(v->write_buf); + v->write_buf = NULL; +#endif v->vf = NULL; }