diff --git a/components/codecs/component.mk b/components/codecs/component.mk index b5966e79..b08ea173 100644 --- a/components/codecs/component.mk +++ b/components/codecs/component.mk @@ -7,7 +7,8 @@ COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) \ $(COMPONENT_PATH)/lib/libesp-flac.a \ $(COMPONENT_PATH)/lib/libfaad.a \ $(COMPONENT_PATH)/lib/libvorbisidec.a \ - $(COMPONENT_PATH)/lib/libogg.a + $(COMPONENT_PATH)/lib/libogg.a \ + $(COMPONENT_PATH)/lib/libalac.a #$(COMPONENT_PATH)/lib/libvorbisidec.a #$(COMPONENT_PATH)/lib/libogg.a diff --git a/components/codecs/inc/alac/alac_wrapper.h b/components/codecs/inc/alac/alac_wrapper.h new file mode 100644 index 00000000..5f014839 --- /dev/null +++ b/components/codecs/inc/alac/alac_wrapper.h @@ -0,0 +1,40 @@ +/***************************************************************************** + * alac_wrapper.h: ALAC coder wrapper + * + * Copyright (C) 2016 Philippe + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + *****************************************************************************/ +#ifndef __ALAC_WRAPPER_H_ +#define __ALAC_WRAPPER_H_ + +struct alac_codec_s; + +#ifdef __cplusplus +extern "C" { +#endif + +struct alac_codec_s *alac_create_decoder(int magic_cookie_size, unsigned char *magic_cookie, + unsigned char *sample_size, unsigned *sample_rate, + unsigned char *channels); +void alac_delete_decoder(struct alac_codec_s *codec); +bool alac_to_pcm(struct alac_codec_s *codec, unsigned char* input, + unsigned char *output, char channels, unsigned *out_frames); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/components/codecs/inc/vorbis/vorbisfile.h b/components/codecs/inc/vorbis/ivorbisfile.h similarity index 100% rename from components/codecs/inc/vorbis/vorbisfile.h rename to components/codecs/inc/vorbis/ivorbisfile.h diff --git a/components/codecs/lib/libalac.a b/components/codecs/lib/libalac.a new file mode 100644 index 00000000..48d4a859 Binary files /dev/null and b/components/codecs/lib/libalac.a differ diff --git a/main/alac.c b/main/alac.c new file mode 100644 index 00000000..36e98c48 --- /dev/null +++ b/main/alac.c @@ -0,0 +1,538 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * (c) 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 + +#define BLOCK_SIZE (4096 * BYTES_PER_FRAME) +#define MIN_READ BLOCK_SIZE +#define MIN_SPACE (MIN_READ * 4) + +struct chunk_table { + u32_t sample, offset; +}; + +struct alac { + void *decoder; + u8_t *writebuf; + // 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; + u32_t *block_size, default_block_size, block_index; + unsigned sample_rate; + unsigned char channels, sample_size; + unsigned trak, play; +}; + +static struct alac *l; + +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 LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) +#define UNLOCK_O_not_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 LOCK_O_not_direct +#define UNLOCK_O_not_direct +#define IF_DIRECT(x) { x } +#define IF_PROCESS(x) +#endif + +// read mp4 header to extract config data +static int read_mp4_header(void) { + 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 + u32_t consume; + + len = unpackN((u32_t *)streambuf->readp); + memcpy(type, streambuf->readp + 4, 4); + type[4] = '\0'; + + if (!strcmp(type, "moov")) { + l->trak = 0; + l->play = 0; + } + if (!strcmp(type, "trak")) { + l->trak++; + } + + // extract audio config from within alac + if (!strcmp(type, "alac") && bytes > len) { + u8_t *ptr = streambuf->readp + 36; + l->decoder = alac_create_decoder(len - 36, ptr, &l->sample_size, &l->sample_rate, &l->channels); + l->play = l->trak; + } + + // extract the total number of samples from stts + if (!strcmp(type, "stsz") && bytes > len) { + u32_t i; + u8_t *ptr = streambuf->readp + 12; + l->default_block_size = unpackN((u32_t *) ptr); ptr += 4; + if (!l->default_block_size) { + u32_t entries = unpackN((u32_t *)ptr); ptr += 4; + l->block_size = malloc((entries + 1)* 4); + for (i = 0; i < entries; i++) { + l->block_size[i] = unpackN((u32_t *)ptr); ptr += 4; + } + l->block_size[entries] = 0; + LOG_DEBUG("total blocksize contained in stsz %u", entries); + } else { + LOG_DEBUG("fixed blocksize in stsz %u", l->default_block_size); + } + } + + // 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)); + l->sttssamples += count * size; + ptr += 8; + } + LOG_DEBUG("total number of samples contained in stts: " FMT_u64, l->sttssamples); + } + + // stash sample to chunk info, assume it comes before stco + if (!strcmp(type, "stsc") && bytes > len && !l->chunkinfo) { + l->stsc = malloc(len - 12); + if (l->stsc == NULL) { + LOG_WARN("malloc fail"); + return -1; + } + memcpy(l->stsc, streambuf->readp + 12, len - 12); + } + + // build offsets table from stco and stored stsc + if (!strcmp(type, "stco") && bytes > len && l->play == l->trak) { + u32_t i; + // extract chunk offsets + u8_t *ptr = streambuf->readp + 12; + u32_t entries = unpackN((u32_t *)ptr); + ptr += 4; + l->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1)); + if (l->chunkinfo == NULL) { + LOG_WARN("malloc fail"); + return -1; + } + for (i = 0; i < entries; ++i) { + l->chunkinfo[i].offset = unpackN((u32_t *)ptr); + l->chunkinfo[i].sample = 0; + ptr += 4; + } + l->chunkinfo[i].sample = 0; + l->chunkinfo[i].offset = 0; + // fill in first sample id for each chunk from stored stsc + if (l->stsc) { + u32_t stsc_entries = unpackN((u32_t *)l->stsc); + u32_t sample = 0; + u32_t last = 0, last_samples = 0; + u8_t *ptr = (u8_t *)l->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) { + l->chunkinfo[i].sample = sample; + sample += last_samples; + } + } + if (stsc_entries == 0) { + for (i = first - 1; i < entries; ++i) { + l->chunkinfo[i].sample = sample; + sample += samples; + } + } + last = first; + last_samples = samples; + ptr += 12; + } + free(l->stsc); + l->stsc = NULL; + } + } + + // found media data, advance to start of first chunk and return + if (!strcmp(type, "mdat")) { + _buf_inc_readp(streambuf, 8); + l->pos += 8; + bytes -= 8; + if (l->play) { + LOG_DEBUG("type: mdat len: %u pos: %u", len, l->pos); + if (l->chunkinfo && l->chunkinfo[0].offset > l->pos) { + u32_t skip = l->chunkinfo[0].offset - l->pos; + LOG_DEBUG("skipping: %u", skip); + if (skip <= bytes) { + _buf_inc_readp(streambuf, skip); + l->pos += skip; + } else { + l->consume = skip; + } + } + l->sample = l->nextchunk = 1; + l->block_index = 0; + 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 (l->sttssamples && l->sttssamples < b + c + d) { + LOG_DEBUG("reducing samples as stts count is less"); + d = l->sttssamples - (b + c); + } + l->skip = b; + l->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); + l->pos += consume; + bytes -= consume; + } else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") || + !strcmp(type, "stsz") || !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); + l->pos += bytes; + l->consume = consume - bytes; + break; + } else { + break; + } + } + + return 0; +} + +static decode_state alac_decode(void) { + size_t bytes; + bool endstream; + u8_t *iptr; + u32_t frames, block_size; + + LOCK_S; + + // data not reached yet + if (l->consume) { + u32_t consume = min(l->consume, _buf_used(streambuf)); + LOG_DEBUG("consume: %u of %u", consume, l->consume); + _buf_inc_readp(streambuf, consume); + l->pos += consume; + l->consume -= consume; + UNLOCK_S; + return DECODE_RUNNING; + } + + if (decode.new_stream) { + int found = 0; + + // mp4 - read header + found = read_mp4_header(); + + if (found == 1) { + bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); + + LOG_INFO("setting track_start"); + LOCK_O_not_direct; + + output.next_sample_rate = decode_newstream(l->sample_rate, output.supported_rates); + output.track_start = outputbuf->writep; + if (output.fade_mode) _checkfade(true); + decode.new_stream = false; + + UNLOCK_O_not_direct; + } else if (found == -1) { + LOG_WARN("[%p]: error reading stream header"); + UNLOCK_S; + return DECODE_ERROR; + } else { + // not finished header parsing come back next time + UNLOCK_S; + return DECODE_RUNNING; + } + } + + bytes = _buf_used(streambuf); + block_size = l->default_block_size ? l->default_block_size : l->block_size[l->block_index]; + + // stream terminated + if (stream.state <= DISCONNECT && (bytes == 0 || block_size == 0)) { + UNLOCK_S; + LOG_DEBUG("end of stream"); + return DECODE_COMPLETE; + } + + // enough data for coding + if (bytes < block_size) { + UNLOCK_S; + return DECODE_RUNNING; + } else if (block_size != l->default_block_size) l->block_index++; + + bytes = min(bytes, _buf_cont_read(streambuf)); + + // need to create a buffer with contiguous data + if (bytes < block_size) { + u8_t *buffer = malloc(block_size); + memcpy(buffer, streambuf->readp, bytes); + memcpy(buffer + bytes, streambuf->buf, block_size - bytes); + iptr = buffer; + } else iptr = streambuf->readp; + + if (!alac_to_pcm(l->decoder, iptr, l->writebuf, 2, &frames)) { + LOG_ERROR("decode error"); + UNLOCK_S; + return DECODE_ERROR; + } + + // and free it + if (bytes < block_size) free(iptr); + + LOG_SDEBUG("block of %u bytes (%u frames)", block_size, frames); + + endstream = false; + // mp4 end of chunk - skip to next offset + if (l->chunkinfo && l->chunkinfo[l->nextchunk].offset && l->sample++ == l->chunkinfo[l->nextchunk].sample) { + if (l->chunkinfo[l->nextchunk].offset > l->pos) { + u32_t skip = l->chunkinfo[l->nextchunk].offset - l->pos; + if (_buf_used(streambuf) >= skip) { + _buf_inc_readp(streambuf, skip); + l->pos += skip; + } else { + l->consume = skip; + } + l->nextchunk++; + } else { + LOG_ERROR("error: need to skip backwards!"); + endstream = true; + } + // mp4 when not at end of chunk + } else if (frames) { + _buf_inc_readp(streambuf, block_size); + l->pos += block_size; + } else { + endstream = true; + } + + UNLOCK_S; + + if (endstream) { + LOG_WARN("unable to decode further"); + return DECODE_ERROR; + } + + // now point at the beginning of decoded samples + iptr = l->writebuf; + + if (l->skip) { + u32_t skip; + if (l->empty) { + l->empty = false; + l->skip -= frames; + LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames); + } + skip = min(frames, l->skip); + LOG_DEBUG("gapless: skipping %u frames at start", skip); + frames -= skip; + l->skip -= skip; + iptr += skip * l->channels * l->sample_size; + } + + if (l->samples) { + if (l->samples < frames) { + LOG_DEBUG("gapless: trimming %u frames from end", frames - l->samples); + frames = (u32_t) l->samples; + } + l->samples -= frames; + } + + LOCK_O_direct; + + while (frames > 0) { + size_t f, count; + s32_t *optr; + + IF_DIRECT( + f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME); + optr = (s32_t *)outputbuf->writep; + ); + IF_PROCESS( + f = min(frames, process.max_in_frames - process.in_frames); + optr = (s32_t *)((u8_t *) process.inbuf + process.in_frames * BYTES_PER_FRAME); + ); + + f = min(f, frames); + count = f; + + if (l->sample_size == 8) { + while (count--) { + *optr++ = (*(u32_t*) iptr) << 24; + *optr++ = (*(u32_t*) (iptr + 1)) << 24; + iptr += 2; + } + } else if (l->sample_size == 16) { + while (count--) { + *optr++ = (*(u32_t*) iptr) << 16; + *optr++ = (*(u32_t*) (iptr + 2)) << 16; + iptr += 4; + } + } else if (l->sample_size == 24) { + while (count--) { + *optr++ = (*(u32_t*) iptr) << 8; + *optr++ = (*(u32_t*) (iptr + 3)) << 8; + iptr += 6; + } + } else if (l->sample_size == 32) { + while (count--) { + *optr++ = (*(u32_t*) iptr); + *optr++ = (*(u32_t*) (iptr + 4)); + iptr += 8; + } + } else { + LOG_ERROR("unsupported bits per sample: %u", l->sample_size); + } + + frames -= f; + + IF_DIRECT( + _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); + ); + IF_PROCESS( + process.in_frames = f; + // called only if there is enough space in process buffer + if (frames) LOG_ERROR("unhandled case"); + ); + } + + UNLOCK_O_direct; + + return DECODE_RUNNING; +} + +static void alac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { + if (l->decoder) alac_delete_decoder(l->decoder); + else l->writebuf = malloc(BLOCK_SIZE * 2); + + if (l->chunkinfo) free(l->chunkinfo); + if (l->block_size) free(l->block_size); + if (l->stsc) free(l->stsc); + l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL; + l->skip = 0; + l->samples = l->sttssamples = 0; + l->empty = false; + l->pos = l->consume = l->sample = l->nextchunk = 0; +} + +static void alac_close(void) { + if (l->decoder) alac_delete_decoder(l->decoder); + if (l->chunkinfo) free(l->chunkinfo); + if (l->block_size) free(l->block_size); + if (l->stsc) free(l->stsc); + l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL; + free(l->writebuf); +} + +struct codec *register_alac(void) { + static struct codec ret = { + 'l', // id + "alc", // types + MIN_READ, // min read + MIN_SPACE, // min space assuming a ratio of 2 + alac_open, // open + alac_close, // close + alac_decode, // decode + }; + + l = malloc(sizeof(struct alac)); + if (!l) { + return NULL; + } + + l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL; + + LOG_INFO("using alac to decode alc"); + return &ret; +} diff --git a/main/component.mk b/main/component.mk index 5ecbdcfb..a5f66381 100644 --- a/main/component.mk +++ b/main/component.mk @@ -2,10 +2,12 @@ # "main" pseudo-component makefile. # # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) -CFLAGS += -DPOSIX -DLINKALL -DLOOPBACK -DDACAUDIO \ +CFLAGS += -DPOSIX -DLINKALL -DLOOPBACK -DDACAUDIO -DTREMOR_ONLY \ -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/faad2 \ + -I$(COMPONENT_PATH)/../components/codecs/inc/alac \ + -I$(COMPONENT_PATH)/../components/codecs/inc/vorbis LDFLAGS += -s diff --git a/main/decode.c b/main/decode.c index f8751025..916c036b 100644 --- a/main/decode.c +++ b/main/decode.c @@ -54,7 +54,6 @@ static bool running = true; #endif static void *decode_thread() { - while (running) { size_t bytes, space, min_space; @@ -168,6 +167,9 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("wma")); #endif */ + if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_alac()); + #ifndef NO_FAAD if (!strstr(exclude_codecs, "aac") && (!include_codecs || (order_codecs = strstr(include_codecs, "aac")))) sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_faad()); diff --git a/main/squeezelite.h b/main/squeezelite.h index 4df48563..b6d19f1d 100644 --- a/main/squeezelite.h +++ b/main/squeezelite.h @@ -755,6 +755,7 @@ struct codec *register_mpg(void); struct codec *register_vorbis(void); struct codec *register_faad(void); struct codec *register_dsd(void); +struct codec *register_alac(void); struct codec *register_ff(const char *codec); //gpio.c diff --git a/main/vorbis.c b/main/vorbis.c index 892a7337..67ea6f47 100644 --- a/main/vorbis.c +++ b/main/vorbis.c @@ -28,7 +28,11 @@ // tremor's OggVorbis_File struct is normally smaller so this is ok, but padding added to malloc in case it is bigger #define OV_EXCLUDE_STATIC_CALLBACKS +#ifdef TREMOR_ONLY +#include +#else #include +#endif struct vorbis { OggVorbis_File *vf; @@ -183,7 +187,9 @@ static decode_state vorbis_decode(void) { ); // write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them -#if 0 +#ifdef TREMOR_ONLY + n = OV(v, read, v->vf, (char *)write_buf, bytes, &s); +#else if (!TREMOR(v)) { #if SL_LITTLE_ENDIAN n = OV(v, read, v->vf, (char *)write_buf, bytes, 0, 2, 1, &s); @@ -196,7 +202,6 @@ static decode_state vorbis_decode(void) { #endif } #endif - n = OV(v, read, v->vf, (char *)write_buf, bytes, &s); if (n > 0) { @@ -315,8 +320,8 @@ struct codec *register_vorbis(void) { static struct codec ret = { 'o', // id "ogg", // types - 4096, // min read - 40960, // min space + 2048, // min read + 20480, // min space vorbis_open, // open vorbis_close, // close vorbis_decode,// decode