mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 19:47:02 +03:00
550 lines
15 KiB
C
550 lines
15 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "squeezelite.h"
|
|
|
|
#include <alac_wrapper.h>
|
|
|
|
#if BYTES_PER_FRAME == 4
|
|
#define ALIGN8(n) (n << 8)
|
|
#define ALIGN16(n) (n)
|
|
#define ALIGN24(n) (n >> 8)
|
|
#define ALIGN32(n) (n >> 16)
|
|
#else
|
|
#define ALIGN8(n) (n << 24)
|
|
#define ALIGN16(n) (n << 16)
|
|
#define ALIGN24(n) (n << 8)
|
|
#define ALIGN32(n) (n)
|
|
#endif
|
|
|
|
#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;
|
|
ISAMPLE_T *optr;
|
|
|
|
IF_DIRECT(
|
|
f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME);
|
|
optr = (ISAMPLE_T *)outputbuf->writep;
|
|
);
|
|
IF_PROCESS(
|
|
f = min(frames, process.max_in_frames - process.in_frames);
|
|
optr = (ISAMPLE_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++ = ALIGN8(*iptr++);
|
|
*optr++ = ALIGN8(*iptr++);
|
|
}
|
|
} else if (l->sample_size == 16) {
|
|
u16_t *_iptr = (u16_t*) iptr;
|
|
while (count--) {
|
|
*optr++ = ALIGN16(*_iptr++);
|
|
*optr++ = ALIGN16(*_iptr++);
|
|
}
|
|
} else if (l->sample_size == 24) {
|
|
while (count--) {
|
|
*optr++ = ALIGN24(*(u32_t*) iptr);
|
|
*optr++ = ALIGN24(*(u32_t*) (iptr + 3));
|
|
iptr += 6;
|
|
}
|
|
} else if (l->sample_size == 32) {
|
|
u32_t *_iptr = (u32_t*) iptr;
|
|
while (count--) {
|
|
*optr++ = ALIGN32(*_iptr++);
|
|
*optr++ = ALIGN32(*_iptr++);
|
|
}
|
|
} 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;
|
|
}
|