refactoring step 3 - components

squeezelite is a component
platform_esp32 is the main
This commit is contained in:
philippe44
2019-06-29 13:16:46 -07:00
parent 4b54f1733b
commit 53b0ab2390
49 changed files with 98 additions and 42 deletions

View File

@@ -0,0 +1,549 @@
/*
* 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;
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;
} 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;
}

View File

@@ -0,0 +1,115 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// fifo bufffers
#define _GNU_SOURCE
#include "squeezelite.h"
// _* called with muxtex locked
inline unsigned _buf_used(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->size - (buf->readp - buf->writep);
}
unsigned _buf_space(struct buffer *buf) {
return buf->size - _buf_used(buf) - 1; // reduce by one as full same as empty otherwise
}
unsigned _buf_cont_read(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->wrap - buf->readp;
}
unsigned _buf_cont_write(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->wrap - buf->writep : buf->readp - buf->writep;
}
void _buf_inc_readp(struct buffer *buf, unsigned by) {
buf->readp += by;
if (buf->readp >= buf->wrap) {
buf->readp -= buf->size;
}
}
void _buf_inc_writep(struct buffer *buf, unsigned by) {
buf->writep += by;
if (buf->writep >= buf->wrap) {
buf->writep -= buf->size;
}
}
void buf_flush(struct buffer *buf) {
mutex_lock(buf->mutex);
buf->readp = buf->buf;
buf->writep = buf->buf;
mutex_unlock(buf->mutex);
}
// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
void buf_adjust(struct buffer *buf, size_t mod) {
size_t size;
mutex_lock(buf->mutex);
size = ((unsigned)(buf->base_size / mod)) * mod;
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
mutex_unlock(buf->mutex);
}
// called with mutex locked to resize, does not retain contents, reverts to original size if fails
void _buf_resize(struct buffer *buf, size_t size) {
free(buf->buf);
buf->buf = malloc(size);
if (!buf->buf) {
size = buf->size;
buf->buf= malloc(size);
if (!buf->buf) {
size = 0;
}
}
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
}
void buf_init(struct buffer *buf, size_t size) {
buf->buf = malloc(size);
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
mutex_create_p(buf->mutex);
}
void buf_destroy(struct buffer *buf) {
if (buf->buf) {
free(buf->buf);
buf->buf = NULL;
buf->size = 0;
buf->base_size = 0;
mutex_destroy(buf->mutex);
}
}

View File

@@ -0,0 +1,18 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 \
-I$(COMPONENT_PATH)/../codecs/inc \
-I$(COMPONENT_PATH)/../codecs/inc/mad \
-I$(COMPONENT_PATH)/../codecs/inc/alac \
-I$(COMPONENT_PATH)/../codecs/inc/helix-aac \
-I$(COMPONENT_PATH)/../codecs/inc/vorbis \
-I$(COMPONENT_PATH)/../codecs/inc/soxr \
-I$(COMPONENT_PATH)/../codecs/inc/resample16 \
-I$(COMPONENT_PATH)/../tools
# -I$(COMPONENT_PATH)/../codecs/inc/faad2

View File

@@ -0,0 +1,308 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// decode thread
#include "squeezelite.h"
log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct processstate process;
struct decodestate decode;
struct codec *codecs[MAX_CODECS];
struct codec *codec;
static bool running = true;
#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)
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
#if PROCESS
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#define MAY_PROCESS(x) { x }
#else
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#define MAY_PROCESS(x)
#endif
static void *decode_thread() {
while (running) {
size_t bytes, space, min_space;
bool toend;
bool ran = false;
LOCK_S;
bytes = _buf_used(streambuf);
toend = (stream.state <= DISCONNECT);
UNLOCK_S;
LOCK_O;
space = _buf_space(outputbuf);
UNLOCK_O;
LOCK_D;
if (decode.state == DECODE_RUNNING && codec) {
LOG_SDEBUG("streambuf bytes: %u outputbuf space: %u", bytes, space);
IF_DIRECT(
min_space = codec->min_space;
);
IF_PROCESS(
min_space = process.max_out_frames * BYTES_PER_FRAME;
);
if (space > min_space && (bytes > codec->min_read_bytes || toend)) {
decode.state = codec->decode();
IF_PROCESS(
if (process.in_frames) {
process_samples();
}
if (decode.state == DECODE_COMPLETE) {
process_drain();
}
);
if (decode.state != DECODE_RUNNING) {
LOG_INFO("decode %s", decode.state == DECODE_COMPLETE ? "complete" : "error");
LOCK_O;
if (output.fade_mode) _checkfade(false);
UNLOCK_O;
wake_controller();
}
ran = true;
}
}
UNLOCK_D;
if (!ran) {
usleep(100000);
}
}
return 0;
}
static void sort_codecs(int pry, struct codec* ptr) {
static int priority[MAX_CODECS];
int i, tpry;
struct codec* tptr;
for (i = 0; i < MAX_CODECS; i++) {
if (!codecs[i]) {
codecs[i] = ptr;
priority[i] = pry;
return;
}
if (pry < priority[i]) {
tptr = codecs[i];
codecs[i] = ptr;
ptr = tptr;
tpry = priority[i];
priority[i] = pry;
pry = tpry;
}
}
}
static thread_type thread;
void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs) {
int i;
char* order_codecs = NULL;
loglevel = level;
LOG_INFO("init decode");
// register codecs
// dsf,dff,alc,wma,wmap,wmal,aac,spt,ogg,ogf,flc,aif,pcm,mp3
i = 0;
#if DSD
if (!strstr(exclude_codecs, "dsd") && (!include_codecs || (order_codecs = strstr(include_codecs, "dsd"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_dsd());
#endif
#if FFMPEG
if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("alc"));
if (!strstr(exclude_codecs, "wma") && (!include_codecs || (order_codecs = strstr(include_codecs, "wma"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("wma"));
#else
if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_alac());
#endif
#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());
#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());
if (!strstr(exclude_codecs, "flac") && (!include_codecs || (order_codecs = strstr(include_codecs, "flac"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_flac());
if (!strstr(exclude_codecs, "pcm") && (!include_codecs || (order_codecs = strstr(include_codecs, "pcm"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_pcm());
// try mad then mpg for mp3 unless command line option passed
if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mad")) &&
(!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mad"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mad());
else if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mpg")) &&
(!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mpg"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg());
LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs);
mutex_create(decode.mutex);
#if LINUX || OSX || FREEBSD || EMBEDDED
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + DECODE_THREAD_STACK_SIZE);
#endif
pthread_create(&thread, &attr, decode_thread, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "decode");
#endif
#endif
#if WIN
thread = CreateThread(NULL, DECODE_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&decode_thread, NULL, 0, NULL);
#endif
decode.new_stream = true;
decode.state = DECODE_STOPPED;
MAY_PROCESS(
decode.direct = true;
decode.process = false;
);
}
void decode_close(void) {
LOG_INFO("close decode");
LOCK_D;
if (codec) {
codec->close();
codec = NULL;
}
running = false;
UNLOCK_D;
#if LINUX || OSX || FREEBSD
pthread_join(thread, NULL);
#endif
mutex_destroy(decode.mutex);
}
void decode_flush(void) {
LOG_INFO("decode flush");
LOCK_D;
decode.state = DECODE_STOPPED;
IF_PROCESS(
process_flush();
);
UNLOCK_D;
}
unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]) {
// called with O locked to get sample rate for potentially processed output stream
// release O mutex during process_newstream as it can take some time
MAY_PROCESS(
if (decode.process) {
UNLOCK_O;
sample_rate = process_newstream(&decode.direct, sample_rate, supported_rates);
LOCK_O;
}
);
return sample_rate;
}
void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) {
int i;
LOG_INFO("codec open: '%c'", format);
LOCK_D;
decode.new_stream = true;
decode.state = DECODE_STOPPED;
MAY_PROCESS(
decode.direct = true; // potentially changed within codec when processing enabled
);
// find the required codec
for (i = 0; i < MAX_CODECS; ++i) {
if (codecs[i] && codecs[i]->id == format) {
if (codec && codec != codecs[i]) {
LOG_INFO("closing codec: '%c'", codec->id);
codec->close();
}
codec = codecs[i];
codec->open(sample_size, sample_rate, channels, endianness);
decode.state = DECODE_READY;
UNLOCK_D;
return;
}
}
UNLOCK_D;
LOG_ERROR("codec not found");
}

View File

@@ -0,0 +1,44 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, 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 "esp_pthread.h"
#include "esp_system.h"
void get_mac(u8_t mac[]) {
esp_read_mac(mac, ESP_MAC_WIFI_STA);
}
_sig_func_ptr signal(int sig, _sig_func_ptr func) {
return NULL;
}
void *audio_calloc(size_t nmemb, size_t size) {
return calloc(nmemb, size);
}
int pthread_setname_np(pthread_t thread, const char *name) {
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= name;
cfg.inherit_cfg = true;
return esp_pthread_set_cfg(&cfg);
}

View File

@@ -0,0 +1,22 @@
#ifndef EMBEDDED_H
#define EMBEDDED_H
#include <inttypes.h>
#define HAS_MUTEX_CREATE_P 0
#define HAS_PTHREAD_SETNAME_NP 1
#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN 256
#endif
typedef int16_t s16_t;
typedef int32_t s32_t;
typedef int64_t s64_t;
typedef unsigned long long u64_t;
#define exit(code) { int ret = code; pthread_exit(&ret); }
int pthread_setname_np(pthread_t thread, const char *name);
#endif // EMBEDDED_H

View File

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

View File

@@ -0,0 +1,308 @@
/*
* Squeezelite - lightweight headless squeezeplay emulator for linux
*
* (c) Adrian Smith 2012, triode1@btinternet.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 <FLAC/stream_decoder.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
struct flac {
FLAC__StreamDecoder *decoder;
#if !LINKALL
// FLAC symbols to be dynamically loaded
const char **FLAC__StreamDecoderErrorStatusString;
const char **FLAC__StreamDecoderStateString;
FLAC__StreamDecoder * (* FLAC__stream_decoder_new)(void);
FLAC__bool (* FLAC__stream_decoder_reset)(FLAC__StreamDecoder *decoder);
void (* FLAC__stream_decoder_delete)(FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderInitStatus (* FLAC__stream_decoder_init_stream)(
FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderReadCallback read_callback,
FLAC__StreamDecoderSeekCallback seek_callback,
FLAC__StreamDecoderTellCallback tell_callback,
FLAC__StreamDecoderLengthCallback length_callback,
FLAC__StreamDecoderEofCallback eof_callback,
FLAC__StreamDecoderWriteCallback write_callback,
FLAC__StreamDecoderMetadataCallback metadata_callback,
FLAC__StreamDecoderErrorCallback error_callback,
void *client_data
);
FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder);
#endif
};
static struct flac *f;
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 FLAC(h, fn, ...) (FLAC__ ## fn)(__VA_ARGS__)
#define FLAC_A(h, a) (FLAC__ ## a)
#else
#define FLAC(h, fn, ...) (h)->FLAC__##fn(__VA_ARGS__)
#define FLAC_A(h, a) (h)->FLAC__ ## a
#endif
static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *want, void *client_data) {
size_t bytes;
bool end;
LOCK_S;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(bytes, *want);
end = (stream.state <= DISCONNECT && bytes == 0);
memcpy(buffer, streambuf->readp, bytes);
_buf_inc_readp(streambuf, bytes);
UNLOCK_S;
*want = bytes;
return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
const FLAC__int32 *const buffer[], void *client_data) {
size_t frames = frame->header.blocksize;
unsigned bits_per_sample = frame->header.bits_per_sample;
unsigned channels = frame->header.channels;
FLAC__int32 *lptr = (FLAC__int32 *)buffer[0];
FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0];
if (decode.new_stream) {
LOCK_O;
LOG_INFO("setting track_start");
output.track_start = outputbuf->writep;
decode.new_stream = false;
#if DSD
#if SL_LITTLE_ENDIAN
#define MARKER_OFFSET 2
#else
#define MARKER_OFFSET 1
#endif
if (bits_per_sample == 24 && is_stream_dop(((u8_t *)lptr) + MARKER_OFFSET, ((u8_t *)rptr) + MARKER_OFFSET, 4, frames)) {
LOG_INFO("file contains DOP");
if (output.dsdfmt == DOP_S24_LE || output.dsdfmt == DOP_S24_3LE)
output.next_fmt = output.dsdfmt;
else
output.next_fmt = DOP;
output.next_sample_rate = frame->header.sample_rate;
output.fade = FADE_INACTIVE;
} else {
output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates);
output.next_fmt = PCM;
if (output.fade_mode) _checkfade(true);
}
#else
output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates);
if (output.fade_mode) _checkfade(true);
#endif
UNLOCK_O;
}
LOCK_O_direct;
while (frames > 0) {
frames_t f;
frames_t count;
ISAMPLE_T *optr;
IF_DIRECT(
optr = (ISAMPLE_T *)outputbuf->writep;
f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
);
IF_PROCESS(
optr = (ISAMPLE_T *)process.inbuf;
f = process.max_in_frames;
);
f = min(f, frames);
count = f;
if (bits_per_sample == 8) {
while (count--) {
*optr++ = ALIGN8(*lptr++);
*optr++ = ALIGN8(*rptr++);
}
} else if (bits_per_sample == 16) {
while (count--) {
*optr++ = ALIGN16(*lptr++);
*optr++ = ALIGN16(*rptr++);
}
} else if (bits_per_sample == 24) {
while (count--) {
*optr++ = ALIGN24(*lptr++);
*optr++ = ALIGN24(*rptr++);
}
} else if (bits_per_sample == 32) {
while (count--) {
*optr++ = ALIGN32(*lptr++);
*optr++ = ALIGN32(*rptr++);
}
} else {
LOG_ERROR("unsupported bits per sample: %u", bits_per_sample);
}
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 FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
static void error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderErrorStatusString)[status]);
}
static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) {
if (f->decoder) {
FLAC(f, stream_decoder_reset, f->decoder);
} else {
f->decoder = FLAC(f, stream_decoder_new);
}
FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
}
static void flac_close(void) {
FLAC(f, stream_decoder_delete, f->decoder);
f->decoder = NULL;
}
static decode_state flac_decode(void) {
bool ok = FLAC(f, stream_decoder_process_single, f->decoder);
FLAC__StreamDecoderState state = FLAC(f, stream_decoder_get_state, f->decoder);
if (!ok && state != FLAC__STREAM_DECODER_END_OF_STREAM) {
LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderStateString)[state]);
};
if (state == FLAC__STREAM_DECODER_END_OF_STREAM) {
return DECODE_COMPLETE;
} else if (state > FLAC__STREAM_DECODER_END_OF_STREAM) {
return DECODE_ERROR;
} else {
return DECODE_RUNNING;
}
}
static bool load_flac() {
#if !LINKALL
void *handle = dlopen(LIBFLAC, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
f->FLAC__StreamDecoderErrorStatusString = dlsym(handle, "FLAC__StreamDecoderErrorStatusString");
f->FLAC__StreamDecoderStateString = dlsym(handle, "FLAC__StreamDecoderStateString");
f->FLAC__stream_decoder_new = dlsym(handle, "FLAC__stream_decoder_new");
f->FLAC__stream_decoder_reset = dlsym(handle, "FLAC__stream_decoder_reset");
f->FLAC__stream_decoder_delete = dlsym(handle, "FLAC__stream_decoder_delete");
f->FLAC__stream_decoder_init_stream = dlsym(handle, "FLAC__stream_decoder_init_stream");
f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single");
f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBFLAC);
#endif
return true;
}
struct codec *register_flac(void) {
static struct codec ret = {
'f', // id
"flc", // types
16384, // min read
204800, // min space
flac_open, // open
flac_close, // close
flac_decode, // decode
};
f = malloc(sizeof(struct flac));
if (!f) {
return NULL;
}
f->decoder = NULL;
if (!load_flac()) {
return NULL;
}
LOG_INFO("using flac to decode flc");
return &ret;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
#include <aacdec.h>
// 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;
}

View File

@@ -0,0 +1,419 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
#include <mad.h>
#define MAD_DELAY 529
#define READBUF_SIZE 2048 // local buffer used by decoder: FIXME merge with any other decoders needing one?
struct mad {
u8_t *readbuf;
unsigned readbuf_len;
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
enum mad_error last_error;
// for lame gapless processing
int checktags;
u32_t consume;
u32_t skip;
u64_t samples;
u32_t padding;
#if !LINKALL
// mad symbols to be dynamically loaded
void (* mad_stream_init)(struct mad_stream *);
void (* mad_frame_init)(struct mad_frame *);
void (* mad_synth_init)(struct mad_synth *);
void (* mad_frame_finish)(struct mad_frame *);
void (* mad_stream_finish)(struct mad_stream *);
void (* mad_stream_buffer)(struct mad_stream *, unsigned char const *, unsigned long);
int (* mad_frame_decode)(struct mad_frame *, struct mad_stream *);
void (* mad_synth_frame)(struct mad_synth *, struct mad_frame const *);
char const *(* mad_stream_errorstr)(struct mad_stream const *);
#endif
};
static struct mad *m;
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 MAD(h, fn, ...) (mad_ ## fn)(__VA_ARGS__)
#else
#define MAD(h, fn, ...) (h)->mad_##fn(__VA_ARGS__)
#endif
// based on libmad minimad.c scale
static inline ISAMPLE_T scale(mad_fixed_t sample) {
sample += (1L << (MAD_F_FRACBITS - 24));
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
#if BYTES_PER_FRAME == 4
return (ISAMPLE_T)((sample >> (MAD_F_FRACBITS + 1 - 24)) >> 8);
#else
return (ISAMPLE_T)((sample >> (MAD_F_FRACBITS + 1 - 24)) << 8);
#endif
}
// check for id3.2 tag at start of file - http://id3.org/id3v2.4.0-structure, return length
static unsigned _check_id3_tag(size_t bytes) {
u8_t *ptr = streambuf->readp;
u32_t size = 0;
if (bytes > 10 && *ptr == 'I' && *(ptr+1) == 'D' && *(ptr+2) == '3') {
// size is encoded as syncsafe integer, add 10 if footer present
if (*(ptr+6) < 0x80 && *(ptr+7) < 0x80 && *(ptr+8) < 0x80 && *(ptr+9) < 0x80) {
size = 10 + (*(ptr+6) << 21) + (*(ptr+7) << 14) + (*(ptr+8) << 7) + *(ptr+9) + ((*(ptr+5) & 0x10) ? 10 : 0);
LOG_DEBUG("id3.2 tag len: %u", size);
}
}
return size;
}
// check for lame gapless params, don't advance streambuf
static void _check_lame_header(size_t bytes) {
u8_t *ptr = streambuf->readp;
if (*ptr == 0xff && (*(ptr+1) & 0xf0) == 0xf0 && bytes > 180) {
u32_t frame_count = 0, enc_delay = 0, enc_padding = 0;
u8_t flags;
// 2 channels
if (!memcmp(ptr + 36, "Xing", 4) || !memcmp(ptr + 36, "Info", 4)) {
ptr += 36 + 7;
// mono
} else if (!memcmp(ptr + 21, "Xing", 4) || !memcmp(ptr + 21, "Info", 4)) {
ptr += 21 + 7;
}
flags = *ptr;
if (flags & 0x01) {
frame_count = unpackN((u32_t *)(ptr + 1));
ptr += 4;
}
if (flags & 0x02) ptr += 4;
if (flags & 0x04) ptr += 100;
if (flags & 0x08) ptr += 4;
if (!!memcmp(ptr+1, "LAME", 4)) {
return;
}
ptr += 22;
enc_delay = (*ptr << 4 | *(ptr + 1) >> 4) + MAD_DELAY;
enc_padding = (*(ptr + 1) & 0xF) << 8 | *(ptr + 2);
enc_padding = enc_padding > MAD_DELAY ? enc_padding - MAD_DELAY : 0;
// add one frame to initial skip for this (empty) frame
m->skip = enc_delay + 1152;
m->samples = frame_count * 1152 - enc_delay - enc_padding;
m->padding = enc_padding;
LOG_INFO("gapless: skip: %u samples: " FMT_u64 " delay: %u padding: %u", m->skip, m->samples, enc_delay, enc_padding);
}
}
static decode_state mad_decode(void) {
size_t bytes;
bool eos = false;
LOCK_S;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
if (m->checktags) {
if (m->checktags == 1) {
m->consume = _check_id3_tag(bytes);
m->checktags = 2;
}
if (m->consume) {
u32_t consume = min(m->consume, bytes);
LOG_DEBUG("consume: %u of %u", consume, m->consume);
_buf_inc_readp(streambuf, consume);
m->consume -= consume;
UNLOCK_S;
return DECODE_RUNNING;
}
if (m->checktags == 2) {
if (!stream.meta_interval) {
_check_lame_header(bytes);
}
m->checktags = 0;
}
}
if (m->stream.next_frame && m->readbuf_len) {
m->readbuf_len -= m->stream.next_frame - m->readbuf;
memmove(m->readbuf, m->stream.next_frame, m->readbuf_len);
}
bytes = min(bytes, READBUF_SIZE - m->readbuf_len);
memcpy(m->readbuf + m->readbuf_len, streambuf->readp, bytes);
m->readbuf_len += bytes;
_buf_inc_readp(streambuf, bytes);
if (stream.state <= DISCONNECT && _buf_used(streambuf) == 0) {
eos = true;
LOG_DEBUG("end of stream");
memset(m->readbuf + m->readbuf_len, 0, MAD_BUFFER_GUARD);
m->readbuf_len += MAD_BUFFER_GUARD;
}
UNLOCK_S;
MAD(m, stream_buffer, &m->stream, m->readbuf, m->readbuf_len);
while (true) {
size_t frames;
s32_t *iptrl;
s32_t *iptrr;
unsigned max_frames;
if (MAD(m, frame_decode, &m->frame, &m->stream) == -1) {
decode_state ret;
if (!eos && m->stream.error == MAD_ERROR_BUFLEN) {
ret = DECODE_RUNNING;
} else if (eos && (m->stream.error == MAD_ERROR_BUFLEN || m->stream.error == MAD_ERROR_LOSTSYNC
|| m->stream.error == MAD_ERROR_BADBITRATE)) {
ret = DECODE_COMPLETE;
} else if (!MAD_RECOVERABLE(m->stream.error)) {
LOG_INFO("mad_frame_decode error: %s - stopping decoder", MAD(m, stream_errorstr, &m->stream));
ret = DECODE_COMPLETE;
} else {
if (m->stream.error != m->last_error) {
// suppress repeat error messages
LOG_DEBUG("mad_frame_decode error: %s", MAD(m, stream_errorstr, &m->stream));
}
ret = DECODE_RUNNING;
}
m->last_error = m->stream.error;
return ret;
};
MAD(m, synth_frame, &m->synth, &m->frame);
if (decode.new_stream) {
LOCK_O;
LOG_INFO("setting track_start");
output.next_sample_rate = decode_newstream(m->synth.pcm.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;
}
LOCK_O_direct;
IF_DIRECT(
max_frames = _buf_space(outputbuf) / BYTES_PER_FRAME;
);
IF_PROCESS(
max_frames = process.max_in_frames - process.in_frames;
);
if (m->synth.pcm.length > max_frames) {
LOG_WARN("too many samples - dropping samples");
m->synth.pcm.length = max_frames;
}
frames = m->synth.pcm.length;
iptrl = m->synth.pcm.samples[0];
iptrr = m->synth.pcm.samples[ m->synth.pcm.channels - 1 ];
if (m->skip) {
u32_t skip = min(m->skip, frames);
LOG_DEBUG("gapless: skipping %u frames at start", skip);
frames -= skip;
m->skip -= skip;
iptrl += skip;
iptrr += skip;
}
if (m->samples) {
if (m->samples < frames) {
LOG_DEBUG("gapless: trimming %u frames from end", frames - m->samples);
frames = (size_t)m->samples;
}
m->samples -= frames;
if (m->samples > 0 && eos && !(m->stream.next_frame[0] == 0xff && (m->stream.next_frame[1] & 0xf0) == 0xf0)) {
// this is the last frame to be decoded, but more samples expected so we must have skipped, remove padding
// note this only works if the padding is less than one frame of 1152 bytes otherswise some gap will remain
LOG_DEBUG("gapless: early end - trimming padding from end");
if (frames >= m->padding) {
frames -= m->padding;
} else {
frames = 0;
}
m->samples = 0;
}
}
LOG_SDEBUG("write %u frames", frames);
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);
);
count = f;
while (count--) {
*optr++ = scale(*iptrl++);
*optr++ = scale(*iptrr++);
}
frames -= f;
IF_DIRECT(
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames += f;
);
}
UNLOCK_O_direct;
}
return eos ? DECODE_COMPLETE : DECODE_RUNNING;
}
static void mad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (!m->readbuf) {
m->readbuf = malloc(READBUF_SIZE + MAD_BUFFER_GUARD);
}
m->checktags = 1;
m->consume = 0;
m->skip = MAD_DELAY;
m->samples = 0;
m->readbuf_len = 0;
m->last_error = MAD_ERROR_NONE;
MAD(m, stream_init, &m->stream);
MAD(m, frame_init, &m->frame);
MAD(m, synth_init, &m->synth);
}
static void mad_close(void) {
mad_synth_finish(&m->synth); // macro only in current version
MAD(m, frame_finish, &m->frame);
MAD(m, stream_finish, &m->stream);
free(m->readbuf);
m->readbuf = NULL;
}
static bool load_mad() {
#if !LINKALL
void *handle = dlopen(LIBMAD, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
m->mad_stream_init = dlsym(handle, "mad_stream_init");
m->mad_frame_init = dlsym(handle, "mad_frame_init");
m->mad_synth_init = dlsym(handle, "mad_synth_init");
m->mad_frame_finish = dlsym(handle, "mad_frame_finish");
m->mad_stream_finish = dlsym(handle, "mad_stream_finish");
m->mad_stream_buffer = dlsym(handle, "mad_stream_buffer");
m->mad_frame_decode = dlsym(handle, "mad_frame_decode");
m->mad_synth_frame = dlsym(handle, "mad_synth_frame");
m->mad_stream_errorstr = dlsym(handle, "mad_stream_errorstr");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBMAD);
#endif
return true;
}
struct codec *register_mad(void) {
static struct codec ret = {
'm', // id
"mp3", // types
READBUF_SIZE, // min read
206800, // min space
mad_open, // open
mad_close, // close
mad_decode, // decode
};
m = malloc(sizeof(struct mad));
if (!m) {
return NULL;
}
m->readbuf = NULL;
m->readbuf_len = 0;
if (!load_mad()) {
return NULL;
}
LOG_INFO("using mad to decode mp3");
return &ret;
}

View File

@@ -0,0 +1,848 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additions (c) Paul Hermann, 2015-2017 under the same license terms
* -Control of Raspberry pi GPIO for amplifier power
* -Launch script on power status change from LMS
*/
#include "squeezelite.h"
#include <signal.h>
#define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith, 2015-2019 Ralph Irving."
#define CODECS_BASE "flac,pcm,mp3,ogg"
#if NO_FAAD
#define CODECS_AAC ""
#else
#define CODECS_AAC ",aac"
#endif
#if FFMPEG
#define CODECS_FF ",wma,alac"
#else
#define CODECS_FF ""
#endif
#if DSD
#define CODECS_DSD ",dsd"
#else
#define CODECS_DSD ""
#endif
#define CODECS_MP3 " (mad,mpg for specific mp3 codec)"
#define CODECS CODECS_BASE CODECS_AAC CODECS_FF CODECS_DSD CODECS_MP3
static void usage(const char *argv0) {
printf(TITLE " See -t for license terms\n"
"Usage: %s [options]\n"
" -s <server>[:<port>]\tConnect to specified server, otherwise uses autodiscovery to find server\n"
" -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n"
" -l \t\t\tList output devices\n"
#if ALSA
" -a <b>:<p>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n"
#endif
#if PORTAUDIO
#if PA18API
" -a <frames>:<buffers>\tSpecify output target 4 byte frames per buffer, number of buffers\n"
#elif OSX && !defined(OSXPPC)
" -a <l>:<r>\t\tSpecify Portaudio params to open output device, l = target latency in ms, r = allow OSX to resample (0|1)\n"
#elif WIN
" -a <l>:<e>\t\tSpecify Portaudio params to open output device, l = target latency in ms, e = use exclusive mode for WASAPI (0|1)\n"
#else
" -a <l>\t\tSpecify Portaudio params to open output device, l = target latency in ms\n"
#endif
#endif
" -a <f>\t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n"
" -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n"
" -c <codec1>,<codec2>\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n"
" \t\t\tCodecs reported to LMS in order listed, allowing codec priority refinement.\n"
" -C <timeout>\t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n"
#if !IR
" -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n"
#else
" -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output|ir, level: info|debug|sdebug\n"
#endif
#if defined(GPIO) && defined(RPI)
" -G <Rpi GPIO#>:<H/L>\tSpecify the BCM GPIO# to use for Amp Power Relay and if the output should be Active High or Low\n"
#endif
" -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n"
" -f <logfile>\t\tWrite debug to logfile\n"
#if IR
" -i [<filename>]\tEnable lirc remote control support (lirc config file ~/.lircrc used if filename not specified)\n"
#endif
" -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n"
" -M <modelname>\tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n"
" -n <name>\t\tSet the player name\n"
" -N <filename>\t\tStore player name in filename to allow server defined name changes to be shared between servers (not supported with -n)\n"
" -W\t\t\tRead wave and aiff format from header, ignore server parameters\n"
#if ALSA
" -p <priority>\t\tSet real time priority of output thread (1-99)\n"
#endif
#if LINUX || FREEBSD || SUN
" -P <filename>\t\tStore the process id (PID) in filename\n"
#endif
" -r <rates>[:<delay>]\tSample rates supported, allows output to be off when squeezelite is started; rates = <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>; delay = optional delay switching rates in ms\n"
#if GPIO
" -S <Power Script>\tAbsolute path to script to launch on power commands from LMS\n"
#endif
#if RESAMPLE
" -R -u [params]\tResample, params = <recipe>:<flags>:<attenuation>:<precision>:<passband_end>:<stopband_start>:<phase_response>,\n"
" \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n"
" \t\t\t flags = num in hex,\n"
" \t\t\t attenuation = attenuation in dB to apply (default is -1db if not explicitly set),\n"
" \t\t\t precision = number of bits precision (NB. HQ = 20. VHQ = 28),\n"
" \t\t\t passband_end = number in percent (0dB pt. bandwidth to preserve. nyquist = 100%%),\n"
" \t\t\t stopband_start = number in percent (Aliasing/imaging control. > passband_end),\n"
" \t\t\t phase_response = 0-100 (0 = minimum / 50 = linear / 100 = maximum)\n"
#endif
#if RESAMPLE16
" -R -u [params]\tResample, params = (b|l|m)[:i],\n"
" \t\t\t b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients\n"
#endif
#if DSD
#if ALSA
" -D [delay][:format]\tOutput device supports DSD, delay = optional delay switching between PCM and DSD in ms\n"
" \t\t\t format = dop (default if not specified), u8, u16le, u16be, u32le or u32be.\n"
#else
" -D [delay]\t\tOutput device supports DSD over PCM (DoP), delay = optional delay switching between PCM and DoP in ms\n"
#endif
#endif
#if VISEXPORT
" -v \t\t\tVisualiser support\n"
#endif
# if ALSA
" -O <mixer device>\tSpecify mixer device, defaults to 'output device'\n"
" -L \t\t\tList volume controls for output device\n"
" -U <control>\t\tUnmute ALSA control and set to full volume (not supported with -V)\n"
" -V <control>\t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n"
" -X \t\t\tUse linear volume adjustments instead of in terms of dB (only for hardware volume control)\n"
#endif
#if LINUX || FREEBSD || SUN
" -z \t\t\tDaemonize\n"
#endif
#if RESAMPLE || RESAMPLE16
" -Z <rate>\t\tReport rate to server in helo as the maximum sample rate we can support\n"
#endif
" -t \t\t\tLicense terms\n"
" -? \t\t\tDisplay this help text\n"
"\n"
"Build options:"
#if SUN
" SOLARIS"
#elif LINUX
" LINUX"
#endif
#if WIN
" WIN"
#endif
#if OSX
" OSX"
#endif
#if OSXPPC
"PPC"
#endif
#if FREEBSD
" FREEBSD"
#endif
#if ALSA
" ALSA"
#endif
#if PORTAUDIO
" PORTAUDIO"
#if PA18API
"18"
#endif
#endif
#if EMBEDDED
" EMBEDDED"
#endif
#if EVENTFD
" EVENTFD"
#endif
#if SELFPIPE
" SELFPIPE"
#endif
#if LOOPBACK
" LOOPBACK"
#endif
#if WINEVENT
" WINEVENT"
#endif
#if RESAMPLE_MP
" RESAMPLE_MP"
#else
#if RESAMPLE
" RESAMPLE"
#endif
#if RESAMPLE16
" RESAMPLE16"
#endif
#endif
#if FFMPEG
" FFMPEG"
#endif
#if NO_FAAD
" NO_FAAD"
#endif
#if VISEXPORT
" VISEXPORT"
#endif
#if IR
" IR"
#endif
#if GPIO
" GPIO"
#endif
#if RPI
" RPI"
#endif
#if DSD
" DSD"
#endif
#if USE_SSL
" SSL"
#endif
#if LINKALL
" LINKALL"
#endif
#if STATUSHACK
" STATUSHACK"
#endif
"\n\n",
argv0);
}
static void license(void) {
printf(TITLE "\n\n"
"This program is free software: you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation, either version 3 of the License, or\n"
"(at your option) any later version.\n\n"
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n\n"
"You should have received a copy of the GNU General Public License\n"
"along with this program. If not, see <http://www.gnu.org/licenses/>.\n"
#if DSD
"\nContains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n"
"is subject to its own license.\n"
"\nContains the Daphile Project full dsd patch Copyright 2013-2017 Daphile,\n"
"which is subject to its own license.\n"
#endif
"\nOption to allow server side upsampling for PCM streams (-W) from\n"
"squeezelite-R2 (c) Marco Curti 2015, marcoc1712@gmail.com.\n"
#if GPIO
"\nAdditions (c) Paul Hermann, 2015, 2017 under the same license terms\n"
"- Launch a script on power status change\n"
"- Control of Raspberry pi GPIO for amplifier power\n"
#endif
#if RPI
"\nContains wiringpi GPIO Interface library Copyright (c) 2012-2017\n"
"Gordon Henderson, which is subject to its own license.\n"
#endif
#if FFMPEG
"\nThis software uses libraries from the FFmpeg project under\n"
"the LGPLv2.1 and its source can be downloaded from\n"
"<https://sourceforge.net/projects/lmsclients/files/source/>\n"
#endif
"\n"
);
}
static void sighandler(int signum) {
slimproto_stop();
// remove ourselves in case above does not work, second SIGINT will cause non gracefull shutdown
signal(signum, SIG_DFL);
}
int main(int argc, char **argv) {
char *server = NULL;
char *output_device = "default";
char *include_codecs = NULL;
char *exclude_codecs = "";
char *name = NULL;
char *namefile = NULL;
char *modelname = NULL;
extern bool pcm_check_header;
extern bool user_rates;
char *logfile = NULL;
u8_t mac[6];
unsigned stream_buf_size = STREAMBUF_SIZE;
unsigned output_buf_size = 0; // set later
unsigned rates[MAX_SUPPORTED_SAMPLERATES] = { 0 };
unsigned rate_delay = 0;
char *resample = NULL;
char *output_params = NULL;
unsigned idle = 0;
#if LINUX || FREEBSD || SUN
bool daemonize = false;
char *pidfile = NULL;
FILE *pidfp = NULL;
#endif
#if ALSA
unsigned rt_priority = OUTPUT_RT_PRIORITY;
char *mixer_device = output_device;
char *output_mixer = NULL;
bool output_mixer_unmute = false;
bool linear_volume = false;
#endif
#if DSD
unsigned dsd_delay = 0;
dsd_format dsd_outfmt = PCM;
#endif
#if VISEXPORT
bool visexport = false;
#endif
#if IR
char *lircrc = NULL;
#endif
log_level log_output = lWARN;
log_level log_stream = lWARN;
log_level log_decode = lWARN;
log_level log_slimproto = lWARN;
#if IR
log_level log_ir = lWARN;
#endif
int maxSampleRate = 0;
char *optarg = NULL;
int optind = 1;
int i;
#define MAXCMDLINE 512
char cmdline[MAXCMDLINE] = "";
get_mac(mac);
for (i = 0; i < argc && (strlen(argv[i]) + strlen(cmdline) + 2 < MAXCMDLINE); i++) {
strcat(cmdline, argv[i]);
strcat(cmdline, " ");
}
while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') {
char *opt = argv[optind] + 1;
if (strstr("oabcCdefmMnNpPrs"
#if ALSA
"UVO"
#endif
/*
* only allow '-Z <rate>' override of maxSampleRate
* reported by client if built with the capability to resample!
*/
#if RESAMPLE || RESAMPLE16
"Z"
#endif
, opt) && optind < argc - 1) {
optarg = argv[optind + 1];
optind += 2;
} else if (strstr("ltz?W"
#if ALSA
"LX"
#endif
#if RESAMPLE || RESAMPLE16
"uR"
#endif
#if DSD
"D"
#endif
#if VISEXPORT
"v"
#endif
#if IR
"i"
#endif
#if defined(GPIO) && defined(RPI)
"G"
#endif
#if GPIO
"S"
#endif
, opt)) {
optarg = NULL;
optind += 1;
} else {
fprintf(stderr, "\nOption error: -%s\n\n", opt);
usage(argv[0]);
exit(1);
}
switch (opt[0]) {
case 'o':
output_device = optarg;
#if ALSA
mixer_device = optarg;
#endif
break;
case 'a':
output_params = optarg;
break;
case 'b':
{
char *s = next_param(optarg, ':');
char *o = next_param(NULL, ':');
if (s) stream_buf_size = atoi(s) * 1024;
if (o) output_buf_size = atoi(o) * 1024;
}
break;
case 'c':
include_codecs = optarg;
break;
case 'C':
if (atoi(optarg) > 0) {
idle = atoi(optarg) * 1000;
}
break;
case 'e':
exclude_codecs = optarg;
break;
case 'd':
{
char *l = strtok(optarg, "=");
char *v = strtok(NULL, "=");
log_level new = lWARN;
if (l && v) {
if (!strcmp(v, "info")) new = lINFO;
if (!strcmp(v, "debug")) new = lDEBUG;
if (!strcmp(v, "sdebug")) new = lSDEBUG;
if (!strcmp(l, "all") || !strcmp(l, "slimproto")) log_slimproto = new;
if (!strcmp(l, "all") || !strcmp(l, "stream")) log_stream = new;
if (!strcmp(l, "all") || !strcmp(l, "decode")) log_decode = new;
if (!strcmp(l, "all") || !strcmp(l, "output")) log_output = new;
#if IR
if (!strcmp(l, "all") || !strcmp(l, "ir")) log_ir = new;
#endif
} else {
fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg);
usage(argv[0]);
exit(1);
}
}
break;
case 'f':
logfile = optarg;
break;
case 'm':
{
int byte = 0;
char *tmp;
if (!strncmp(optarg, "00:04:20", 8)) {
LOG_ERROR("ignoring mac address from hardware player range 00:04:20:**:**:**");
} else {
char *t = strtok(optarg, ":");
while (t && byte < 6) {
mac[byte++] = (u8_t)strtoul(t, &tmp, 16);
t = strtok(NULL, ":");
}
}
}
break;
case 'M':
modelname = optarg;
break;
case 'r':
{
char *rstr = next_param(optarg, ':');
char *dstr = next_param(NULL, ':');
if (rstr && strstr(rstr, ",")) {
// parse sample rates and sort them
char *r = next_param(rstr, ',');
unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 };
int i, j;
int last = 999999;
for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) {
tmp[i] = atoi(r);
r = next_param(NULL, ',');
}
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
int largest = 0;
for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) {
if (tmp[j] > largest && tmp[j] < last) {
largest = tmp[j];
}
}
rates[i] = last = largest;
}
} else if (rstr) {
// optstr is <min>-<max> or <max>, extract rates from test rates within this range
unsigned ref[] TEST_RATES;
char *str1 = next_param(rstr, '-');
char *str2 = next_param(NULL, '-');
unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]);
unsigned min = str1 && str2 ? atoi(str1) : 0;
unsigned tmp;
int i, j;
if (max < min) { tmp = max; max = min; min = tmp; }
rates[0] = max;
for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
if (ref[i] < rates[j-1] && ref[i] >= min) {
rates[j++] = ref[i];
}
}
}
if (dstr) {
rate_delay = atoi(dstr);
}
if (rates[0]) {
user_rates = true;
}
}
break;
case 's':
server = optarg;
break;
case 'n':
name = optarg;
break;
case 'N':
namefile = optarg;
break;
case 'W':
pcm_check_header = true;
break;
#if ALSA
case 'p':
rt_priority = atoi(optarg);
if (rt_priority > 99 || rt_priority < 1) {
fprintf(stderr, "\nError: invalid priority: %s\n\n", optarg);
usage(argv[0]);
exit(1);
}
break;
#endif
#if LINUX || FREEBSD || SUN
case 'P':
pidfile = optarg;
break;
#endif
#ifndef EMBEDDED
case 'l':
list_devices();
exit(0);
break;
#endif
#if RESAMPLE || RESAMPLE16
case 'u':
case 'R':
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
resample = argv[optind++];
} else {
resample = "";
}
break;
case 'Z':
maxSampleRate = atoi(optarg);
break;
#endif
#if DSD
case 'D':
dsd_outfmt = DOP;
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
char *dstr = next_param(argv[optind++], ':');
char *fstr = next_param(NULL, ':');
dsd_delay = dstr ? atoi(dstr) : 0;
if (fstr) {
if (!strcmp(fstr, "dop")) dsd_outfmt = DOP;
if (!strcmp(fstr, "u8")) dsd_outfmt = DSD_U8;
if (!strcmp(fstr, "u16le")) dsd_outfmt = DSD_U16_LE;
if (!strcmp(fstr, "u32le")) dsd_outfmt = DSD_U32_LE;
if (!strcmp(fstr, "u16be")) dsd_outfmt = DSD_U16_BE;
if (!strcmp(fstr, "u32be")) dsd_outfmt = DSD_U32_BE;
if (!strcmp(fstr, "dop24")) dsd_outfmt = DOP_S24_LE;
if (!strcmp(fstr, "dop24_3")) dsd_outfmt = DOP_S24_3LE;
}
}
break;
#endif
#if VISEXPORT
case 'v':
visexport = true;
break;
#endif
#if ALSA
case 'O':
mixer_device = optarg;
break;
case 'L':
list_mixers(mixer_device);
exit(0);
break;
case 'X':
linear_volume = true;
break;
case 'U':
output_mixer_unmute = true;
case 'V':
if (output_mixer) {
fprintf(stderr, "-U and -V option should not be used at same time\n");
exit(1);
}
output_mixer = optarg;
break;
#endif
#if IR
case 'i':
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
lircrc = argv[optind++];
} else {
lircrc = "~/.lircrc"; // liblirc_client will expand ~/
}
break;
#endif
#if defined(GPIO) && defined(RPI)
case 'G':
if (power_script != NULL){
fprintf(stderr, "-G and -S options cannot be used together \n\n" );
usage(argv[0]);
exit(1);
}
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
char *gp = next_param(argv[optind++], ':');
char *go = next_param (NULL, ':');
gpio_pin = atoi(gp);
if (go != NULL){
if ((strcmp(go, "H")==0)|(strcmp(go, "h")==0)){
gpio_active_low=false;
}else if((strcmp(go, "L")==0)|(strcmp(go, "l")==0)){
gpio_active_low=true;
}else{
fprintf(stderr,"Must set output to be active High or Low i.e. -G18:H or -G18:L\n");
usage(argv[0]);
exit(1);
}
}else{
fprintf(stderr,"-G Option Error\n");
usage(argv[0]);
exit(1);
}
gpio_active = true;
relay(0);
} else {
fprintf(stderr, "Error in GPIO Pin assignment.\n");
usage(argv[0]);
exit(1);
}
break;
#endif
#if GPIO
case 'S':
if (gpio_active){
fprintf(stderr, "-G and -S options cannot be used together \n\n" );
usage(argv[0]);
exit(1);
}
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
power_script = argv[optind++];
if( access( power_script, R_OK|X_OK ) == -1 ) {
// file doesn't exist
fprintf(stderr, "Script %s, not found\n\n", argv[optind-1]);
usage(argv[0]);
exit(1);
}
} else {
fprintf(stderr, "No Script Name Given.\n\n");
usage(argv[0]);
exit(1);
}
relay_script(0);
break;
#endif
#if LINUX || FREEBSD || SUN
case 'z':
daemonize = true;
#if SUN
init_daemonize();
#endif /* SUN */
break;
#endif
case 't':
license();
exit(0);
case '?':
usage(argv[0]);
exit(0);
break;
default:
fprintf(stderr, "Arg error: %s\n", argv[optind]);
break;
}
}
// warn if command line includes something which isn't parsed
if (optind < argc) {
fprintf(stderr, "\nError: command line argument error\n\n");
usage(argv[0]);
exit(1);
}
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
#if defined(SIGQUIT)
signal(SIGQUIT, sighandler);
#endif
#if defined(SIGHUP)
signal(SIGHUP, sighandler);
#endif
#if USE_SSL && !LINKALL
ssl_loaded = load_ssl_symbols();
#endif
// set the output buffer size if not specified on the command line, take account of resampling
if (!output_buf_size) {
output_buf_size = OUTPUTBUF_SIZE;
if (resample) {
unsigned scale = 8;
if (rates[0]) {
scale = rates[0] / 44100;
if (scale > 8) scale = 8;
if (scale < 1) scale = 1;
}
output_buf_size *= scale;
}
}
if (logfile) {
if (!freopen(logfile, "a", stderr)) {
fprintf(stderr, "error opening logfile %s: %s\n", logfile, strerror(errno));
} else {
if (log_output >= lINFO || log_stream >= lINFO || log_decode >= lINFO || log_slimproto >= lINFO) {
fprintf(stderr, "\n%s\n", cmdline);
}
}
}
#if LINUX || FREEBSD || SUN
if (pidfile) {
if (!(pidfp = fopen(pidfile, "w")) ) {
fprintf(stderr, "Error opening pidfile %s: %s\n", pidfile, strerror(errno));
exit(1);
}
pidfile = realpath(pidfile, NULL); // daemonize will change cwd
}
if (daemonize) {
if (daemon(0, logfile ? 1 : 0)) {
fprintf(stderr, "error daemonizing: %s\n", strerror(errno));
}
}
if (pidfp) {
fprintf(pidfp, "%d\n", (int) getpid());
fclose(pidfp);
}
#endif
#if WIN
winsock_init();
#endif
stream_init(log_stream, stream_buf_size);
#if EMBEDDED
output_init_embedded(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
#else
if (!strcmp(output_device, "-")) {
output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay);
} else {
#if ALSA
output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, mixer_device, output_mixer,
output_mixer_unmute, linear_volume);
#endif
#if PORTAUDIO
output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
#endif
}
#endif
#if DSD
dsd_init(dsd_outfmt, dsd_delay);
#endif
#if VISEXPORT
if (visexport) {
output_vis_init(log_output, mac);
}
#endif
decode_init(log_decode, include_codecs, exclude_codecs);
#if RESAMPLE || RESAMPLE16
if (resample) {
process_init(resample);
}
#endif
#if IR
if (lircrc) {
ir_init(log_ir, lircrc);
}
#endif
if (name && namefile) {
fprintf(stderr, "-n and -N option should not be used at same time\n");
exit(1);
}
slimproto(log_slimproto, server, mac, name, namefile, modelname, maxSampleRate);
decode_close();
stream_close();
#if EMBEDDED
output_close_embedded();
#else
if (!strcmp(output_device, "-")) {
output_close_stdout();
} else {
#if ALSA
output_close_alsa();
#endif
#if PORTAUDIO
output_close_pa();
#endif
}
#endif
#if IR
ir_close();
#endif
#if WIN
winsock_close();
#endif
#if LINUX || FREEBSD || SUN
if (pidfile) {
unlink(pidfile);
free(pidfile);
}
#endif
#if USE_SSL && !LINKALL
free_ssl_symbols();
#endif
exit(0);
}

View File

@@ -0,0 +1,8 @@
#include "squeezelite.h"
extern log_level loglevel;
struct codec *register_mpg(void) {
LOG_INFO("mpg unavailable");
return NULL;
}

View File

@@ -0,0 +1,448 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Common output function
#include "squeezelite.h"
static log_level loglevel;
struct outputstate output;
static struct buffer buf;
struct buffer *outputbuf = &buf;
u8_t *silencebuf;
#if DSD
u8_t *silencebuf_dsd;
#endif
bool user_rates = false;
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
// functions starting _* are called with mutex locked
frames_t _output_frames(frames_t avail) {
frames_t frames, size;
bool silence;
s32_t cross_gain_in = 0, cross_gain_out = 0; ISAMPLE_T *cross_ptr = NULL;
s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL;
s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR;
if (output.invert) { gainL = -gainL; gainR = -gainR; }
frames = _buf_used(outputbuf) / BYTES_PER_FRAME;
silence = false;
// start when threshold met
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
output.state = OUTPUT_RUNNING;
LOG_INFO("start buffer frames: %u", frames);
wake_controller();
}
// skip ahead - consume outputbuf but play nothing
if (output.state == OUTPUT_SKIP_FRAMES) {
if (frames > 0) {
frames_t skip = min(frames, output.skip_frames);
LOG_INFO("skip %u of %u frames", skip, output.skip_frames);
frames -= skip;
output.frames_played += skip;
while (skip > 0) {
frames_t cont_frames = min(skip, _buf_cont_read(outputbuf) / BYTES_PER_FRAME);
skip -= cont_frames;
_buf_inc_readp(outputbuf, cont_frames * BYTES_PER_FRAME);
}
}
output.state = OUTPUT_RUNNING;
}
// pause frames - play silence for required frames
if (output.state == OUTPUT_PAUSE_FRAMES) {
LOG_INFO("pause %u frames", output.pause_frames);
if (output.pause_frames == 0) {
output.state = OUTPUT_RUNNING;
} else {
silence = true;
frames = min(avail, output.pause_frames);
frames = min(frames, MAX_SILENCE_FRAMES);
output.pause_frames -= frames;
}
}
// start at - play silence until jiffies reached
if (output.state == OUTPUT_START_AT) {
u32_t now = gettime_ms();
if (now >= output.start_at || output.start_at > now + 10000) {
output.state = OUTPUT_RUNNING;
} else {
u32_t delta_frames = (output.start_at - now) * output.current_sample_rate / 1000;
silence = true;
frames = min(avail, delta_frames);
frames = min(frames, MAX_SILENCE_FRAMES);
}
}
// play silence if buffering or no frames
if (output.state <= OUTPUT_BUFFER || frames == 0) {
silence = true;
frames = min(avail, MAX_SILENCE_FRAMES);
}
LOG_SDEBUG("avail: %d frames: %d silence: %d", avail, frames, silence);
frames = min(frames, avail);
size = frames;
while (size > 0) {
frames_t out_frames;
frames_t cont_frames = _buf_cont_read(outputbuf) / BYTES_PER_FRAME;
int wrote;
if (output.track_start && !silence) {
if (output.track_start == outputbuf->readp) {
unsigned delay = 0;
if (output.current_sample_rate != output.next_sample_rate) {
delay = output.rate_delay;
}
IF_DSD(
if (output.outfmt != output.next_fmt) {
delay = output.dsd_delay;
}
)
frames -= size;
// add silence delay in two halves, before and after track start on rate or pcm-dop change
if (delay) {
output.state = OUTPUT_PAUSE_FRAMES;
if (!output.delay_active) {
output.pause_frames = output.current_sample_rate * delay / 2000;
output.delay_active = true; // first delay - don't process track start
break;
} else {
output.pause_frames = output.next_sample_rate * delay / 2000;
output.delay_active = false; // second delay - process track start
}
}
LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain);
output.frames_played = 0;
output.track_started = true;
output.track_start_time = gettime_ms();
output.current_sample_rate = output.next_sample_rate;
IF_DSD(
output.outfmt = output.next_fmt;
)
if (output.fade == FADE_INACTIVE || output.fade_mode != FADE_CROSSFADE) {
output.current_replay_gain = output.next_replay_gain;
}
output.track_start = NULL;
break;
} else if (output.track_start > outputbuf->readp) {
// reduce cont_frames so we find the next track start at beginning of next chunk
cont_frames = min(cont_frames, (output.track_start - outputbuf->readp) / BYTES_PER_FRAME);
}
}
IF_DSD(
if (output.outfmt != PCM) {
gainL = gainR = FIXED_ONE;
}
)
if (output.fade && !silence) {
if (output.fade == FADE_DUE) {
if (output.fade_start == outputbuf->readp) {
LOG_INFO("fade start reached");
output.fade = FADE_ACTIVE;
} else if (output.fade_start > outputbuf->readp) {
cont_frames = min(cont_frames, (output.fade_start - outputbuf->readp) / BYTES_PER_FRAME);
}
}
if (output.fade == FADE_ACTIVE) {
// find position within fade
frames_t cur_f = outputbuf->readp >= output.fade_start ? (outputbuf->readp - output.fade_start) / BYTES_PER_FRAME :
(outputbuf->readp + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
frames_t dur_f = output.fade_end >= output.fade_start ? (output.fade_end - output.fade_start) / BYTES_PER_FRAME :
(output.fade_end + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
if (cur_f >= dur_f) {
if (output.fade_mode == FADE_INOUT && output.fade_dir == FADE_DOWN) {
LOG_INFO("fade down complete, starting fade up");
output.fade_dir = FADE_UP;
output.fade_start = outputbuf->readp;
output.fade_end = outputbuf->readp + dur_f * BYTES_PER_FRAME;
if (output.fade_end >= outputbuf->wrap) {
output.fade_end -= outputbuf->size;
}
cur_f = 0;
} else if (output.fade_mode == FADE_CROSSFADE) {
LOG_INFO("crossfade complete");
if (_buf_used(outputbuf) >= dur_f * BYTES_PER_FRAME) {
_buf_inc_readp(outputbuf, dur_f * BYTES_PER_FRAME);
LOG_INFO("skipped crossfaded start");
} else {
LOG_WARN("unable to skip crossfaded start");
}
output.fade = FADE_INACTIVE;
output.current_replay_gain = output.next_replay_gain;
} else {
LOG_INFO("fade complete");
output.fade = FADE_INACTIVE;
}
}
// if fade in progress set fade gain, ensure cont_frames reduced so we get to end of fade at start of chunk
if (output.fade) {
if (output.fade_end > outputbuf->readp) {
cont_frames = min(cont_frames, (output.fade_end - outputbuf->readp) / BYTES_PER_FRAME);
}
if (output.fade_dir == FADE_UP || output.fade_dir == FADE_DOWN) {
// fade in, in-out, out handled via altering standard gain
s32_t fade_gain;
if (output.fade_dir == FADE_DOWN) {
cur_f = dur_f - cur_f;
}
fade_gain = to_gain((float)cur_f / (float)dur_f);
gainL = gain(gainL, fade_gain);
gainR = gain(gainR, fade_gain);
if (output.invert) { gainL = -gainL; gainR = -gainR; }
}
if (output.fade_dir == FADE_CROSS) {
// cross fade requires special treatment - performed later based on these values
// support different replay gain for old and new track by retaining old value until crossfade completes
if (_buf_used(outputbuf) / BYTES_PER_FRAME > dur_f + size) {
cross_gain_in = to_gain((float)cur_f / (float)dur_f);
cross_gain_out = FIXED_ONE - cross_gain_in;
if (output.current_replay_gain) {
cross_gain_out = gain(cross_gain_out, output.current_replay_gain);
}
if (output.next_replay_gain) {
cross_gain_in = gain(cross_gain_in, output.next_replay_gain);
}
gainL = output.gainL;
gainR = output.gainR;
if (output.invert) { gainL = -gainL; gainR = -gainR; }
cross_ptr = (ISAMPLE_T *)(output.fade_end + cur_f * BYTES_PER_FRAME);
} else {
LOG_INFO("unable to continue crossfade - too few samples");
output.fade = FADE_INACTIVE;
}
}
}
}
}
out_frames = !silence ? min(size, cont_frames) : size;
wrote = output.write_cb(out_frames, silence, gainL, gainR, cross_gain_in, cross_gain_out, &cross_ptr);
if (wrote <= 0) {
frames -= size;
break;
} else {
out_frames = (frames_t)wrote;
}
size -= out_frames;
_vis_export(outputbuf, &output, out_frames, silence);
if (!silence) {
_buf_inc_readp(outputbuf, out_frames * BYTES_PER_FRAME);
output.frames_played += out_frames;
}
}
LOG_SDEBUG("wrote %u frames", frames);
return frames;
}
void _checkfade(bool start) {
frames_t bytes;
LOG_INFO("fade mode: %u duration: %u %s", output.fade_mode, output.fade_secs, start ? "track-start" : "track-end");
bytes = output.next_sample_rate * BYTES_PER_FRAME * output.fade_secs;
if (output.fade_mode == FADE_INOUT) {
/* align on a frame boundary */
bytes = ((bytes / 2) / BYTES_PER_FRAME) * BYTES_PER_FRAME;
}
if (start && (output.fade_mode == FADE_IN || (output.fade_mode == FADE_INOUT && _buf_used(outputbuf) == 0))) {
bytes = min(bytes, outputbuf->size - BYTES_PER_FRAME); // shorter than full buffer otherwise start and end align
LOG_INFO("fade IN: %u frames", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_UP;
output.fade_start = outputbuf->writep;
output.fade_end = output.fade_start + bytes;
if (output.fade_end >= outputbuf->wrap) {
output.fade_end -= outputbuf->size;
}
}
if (!start && (output.fade_mode == FADE_OUT || output.fade_mode == FADE_INOUT)) {
bytes = min(_buf_used(outputbuf), bytes);
LOG_INFO("fade %s: %u frames", output.fade_mode == FADE_INOUT ? "IN-OUT" : "OUT", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_DOWN;
output.fade_start = outputbuf->writep - bytes;
if (output.fade_start < outputbuf->buf) {
output.fade_start += outputbuf->size;
}
output.fade_end = outputbuf->writep;
}
if (start && output.fade_mode == FADE_CROSSFADE) {
if (_buf_used(outputbuf) != 0) {
if (output.next_sample_rate != output.current_sample_rate) {
LOG_INFO("crossfade disabled as sample rates differ");
return;
}
bytes = min(bytes, _buf_used(outputbuf)); // max of current remaining samples from previous track
bytes = min(bytes, (frames_t)(outputbuf->size * 0.9)); // max of 90% of outputbuf as we consume additional buffer during crossfade
LOG_INFO("CROSSFADE: %u frames", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_CROSS;
output.fade_start = outputbuf->writep - bytes;
if (output.fade_start < outputbuf->buf) {
output.fade_start += outputbuf->size;
}
output.fade_end = outputbuf->writep;
output.track_start = output.fade_start;
} else if (outputbuf->size == OUTPUTBUF_SIZE && outputbuf->readp == outputbuf->buf) {
// if default setting used and nothing in buffer attempt to resize to provide full crossfade support
LOG_INFO("resize outputbuf for crossfade");
_buf_resize(outputbuf, OUTPUTBUF_SIZE_CROSSFADE);
#if LINUX || FREEBSD
touch_memory(outputbuf->buf, outputbuf->size);
#endif
}
}
}
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle) {
unsigned i;
loglevel = level;
output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
LOG_DEBUG("outputbuf size: %u", output_buf_size);
buf_init(outputbuf, output_buf_size);
if (!outputbuf->buf) {
LOG_ERROR("unable to malloc output buffer");
exit(0);
}
silencebuf = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
if (!silencebuf) {
LOG_ERROR("unable to malloc silence buffer");
exit(0);
}
memset(silencebuf, 0, MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
IF_DSD(
silencebuf_dsd = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
if (!silencebuf_dsd) {
LOG_ERROR("unable to malloc silence dsd buffer");
exit(0);
}
dsd_silence_frames((u32_t *)silencebuf_dsd, MAX_SILENCE_FRAMES);
)
LOG_DEBUG("idle timeout: %u", idle);
output.state = idle ? OUTPUT_OFF: OUTPUT_STOPPED;
output.device = device;
output.fade = FADE_INACTIVE;
output.invert = false;
output.error_opening = false;
output.idle_to = (u32_t) idle;
/* Skip test_open for stdout, set default sample rates */
if ( output.device[0] == '-' ) {
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
output.supported_rates[i] = rates[i];
}
}
else {
if (!test_open(output.device, output.supported_rates, user_rates)) {
LOG_ERROR("unable to open output device: %s", output.device);
exit(0);
}
}
if (user_rates) {
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
output.supported_rates[i] = rates[i];
}
}
// set initial sample rate, preferring 44100
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
if (output.supported_rates[i] == 44100) {
output.default_sample_rate = 44100;
break;
}
}
if (!output.default_sample_rate) {
output.default_sample_rate = output.supported_rates[0];
}
output.current_sample_rate = output.default_sample_rate;
if (loglevel >= lINFO) {
char rates_buf[10 * MAX_SUPPORTED_SAMPLERATES] = "";
for (i = 0; output.supported_rates[i]; ++i) {
char s[10];
sprintf(s, "%d ", output.supported_rates[i]);
strcat(rates_buf, s);
}
LOG_INFO("supported rates: %s", rates_buf);
}
}
void output_close_common(void) {
buf_destroy(outputbuf);
free(silencebuf);
IF_DSD(
free(silencebuf_dsd);
)
}
void output_flush(void) {
LOG_INFO("flush output buffer");
buf_flush(outputbuf);
LOCK;
output.fade = FADE_INACTIVE;
if (output.state != OUTPUT_OFF) {
output.state = OUTPUT_STOPPED;
if (output.error_opening) {
output.current_sample_rate = output.default_sample_rate;
}
output.delay_active = false;
}
output.frames_played = 0;
UNLOCK;
}

View File

@@ -0,0 +1,175 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, 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 "perf_trace.h"
extern struct outputstate output;
extern struct buffer *outputbuf;
extern struct buffer *streambuf;
extern u8_t *silencebuf;
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
#define LOCK_S mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define FRAME_BLOCK MAX_SILENCE_FRAMES
#define STATS_REPORT_DELAY_MS 15000
extern void hal_bluetooth_init(const char * options);
static log_level loglevel;
uint8_t * btout;
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);
#define DECLARE_ALL_MIN_MAX \
DECLARE_MIN_MAX(req);\
DECLARE_MIN_MAX(rec);\
DECLARE_MIN_MAX(bt);\
DECLARE_MIN_MAX(under);\
DECLARE_MIN_MAX(stream_buf);\
DECLARE_MIN_MAX_DURATION(lock_out_time)
#define RESET_ALL_MIN_MAX \
RESET_MIN_MAX(bt); \
RESET_MIN_MAX(req); \
RESET_MIN_MAX(rec); \
RESET_MIN_MAX(under); \
RESET_MIN_MAX(stream_buf); \
RESET_MIN_MAX_DURATION(lock_out_time)
DECLARE_ALL_MIN_MAX;
void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
hal_bluetooth_init(device);
output.write_cb = &_write_frames;
}
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) {
assert(btout != NULL);
if (!silence ) {
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
memcpy(btout, 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;
while (count--) {
*_optr++ = *_iptr++ >> 16;
*_optr++ = *_iptr++ >> 16;
}
}
#endif
} else {
u8_t *buf = silencebuf;
memcpy(btout, buf, out_frames * BYTES_PER_FRAME);
}
return (int)out_frames;
}
int32_t output_bt_data(uint8_t *data, int32_t len) {
int32_t avail_data = 0, wanted_len = 0, start_timer = 0;
if (len < 0 || data == NULL ) {
return 0;
}
btout = data;
// This is how the BTC layer calculates the number of bytes to
// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes
wanted_len=len;
SET_MIN_MAX(len,req);
TIME_MEASUREMENT_START(start_timer);
LOCK;
output.device_frames = 0; // todo: check if this is the right way do to this.
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
SET_MIN_MAX_SIZED(_buf_used(outputbuf),bt,outputbuf->size);
do {
avail_data = _output_frames( wanted_len/BYTES_PER_FRAME )*BYTES_PER_FRAME; // Keep the transfer buffer full
wanted_len-=avail_data;
} while (wanted_len > 0 && avail_data != 0);
if (wanted_len > 0) {
SET_MIN_MAX(wanted_len, under);
}
UNLOCK;
SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);
SET_MIN_MAX((len-wanted_len), rec);
TIME_MEASUREMENT_START(start_timer);
return len-wanted_len;
}
void output_bt_tick(void) {
static time_t lastTime=0;
LOCK_S;
SET_MIN_MAX_SIZED(_buf_used(streambuf), stream_buf, streambuf->size);
UNLOCK_S;
if (lastTime <= gettime_ms() )
{
lastTime = gettime_ms() + STATS_REPORT_DELAY_MS;
LOG_INFO("Statistics over %u secs. " , STATS_REPORT_DELAY_MS/1000);
LOG_INFO(" +==========+==========+================+=====+================+");
LOG_INFO(" | max | min | average | avg | count |");
LOG_INFO(" | (bytes) | (bytes) | (bytes) | pct | |");
LOG_INFO(" +==========+==========+================+=====+================+");
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("stream avl",stream_buf));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output avl",bt));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("requested",req));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("underrun",under));
LOG_INFO( " +==========+==========+================+=====+================+");
LOG_INFO("\n");
LOG_INFO(" ==========+==========+===========+===========+ ");
LOG_INFO(" max (us) | min (us) | avg(us) | count | ");
LOG_INFO(" ==========+==========+===========+===========+ ");
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Out Buf Lock",lock_out_time));
LOG_INFO(" ==========+==========+===========+===========+");
RESET_ALL_MIN_MAX;
}
}

View File

@@ -0,0 +1,118 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, 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"
extern struct outputstate output;
extern struct buffer *outputbuf;
#define FRAME_BLOCK MAX_SILENCE_FRAMES
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
extern void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle);
extern void output_init_i2s(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle);
extern void output_close_i2s(void);
static log_level loglevel;
static void (*volume_cb)(unsigned left, unsigned right);
static void (*close_cb)(void);
void output_init_embedded(log_level level, char *device, unsigned output_buf_size, char *params,
unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
LOG_INFO("init device: %s", device);
memset(&output, 0, sizeof(output));
output_init_common(level, device, output_buf_size, rates, idle);
output.start_frames = FRAME_BLOCK;
output.rate_delay = rate_delay;
if (strstr(device, "BT ")) {
LOG_INFO("init Bluetooth");
output_init_bt(level, device, output_buf_size, params, rates, rate_delay, idle);
} else {
LOG_INFO("init I2S");
close_cb = output_close_i2s;
output_init_i2s(level, device, output_buf_size, params, rates, rate_delay, idle);
}
LOG_INFO("init completed.");
}
void output_close_embedded(void) {
LOG_INFO("close output");
output_close_common();
if (close_cb) (*close_cb)();
}
void set_volume(unsigned left, unsigned right) {
LOG_DEBUG("setting internal gain left: %u right: %u", left, right);
if (!volume_cb) {
LOCK;
output.gainL = left;
output.gainR = right;
UNLOCK;
} else (*volume_cb)(left, right);
}
bool test_open(const char *device, unsigned rates[], bool userdef_rates) {
memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned));
if (!strcmp(device, "BT")) {
rates[0] = 44100;
} else {
unsigned _rates[] = { 96000, 88200, 48000, 44100, 32000, 0 };
memcpy(rates, _rates, sizeof(_rates));
}
return true;
}
char* output_state_str(void){
output_state state;
LOCK;
state = output.state;
UNLOCK;
switch (state) {
case OUTPUT_OFF: return STR(OUTPUT_OFF);
case OUTPUT_STOPPED: return STR(OUTPUT_STOPPED);
case OUTPUT_BUFFER: return STR(OUTPUT_BUFFER);
case OUTPUT_RUNNING: return STR(OUTPUT_RUNNING);
case OUTPUT_PAUSE_FRAMES: return STR(OUTPUT_PAUSE_FRAMES);
case OUTPUT_SKIP_FRAMES: return STR(OUTPUT_SKIP_FRAMES);
case OUTPUT_START_AT: return STR(OUTPUT_START_AT);
default: return "OUTPUT_UNKNOWN_STATE";
}
}
bool output_stopped(void) {
output_state state;
LOCK;
state = output.state;
UNLOCK;
return state <= OUTPUT_STOPPED;
}

View File

@@ -0,0 +1,380 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, 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 "driver/i2s.h"
#include "perf_trace.h"
#include <signal.h>
#include "time.h"
#define DECLARE_ALL_MIN_MAX \
DECLARE_MIN_MAX(o); \
DECLARE_MIN_MAX(s); \
DECLARE_MIN_MAX(loci2sbuf); \
DECLARE_MIN_MAX(req); \
DECLARE_MIN_MAX(rec); \
DECLARE_MIN_MAX(over); \
DECLARE_MIN_MAX(i2savailable);\
DECLARE_MIN_MAX(i2s_time); \
DECLARE_MIN_MAX(buffering);
#define RESET_ALL_MIN_MAX \
RESET_MIN_MAX(o); \
RESET_MIN_MAX(s); \
RESET_MIN_MAX(loci2sbuf); \
RESET_MIN_MAX(req); \
RESET_MIN_MAX(rec); \
RESET_MIN_MAX(over); \
RESET_MIN_MAX(i2savailable);\
RESET_MIN_MAX(i2s_time);\
RESET_MIN_MAX(buffering);
#define STATS_PERIOD_MS 5000
// Prevent compile errors if dac output is
// included in the build and not actually activated in menuconfig
#ifndef CONFIG_I2S_BCK_IO
#define CONFIG_I2S_BCK_IO -1
#endif
#ifndef CONFIG_I2S_WS_IO
#define CONFIG_I2S_WS_IO -1
#endif
#ifndef CONFIG_I2S_DO_IO
#define CONFIG_I2S_DO_IO -1
#endif
#ifndef CONFIG_I2S_NUM
#define CONFIG_I2S_NUM -1
#endif
#if REPACK && BYTES_PER_FRAMES == 4
#error "REPACK is not compatible with BYTES_PER_FRAME=4"
#endif
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
#define FRAME_BLOCK MAX_SILENCE_FRAMES
extern struct outputstate output;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern u8_t *silencebuf;
static log_level loglevel;
static size_t i2s_buffer_size = 0;
static bool running = true;
static bool isI2SStarted=false;
static struct buffer _i2s_buffer_structure;
static struct buffer *i2sbuffer=&_i2s_buffer_structure;
static i2s_config_t i2s_config;
static int bytes_per_frame;
static thread_type thread;
static pthread_t stats_thread;
DECLARE_ALL_MIN_MAX;
static int _i2s_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);
static void *output_thread_i2s();
static void *output_thread_i2s_stats();
/****************************************************************************************
* Initialize the DAC output
*/
void output_init_i2s(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
#ifdef CONFIG_I2S_BITS_PER_CHANNEL
switch (CONFIG_I2S_BITS_PER_CHANNEL) {
case 24:
output.format = S24_BE;
bytes_per_frame = 2*3;
break;
case 16:
output.format = S16_BE;
bytes_per_frame = 2*2;
break;
case 8:
output.format = S8_BE;
bytes_per_frame = 2*4;
break;
default:
LOG_ERROR("Unsupported bit depth %d",CONFIG_I2S_BITS_PER_CHANNEL);
break;
}
#else
output.format = S16_LE;
bytes_per_frame = 2*2;
#endif
output.write_cb = &_i2s_write_frames;
running=true;
// todo: move this to a hardware abstraction layer
//hal_dac_init(device);
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX; // Only TX
i2s_config.sample_rate = output.current_sample_rate;
i2s_config.bits_per_sample = bytes_per_frame * 8 / 2;
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; //2-channels
i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
// todo: tune this parameter. Expressed in number of samples. Byte size depends on bit depth.
i2s_config.dma_buf_count = 10;
// From the I2S driver source, the DMA buffer size is 4092 bytes.
// so buf_len * 2 channels * 2 bytes/sample should be < 4092 or else it will be resized.
i2s_config.dma_buf_len = FRAME_BLOCK/2;
i2s_config.use_apll = false;
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
i2s_pin_config_t pin_config = { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num =
CONFIG_I2S_WS_IO, .data_out_num = CONFIG_I2S_DO_IO, .data_in_num = -1 //Not used
};
LOG_INFO("Initializing I2S with rate: %d, bits per sample: %d, buffer len: %d, number of buffers: %d ",
i2s_config.sample_rate, i2s_config.bits_per_sample, i2s_config.dma_buf_len, i2s_config.dma_buf_count);
i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(CONFIG_I2S_NUM, &pin_config);
i2s_set_clk(CONFIG_I2S_NUM, output.current_sample_rate, i2s_config.bits_per_sample, 2);
isI2SStarted=false;
i2s_stop(CONFIG_I2S_NUM);
i2s_buffer_size = 5*FRAME_BLOCK*bytes_per_frame;
LOG_INFO("Allocating local DAC transfer buffer of %u bytes.",i2s_buffer_size);
buf_init(i2sbuffer,i2s_buffer_size);
if (!i2sbuffer->buf) {
LOG_ERROR("unable to malloc i2s buffer");
exit(0);
}
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE);
pthread_create(&thread, &attr, output_thread_i2s, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "output_i2s");
#endif
// leave stack size to default
pthread_create(&stats_thread, NULL, output_thread_i2s_stats, NULL);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(stats_thread, "output_i2s_sts");
#endif
}
/****************************************************************************************
* Terminate DAC output
*/
void output_close_i2s(void) {
i2s_driver_uninstall(CONFIG_I2S_NUM);
buf_destroy(i2sbuffer);
}
/****************************************************************************************
* Write frames to the output buffer
*/
static int _i2s_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) {
size_t bytes = out_frames * bytes_per_frame;
assert(bytes > 0);
if (!silence) {
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 !REPACK
if (gainL != FIXED_ONE || gainR!= FIXED_ONE) {
_apply_gain(outputbuf, out_frames, gainL, gainR);
}
memcpy(i2sbuffer->writep, outputbuf->readp, bytes);
#else
obuf = outputbuf->readp;
#endif
} else {
#if !REPACK
memcpy(i2sbuffer->writep, silencebuf, bytes);
#endif
}
#if REPACK
_scale_and_pack_frames(optr, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format);
#endif
_buf_inc_writep(i2sbuffer, bytes);
return bytes / bytes_per_frame;
}
/****************************************************************************************
* Main output thread
*/
static void *output_thread_i2s() {
frames_t frames=0;
frames_t available_frames_space=0;
size_t bytes_to_send_i2s=0, // Contiguous buffer which can be addressed
i2s_bytes_written = 0,
i2s_total_bytes_written=0; //actual size that the i2s port was able to write
uint32_t timer_start=0;
static int count = 0;
output_state state;
while (running) {
i2s_bytes_written=0;
frames=0;
available_frames_space=0;
bytes_to_send_i2s=0, // Contiguous buffer which can be addressed
i2s_bytes_written = 0; //actual size that the i2s port was able to write
TIME_MEASUREMENT_START(timer_start);
LOCK;
state =output.state;
if (output.state == OUTPUT_OFF) {
UNLOCK;
LOG_INFO("Output state is off.");
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
if(isI2SStarted) {
isI2SStarted=false;
i2s_stop(CONFIG_I2S_NUM);
}
usleep(200000);
continue;
}
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
output.device_frames =0;
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
do{
// fill our buffer
available_frames_space = min(_buf_space(i2sbuffer), _buf_cont_write(i2sbuffer)) / bytes_per_frame;
if(available_frames_space)
{
frames = _output_frames( available_frames_space ); // Keep the transfer buffer full
SET_MIN_MAX( available_frames_space,req);
SET_MIN_MAX(frames,rec);
}
}while(available_frames_space>0 && frames>0);
SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);
UNLOCK;
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),buffering);
SET_MIN_MAX_SIZED(_buf_used(i2sbuffer),loci2sbuf,i2sbuffer->size);
bytes_to_send_i2s = _buf_cont_read(i2sbuffer);
SET_MIN_MAX(bytes_to_send_i2s,i2savailable);
i2s_total_bytes_written=0;
while (bytes_to_send_i2s>0 )
{
// now send all the data
TIME_MEASUREMENT_START(timer_start);
if(!isI2SStarted)
{
isI2SStarted=true;
LOG_INFO("Restarting I2S.");
i2s_start(CONFIG_I2S_NUM);
if( i2s_config.sample_rate != output.current_sample_rate)
{
i2s_config.sample_rate = output.current_sample_rate;
i2s_set_sample_rates(CONFIG_I2S_NUM, i2s_config.sample_rate);
}
}
count++;
LOG_SDEBUG("Outputting to I2S");
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
i2s_write(CONFIG_I2S_NUM, i2sbuffer->readp,bytes_to_send_i2s, &i2s_bytes_written, portMAX_DELAY);
_buf_inc_readp(i2sbuffer,i2s_bytes_written);
if(i2s_bytes_written!=bytes_to_send_i2s)
{
LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes", bytes_to_send_i2s,i2s_bytes_written);
}
LOG_SDEBUG("DONE Outputting to I2S. Wrote: %d bytes out of %d", i2s_bytes_written,bytes_to_send_i2s);
LOG_SDEBUG("Current buffer free: %10d, cont read: %10d",_buf_space(i2sbuffer),_buf_cont_read(i2sbuffer));
i2s_total_bytes_written+=i2s_bytes_written;
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),i2s_time);
if(bytes_to_send_i2s>0) {
SET_MIN_MAX(bytes_to_send_i2s-i2s_bytes_written,over);
}
bytes_to_send_i2s = _buf_cont_read(i2sbuffer);
SET_MIN_MAX(bytes_to_send_i2s,i2savailable);
}
}
return 0;
}
/****************************************************************************************
* Stats output thread
*/
static void *output_thread_i2s_stats() {
while (running) {
LOCK;
output_state state = output.state;
UNLOCK;
if(state>OUTPUT_STOPPED){
LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d",state,output.current_sample_rate, bytes_per_frame);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD1);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD2);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD3);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD4);
LOG_INFO(LINE_MIN_MAX_FORMAT_STREAM, LINE_MIN_MAX_STREAM("stream",s));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output",o));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("dac buf used",loci2sbuf));
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("i2swrite",i2savailable));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("requested",req));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("overflow",over));
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
LOG_INFO("");
LOG_INFO(" ----------+----------+-----------+-----------+ ");
LOG_INFO(" max (us) | min (us) | avg(us) | count | ");
LOG_INFO(" ----------+----------+-----------+-----------+ ");
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Buffering(us)",buffering));
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("i2s tfr(us)",i2s_time));
LOG_INFO(" ----------+----------+-----------+-----------+");
RESET_ALL_MIN_MAX;
}
usleep(STATS_PERIOD_MS *1000);
}
return NULL;
}

View File

@@ -0,0 +1,372 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Scale and pack functions
#include "squeezelite.h"
#if BYTES_PER_FRAM == 4
#define MAX_VAL16 0x7fffffffLL
#define MAX_SCALESAMPLE 0x7fffffffffffLL
#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
#else
#define MAX_SCALESAMPLE 0x7fffffffffffLL
#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
#endif
// inlining these on windows prevents them being linkable...
#if !WIN
inline
#endif
s32_t gain(s32_t gain, s32_t sample) {
s64_t res = (s64_t)gain * (s64_t)sample;
if (res > MAX_SCALESAMPLE) res = MAX_SCALESAMPLE;
if (res < MIN_SCALESAMPLE) res = MIN_SCALESAMPLE;
return (s32_t) (res >> 16);
}
#if !WIN
inline
#endif
s32_t to_gain(float f) {
return (s32_t)(f * 65536.0F);
}
void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format) {
switch(format) {
#if DSD
case U32_LE:
{
#if SL_LITTLE_ENDIAN
memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
#else
u32_t *optr = (u32_t *)(void *)outputptr;
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
#endif
}
break;
case U32_BE:
{
#if SL_LITTLE_ENDIAN
u32_t *optr = (u32_t *)(void *)outputptr;
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
#else
memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
#endif
}
break;
case U16_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
while (cnt--) {
*(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000);
inputptr += 2;
}
#else
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
#endif
}
break;
case U16_BE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(rsample & 0xff000000) >> 8 | (rsample & 0x00ff0000) << 8;
}
#else
while (cnt--) {
*(optr++) = (*(inputptr) & 0xffff0000) | (*(inputptr + 1) >> 16 & 0x0000ffff);
inputptr += 2;
}
#endif
}
break;
case U8:
{
u16_t *optr = (u16_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
while (cnt--) {
*(optr++) = (u16_t)((*(inputptr) >> 24 & 0x000000ff) | (*(inputptr + 1) >> 16 & 0x0000ff00));
inputptr += 2;
}
#else
while (cnt--) {
*(optr++) = (u16_t)((*(inputptr) >> 16 & 0x0000ff00) | (*(inputptr + 1) >> 24 & 0x000000ff));
inputptr += 2;
}
#endif
}
break;
#endif
case S16_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
*(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000);
inputptr += 2;
}
} else {
while (cnt--) {
*(optr++) = (gain(gainL, *(inputptr)) >> 16 & 0x0000ffff) | (gain(gainR, *(inputptr+1)) & 0xffff0000);
inputptr += 2;
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
}
#endif
}
break;
case S24_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
*(optr++) = *(inputptr++) >> 8;
*(optr++) = *(inputptr++) >> 8;
}
} else {
while (cnt--) {
*(optr++) = gain(gainL, *(inputptr++)) >> 8;
*(optr++) = gain(gainR, *(inputptr++)) >> 8;
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
*(optr++) =
(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
*(optr++) =
(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
}
}
#endif
}
break;
case S24_3LE:
{
u8_t *optr = (u8_t *)(void *)outputptr;
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt) {
// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
// falls through to exception case when not aligned or if less than 2 frames to move
if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
u32_t *o_ptr = (u32_t *)(void *)optr;
while (cnt >= 2) {
s32_t l1 = *(inputptr++); s32_t r1 = *(inputptr++);
s32_t l2 = *(inputptr++); s32_t r2 = *(inputptr++);
#if SL_LITTLE_ENDIAN
*(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16;
*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8;
*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
#else
*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
(r1 & 0x0000ff00) >> 8;
*(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
(l2 & 0x00ff0000) >> 16;
*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
(r2 & 0xff000000) >> 24;
#endif
optr += 12;
cnt -= 2;
}
} else {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) = (lsample & 0x0000ff00) >> 8;
*(optr++) = (lsample & 0x00ff0000) >> 16;
*(optr++) = (lsample & 0xff000000) >> 24;
*(optr++) = (rsample & 0x0000ff00) >> 8;
*(optr++) = (rsample & 0x00ff0000) >> 16;
*(optr++) = (rsample & 0xff000000) >> 24;
cnt--;
}
}
} else {
while (cnt) {
// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
// falls through to exception case when not aligned or if less than 2 frames to move
if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
u32_t *o_ptr = (u32_t *)(void *)optr;
while (cnt >= 2) {
s32_t l1 = gain(gainL, *(inputptr++)); s32_t r1 = gain(gainR, *(inputptr++));
s32_t l2 = gain(gainL, *(inputptr++)); s32_t r2 = gain(gainR, *(inputptr++));
#if SL_LITTLE_ENDIAN
*(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16;
*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8;
*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
#else
*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
(r1 & 0x0000ff00) >> 8;
*(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
(l2 & 0x00ff0000) >> 16;
*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
(r2 & 0xff000000) >> 24;
#endif
optr += 12;
cnt -= 2;
}
} else {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) = (lsample & 0x0000ff00) >> 8;
*(optr++) = (lsample & 0x00ff0000) >> 16;
*(optr++) = (lsample & 0xff000000) >> 24;
*(optr++) = (rsample & 0x0000ff00) >> 8;
*(optr++) = (rsample & 0x00ff0000) >> 16;
*(optr++) = (rsample & 0xff000000) >> 24;
cnt--;
}
}
}
}
break;
case S32_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
} else {
while (cnt--) {
*(optr++) = gain(gainL, *(inputptr++));
*(optr++) = gain(gainR, *(inputptr++));
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 |
(lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24;
*(optr++) =
(rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 |
(rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24;
}
}
#endif
}
break;
default:
break;
}
}
#if !WIN
inline
#endif
void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) {
ISAMPLE_T *ptr = (ISAMPLE_T *)(void *)outputbuf->readp;
frames_t count = out_frames * 2;
while (count--) {
if (*cross_ptr > (ISAMPLE_T *)outputbuf->wrap) {
*cross_ptr -= outputbuf->size / BYTES_PER_FRAME * 2;
}
*ptr = gain(cross_gain_out, *ptr) + gain(cross_gain_in, **cross_ptr);
ptr++; (*cross_ptr)++;
}
}
#if !WIN
inline
#endif
void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR) {
ISAMPLE_T *ptrL = (ISAMPLE_T *)(void *)outputbuf->readp;
ISAMPLE_T *ptrR = (ISAMPLE_T *)(void *)outputbuf->readp + 1;
while (count--) {
*ptrL = gain(gainL, *ptrL);
*ptrR = gain(gainR, *ptrR);
ptrL += 2;
ptrR += 2;
}
}

View File

@@ -0,0 +1,488 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
#if BYTES_PER_FRAME == 4
#define SHIFT 16
#define OPTR_T u16_t
#else
#define OPTR_T u32_t
#define SHIFT 0
#endif
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;
bool pcm_check_header = false;
#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
#define MAX_DECODE_FRAMES 4096
static u32_t sample_rates[] = {
11025, 22050, 32000, 44100, 48000, 8000, 12000, 16000, 24000, 96000, 88200, 176400, 192000, 352800, 384000, 705600, 768000
};
static u32_t sample_rate;
static u32_t sample_size;
static u32_t channels;
static bool bigendian;
static bool limit;
static u32_t audio_left;
static u32_t bytes_per_frame;
typedef enum { UNKNOWN = 0, WAVE, AIFF } header_format;
static void _check_header(void) {
u8_t *ptr = streambuf->readp;
unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
header_format format = UNKNOWN;
// simple parsing of wav and aiff headers and get to samples
if (bytes > 12) {
if (!memcmp(ptr, "RIFF", 4) && !memcmp(ptr+8, "WAVE", 4)) {
LOG_INFO("WAVE");
format = WAVE;
} else if (!memcmp(ptr, "FORM", 4) && (!memcmp(ptr+8, "AIFF", 4) || !memcmp(ptr+8, "AIFC", 4))) {
LOG_INFO("AIFF");
format = AIFF;
}
}
if (format != UNKNOWN) {
ptr += 12;
bytes -= 12;
while (bytes >= 8) {
char id[5];
unsigned len;
memcpy(id, ptr, 4);
id[4] = '\0';
if (format == WAVE) {
len = *(ptr+4) | *(ptr+5) << 8 | *(ptr+6) << 16| *(ptr+7) << 24;
} else {
len = *(ptr+4) << 24 | *(ptr+5) << 16 | *(ptr+6) << 8 | *(ptr+7);
}
LOG_INFO("header: %s len: %d", id, len);
if (format == WAVE && !memcmp(ptr, "data", 4)) {
ptr += 8;
_buf_inc_readp(streambuf, ptr - streambuf->readp);
audio_left = len;
if ((audio_left == 0xFFFFFFFF) || (audio_left == 0x7FFFEFFC)) {
LOG_INFO("wav audio size unknown: %u", audio_left);
limit = false;
} else {
LOG_INFO("wav audio size: %u", audio_left);
limit = true;
}
return;
}
if (format == AIFF && !memcmp(ptr, "SSND", 4) && bytes >= 16) {
unsigned offset = *(ptr+8) << 24 | *(ptr+9) << 16 | *(ptr+10) << 8 | *(ptr+11);
// following 4 bytes is blocksize - ignored
ptr += 8 + 8;
_buf_inc_readp(streambuf, ptr + offset - streambuf->readp);
// Reading from an upsampled stream, length could be wrong.
// Only use length in header for files.
if (stream.state == STREAMING_FILE) {
audio_left = len - 8 - offset;
LOG_INFO("aif audio size: %u", audio_left);
limit = true;
}
return;
}
if (format == WAVE && !memcmp(ptr, "fmt ", 4) && bytes >= 24) {
// override the server parsed values with our own
channels = *(ptr+10) | *(ptr+11) << 8;
sample_rate = *(ptr+12) | *(ptr+13) << 8 | *(ptr+14) << 16 | *(ptr+15) << 24;
sample_size = (*(ptr+22) | *(ptr+23) << 8) / 8;
bigendian = 0;
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
}
if (format == AIFF && !memcmp(ptr, "COMM", 4) && bytes >= 26) {
int exponent;
// override the server parsed values with our own
channels = *(ptr+8) << 8 | *(ptr+9);
sample_size = (*(ptr+14) << 8 | *(ptr+15)) / 8;
bigendian = 1;
// sample rate is encoded as IEEE 80 bit extended format
// make some assumptions to simplify processing - only use first 32 bits of mantissa
exponent = ((*(ptr+16) & 0x7f) << 8 | *(ptr+17)) - 16383 - 31;
sample_rate = *(ptr+18) << 24 | *(ptr+19) << 16 | *(ptr+20) << 8 | *(ptr+21);
while (exponent < 0) { sample_rate >>= 1; ++exponent; }
while (exponent > 0) { sample_rate <<= 1; --exponent; }
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
}
if (bytes >= len + 8) {
ptr += len + 8;
bytes -= (len + 8);
} else {
LOG_WARN("run out of data");
return;
}
}
} else {
LOG_WARN("unknown format - can't parse header");
}
}
static decode_state pcm_decode(void) {
unsigned bytes, in, out;
frames_t frames, count;
OPTR_T *optr;
u8_t *iptr;
u8_t tmp[3*8];
LOCK_S;
if ( decode.new_stream && ( ( stream.state == STREAMING_FILE ) || pcm_check_header ) ) {
_check_header();
}
LOCK_O_direct;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
IF_DIRECT(
out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
);
IF_PROCESS(
out = process.max_in_frames;
);
if ((stream.state <= DISCONNECT && bytes == 0) || (limit && audio_left == 0)) {
UNLOCK_O_direct;
UNLOCK_S;
return DECODE_COMPLETE;
}
if (decode.new_stream) {
LOG_INFO("setting track_start");
LOCK_O_not_direct;
output.track_start = outputbuf->writep;
decode.new_stream = false;
#if DSD
if (sample_size == 3 &&
is_stream_dop(((u8_t *)streambuf->readp) + (bigendian?0:2),
((u8_t *)streambuf->readp) + (bigendian?0:2) + sample_size,
sample_size * channels, bytes / (sample_size * channels))) {
LOG_INFO("file contains DOP");
if (output.dsdfmt == DOP_S24_LE || output.dsdfmt == DOP_S24_3LE)
output.next_fmt = output.dsdfmt;
else
output.next_fmt = DOP;
output.next_sample_rate = sample_rate;
output.fade = FADE_INACTIVE;
} else {
output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates);
output.next_fmt = PCM;
if (output.fade_mode) _checkfade(true);
}
#else
output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates);
if (output.fade_mode) _checkfade(true);
#endif
UNLOCK_O_not_direct;
IF_PROCESS(
out = process.max_in_frames;
);
bytes_per_frame = channels * sample_size;
}
IF_DIRECT(
optr = (OPTR_T *)outputbuf->writep;
);
IF_PROCESS(
optr = (OPTR_T *)process.inbuf;
);
iptr = (u8_t *)streambuf->readp;
in = bytes / bytes_per_frame;
// handle frame wrapping round end of streambuf
// - only need if resizing of streambuf does not avoid this, could occur in localfile case
if (in == 0 && bytes > 0 && _buf_used(streambuf) >= bytes_per_frame) {
memcpy(tmp, iptr, bytes);
memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes);
iptr = tmp;
in = 1;
}
frames = min(in, out);
frames = min(frames, MAX_DECODE_FRAMES);
if (limit && frames * bytes_per_frame > audio_left) {
LOG_INFO("reached end of audio");
frames = audio_left / bytes_per_frame;
}
count = frames * channels;
if (channels == 2) {
if (sample_size == 1) {
while (count--) {
*optr++ = *iptr++ << (24-SHIFT);
}
} else if (sample_size == 2) {
if (bigendian) {
#if BYTES_PER_FRAME == 4 && !SL_LITTLE_ENDIAN
// while loop below works as is, but memcpy is a win for that 16/16 typical case
memcpy(optr, iptr, count * BYTES_PER_FRAME / 2);
#else
while (count--) {
*optr++ = *(iptr) << (24-SHIFT) | *(iptr+1) << (16-SHIFT);
iptr += 2;
}
#endif
} else {
#if BYTES_PER_FRAME == 4 && SL_LITTLE_ENDIAN
// while loop below works as is, but memcpy is a win for that 16/16 typical case
memcpy(optr, iptr, count * BYTES_PER_FRAME / 2);
#else
while (count--) {
*optr++ = *(iptr) << (16-SHIFT) | *(iptr+1) << (24-SHIFT);
iptr += 2;
}
#endif
}
} else if (sample_size == 3) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
#endif
iptr += 3;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+1) | *(iptr+2) << 8;
#else
*optr++ = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
#endif
iptr += 3;
}
}
} else if (sample_size == 4) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
#endif
iptr += 4;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+2) | *(iptr+3) << 8;
#else
*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
#endif
iptr += 4;
}
}
}
} else if (channels == 1) {
if (sample_size == 1) {
while (count--) {
*optr = *iptr++ << (24-SHIFT);
*(optr+1) = *optr;
optr += 2;
}
} else if (sample_size == 2) {
if (bigendian) {
while (count--) {
*optr = *(iptr) << (24-SHIFT) | *(iptr+1) << (16-SHIFT);
*(optr+1) = *optr;
iptr += 2;
optr += 2;
}
} else {
while (count--) {
*optr = *(iptr) << (16-SHIFT) | *(iptr+1) << (24-SHIFT);
*(optr+1) = *optr;
iptr += 2;
optr += 2;
}
}
} else if (sample_size == 3) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
#endif
*(optr+1) = *optr;
iptr += 3;
optr += 2;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+1) | *(iptr+2) << 8;
#else
*optr = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
#endif
*(optr+1) = *optr;
iptr += 3;
optr += 2;
}
}
} else if (sample_size == 4) {
if (bigendian) {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr) << 8 | *(iptr+1);
#else
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
#endif
*(optr+1) = *optr;
iptr += 4;
optr += 2;
}
} else {
while (count--) {
#if BYTES_PER_FRAME == 4
*optr++ = *(iptr+2) | *(iptr+3) << 8;
#else
*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
#endif
*(optr+1) = *optr;
iptr += 4;
optr += 2;
}
}
}
} else {
LOG_ERROR("unsupported channels");
}
LOG_SDEBUG("decoded %u frames", frames);
_buf_inc_readp(streambuf, frames * bytes_per_frame);
if (limit) {
audio_left -= frames * bytes_per_frame;
}
IF_DIRECT(
_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = frames;
);
UNLOCK_O_direct;
UNLOCK_S;
return DECODE_RUNNING;
}
static void pcm_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
sample_size = size - '0' + 1;
sample_rate = sample_rates[rate - '0'];
channels = chan - '0';
bigendian = (endianness == '0');
limit = false;
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
buf_adjust(streambuf, sample_size * channels);
}
static void pcm_close(void) {
buf_adjust(streambuf, 1);
}
struct codec *register_pcm(void) {
if ( pcm_check_header )
{
static struct codec ret = {
'p', // id
"wav,aif,pcm", // types
4096, // min read
102400, // min space
pcm_open, // open
pcm_close, // close
pcm_decode, // decode
};
LOG_INFO("using pcm to decode wav,aif,pcm");
return &ret;
}
else
{
static struct codec ret = {
'p', // id
"aif,pcm", // types
4096, // min read
102400, // min space
pcm_open, // open
pcm_close, // close
pcm_decode, // decode
};
LOG_INFO("using pcm to decode aif,pcm");
return &ret;
}
return NULL;
}

View File

@@ -0,0 +1,194 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// sample processing - only included when building with PROCESS set
#include "squeezelite.h"
#if PROCESS
extern log_level loglevel;
extern struct buffer *outputbuf;
extern struct decodestate decode;
struct processstate process;
extern struct codec *codec;
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
// macros to map to processing functions - currently only resample.c
// this can be made more generic when multiple processing mechanisms get added
#if RESAMPLE || RESAMPLE16
#define SAMPLES_FUNC resample_samples
#define DRAIN_FUNC resample_drain
#define NEWSTREAM_FUNC resample_newstream
#define FLUSH_FUNC resample_flush
#define INIT_FUNC resample_init
#endif
// transfer all processed frames to the output buf
static void _write_samples(void) {
frames_t frames = process.out_frames;
ISAMPLE_T *iptr = (ISAMPLE_T *) process.outbuf;
unsigned cnt = 10;
LOCK_O;
while (frames > 0) {
frames_t f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
ISAMPLE_T *optr = (ISAMPLE_T*) outputbuf->writep;
if (f > 0) {
f = min(f, frames);
memcpy(optr, iptr, f * BYTES_PER_FRAME);
frames -= f;
_buf_inc_writep(outputbuf, f * BYTES_PER_FRAME);
iptr += f * BYTES_PER_FRAME / sizeof(*iptr);
} else if (cnt--) {
// there should normally be space in the output buffer, but may need to wait during drain phase
UNLOCK_O;
usleep(10000);
LOCK_O;
} else {
// bail out if no space found after 100ms to avoid locking
LOG_ERROR("unable to get space in output buffer");
UNLOCK_O;
return;
}
}
UNLOCK_O;
}
// process samples - called with decode mutex set
void process_samples(void) {
SAMPLES_FUNC(&process);
_write_samples();
process.in_frames = 0;
}
// drain at end of track - called with decode mutex set
void process_drain(void) {
bool done;
do {
done = DRAIN_FUNC(&process);
_write_samples();
} while (!done);
LOG_DEBUG("processing track complete - frames in: %lu out: %lu", process.total_in, process.total_out);
}
// new stream - called with decode mutex set
unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]) {
bool active = NEWSTREAM_FUNC(&process, raw_sample_rate, supported_rates);
LOG_INFO("processing: %s", active ? "active" : "inactive");
*direct = !active;
if (active) {
unsigned max_in_frames, max_out_frames;
process.in_frames = process.out_frames = 0;
process.total_in = process.total_out = 0;
max_in_frames = codec->min_space / BYTES_PER_FRAME ;
// increase size of output buffer by 10% as output rate is not an exact multiple of input rate
if (process.out_sample_rate % process.in_sample_rate == 0) {
max_out_frames = max_in_frames * (process.out_sample_rate / process.in_sample_rate);
} else {
max_out_frames = (int)(1.1 * (float)max_in_frames * (float)process.out_sample_rate / (float)process.in_sample_rate);
}
if (process.max_in_frames != max_in_frames) {
LOG_DEBUG("creating process buf in frames: %u", max_in_frames);
if (process.inbuf) free(process.inbuf);
process.inbuf = malloc(max_in_frames * BYTES_PER_FRAME);
process.max_in_frames = max_in_frames;
}
if (process.max_out_frames != max_out_frames) {
LOG_DEBUG("creating process buf out frames: %u", max_out_frames);
if (process.outbuf) free(process.outbuf);
process.outbuf = malloc(max_out_frames * BYTES_PER_FRAME);
process.max_out_frames = max_out_frames;
}
if (!process.inbuf || !process.outbuf) {
LOG_ERROR("malloc fail creating process buffers");
*direct = true;
return raw_sample_rate;
}
return process.out_sample_rate;
}
return raw_sample_rate;
}
// process flush - called with decode mutex set
void process_flush(void) {
LOG_INFO("process flush");
FLUSH_FUNC();
process.in_frames = 0;
}
// init - called with no mutex
void process_init(char *opt) {
bool enabled = INIT_FUNC(opt);
memset(&process, 0, sizeof(process));
if (enabled) {
LOCK_D;
decode.process = true;
UNLOCK_D;
}
}
#endif // #if PROCESS

View File

@@ -0,0 +1,379 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// upsampling using libsoxr - only included if RESAMPLE set
#include "squeezelite.h"
#if RESAMPLE
#include <math.h>
#include <soxr.h>
extern log_level loglevel;
struct soxr {
soxr_t resampler;
size_t old_clips;
unsigned long q_recipe;
unsigned long q_flags;
double q_precision; /* Conversion precision (in bits). 20 */
double q_phase_response; /* 0=minimum, ... 50=linear, ... 100=maximum 50 */
double q_passband_end; /* 0dB pt. bandwidth to preserve; nyquist=1 0.913 */
double q_stopband_begin; /* Aliasing/imaging control; > passband_end 1 */
double scale;
bool max_rate;
bool exception;
#if !LINKALL
// soxr symbols to be dynamically loaded
soxr_io_spec_t (* soxr_io_spec)(soxr_datatype_t itype, soxr_datatype_t otype);
soxr_quality_spec_t (* soxr_quality_spec)(unsigned long recipe, unsigned long flags);
soxr_t (* soxr_create)(double, double, unsigned, soxr_error_t *,
soxr_io_spec_t const *, soxr_quality_spec_t const *, soxr_runtime_spec_t const *);
void (* soxr_delete)(soxr_t);
soxr_error_t (* soxr_process)(soxr_t, soxr_in_t, size_t, size_t *, soxr_out_t, size_t olen, size_t *);
size_t *(* soxr_num_clips)(soxr_t);
#if RESAMPLE_MP
soxr_runtime_spec_t (* soxr_runtime_spec)(unsigned num_threads);
#endif
// soxr_strerror is a macro so not included here
#endif
};
static struct soxr *r;
#if LINKALL
#define SOXR(h, fn, ...) (soxr_ ## fn)(__VA_ARGS__)
#else
#define SOXR(h, fn, ...) (h)->soxr_##fn(__VA_ARGS__)
#endif
void resample_samples(struct processstate *process) {
size_t idone, odone;
size_t clip_cnt;
soxr_error_t error =
SOXR(r, process, r->resampler, process->inbuf, process->in_frames, &idone, process->outbuf, process->max_out_frames, &odone);
if (error) {
LOG_INFO("soxr_process error: %s", soxr_strerror(error));
return;
}
if (idone != process->in_frames) {
// should not get here if buffers are big enough...
LOG_ERROR("should not get here - partial sox process: %u of %u processed %u of %u out",
(unsigned)idone, process->in_frames, (unsigned)odone, process->max_out_frames);
}
process->out_frames = odone;
process->total_in += idone;
process->total_out += odone;
clip_cnt = *(SOXR(r, num_clips, r->resampler));
if (clip_cnt - r->old_clips) {
LOG_SDEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips));
r->old_clips = clip_cnt;
}
}
bool resample_drain(struct processstate *process) {
size_t odone;
size_t clip_cnt;
soxr_error_t error = SOXR(r, process, r->resampler, NULL, 0, NULL, process->outbuf, process->max_out_frames, &odone);
if (error) {
LOG_INFO("soxr_process error: %s", soxr_strerror(error));
return true;
}
process->out_frames = odone;
process->total_out += odone;
clip_cnt = *(SOXR(r, num_clips, r->resampler));
if (clip_cnt - r->old_clips) {
LOG_DEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips));
r->old_clips = clip_cnt;
}
if (odone == 0) {
LOG_INFO("resample track complete - total track clips: %u", r->old_clips);
SOXR(r, delete, r->resampler);
r->resampler = NULL;
return true;
} else {
return false;
}
}
bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) {
unsigned outrate = 0;
int i;
if (r->exception) {
// find direct match - avoid resampling
for (i = 0; supported_rates[i]; i++) {
if (raw_sample_rate == supported_rates[i]) {
outrate = raw_sample_rate;
break;
}
}
// else find next highest sync sample rate
while (!outrate && i >= 0) {
if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) {
outrate = supported_rates[i];
break;
}
i--;
}
}
if (!outrate) {
if (r->max_rate) {
// resample to max rate for device
outrate = supported_rates[0];
} else {
// resample to max sync sample rate
for (i = 0; supported_rates[i]; i++) {
if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) {
outrate = supported_rates[i];
break;
}
}
}
if (!outrate) {
outrate = supported_rates[0];
}
}
process->in_sample_rate = raw_sample_rate;
process->out_sample_rate = outrate;
if (r->resampler) {
SOXR(r, delete, r->resampler);
r->resampler = NULL;
}
if (raw_sample_rate != outrate) {
soxr_io_spec_t io_spec;
soxr_quality_spec_t q_spec;
soxr_error_t error;
#if RESAMPLE_MP
soxr_runtime_spec_t r_spec;
#endif
LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate);
#if BYTES_PER_FRAME == 4
io_spec = SOXR(r, io_spec, SOXR_INT16_I, SOXR_INT16_I);
io_spec.flags = SOXR_NO_DITHER;
#else
io_spec = SOXR(r, io_spec, SOXR_INT32_I, SOXR_INT32_I);
#endif
io_spec.scale = r->scale;
q_spec = SOXR(r, quality_spec, r->q_recipe, r->q_flags);
if (r->q_precision > 0) {
q_spec.precision = r->q_precision;
}
if (r->q_passband_end > 0) {
q_spec.passband_end = r->q_passband_end;
}
if (r->q_stopband_begin > 0) {
q_spec.stopband_begin = r->q_stopband_begin;
}
if (r->q_phase_response > -1) {
q_spec.phase_response = r->q_phase_response;
}
#if RESAMPLE_MP
r_spec = SOXR(r, runtime_spec, 0); // make use of libsoxr OpenMP support allowing parallel execution if multiple cores
#endif
LOG_DEBUG("resampling with soxr_quality_spec_t[precision: %03.1f, passband_end: %03.6f, stopband_begin: %03.6f, "
"phase_response: %03.1f, flags: 0x%02x], soxr_io_spec_t[scale: %03.2f]", q_spec.precision,
q_spec.passband_end, q_spec.stopband_begin, q_spec.phase_response, q_spec.flags, io_spec.scale);
#if RESAMPLE_MP
r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, &r_spec);
#else
r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, NULL);
#endif
if (error) {
LOG_INFO("soxr_create error: %s", soxr_strerror(error));
return false;
}
r->old_clips = 0;
return true;
} else {
LOG_INFO("disable resampling - rates match");
return false;
}
}
void resample_flush(void) {
if (r->resampler) {
SOXR(r, delete, r->resampler);
r->resampler = NULL;
}
}
static bool load_soxr(void) {
#if !LINKALL
void *handle = dlopen(LIBSOXR, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
r->soxr_io_spec = dlsym(handle, "soxr_io_spec");
r->soxr_quality_spec = dlsym(handle, "soxr_quality_spec");
r->soxr_create = dlsym(handle, "soxr_create");
r->soxr_delete = dlsym(handle, "soxr_delete");
r->soxr_process = dlsym(handle, "soxr_process");
r->soxr_num_clips = dlsym(handle, "soxr_num_clips");
#if RESAMPLE_MP
r->soxr_runtime_spec = dlsym(handle, "soxr_runtime_spec");
#endif
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded "LIBSOXR);
#endif
return true;
}
bool resample_init(char *opt) {
char *recipe = NULL, *flags = NULL;
char *atten = NULL;
char *precision = NULL, *passband_end = NULL, *stopband_begin = NULL, *phase_response = NULL;
r = malloc(sizeof(struct soxr));
if (!r) {
LOG_WARN("resampling disabled");
return false;
}
r->resampler = NULL;
r->old_clips = 0;
r->max_rate = false;
r->exception = false;
if (!load_soxr()) {
LOG_WARN("resampling disabled");
return false;
}
if (opt) {
recipe = next_param(opt, ':');
flags = next_param(NULL, ':');
atten = next_param(NULL, ':');
precision = next_param(NULL, ':');
passband_end = next_param(NULL, ':');
stopband_begin = next_param(NULL, ':');
phase_response = next_param(NULL, ':');
}
#if BYTES_PER_FRAME == 4
// default to LQ (16 bits) if not user specified
r->q_recipe = SOXR_LQ | SOXR_MINIMUM_PHASE;
r->q_flags = SOXR_ROLLOFF_NONE;
r->q_phase_response = 0;
#else
// default to HQ (20 bits) if not user specified
r->q_recipe = SOXR_HQ;
r->q_flags = 0;
r->q_phase_response = -1;
#endif
// default to 1db of attenuation if not user specified
r->scale = pow(10, -1.0 / 20);
// override recipe derived values with user specified values
r->q_precision = 0;
r->q_passband_end = 0.75;
r->q_stopband_begin = 1.25;
if (recipe && recipe[0] != '\0') {
if (strchr(recipe, 'v')) r->q_recipe = SOXR_VHQ;
if (strchr(recipe, 'h')) r->q_recipe = SOXR_HQ;
if (strchr(recipe, 'm')) r->q_recipe = SOXR_MQ;
if (strchr(recipe, 'l')) r->q_recipe = SOXR_LQ;
if (strchr(recipe, 'q')) r->q_recipe = SOXR_QQ;
if (strchr(recipe, 'L')) r->q_recipe |= SOXR_LINEAR_PHASE;
if (strchr(recipe, 'I')) r->q_recipe |= SOXR_INTERMEDIATE_PHASE;
if (strchr(recipe, 'M')) r->q_recipe |= SOXR_MINIMUM_PHASE;
if (strchr(recipe, 's')) r->q_recipe |= SOXR_STEEP_FILTER;
// X = async resampling to max_rate
if (strchr(recipe, 'X')) r->max_rate = true;
// E = exception, only resample if native rate is not supported
if (strchr(recipe, 'E')) r->exception = true;
}
if (flags) {
r->q_flags = strtoul(flags, 0, 16);
}
if (atten) {
double scale = pow(10, -atof(atten) / 20);
if (scale > 0 && scale <= 1.0) {
r->scale = scale;
}
}
if (precision) {
r->q_precision = atof(precision);
}
if (passband_end) {
r->q_passband_end = atof(passband_end) / 100;
}
if (stopband_begin) {
r->q_stopband_begin = atof(stopband_begin) / 100;
}
if (phase_response) {
r->q_phase_response = atof(phase_response);
}
LOG_INFO("resampling %s recipe: 0x%02x, flags: 0x%02x, scale: %03.2f, precision: %03.1f, passband_end: %03.5f, stopband_begin: %03.5f, phase_response: %03.1f",
r->max_rate ? "async" : "sync",
r->q_recipe, r->q_flags, r->scale, r->q_precision, r->q_passband_end, r->q_stopband_begin, r->q_phase_response);
return true;
}
#endif // #if RESAMPLE

View File

@@ -0,0 +1,164 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// upsampling using libsoxr - only included if RESAMPLE set
#include "squeezelite.h"
#if RESAMPLE16
#include <resample16.h>
extern log_level loglevel;
struct resample16 {
struct resample16_s *resampler;
bool max_rate;
bool exception;
bool interp;
resample16_filter_e filter;
};
static struct resample16 r;
void resample_samples(struct processstate *process) {
ssize_t odone;
odone = resample16(r.resampler, (HWORD*) process->inbuf, process->in_frames, (HWORD*) process->outbuf);
if (odone < 0) {
LOG_INFO("resample16 error");
return;
}
process->out_frames = odone;
process->total_in += process->in_frames;
process->total_out += odone;
}
bool resample_drain(struct processstate *process) {
process->out_frames = 0;
LOG_INFO("resample track complete");
resample16_delete(r.resampler);
r.resampler = NULL;
return true;
}
bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) {
unsigned outrate = 0;
int i;
if (r.exception) {
// find direct match - avoid resampling
for (i = 0; supported_rates[i]; i++) {
if (raw_sample_rate == supported_rates[i]) {
outrate = raw_sample_rate;
break;
}
}
// else find next highest sync sample rate
while (!outrate && i >= 0) {
if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) {
outrate = supported_rates[i];
break;
}
i--;
}
}
if (!outrate) {
if (r.max_rate) {
// resample to max rate for device
outrate = supported_rates[0];
} else {
// resample to max sync sample rate
for (i = 0; supported_rates[i]; i++) {
if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) {
outrate = supported_rates[i];
break;
}
}
}
if (!outrate) {
outrate = supported_rates[0];
}
}
process->in_sample_rate = raw_sample_rate;
process->out_sample_rate = outrate;
if (r.resampler) {
resample16_delete(r.resampler);
r.resampler = NULL;
}
if (raw_sample_rate != outrate) {
LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate);
r.resampler = resample16_create((float) outrate / raw_sample_rate, r.filter, NULL, false);
return true;
} else {
LOG_INFO("disable resampling - rates match");
return false;
}
}
void resample_flush(void) {
if (r.resampler) {
resample16_delete(r.resampler);
r.resampler = NULL;
}
}
bool resample_init(char *opt) {
char *filter = NULL, *interp = NULL;
r.resampler = NULL;
r.max_rate = false;
r.exception = false;
if (opt) {
filter = next_param(opt, ':');
interp = next_param(NULL, ':');
}
if (filter) {
if (*filter == 'm') r.filter = RESAMPLE16_MED;
else if (*filter == 'l') r.filter = RESAMPLE16_LOW;
else r.filter = RESAMPLE16_BASIC;
}
if (interp && *interp == 'i') {
r.interp = true;
}
LOG_INFO("Resampling with filter %d %s", r.filter, r.interp ? "(interpolated)" : "");
return true;
}
#endif // #if RESAMPLE16

View File

@@ -0,0 +1,976 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additions (c) Paul Hermann, 2015-2017 under the same license terms
* -Control of Raspberry pi GPIO for amplifier power
* -Launch script on power status change from LMS
*/
#include "squeezelite.h"
#include "slimproto.h"
static log_level loglevel;
#define SQUEEZENETWORK "mysqueezebox.com:3483"
#define PORT 3483
#define MAXBUF 4096
#if SL_LITTLE_ENDIAN
#define LOCAL_PLAYER_IP 0x0100007f // 127.0.0.1
#define LOCAL_PLAYER_PORT 0x9b0d // 3483
#else
#define LOCAL_PLAYER_IP 0x7f000001 // 127.0.0.1
#define LOCAL_PLAYER_PORT 0x0d9b // 3483
#endif
static sockfd sock = -1;
static in_addr_t slimproto_ip = 0;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct codec *codecs[];
#if IR
extern struct irstate ir;
#endif
event_event wake_e;
#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)
#define LOCK_D mutex_lock(decode.mutex)
#define UNLOCK_D mutex_unlock(decode.mutex)
#if IR
#define LOCK_I mutex_lock(ir.mutex)
#define UNLOCK_I mutex_unlock(ir.mutex)
#endif
static struct {
u32_t updated;
u32_t stream_start;
u32_t stream_full;
u32_t stream_size;
u64_t stream_bytes;
u32_t output_full;
u32_t output_size;
u32_t frames_played;
u32_t device_frames;
u32_t current_sample_rate;
u32_t last;
stream_state stream_state;
} status;
int autostart;
bool sentSTMu, sentSTMo, sentSTMl;
u32_t new_server;
char *new_server_cap;
#define PLAYER_NAME_LEN 64
char player_name[PLAYER_NAME_LEN + 1] = "";
const char *name_file = NULL;
void send_packet(u8_t *packet, size_t len) {
u8_t *ptr = packet;
unsigned try = 0;
ssize_t n;
while (len) {
n = send(sock, ptr, len, MSG_NOSIGNAL);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK && try < 10) {
LOG_DEBUG("retrying (%d) writing to socket", ++try);
usleep(1000);
continue;
}
LOG_INFO("failed writing to socket: %s", strerror(last_error()));
return;
}
ptr += n;
len -= n;
}
}
static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) {
#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
#define SSL_CAP "CanHTTPS=1"
const char *base_cap;
struct HELO_packet pkt;
#if USE_SSL
#if !LINKALL
if (ssl_loaded) base_cap = SSL_CAP "," BASE_CAP;
else base_cap = BASE_CAP;
#endif
base_cap = SSL_CAP "," BASE_CAP;
#else
base_cap = BASE_CAP;
#endif
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "HELO", 4);
pkt.length = htonl(sizeof(struct HELO_packet) - 8 + strlen(base_cap) + strlen(fixed_cap) + strlen(var_cap));
pkt.deviceid = 12; // squeezeplay
pkt.revision = 0;
packn(&pkt.wlan_channellist, reconnect ? 0x4000 : 0x0000);
packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
memcpy(pkt.mac, mac, 6);
LOG_INFO("mac: %02x:%02x:%02x:%02x:%02x:%02x", pkt.mac[0], pkt.mac[1], pkt.mac[2], pkt.mac[3], pkt.mac[4], pkt.mac[5]);
LOG_INFO("cap: %s%s%s", base_cap, fixed_cap, var_cap);
send_packet((u8_t *)&pkt, sizeof(pkt));
send_packet((u8_t *)base_cap, strlen(base_cap));
send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
send_packet((u8_t *)var_cap, strlen(var_cap));
}
static void sendSTAT(const char *event, u32_t server_timestamp) {
struct STAT_packet pkt;
u32_t now = gettime_ms();
u32_t ms_played;
if (status.current_sample_rate && status.frames_played && status.frames_played > status.device_frames) {
ms_played = (u32_t)(((u64_t)(status.frames_played - status.device_frames) * (u64_t)1000) / (u64_t)status.current_sample_rate);
#ifndef STATUSHACK
if (now > status.updated) ms_played += (now - status.updated);
#endif
LOG_SDEBUG("ms_played: %u (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
} else if (status.frames_played && now > status.stream_start) {
ms_played = now - status.stream_start;
LOG_SDEBUG("ms_played: %u using elapsed time (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
} else {
LOG_SDEBUG("ms_played: 0");
ms_played = 0;
}
memset(&pkt, 0, sizeof(struct STAT_packet));
memcpy(&pkt.opcode, "STAT", 4);
pkt.length = htonl(sizeof(struct STAT_packet) - 8);
memcpy(&pkt.event, event, 4);
// num_crlf
// mas_initialized; mas_mode;
packN(&pkt.stream_buffer_fullness, status.stream_full);
packN(&pkt.stream_buffer_size, status.stream_size);
packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
pkt.signal_strength = 0xffff;
packN(&pkt.jiffies, now);
packN(&pkt.output_buffer_size, status.output_size);
packN(&pkt.output_buffer_fullness, status.output_full);
packN(&pkt.elapsed_seconds, ms_played / 1000);
// voltage;
packN(&pkt.elapsed_milliseconds, ms_played);
pkt.server_timestamp = server_timestamp; // keep this is server format - don't unpack/pack
// error_code;
LOG_DEBUG("STAT: %s", event);
if (loglevel == lSDEBUG) {
LOG_SDEBUG("received bytesL: %u streambuf: %u outputbuf: %u calc elapsed: %u real elapsed: %u (diff: %d) device: %u delay: %d",
(u32_t)status.stream_bytes, status.stream_full, status.output_full, ms_played, now - status.stream_start,
ms_played - now + status.stream_start, status.device_frames * 1000 / status.current_sample_rate, now - status.updated);
}
send_packet((u8_t *)&pkt, sizeof(pkt));
}
static void sendDSCO(disconnect_code disconnect) {
struct DSCO_packet pkt;
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "DSCO", 4);
pkt.length = htonl(sizeof(pkt) - 8);
pkt.reason = disconnect & 0xFF;
LOG_DEBUG("DSCO: %d", disconnect);
send_packet((u8_t *)&pkt, sizeof(pkt));
}
static void sendRESP(const char *header, size_t len) {
struct RESP_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "RESP", 4);
pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
LOG_DEBUG("RESP");
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)header, len);
}
static void sendMETA(const char *meta, size_t len) {
struct META_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "META", 4);
pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
LOG_DEBUG("META");
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)meta, len);
}
static void sendSETDName(const char *name) {
struct SETD_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "SETD", 4);
pkt_header.id = 0; // id 0 is playername S:P:Squeezebox2
pkt_header.length = htonl(sizeof(pkt_header) + strlen(name) + 1 - 8);
LOG_DEBUG("set playername: %s", name);
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)name, strlen(name) + 1);
}
#if IR
void sendIR(u32_t code, u32_t ts) {
struct IR_packet pkt;
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "IR ", 4);
pkt.length = htonl(sizeof(pkt) - 8);
packN(&pkt.jiffies, ts);
pkt.ir_code = htonl(code);
LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
send_packet((u8_t *)&pkt, sizeof(pkt));
}
#endif
static void process_strm(u8_t *pkt, int len) {
struct strm_packet *strm = (struct strm_packet *)pkt;
LOG_DEBUG("strm command %c", strm->command);
switch(strm->command) {
case 't':
sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
break;
case 'q':
decode_flush();
output_flush();
status.frames_played = 0;
stream_disconnect();
sendSTAT("STMf", 0);
buf_flush(streambuf);
break;
case 'f':
decode_flush();
output_flush();
status.frames_played = 0;
if (stream_disconnect()) {
sendSTAT("STMf", 0);
}
buf_flush(streambuf);
break;
case 'p':
{
unsigned interval = unpackN(&strm->replay_gain);
LOCK_O;
output.pause_frames = interval * status.current_sample_rate / 1000;
if (interval) {
output.state = OUTPUT_PAUSE_FRAMES;
} else {
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
}
UNLOCK_O;
if (!interval) sendSTAT("STMp", 0);
LOG_DEBUG("pause interval: %u", interval);
}
break;
case 'a':
{
unsigned interval = unpackN(&strm->replay_gain);
LOCK_O;
output.skip_frames = interval * status.current_sample_rate / 1000;
output.state = OUTPUT_SKIP_FRAMES;
UNLOCK_O;
LOG_DEBUG("skip ahead interval: %u", interval);
}
break;
case 'u':
{
unsigned jiffies = unpackN(&strm->replay_gain);
LOCK_O;
output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING;
output.start_at = jiffies;
#ifdef STATUSHACK
status.frames_played = output.frames_played;
#endif
UNLOCK_O;
LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms());
sendSTAT("STMr", 0);
}
break;
case 's':
{
unsigned header_len = len - sizeof(struct strm_packet);
char *header = (char *)(pkt + sizeof(struct strm_packet));
in_addr_t ip = (in_addr_t)strm->server_ip; // keep in network byte order
u16_t port = strm->server_port; // keep in network byte order
if (ip == 0) ip = slimproto_ip;
LOG_DEBUG("strm s autostart: %c transition period: %u transition type: %u codec: %c",
strm->autostart, strm->transition_period, strm->transition_type - '0', strm->format);
autostart = strm->autostart - '0';
sendSTAT("STMf", 0);
if (header_len > MAX_HEADER -1) {
LOG_WARN("header too long: %u", header_len);
break;
}
if (strm->format != '?') {
codec_open(strm->format, strm->pcm_sample_size, strm->pcm_sample_rate, strm->pcm_channels, strm->pcm_endianness);
} else if (autostart >= 2) {
// extension to slimproto to allow server to detect codec from response header and send back in codc message
LOG_DEBUG("streaming unknown codec");
} else {
LOG_WARN("unknown codec requires autostart >= 2");
break;
}
if (ip == LOCAL_PLAYER_IP && port == LOCAL_PLAYER_PORT) {
// extension to slimproto for LocalPlayer - header is filename not http header, don't expect cont
stream_file(header, header_len, strm->threshold * 1024);
autostart -= 2;
} else {
stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2);
}
sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false;
LOCK_O;
output.threshold = strm->output_threshold;
output.next_replay_gain = unpackN(&strm->replay_gain);
output.fade_mode = strm->transition_type - '0';
output.fade_secs = strm->transition_period;
output.invert = (strm->flags & 0x03) == 0x03;
LOG_DEBUG("set fade mode: %u", output.fade_mode);
UNLOCK_O;
}
break;
default:
LOG_WARN("unhandled strm %c", strm->command);
break;
}
}
static void process_cont(u8_t *pkt, int len) {
struct cont_packet *cont = (struct cont_packet *)pkt;
cont->metaint = unpackN(&cont->metaint);
LOG_DEBUG("cont metaint: %u loop: %u", cont->metaint, cont->loop);
if (autostart > 1) {
autostart -= 2;
LOCK_S;
if (stream.state == STREAMING_WAIT) {
stream.state = STREAMING_BUFFERING;
stream.meta_interval = stream.meta_next = cont->metaint;
}
UNLOCK_S;
wake_controller();
}
}
static void process_codc(u8_t *pkt, int len) {
struct codc_packet *codc = (struct codc_packet *)pkt;
LOG_DEBUG("codc: %c", codc->format);
codec_open(codc->format, codc->pcm_sample_size, codc->pcm_sample_rate, codc->pcm_channels, codc->pcm_endianness);
}
static void process_aude(u8_t *pkt, int len) {
struct aude_packet *aude = (struct aude_packet *)pkt;
LOG_DEBUG("enable spdif: %d dac: %d", aude->enable_spdif, aude->enable_dac);
LOCK_O;
if (!aude->enable_spdif && output.state != OUTPUT_OFF) {
output.state = OUTPUT_OFF;
}
if (aude->enable_spdif && output.state == OUTPUT_OFF && !output.idle_to) {
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
}
UNLOCK_O;
}
static void process_audg(u8_t *pkt, int len) {
struct audg_packet *audg = (struct audg_packet *)pkt;
audg->gainL = unpackN(&audg->gainL);
audg->gainR = unpackN(&audg->gainR);
LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust);
set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
}
static void process_setd(u8_t *pkt, int len) {
struct setd_packet *setd = (struct setd_packet *)pkt;
// handle player name query and change
if (setd->id == 0) {
if (len == 5) {
if (strlen(player_name)) {
sendSETDName(player_name);
}
} else if (len > 5) {
strncpy(player_name, setd->data, PLAYER_NAME_LEN);
player_name[PLAYER_NAME_LEN] = '\0';
LOG_INFO("set name: %s", setd->data);
// confirm change to server
sendSETDName(setd->data);
// write name to name_file if -N option set
if (name_file) {
FILE *fp = fopen(name_file, "w");
if (fp) {
LOG_INFO("storing name in %s", name_file);
fputs(player_name, fp);
fclose(fp);
} else {
LOG_WARN("unable to store new name in %s", name_file);
}
}
}
}
}
#define SYNC_CAP ",SyncgroupID="
#define SYNC_CAP_LEN 13
static void process_serv(u8_t *pkt, int len) {
struct serv_packet *serv = (struct serv_packet *)pkt;
unsigned slimproto_port = 0;
char squeezeserver[] = SQUEEZENETWORK;
if(pkt[4] == 0 && pkt[5] == 0 && pkt[6] == 0 && pkt[7] == 1) {
server_addr(squeezeserver, &new_server, &slimproto_port);
} else {
new_server = serv->server_ip;
}
LOG_INFO("switch server");
if (len - sizeof(struct serv_packet) == 10) {
if (!new_server_cap) {
new_server_cap = malloc(SYNC_CAP_LEN + 10 + 1);
}
new_server_cap[0] = '\0';
strcat(new_server_cap, SYNC_CAP);
strncat(new_server_cap, (const char *)(pkt + sizeof(struct serv_packet)), 10);
} else {
if (new_server_cap) {
free(new_server_cap);
new_server_cap = NULL;
}
}
}
struct handler {
char opcode[5];
void (*handler)(u8_t *, int);
};
static struct handler handlers[] = {
{ "strm", process_strm },
{ "cont", process_cont },
{ "codc", process_codc },
{ "aude", process_aude },
{ "audg", process_audg },
{ "setd", process_setd },
{ "serv", process_serv },
{ "", NULL },
};
static void process(u8_t *pack, int len) {
struct handler *h = handlers;
while (h->handler && strncmp((char *)pack, h->opcode, 4)) { h++; }
if (h->handler) {
LOG_DEBUG("%s", h->opcode);
h->handler(pack, len);
} else {
pack[4] = '\0';
LOG_WARN("unhandled %s", (char *)pack);
}
}
static bool running;
static void slimproto_run() {
static u8_t buffer[MAXBUF];
int expect = 0;
int got = 0;
u32_t now;
static u32_t last = 0;
event_handle ehandles[2];
int timeouts = 0;
set_readwake_handles(ehandles, sock, wake_e);
while (running && !new_server) {
bool wake = false;
event_type ev;
if ((ev = wait_readwake(ehandles, 1000)) != EVENT_TIMEOUT) {
if (ev == EVENT_READ) {
if (expect > 0) {
int n = recv(sock, buffer + got, expect, 0);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
continue;
}
LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
return;
}
expect -= n;
got += n;
if (expect == 0) {
process(buffer, got);
got = 0;
}
} else if (expect == 0) {
int n = recv(sock, buffer + got, 2 - got, 0);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
continue;
}
LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
return;
}
got += n;
if (got == 2) {
expect = buffer[0] << 8 | buffer[1]; // length pack 'n'
got = 0;
if (expect > MAXBUF) {
LOG_ERROR("FATAL: slimproto packet too big: %d > %d", expect, MAXBUF);
return;
}
}
} else {
LOG_ERROR("FATAL: negative expect");
return;
}
}
if (ev == EVENT_WAKE) {
wake = true;
}
timeouts = 0;
} else if (++timeouts > 35) {
// expect message from server every 5 seconds, but 30 seconds on mysb.com so timeout after 35 seconds
LOG_INFO("No messages from server - connection dead");
return;
}
// update playback state when woken or every 100ms
now = gettime_ms();
if (wake || now - last > 100 || last > now) {
bool _sendSTMs = false;
bool _sendDSCO = false;
bool _sendRESP = false;
bool _sendMETA = false;
bool _sendSTMd = false;
bool _sendSTMt = false;
bool _sendSTMl = false;
bool _sendSTMu = false;
bool _sendSTMo = false;
bool _sendSTMn = false;
bool _stream_disconnect = false;
bool _start_output = false;
decode_state _decode_state;
disconnect_code disconnect_code;
static char header[MAX_HEADER];
size_t header_len = 0;
#if IR
bool _sendIR = false;
u32_t ir_code, ir_ts;
#endif
last = now;
LOCK_S;
status.stream_full = _buf_used(streambuf);
status.stream_size = streambuf->size;
status.stream_bytes = stream.bytes;
status.stream_state = stream.state;
if (stream.state == DISCONNECT) {
disconnect_code = stream.disconnect;
stream.state = STOPPED;
_sendDSCO = true;
}
if (!stream.sent_headers &&
(stream.state == STREAMING_HTTP || stream.state == STREAMING_WAIT || stream.state == STREAMING_BUFFERING)) {
header_len = stream.header_len;
memcpy(header, stream.header, header_len);
_sendRESP = true;
stream.sent_headers = true;
}
if (stream.meta_send) {
header_len = stream.header_len;
memcpy(header, stream.header, header_len);
_sendMETA = true;
stream.meta_send = false;
}
UNLOCK_S;
LOCK_D;
if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE ||
(status.stream_state == DISCONNECT && stream.disconnect == DISCONNECT_OK)) &&
!sentSTMl && decode.state == DECODE_READY) {
if (autostart == 0) {
decode.state = DECODE_RUNNING;
_sendSTMl = true;
sentSTMl = true;
} else if (autostart == 1) {
decode.state = DECODE_RUNNING;
_start_output = true;
}
// autostart 2 and 3 require cont to be received first
}
if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) {
if (decode.state == DECODE_COMPLETE) _sendSTMd = true;
if (decode.state == DECODE_ERROR) _sendSTMn = true;
decode.state = DECODE_STOPPED;
if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) {
_stream_disconnect = true;
}
}
_decode_state = decode.state;
UNLOCK_D;
LOCK_O;
status.output_full = _buf_used(outputbuf);
status.output_size = outputbuf->size;
status.frames_played = output.frames_played_dmp;
status.current_sample_rate = output.current_sample_rate;
status.updated = output.updated;
status.device_frames = output.device_frames;
if (output.track_started) {
_sendSTMs = true;
output.track_started = false;
status.stream_start = output.track_start_time;
#ifdef STATUSHACK
status.frames_played = output.frames_played;
#endif
}
#if PORTAUDIO
if (output.pa_reopen) {
_pa_open();
output.pa_reopen = false;
}
#endif
if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
output.state = OUTPUT_BUFFER;
}
if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
_decode_state == DECODE_STOPPED) {
_sendSTMu = true;
sentSTMu = true;
LOG_DEBUG("output underrun");
output.state = OUTPUT_STOPPED;
output.stop_time = now;
}
if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
_sendSTMo = true;
sentSTMo = true;
}
if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) {
output.state = OUTPUT_OFF;
LOG_DEBUG("output timeout");
}
if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
_sendSTMt = true;
status.last = now;
}
UNLOCK_O;
#if IR
LOCK_I;
if (ir.code) {
_sendIR = true;
ir_code = ir.code;
ir_ts = ir.ts;
ir.code = 0;
}
UNLOCK_I;
#endif
if (_stream_disconnect) stream_disconnect();
// send packets once locks released as packet sending can block
if (_sendDSCO) sendDSCO(disconnect_code);
if (_sendSTMs) sendSTAT("STMs", 0);
if (_sendSTMd) sendSTAT("STMd", 0);
if (_sendSTMt) sendSTAT("STMt", 0);
if (_sendSTMl) sendSTAT("STMl", 0);
if (_sendSTMu) sendSTAT("STMu", 0);
if (_sendSTMo) sendSTAT("STMo", 0);
if (_sendSTMn) sendSTAT("STMn", 0);
if (_sendRESP) sendRESP(header, header_len);
if (_sendMETA) sendMETA(header, header_len);
#if IR
if (_sendIR) sendIR(ir_code, ir_ts);
#endif
}
}
}
// called from other threads to wake state machine above
void wake_controller(void) {
wake_signal(wake_e);
}
in_addr_t discover_server(char *default_server) {
struct sockaddr_in d;
struct sockaddr_in s;
char *buf;
struct pollfd pollinfo;
unsigned port;
int disc_sock = socket(AF_INET, SOCK_DGRAM, 0);
socklen_t enable = 1;
setsockopt(disc_sock, SOL_SOCKET, SO_BROADCAST, (const void *)&enable, sizeof(enable));
buf = "e";
memset(&d, 0, sizeof(d));
d.sin_family = AF_INET;
d.sin_port = htons(PORT);
d.sin_addr.s_addr = htonl(INADDR_BROADCAST);
pollinfo.fd = disc_sock;
pollinfo.events = POLLIN;
do {
LOG_INFO("sending discovery");
memset(&s, 0, sizeof(s));
if (sendto(disc_sock, buf, 1, 0, (struct sockaddr *)&d, sizeof(d)) < 0) {
LOG_INFO("error sending disovery");
}
if (poll(&pollinfo, 1, 5000) == 1) {
char readbuf[10];
socklen_t slen = sizeof(s);
recvfrom(disc_sock, readbuf, 10, 0, (struct sockaddr *)&s, &slen);
LOG_INFO("got response from: %s:%d", inet_ntoa(s.sin_addr), ntohs(s.sin_port));
}
if (default_server) {
server_addr(default_server, &s.sin_addr.s_addr, &port);
}
} while (s.sin_addr.s_addr == 0 && running);
closesocket(disc_sock);
return s.sin_addr.s_addr;
}
#define FIXED_CAP_LEN 256
#define VAR_CAP_LEN 128
void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate) {
struct sockaddr_in serv_addr;
static char fixed_cap[FIXED_CAP_LEN], var_cap[VAR_CAP_LEN] = "";
bool reconnect = false;
unsigned failed_connect = 0;
unsigned slimproto_port = 0;
in_addr_t previous_server = 0;
int i;
memset(&status, 0, sizeof(status));
wake_create(wake_e);
loglevel = level;
running = true;
if (server) {
server_addr(server, &slimproto_ip, &slimproto_port);
}
if (!slimproto_ip) {
slimproto_ip = discover_server(server);
}
if (!slimproto_port) {
slimproto_port = PORT;
}
if (name) {
strncpy(player_name, name, PLAYER_NAME_LEN);
player_name[PLAYER_NAME_LEN] = '\0';
}
if (namefile) {
FILE *fp;
name_file = namefile;
fp = fopen(namefile, "r");
if (fp) {
if (!fgets(player_name, PLAYER_NAME_LEN, fp)) {
player_name[PLAYER_NAME_LEN] = '\0';
} else {
// strip any \n from fgets response
int len = strlen(player_name);
if (len > 0 && player_name[len - 1] == '\n') {
player_name[len - 1] = '\0';
}
LOG_INFO("retrieved name %s from %s", player_name, name_file);
}
fclose(fp);
}
}
if (!running) return;
LOCK_O;
snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING,
((maxSampleRate > 0) ? maxSampleRate : output.supported_rates[0]));
for (i = 0; i < MAX_CODECS; i++) {
if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < FIXED_CAP_LEN - 10) {
strcat(fixed_cap, ",");
strcat(fixed_cap, codecs[i]->types);
}
}
UNLOCK_O;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = slimproto_ip;
serv_addr.sin_port = htons(slimproto_port);
LOG_INFO("connecting to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
new_server = 0;
while (running) {
if (new_server) {
previous_server = slimproto_ip;
slimproto_ip = serv_addr.sin_addr.s_addr = new_server;
LOG_INFO("switching server to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
new_server = 0;
reconnect = false;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
set_nonblock(sock);
set_nosigpipe(sock);
if (connect_timeout(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr), 5) != 0) {
if (previous_server) {
slimproto_ip = serv_addr.sin_addr.s_addr = previous_server;
LOG_INFO("new server not reachable, reverting to previous server %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
} else {
LOG_INFO("unable to connect to server %u", failed_connect);
sleep(5);
}
// rediscover server if it was not set at startup
if (!server && ++failed_connect > 5) {
slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL);
}
} else {
struct sockaddr_in our_addr;
socklen_t len;
LOG_INFO("connected");
var_cap[0] = '\0';
failed_connect = 0;
// check if this is a local player now we are connected & signal to server via 'loc' format
// this requires LocalPlayer server plugin to enable direct file access
len = sizeof(our_addr);
getsockname(sock, (struct sockaddr *) &our_addr, &len);
if (our_addr.sin_addr.s_addr == serv_addr.sin_addr.s_addr) {
LOG_INFO("local player");
strcat(var_cap, ",loc");
}
// add on any capablity to be sent to the new server
if (new_server_cap) {
strcat(var_cap, new_server_cap);
free(new_server_cap);
new_server_cap = NULL;
}
sendHELO(reconnect, fixed_cap, var_cap, mac);
slimproto_run();
if (!reconnect) {
reconnect = true;
}
usleep(100000);
}
previous_server = 0;
closesocket(sock);
}
}
void slimproto_stop(void) {
LOG_INFO("slimproto stop");
running = false;
}

View File

@@ -0,0 +1,185 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// packet formats for slimproto
#ifndef SUN
#pragma pack(push, 1)
#else
#pragma pack(1)
#endif
// from S:N:Slimproto _hello_handler
struct HELO_packet {
char opcode[4];
u32_t length;
u8_t deviceid;
u8_t revision;
u8_t mac[6];
u8_t uuid[16];
u16_t wlan_channellist;
u32_t bytes_received_H, bytes_received_L;
char lang[2];
// u8_t capabilities[];
};
// S:N:Slimproto _stat_handler
struct STAT_packet {
char opcode[4];
u32_t length;
u32_t event;
u8_t num_crlf;
u8_t mas_initialized;
u8_t mas_mode;
u32_t stream_buffer_size;
u32_t stream_buffer_fullness;
u32_t bytes_received_H;
u32_t bytes_received_L;
u16_t signal_strength;
u32_t jiffies;
u32_t output_buffer_size;
u32_t output_buffer_fullness;
u32_t elapsed_seconds;
u16_t voltage;
u32_t elapsed_milliseconds;
u32_t server_timestamp;
u16_t error_code;
};
// S:N:Slimproto _disco_handler
struct DSCO_packet {
char opcode[4];
u32_t length;
u8_t reason;
};
// S:N:Slimproto _http_response_handler
struct RESP_header {
char opcode[4];
u32_t length;
// char header[] - added in sendRESP
};
// S:N:Slimproto _http_metadata_handler
struct META_header {
char opcode[4];
u32_t length;
// char metadata[]
};
// S:N:Slimproto _http_setting_handler
struct SETD_header {
char opcode[4];
u32_t length;
u8_t id;
// data
};
#if IR
struct IR_packet {
char opcode[4];
u32_t length;
u32_t jiffies;
u8_t format; // ignored by server
u8_t bits; // ignored by server
u32_t ir_code;
};
#endif
// from S:P:Squeezebox stream_s
struct strm_packet {
char opcode[4];
char command;
u8_t autostart;
u8_t format;
u8_t pcm_sample_size;
u8_t pcm_sample_rate;
u8_t pcm_channels;
u8_t pcm_endianness;
u8_t threshold;
u8_t spdif_enable;
u8_t transition_period;
u8_t transition_type;
u8_t flags;
u8_t output_threshold;
u8_t slaves;
u32_t replay_gain;
u16_t server_port;
u32_t server_ip;
//char request_string[];
};
// S:P:Squeezebox2
struct aude_packet {
char opcode[4];
u8_t enable_spdif;
u8_t enable_dac;
};
// S:P:Squeezebox2
struct audg_packet {
char opcode[4];
u32_t old_gainL; // unused
u32_t old_gainR; // unused
u8_t adjust;
u8_t preamp; // unused
u32_t gainL;
u32_t gainR;
// squence ids - unused
};
// S:P:Squeezebox2
struct cont_packet {
char opcode[4];
u32_t metaint;
u8_t loop;
// guids we don't use
};
// S:C:Commands
struct serv_packet {
char opcode[4];
u32_t server_ip;
// possible sync group
};
// S:P:Squeezebox2
struct setd_packet {
char opcode[4];
u8_t id;
char data[];
};
// codec open - this is an extension to slimproto to allow the server to read the header and then return decode params
struct codc_packet {
char opcode[4];
u8_t format;
u8_t pcm_sample_size;
u8_t pcm_sample_rate;
u8_t pcm_channels;
u8_t pcm_endianness;
};
#ifndef SUN
#pragma pack(pop)
#else
#pragma pack()
#endif

View File

@@ -0,0 +1,803 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additions (c) Paul Hermann, 2015-2017 under the same license terms
* -Control of Raspberry pi GPIO for amplifier power
* -Launch script on power status change from LMS
*/
// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, GPIO, IR, DSD, LINKALL to influence build
#define MAJOR_VERSION "1.9"
#define MINOR_VERSION "2"
#define MICRO_VERSION "1145"
#if defined(CUSTOM_VERSION)
#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION STR(CUSTOM_VERSION)
#else
#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION
#endif
#if !defined(MODEL_NAME)
#define MODEL_NAME SqueezeLite
#endif
#define QUOTE(name) #name
#define STR(macro) QUOTE(macro)
#define MODEL_NAME_STRING STR(MODEL_NAME)
// build detection
#if defined (EMBEDDED)
#undef EMBEDDED
#define EMBEDDED 1
#elif defined(linux)
#define LINUX 1
#define OSX 0
#define WIN 0
#define FREEBSD 0
#elif defined (__APPLE__)
#define LINUX 0
#define OSX 1
#define WIN 0
#define FREEBSD 0
#elif defined (_MSC_VER)
#define LINUX 0
#define OSX 0
#define WIN 1
#define FREEBSD 0
#elif defined(__FreeBSD__)
#define LINUX 0
#define OSX 0
#define WIN 0
#define FREEBSD 1
#elif defined (__sun)
#define SUN 1
#define LINUX 1
#define PORTAUDIO 1
#define PA18API 1
#define OSX 0
#define WIN 0
#else
#error unknown target
#endif
#if !EMBEDDED
#if LINUX && !defined(PORTAUDIO)
#define ALSA 1
#define PORTAUDIO 0
#else
#define ALSA 0
#define PORTAUDIO 1
#endif
#endif
#if !defined(LOOPBACK)
#if SUN
#define EVENTFD 0
#define WINEVENT 0
#define SELFPIPE 1
#elif LINUX && !defined(SELFPIPE)
#define EVENTFD 1
#define SELFPIPE 0
#define WINEVENT 0
#endif
#if (LINUX && !EVENTFD) || OSX || FREEBSD
#define EVENTFD 0
#define SELFPIPE 1
#define WINEVENT 0
#endif
#if WIN
#define EVENTFD 0
#define SELFPIPE 0
#define WINEVENT 1
#endif
#else
#define EVENTFD 0
#define SELFPIPE 0
#define WINEVENT 0
#undef LOOPBACK
#define LOOPBACK 1
#endif
#if defined(RESAMPLE) || defined(RESAMPLE_MP)
#undef RESAMPLE
#define RESAMPLE 1 // resampling
#define PROCESS 1 // any sample processing (only resampling at present)
#elif defined(RESAMPLE16)
#undef RESAMPLE16
#define RESAMPLE16 1
#define PROCESS 1
#else
#define RESAMPLE 0
#define PROCESS 0
#endif
#if defined(RESAMPLE_MP)
#undef RESAMPLE_MP
#define RESAMPLE_MP 1
#else
#define RESAMPLE_MP 0
#endif
#if defined(FFMPEG)
#undef FFMPEG
#define FFMPEG 1
#else
#define FFMPEG 0
#endif
#if (LINUX || OSX) && defined(VISEXPORT)
#undef VISEXPORT
#define VISEXPORT 1 // visulizer export support uses linux shared memory
#else
#define VISEXPORT 0
#endif
#if LINUX && defined(IR)
#undef IR
#define IR 1
#else
#define IR 0
#endif
#if defined(DSD)
#undef DSD
#define DSD 1
#define IF_DSD(x) { x }
#else
#undef DSD
#define DSD 0
#define IF_DSD(x)
#endif
#if defined(LINKALL)
#undef LINKALL
#define LINKALL 1 // link all libraries at build time - requires all to be available at run time
#else
#define LINKALL 0
#endif
#if defined (USE_SSL)
#undef USE_SSL
#define USE_SSL 1
#else
#define USE_SSL 0
#endif
#if !LINKALL
// dynamically loaded libraries at run time
#if LINUX
#define LIBFLAC "libFLAC.so.8"
#define LIBMAD "libmad.so.0"
#define LIBMPG "libmpg123.so.0"
#define LIBVORBIS "libvorbisfile.so.3"
#define LIBTREMOR "libvorbisidec.so.1"
#define LIBFAAD "libfaad.so.2"
#define LIBAVUTIL "libavutil.so.%d"
#define LIBAVCODEC "libavcodec.so.%d"
#define LIBAVFORMAT "libavformat.so.%d"
#define LIBSOXR "libsoxr.so.0"
#define LIBLIRC "liblirc_client.so.0"
#endif
#if OSX
#define LIBFLAC "libFLAC.8.dylib"
#define LIBMAD "libmad.0.dylib"
#define LIBMPG "libmpg123.0.dylib"
#define LIBVORBIS "libvorbisfile.3.dylib"
#define LIBTREMOR "libvorbisidec.1.dylib"
#define LIBFAAD "libfaad.2.dylib"
#define LIBAVUTIL "libavutil.%d.dylib"
#define LIBAVCODEC "libavcodec.%d.dylib"
#define LIBAVFORMAT "libavformat.%d.dylib"
#define LIBSOXR "libsoxr.0.dylib"
#endif
#if WIN
#define LIBFLAC "libFLAC.dll"
#define LIBMAD "libmad-0.dll"
#define LIBMPG "libmpg123-0.dll"
#define LIBVORBIS "libvorbisfile.dll"
#define LIBTREMOR "libvorbisidec.dll"
#define LIBFAAD "libfaad2.dll"
#define LIBAVUTIL "avutil-%d.dll"
#define LIBAVCODEC "avcodec-%d.dll"
#define LIBAVFORMAT "avformat-%d.dll"
#define LIBSOXR "libsoxr.dll"
#endif
#if FREEBSD
#define LIBFLAC "libFLAC.so.11"
#define LIBMAD "libmad.so.2"
#define LIBMPG "libmpg123.so.0"
#define LIBVORBIS "libvorbisfile.so.6"
#define LIBTREMOR "libvorbisidec.so.1"
#define LIBFAAD "libfaad.so.2"
#define LIBAVUTIL "libavutil.so.%d"
#define LIBAVCODEC "libavcodec.so.%d"
#define LIBAVFORMAT "libavformat.so.%d"
#endif
#endif // !LINKALL
// config options
#define STREAMBUF_SIZE (2 * 1024 * 1024)
#define OUTPUTBUF_SIZE (44100 * 8 * 10)
#define OUTPUTBUF_SIZE_CROSSFADE (OUTPUTBUF_SIZE * 12 / 10)
#define MAX_HEADER 4096 // do not reduce as icy-meta max is 4080
#if ALSA
#define ALSA_BUFFER_TIME 40
#define ALSA_PERIOD_COUNT 4
#define OUTPUT_RT_PRIORITY 45
#endif
#define SL_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#if SUN || OSXPPC
#undef SL_LITTLE_ENDIAN
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#if EMBEDDED
#include "embedded.h"
#endif
#if LINUX || OSX || FREEBSD || EMBEDDED
#include <unistd.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/poll.h>
#if !LINKALL
#include <dlfcn.h>
#endif
#include <pthread.h>
#include <signal.h>
#if SUN
#include <sys/types.h>
#endif /* SUN */
#define STREAM_THREAD_STACK_SIZE 8 * 1024
#define DECODE_THREAD_STACK_SIZE 20 * 1024
#define OUTPUT_THREAD_STACK_SIZE 8 * 1024
#define IR_THREAD_STACK_SIZE 8 * 1024
#if !OSX
#define thread_t pthread_t;
#endif
#define closesocket(s) close(s)
#define last_error() errno
#define ERROR_WOULDBLOCK EWOULDBLOCK
#if !EMBEDDED
#ifdef SUN
typedef uint8_t u8_t;
typedef uint16_t u16_t;
typedef uint32_t u32_t;
typedef uint64_t u64_t;
#else
typedef u_int8_t u8_t;
typedef u_int16_t u16_t;
typedef u_int32_t u32_t;
typedef u_int64_t u64_t;
#endif /* SUN */
typedef int16_t s16_t;
typedef int32_t s32_t;
typedef int64_t s64_t;
#endif
#define mutex_type pthread_mutex_t
#define mutex_create(m) pthread_mutex_init(&m, NULL)
#if HAS_MUTEX_CREATE_P
#define mutex_create_p(m) pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); pthread_mutex_init(&m, &attr); pthread_mutexattr_destroy(&attr)
#else
#define mutex_create_p(m) mutex_create(m)
#endif
#define mutex_lock(m) pthread_mutex_lock(&m)
#define mutex_unlock(m) pthread_mutex_unlock(&m)
#define mutex_destroy(m) pthread_mutex_destroy(&m)
#define thread_type pthread_t
#endif
#if WIN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <io.h>
#define STREAM_THREAD_STACK_SIZE (1024 * 64)
#define DECODE_THREAD_STACK_SIZE (1024 * 128)
#define OUTPUT_THREAD_STACK_SIZE (1024 * 64)
typedef unsigned __int8 u8_t;
typedef unsigned __int16 u16_t;
typedef unsigned __int32 u32_t;
typedef unsigned __int64 u64_t;
typedef __int16 s16_t;
typedef __int32 s32_t;
typedef __int64 s64_t;
typedef BOOL bool;
#define true TRUE
#define false FALSE
#define inline __inline
#define mutex_type HANDLE
#define mutex_create(m) m = CreateMutex(NULL, FALSE, NULL)
#define mutex_create_p mutex_create
#define mutex_lock(m) WaitForSingleObject(m, INFINITE)
#define mutex_unlock(m) ReleaseMutex(m)
#define mutex_destroy(m) CloseHandle(m)
#define thread_type HANDLE
#define usleep(x) Sleep(x/1000)
#define sleep(x) Sleep(x*1000)
#define last_error() WSAGetLastError()
#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
#define open _open
#define read _read
#define snprintf _snprintf
#define in_addr_t u32_t
#define socklen_t int
#define ssize_t int
#define RTLD_NOW 0
#endif
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
#endif
typedef u32_t frames_t;
typedef int sockfd;
#if EVENTFD
#include <sys/eventfd.h>
#define event_event int
#define event_handle struct pollfd
#define wake_create(e) e = eventfd(0, 0)
#define wake_signal(e) eventfd_write(e, 1)
#define wake_clear(e) eventfd_t val; eventfd_read(e, &val)
#define wake_close(e) close(e)
#endif
#if SELFPIPE
#define event_handle struct pollfd
#define event_event struct wake
#define wake_create(e) pipe(e.fds); set_nonblock(e.fds[0]); set_nonblock(e.fds[1])
#define wake_signal(e) write(e.fds[1], ".", 1)
#define wake_clear(e) char c[10]; read(e, &c, 10)
#define wake_close(e) close(e.fds[0]); close(e.fds[1])
struct wake {
int fds[2];
};
#endif
#if LOOPBACK
#define event_handle struct pollfd
#define event_event struct wake
#define wake_create(e) _wake_create(&e)
#define wake_signal(e) send(e.fds[1], ".", 1, 0)
#define wake_clear(e) char c; recv(e, &c, 1, 0)
#define wake_close(e) closesocket(e.mfds); closesocket(e.fds[0]); closesocket(e.fds[1])
struct wake {
int mfds;
int fds[2];
};
void _wake_create(event_event*);
#endif
#if WINEVENT
#define event_event HANDLE
#define event_handle HANDLE
#define wake_create(e) e = CreateEvent(NULL, FALSE, FALSE, NULL)
#define wake_signal(e) SetEvent(e)
#define wake_close(e) CloseHandle(e)
#endif
// printf/scanf formats for u64_t
#if (LINUX && __WORDSIZE == 64) || (FREEBSD && __LP64__)
#define FMT_u64 "%lu"
#define FMT_x64 "%lx"
#elif __GLIBC_HAVE_LONG_LONG || defined __GNUC__ || WIN || SUN
#define FMT_u64 "%llu"
#define FMT_x64 "%llx"
#else
#error can not support u64_t
#endif
#define MAX_SILENCE_FRAMES 2048
#define FIXED_ONE 0x10000
#ifndef BYTES_PER_FRAME
#define BYTES_PER_FRAME 8
#endif
#if BYTES_PER_FRAME == 8
#define ISAMPLE_T s32_t
#else
#define ISAMPLE_T s16_t
#endif
#define min(a,b) (((a) < (b)) ? (a) : (b))
// logging
typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
const char *logtime(void);
void logprint(const char *fmt, ...);
#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
// utils.c (non logging)
typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type;
#if WIN && USE_SSL
char* strcasestr(const char *haystack, const char *needle);
#endif
char *next_param(char *src, char c);
u32_t gettime_ms(void);
void get_mac(u8_t *mac);
void set_nonblock(sockfd s);
int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout);
void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr);
void set_readwake_handles(event_handle handles[], sockfd s, event_event e);
event_type wait_readwake(event_handle handles[], int timeout);
void packN(u32_t *dest, u32_t val);
void packn(u16_t *dest, u16_t val);
u32_t unpackN(u32_t *src);
u16_t unpackn(u16_t *src);
#if OSX
void set_nosigpipe(sockfd s);
#else
#define set_nosigpipe(s)
#endif
#if SUN
void init_daemonize(void);
int daemon(int,int);
#endif
#if WIN
void winsock_init(void);
void winsock_close(void);
void *dlopen(const char *filename, int flag);
void *dlsym(void *handle, const char *symbol);
char *dlerror(void);
int poll(struct pollfd *fds, unsigned long numfds, int timeout);
#endif
#if LINUX || FREEBSD
void touch_memory(u8_t *buf, size_t size);
#endif
// buffer.c
struct buffer {
u8_t *buf;
u8_t *readp;
u8_t *writep;
u8_t *wrap;
size_t size;
size_t base_size;
mutex_type mutex;
};
// _* called with mutex locked
unsigned _buf_used(struct buffer *buf);
unsigned _buf_space(struct buffer *buf);
unsigned _buf_cont_read(struct buffer *buf);
unsigned _buf_cont_write(struct buffer *buf);
void _buf_inc_readp(struct buffer *buf, unsigned by);
void _buf_inc_writep(struct buffer *buf, unsigned by);
void buf_flush(struct buffer *buf);
void buf_adjust(struct buffer *buf, size_t mod);
void _buf_resize(struct buffer *buf, size_t size);
void buf_init(struct buffer *buf, size_t size);
void buf_destroy(struct buffer *buf);
// slimproto.c
void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate);
void slimproto_stop(void);
void wake_controller(void);
// stream.c
typedef enum { STOPPED = 0, DISCONNECT, STREAMING_WAIT,
STREAMING_BUFFERING, STREAMING_FILE, STREAMING_HTTP, SEND_HEADERS, RECV_HEADERS } stream_state;
typedef enum { DISCONNECT_OK = 0, LOCAL_DISCONNECT = 1, REMOTE_DISCONNECT = 2, UNREACHABLE = 3, TIMEOUT = 4 } disconnect_code;
struct streamstate {
stream_state state;
disconnect_code disconnect;
char *header;
size_t header_len;
bool sent_headers;
bool cont_wait;
u64_t bytes;
unsigned threshold;
u32_t meta_interval;
u32_t meta_next;
u32_t meta_left;
bool meta_send;
};
void stream_init(log_level level, unsigned stream_buf_size);
void stream_close(void);
void stream_file(const char *header, size_t header_len, unsigned threshold);
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
bool stream_disconnect(void);
// decode.c
typedef enum { DECODE_STOPPED = 0, DECODE_READY, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state;
struct decodestate {
decode_state state;
bool new_stream;
mutex_type mutex;
#if PROCESS
bool direct;
bool process;
#endif
};
#if PROCESS
struct processstate {
u8_t *inbuf, *outbuf;
unsigned max_in_frames, max_out_frames;
unsigned in_frames, out_frames;
unsigned in_sample_rate, out_sample_rate;
unsigned long total_in, total_out;
};
#endif
struct codec {
char id;
char *types;
unsigned min_read_bytes;
unsigned min_space;
void (*open)(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
void (*close)(void);
decode_state (*decode)(void);
};
void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs);
void decode_close(void);
void decode_flush(void);
unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]);
void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
#if PROCESS
// process.c
void process_samples(void);
void process_drain(void);
void process_flush(void);
unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]);
void process_init(char *opt);
#endif
#if RESAMPLE || RESAMPLE16
// resample.c
void resample_samples(struct processstate *process);
bool resample_drain(struct processstate *process);
bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]);
void resample_flush(void);
bool resample_init(char *opt);
#endif
// output.c output_alsa.c output_pa.c output_pack.c
typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING,
OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state;
#if DSD
typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format;
typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, U8, U16_LE, U16_BE, U32_LE, U32_BE } output_format;
#else
typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, S24_BE, S24_3BE, S16_BE, S8_BE } output_format;
#endif
typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state;
typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir;
typedef enum { FADE_NONE = 0, FADE_CROSSFADE, FADE_IN, FADE_OUT, FADE_INOUT } fade_mode;
#define MAX_SUPPORTED_SAMPLERATES 18
#define TEST_RATES = { 768000, 705600, 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 }
struct outputstate {
output_state state;
output_format format;
const char *device;
#if ALSA
unsigned buffer;
unsigned period;
#endif
bool track_started;
#if PORTAUDIO
bool pa_reopen;
unsigned latency;
int pa_hostapi_option;
#endif
int (* write_cb)(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);
unsigned start_frames;
unsigned frames_played;
unsigned frames_played_dmp;// frames played at the point delay is measured
unsigned current_sample_rate;
unsigned supported_rates[MAX_SUPPORTED_SAMPLERATES]; // ordered largest first so [0] is max_rate
unsigned default_sample_rate;
bool error_opening;
unsigned device_frames;
u32_t updated;
u32_t track_start_time;
u32_t current_replay_gain;
union {
u32_t pause_frames;
u32_t skip_frames;
u32_t start_at;
};
unsigned next_sample_rate; // set in decode thread
u8_t *track_start; // set in decode thread
u32_t gainL; // set by slimproto
u32_t gainR; // set by slimproto
bool invert; // set by slimproto
u32_t next_replay_gain; // set by slimproto
unsigned threshold; // set by slimproto
fade_state fade;
u8_t *fade_start;
u8_t *fade_end;
fade_dir fade_dir;
fade_mode fade_mode; // set by slimproto
unsigned fade_secs; // set by slimproto
unsigned rate_delay;
bool delay_active;
u32_t stop_time;
u32_t idle_to;
#if DSD
dsd_format next_fmt; // set in decode thread
dsd_format outfmt;
dsd_format dsdfmt; // set in dsd_init - output for DSD: DOP, DSD_U8, ...
unsigned dsd_delay; // set in dsd_init - delay in ms switching to/from dop
#endif
};
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
void output_close_common(void);
void output_flush(void);
// _* called with mutex locked
frames_t _output_frames(frames_t avail);
void _checkfade(bool);
// output_alsa.c
#if ALSA
void list_devices(void);
void list_mixers(const char *output_device);
void set_volume(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *mixer_device, char *volume_mixer, bool mixer_unmute, bool mixer_linear);
void output_close_alsa(void);
#endif
// output_pa.c
#if PORTAUDIO
void list_devices(void);
void set_volume(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_pa(void);
void _pa_open(void);
#endif
// output_embedded.c
#if EMBEDDED
void set_volume(unsigned left, unsigned right);
bool test_open(const char *device, unsigned rates[], bool userdef_rates);
void output_init_embedded(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_embedded(void);
#else
// output_stdout.c
void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay);
void output_close_stdout(void);
#endif
// output_pack.c
void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format);
void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR);
s32_t gain(s32_t gain, s32_t sample);
s32_t to_gain(float f);
// output_vis.c
#if VISEXPORT
void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence);
void output_vis_init(log_level level, u8_t *mac);
void vis_stop(void);
#else
#define _vis_export(...)
#define vis_stop()
#endif
// dop.c
#if DSD
bool is_stream_dop(u8_t *lptr, u8_t *rptr, int step, frames_t frames);
void update_dop(u32_t *ptr, frames_t frames, bool invert);
void dsd_silence_frames(u32_t *ptr, frames_t frames);
void dsd_invert(u32_t *ptr, frames_t frames);
void dsd_init(dsd_format format, unsigned delay);
#endif
// codecs
#define MAX_CODECS 9
struct codec *register_flac(void);
struct codec *register_pcm(void);
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);
//gpio.c
#if GPIO
void relay( int state);
void relay_script(int state);
int gpio_pin;
bool gpio_active_low;
bool gpio_active;
char *power_script;
// my amp state
int ampstate;
#endif
// ir.c
#if IR
struct irstate {
mutex_type mutex;
u32_t code;
u32_t ts;
};
void ir_init(log_level level, char *lircrc);
void ir_close(void);
#endif
// sslsym.c
#if USE_SSL && !LINKALL
bool load_ssl_symbols(void);
void free_ssl_symbols(void);
bool ssl_loaded;
#endif

View File

@@ -0,0 +1,593 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// stream thread
#define _GNU_SOURCE
#include "squeezelite.h"
#include <fcntl.h>
#if USE_SSL
#include "openssl/ssl.h"
#include "openssl/err.h"
#endif
#if SUN
#include <signal.h>
#endif
static log_level loglevel;
static struct buffer buf;
struct buffer *streambuf = &buf;
#define LOCK mutex_lock(streambuf->mutex)
#define UNLOCK mutex_unlock(streambuf->mutex)
static sockfd fd;
struct streamstate stream;
#if USE_SSL
static SSL_CTX *SSLctx;
SSL *ssl;
#endif
#if !USE_SSL
#define _recv(ssl, fc, buf, n, opt) recv(fd, buf, n, opt)
#define _send(ssl, fd, buf, n, opt) send(fd, buf, n, opt)
#define _poll(ssl, pollinfo, timeout) poll(pollinfo, 1, timeout)
#define _last_error() last_error()
#else
#define _last_error() ERROR_WOULDBLOCK
static int _recv(SSL *ssl, int fd, void *buffer, size_t bytes, int options) {
int n;
if (!ssl) return recv(fd, buffer, bytes, options);
n = SSL_read(ssl, (u8_t*) buffer, bytes);
if (n <= 0 && SSL_get_error(ssl, n) == SSL_ERROR_ZERO_RETURN) return 0;
return n;
}
static int _send(SSL *ssl, int fd, void *buffer, size_t bytes, int options) {
int n;
if (!ssl) return send(fd, buffer, bytes, options);
while (1) {
int err;
ERR_clear_error();
if ((n = SSL_write(ssl, (u8_t*) buffer, bytes)) >= 0) return n;
err = SSL_get_error(ssl, n);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue;
LOG_INFO("SSL write error %d", err );
return n;
}
}
/*
can't mimic exactly poll as SSL is a real pain. Even if SSL_pending returns
0, there might be bytes to read but when select (poll) return > 0, there might
be no frame available. As well select (poll) < 0 does not mean that there is
no data pending
*/
static int _poll(SSL *ssl, struct pollfd *pollinfo, int timeout) {
if (!ssl) return poll(pollinfo, 1, timeout);
if (pollinfo->events & POLLIN && SSL_pending(ssl)) {
if (pollinfo->events & POLLOUT) poll(pollinfo, 1, 0);
pollinfo->revents = POLLIN;
return 1;
}
return poll(pollinfo, 1, timeout);
}
#endif
static bool send_header(void) {
char *ptr = stream.header;
int len = stream.header_len;
unsigned try = 0;
ssize_t n;
while (len) {
n = _send(ssl, fd, ptr, len, MSG_NOSIGNAL);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK && try < 10) {
LOG_SDEBUG("retrying (%d) writing to socket", ++try);
usleep(1000);
continue;
}
LOG_INFO("failed writing to socket: %s", strerror(last_error()));
stream.disconnect = LOCAL_DISCONNECT;
stream.state = DISCONNECT;
wake_controller();
return false;
}
LOG_SDEBUG("wrote %d bytes to socket", n);
ptr += n;
len -= n;
}
LOG_SDEBUG("wrote header");
return true;
}
static bool running = true;
static void _disconnect(stream_state state, disconnect_code disconnect) {
stream.state = state;
stream.disconnect = disconnect;
#if USE_SSL
if (ssl) {
SSL_shutdown(ssl);
SSL_free(ssl);
ssl = NULL;
}
#endif
closesocket(fd);
fd = -1;
wake_controller();
}
static void *stream_thread() {
while (running) {
struct pollfd pollinfo;
size_t space;
LOCK;
space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
if (fd < 0 || !space || stream.state <= STREAMING_WAIT) {
UNLOCK;
usleep(space ? 100000 : 25000);
continue;
}
if (stream.state == STREAMING_FILE) {
int n = read(fd, streambuf->writep, space);
if (n == 0) {
LOG_INFO("end of stream");
_disconnect(DISCONNECT, DISCONNECT_OK);
}
if (n > 0) {
_buf_inc_writep(streambuf, n);
stream.bytes += n;
LOG_SDEBUG("streambuf read %d bytes", n);
}
if (n < 0) {
LOG_WARN("error reading: %s", strerror(last_error()));
_disconnect(DISCONNECT, REMOTE_DISCONNECT);
}
UNLOCK;
continue;
} else {
pollinfo.fd = fd;
pollinfo.events = POLLIN;
if (stream.state == SEND_HEADERS) {
pollinfo.events |= POLLOUT;
}
}
UNLOCK;
if (_poll(ssl, &pollinfo, 100)) {
LOCK;
// check socket has not been closed while in poll
if (fd < 0) {
UNLOCK;
continue;
}
if ((pollinfo.revents & POLLOUT) && stream.state == SEND_HEADERS) {
if (send_header()) stream.state = RECV_HEADERS;
stream.header_len = 0;
UNLOCK;
continue;
}
if (pollinfo.revents & (POLLIN | POLLHUP)) {
// get response headers
if (stream.state == RECV_HEADERS) {
// read one byte at a time to catch end of header
char c;
static int endtok;
int n = _recv(ssl, fd, &c, 1, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading headers: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
*(stream.header + stream.header_len) = c;
stream.header_len++;
if (stream.header_len > MAX_HEADER - 1) {
LOG_ERROR("received headers too long: %u", stream.header_len);
_disconnect(DISCONNECT, LOCAL_DISCONNECT);
}
if (stream.header_len > 1 && (c == '\r' || c == '\n')) {
endtok++;
if (endtok == 4) {
*(stream.header + stream.header_len) = '\0';
LOG_INFO("headers: len: %d\n%s", stream.header_len, stream.header);
stream.state = stream.cont_wait ? STREAMING_WAIT : STREAMING_BUFFERING;
wake_controller();
}
} else {
endtok = 0;
}
UNLOCK;
continue;
}
// receive icy meta data
if (stream.meta_interval && stream.meta_next == 0) {
if (stream.meta_left == 0) {
// read meta length
u8_t c;
int n = _recv(ssl, fd, &c, 1, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
stream.meta_left = 16 * c;
stream.header_len = 0; // amount of received meta data
// MAX_HEADER must be more than meta max of 16 * 255
}
if (stream.meta_left) {
int n = _recv(ssl, fd, stream.header + stream.header_len, stream.meta_left, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
stream.meta_left -= n;
stream.header_len += n;
}
if (stream.meta_left == 0) {
if (stream.header_len) {
*(stream.header + stream.header_len) = '\0';
LOG_INFO("icy meta: len: %u\n%s", stream.header_len, stream.header);
stream.meta_send = true;
wake_controller();
}
stream.meta_next = stream.meta_interval;
UNLOCK;
continue;
}
// stream body into streambuf
} else {
int n;
space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
if (stream.meta_interval) {
space = min(space, stream.meta_next);
}
n = _recv(ssl, fd, streambuf->writep, space, 0);
if (n == 0) {
LOG_INFO("end of stream");
_disconnect(DISCONNECT, DISCONNECT_OK);
}
if (n < 0 && _last_error() != ERROR_WOULDBLOCK) {
LOG_INFO("error reading: %s", strerror(last_error()));
_disconnect(DISCONNECT, REMOTE_DISCONNECT);
}
if (n > 0) {
_buf_inc_writep(streambuf, n);
stream.bytes += n;
if (stream.meta_interval) {
stream.meta_next -= n;
}
} else {
UNLOCK;
continue;
}
if (stream.state == STREAMING_BUFFERING && stream.bytes > stream.threshold) {
stream.state = STREAMING_HTTP;
wake_controller();
}
LOG_SDEBUG("streambuf read %d bytes", n);
}
}
UNLOCK;
} else {
LOG_SDEBUG("poll timeout");
}
}
#if USE_SSL
if (SSLctx) {
SSL_CTX_free(SSLctx);
}
#endif
return 0;
}
static thread_type thread;
void stream_init(log_level level, unsigned stream_buf_size) {
loglevel = level;
LOG_INFO("init stream");
LOG_DEBUG("streambuf size: %u", stream_buf_size);
buf_init(streambuf, stream_buf_size);
if (streambuf->buf == NULL) {
LOG_ERROR("unable to malloc buffer");
exit(0);
}
#if USE_SSL
#if !LINKALL
if (ssl_loaded) {
#endif
SSL_library_init();
SSLctx = SSL_CTX_new(SSLv23_client_method());
if (SSLctx == NULL) {
LOG_ERROR("unable to allocate SSL context");
exit(0);
}
SSL_CTX_set_options(SSLctx, SSL_OP_NO_SSLv2);
#if !LINKALL
}
#endif
ssl = NULL;
#endif
#if SUN
signal(SIGPIPE, SIG_IGN); /* Force sockets to return -1 with EPIPE on pipe signal */
#endif
stream.state = STOPPED;
stream.header = malloc(MAX_HEADER);
*stream.header = '\0';
fd = -1;
#if LINUX || FREEBSD
touch_memory(streambuf->buf, streambuf->size);
#endif
#if LINUX || OSX || FREEBSD || EMBEDDED
pthread_attr_t attr;
pthread_attr_init(&attr);
#ifdef PTHREAD_STACK_MIN
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + STREAM_THREAD_STACK_SIZE);
#endif
pthread_create(&thread, &attr, stream_thread, NULL);
pthread_attr_destroy(&attr);
#if HAS_PTHREAD_SETNAME_NP
pthread_setname_np(thread, "stream");
#endif
#endif
#if WIN
thread = CreateThread(NULL, STREAM_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&stream_thread, NULL, 0, NULL);
#endif
}
void stream_close(void) {
LOG_INFO("close stream");
LOCK;
running = false;
UNLOCK;
#if LINUX || OSX || FREEBSD || POSIX
pthread_join(thread, NULL);
#endif
free(stream.header);
buf_destroy(streambuf);
}
void stream_file(const char *header, size_t header_len, unsigned threshold) {
buf_flush(streambuf);
LOCK;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
LOG_INFO("opening local file: %s", stream.header);
#if WIN
fd = open(stream.header, O_RDONLY | O_BINARY);
#else
fd = open(stream.header, O_RDONLY);
#endif
stream.state = STREAMING_FILE;
if (fd < 0) {
LOG_INFO("can't open file: %s", stream.header);
stream.state = DISCONNECT;
}
wake_controller();
stream.cont_wait = false;
stream.meta_interval = 0;
stream.meta_next = 0;
stream.meta_left = 0;
stream.meta_send = false;
stream.sent_headers = false;
stream.bytes = 0;
stream.threshold = threshold;
UNLOCK;
}
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
struct sockaddr_in addr;
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
LOG_ERROR("failed to create socket");
return;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = ip;
addr.sin_port = port;
LOG_INFO("connecting to %s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
set_nonblock(sock);
set_nosigpipe(sock);
if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) {
LOG_INFO("unable to connect to server");
LOCK;
stream.state = DISCONNECT;
stream.disconnect = UNREACHABLE;
UNLOCK;
return;
}
#if USE_SSL
if (ntohs(port) == 443) {
char *server = strcasestr(header, "Host:");
ssl = SSL_new(SSLctx);
SSL_set_fd(ssl, sock);
// add SNI
if (server) {
char *p, *servername = malloc(1024);
sscanf(server, "Host:%255[^:]s", servername);
for (p = servername; *p == ' '; p++);
SSL_set_tlsext_host_name(ssl, p);
free(servername);
}
while (1) {
int status, err = 0;
ERR_clear_error();
status = SSL_connect(ssl);
// successful negotiation
if (status == 1) break;
// error or non-blocking requires more time
if (status < 0) {
err = SSL_get_error(ssl, status);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue;
}
LOG_WARN("unable to open SSL socket %d (%d)", status, err);
closesocket(sock);
SSL_free(ssl);
ssl = NULL;
LOCK;
stream.state = DISCONNECT;
stream.disconnect = UNREACHABLE;
UNLOCK;
return;
}
} else {
ssl = NULL;
}
#endif
buf_flush(streambuf);
LOCK;
fd = sock;
stream.state = SEND_HEADERS;
stream.cont_wait = cont_wait;
stream.meta_interval = 0;
stream.meta_next = 0;
stream.meta_left = 0;
stream.meta_send = false;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
LOG_INFO("header: %s", stream.header);
stream.sent_headers = false;
stream.bytes = 0;
stream.threshold = threshold;
UNLOCK;
}
bool stream_disconnect(void) {
bool disc = false;
LOCK;
#if USE_SSL
if (ssl) {
SSL_shutdown(ssl);
SSL_free(ssl);
ssl = NULL;
}
#endif
if (fd != -1) {
closesocket(fd);
fd = -1;
disc = true;
}
stream.state = STOPPED;
UNLOCK;
return disc;
}

View File

@@ -0,0 +1,563 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
#if LINUX || OSX || FREEBSD || EMBEDDED
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#if FREEBSD
#include <ifaddrs.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#endif
#endif
#if SUN
#include <sys/socket.h>
#include <sys/sockio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#endif
#if WIN
#include <iphlpapi.h>
#if USE_SSL
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#endif
#endif
#if OSX
#include <net/if_dl.h>
#include <net/if_types.h>
#include <ifaddrs.h>
#include <netdb.h>
#endif
#include <fcntl.h>
// logging functions
const char *logtime(void) {
static char buf[100];
#if WIN
SYSTEMTIME lt;
GetLocalTime(&lt);
sprintf(buf, "[%02d:%02d:%02d.%03d]", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
strftime(buf, sizeof(buf), "[%T.", localtime(&tv.tv_sec));
sprintf(buf+strlen(buf), "%06ld]", (long)tv.tv_usec);
#endif
return buf;
}
void logprint(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
fflush(stderr);
}
// cmdline parsing
char *next_param(char *src, char c) {
static char *str = NULL;
char *ptr, *ret;
if (src) str = src;
if (str && (ptr = strchr(str, c))) {
ret = str;
*ptr = '\0';
str = ptr + 1;
} else {
ret = str;
str = NULL;
}
return ret && ret[0] ? ret : NULL;
}
// clock
u32_t gettime_ms(void) {
#if WIN
return GetTickCount();
#else
#if LINUX || FREEBSD || EMBEDDED
struct timespec ts;
#ifdef CLOCK_MONOTONIC
if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
#else
if (!clock_gettime(CLOCK_REALTIME, &ts)) {
#endif
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
#endif
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}
// mac address
#if LINUX && !defined(SUN)
// search first 4 interfaces returned by IFCONF
void get_mac(u8_t mac[]) {
char *utmac;
struct ifconf ifc;
struct ifreq *ifr, *ifend;
struct ifreq ifreq;
struct ifreq ifs[4];
utmac = getenv("UTMAC");
if (utmac)
{
if ( strlen(utmac) == 17 )
{
if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6)
{
return;
}
}
}
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
int s = socket(AF_INET, SOCK_DGRAM, 0);
ifc.ifc_len = sizeof(ifs);
ifc.ifc_req = ifs;
if (ioctl(s, SIOCGIFCONF, &ifc) == 0) {
ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq));
for (ifr = ifc.ifc_req; ifr < ifend; ifr++) {
if (ifr->ifr_addr.sa_family == AF_INET) {
strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name));
if (ioctl (s, SIOCGIFHWADDR, &ifreq) == 0) {
memcpy(mac, ifreq.ifr_hwaddr.sa_data, 6);
if (mac[0]+mac[1]+mac[2] != 0) {
break;
}
}
}
}
}
close(s);
}
#endif
#if SUN
void get_mac(u8_t mac[]) {
struct arpreq parpreq;
struct sockaddr_in *psa;
struct in_addr inaddr;
struct hostent *phost;
char hostname[MAXHOSTNAMELEN];
char **paddrs;
char *utmac;
int sock;
int status=0;
utmac = getenv("UTMAC");
if (utmac)
{
if ( strlen(utmac) == 17 )
{
if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
&mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6)
{
return;
}
}
}
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
gethostname(hostname, MAXHOSTNAMELEN);
phost = gethostbyname(hostname);
paddrs = phost->h_addr_list;
memcpy(&inaddr.s_addr, *paddrs, sizeof(inaddr.s_addr));
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sock == -1)
{
mac[5] = 1;
return;
}
memset(&parpreq, 0, sizeof(struct arpreq));
psa = (struct sockaddr_in *) &parpreq.arp_pa;
memset(psa, 0, sizeof(struct sockaddr_in));
psa->sin_family = AF_INET;
memcpy(&psa->sin_addr, *paddrs, sizeof(struct in_addr));
status = ioctl(sock, SIOCGARP, &parpreq);
if(status == -1)
{
mac[5] = 2;
return;
}
mac[0] = (unsigned char) parpreq.arp_ha.sa_data[0];
mac[1] = (unsigned char) parpreq.arp_ha.sa_data[1];
mac[2] = (unsigned char) parpreq.arp_ha.sa_data[2];
mac[3] = (unsigned char) parpreq.arp_ha.sa_data[3];
mac[4] = (unsigned char) parpreq.arp_ha.sa_data[4];
mac[5] = (unsigned char) parpreq.arp_ha.sa_data[5];
}
#endif
#if OSX || FREEBSD
void get_mac(u8_t mac[]) {
struct ifaddrs *addrs, *ptr;
const struct sockaddr_dl *dlAddr;
const unsigned char *base;
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
if (getifaddrs(&addrs) == 0) {
ptr = addrs;
while (ptr) {
if (ptr->ifa_addr->sa_family == AF_LINK && ((const struct sockaddr_dl *) ptr->ifa_addr)->sdl_type == IFT_ETHER) {
dlAddr = (const struct sockaddr_dl *)ptr->ifa_addr;
base = (const unsigned char*) &dlAddr->sdl_data[dlAddr->sdl_nlen];
memcpy(mac, base, min(dlAddr->sdl_alen, 6));
break;
}
ptr = ptr->ifa_next;
}
freeifaddrs(addrs);
}
}
#endif
#if WIN
#pragma comment(lib, "IPHLPAPI.lib")
void get_mac(u8_t mac[]) {
IP_ADAPTER_INFO AdapterInfo[16];
DWORD dwBufLen = sizeof(AdapterInfo);
DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen);
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_SUCCESS) {
memcpy(mac, AdapterInfo[0].Address, 6);
}
}
#endif
void set_nonblock(sockfd s) {
#if WIN
u_long iMode = 1;
ioctlsocket(s, FIONBIO, &iMode);
#else
int flags = fcntl(s, F_GETFL,0);
fcntl(s, F_SETFL, flags | O_NONBLOCK);
#endif
}
// connect for socket already set to non blocking with timeout in seconds
int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
fd_set w, e;
struct timeval tval;
if (connect(sock, addr, addrlen) < 0) {
#if !WIN
if (last_error() != EINPROGRESS) {
#else
if (last_error() != WSAEWOULDBLOCK) {
#endif
return -1;
}
}
FD_ZERO(&w);
FD_SET(sock, &w);
e = w;
tval.tv_sec = timeout;
tval.tv_usec = 0;
// only return 0 if w set and sock error is zero, otherwise return error code
if (select(sock + 1, NULL, &w, &e, timeout ? &tval : NULL) == 1 && FD_ISSET(sock, &w)) {
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *)&error, &len);
return error;
}
return -1;
}
void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr) {
struct addrinfo *res = NULL;
struct addrinfo hints;
const char *port = NULL;
if (strtok(server, ":")) {
port = strtok(NULL, ":");
if (port) {
*port_ptr = atoi(port);
}
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
getaddrinfo(server, NULL, &hints, &res);
if (res && res->ai_addr) {
*ip_ptr = ((struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr;
}
if (res) {
freeaddrinfo(res);
}
}
void set_readwake_handles(event_handle handles[], sockfd s, event_event e) {
#if WINEVENT
handles[0] = WSACreateEvent();
handles[1] = e;
WSAEventSelect(s, handles[0], FD_READ | FD_CLOSE);
#elif SELFPIPE || LOOPBACK
handles[0].fd = s;
handles[1].fd = e.fds[0];
handles[0].events = POLLIN;
handles[1].events = POLLIN;
#else
handles[0].fd = s;
handles[1].fd = e;
handles[0].events = POLLIN;
handles[1].events = POLLIN;
#endif
}
event_type wait_readwake(event_handle handles[], int timeout) {
#if WINEVENT
int wait = WSAWaitForMultipleEvents(2, handles, FALSE, timeout, FALSE);
if (wait == WSA_WAIT_EVENT_0) {
WSAResetEvent(handles[0]);
return EVENT_READ;
} else if (wait == WSA_WAIT_EVENT_0 + 1) {
return EVENT_WAKE;
} else {
return EVENT_TIMEOUT;
}
#else
if (poll(handles, 2, timeout) > 0) {
if (handles[0].revents) {
return EVENT_READ;
}
if (handles[1].revents) {
wake_clear(handles[1].fd);
return EVENT_WAKE;
}
}
return EVENT_TIMEOUT;
#endif
}
#if LOOPBACK
void _wake_create(event_event* e) {
struct sockaddr_in addr;
short port;
socklen_t len;
e->mfds = e->fds[0] = e->fds[1] = -1;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// create sending socket - will wait for connections
addr.sin_port = 0;
e->mfds = socket(AF_INET, SOCK_STREAM, 0);
bind(e->mfds, (struct sockaddr*) &addr, sizeof(addr));
len = sizeof(struct sockaddr);
// get assigned port & listen
getsockname(e->mfds, (struct sockaddr *) &addr, &len);
port = addr.sin_port;
listen(e->mfds, 1);
// create receiving socket
addr.sin_port = 0;
e->fds[0] = socket(AF_INET, SOCK_STREAM, 0);
bind(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
// connect to sender (we listen so it can be blocking)
addr.sin_port = port;
connect(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
// this one will work or fail, but not block
len = sizeof(struct sockaddr);
e->fds[1] = accept(e->mfds, (struct sockaddr*) &addr, &len);
}
#endif
// pack/unpack to network byte order
void packN(u32_t *dest, u32_t val) {
u8_t *ptr = (u8_t *)dest;
*(ptr) = (val >> 24) & 0xFF; *(ptr+1) = (val >> 16) & 0xFF; *(ptr+2) = (val >> 8) & 0xFF; *(ptr+3) = val & 0xFF;
}
void packn(u16_t *dest, u16_t val) {
u8_t *ptr = (u8_t *)dest;
*(ptr) = (val >> 8) & 0xFF; *(ptr+1) = val & 0xFF;
}
u32_t unpackN(u32_t *src) {
u8_t *ptr = (u8_t *)src;
return *(ptr) << 24 | *(ptr+1) << 16 | *(ptr+2) << 8 | *(ptr+3);
}
u16_t unpackn(u16_t *src) {
u8_t *ptr = (u8_t *)src;
return *(ptr) << 8 | *(ptr+1);
}
#if OSX
void set_nosigpipe(sockfd s) {
int set = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
}
#endif
#if WIN
void winsock_init(void) {
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
int WSerr = WSAStartup(wVersionRequested, &wsaData);
if (WSerr != 0) {
LOG_ERROR("Bad winsock version");
exit(1);
}
}
void winsock_close(void) {
WSACleanup();
}
void *dlopen(const char *filename, int flag) {
SetLastError(0);
return LoadLibrary((LPCTSTR)filename);
}
void *dlsym(void *handle, const char *symbol) {
SetLastError(0);
return (void *)GetProcAddress(handle, symbol);
}
char *dlerror(void) {
static char ret[32];
int last = GetLastError();
if (last) {
sprintf(ret, "code: %i", last);
SetLastError(0);
return ret;
}
return NULL;
}
int poll(struct pollfd *fds, unsigned long numfds, int timeout) {
fd_set r, w;
struct timeval tv;
int ret, i, max_fds = fds[0].fd;
FD_ZERO(&r);
FD_ZERO(&w);
for (i = 0; i < numfds; i++) {
if (fds[i].events & POLLIN) FD_SET(fds[i].fd, &r);
if (fds[i].events & POLLOUT) FD_SET(fds[i].fd, &w);
if (max_fds < fds[i].fd) max_fds = fds[i].fd;
}
tv.tv_sec = timeout / 1000;
tv.tv_usec = 1000 * (timeout % 1000);
ret = select(max_fds + 1, &r, &w, NULL, &tv);
if (ret < 0) return ret;
for (i = 0; i < numfds; i++) {
fds[i].revents = 0;
if (FD_ISSET(fds[i].fd, &r)) fds[i].revents |= POLLIN;
if (FD_ISSET(fds[i].fd, &w)) fds[i].revents |= POLLOUT;
}
return ret;
}
#endif
#if LINUX || FREEBSD
void touch_memory(u8_t *buf, size_t size) {
u8_t *ptr;
for (ptr = buf; ptr < buf + size; ptr += sysconf(_SC_PAGESIZE)) {
*ptr = 0;
}
}
#endif
#if WIN && USE_SSL
char *strcasestr(const char *haystack, const char *needle) {
size_t length_needle;
size_t length_haystack;
size_t i;
if (!haystack || !needle)
return NULL;
length_needle = strlen(needle);
length_haystack = strlen(haystack) - length_needle + 1;
for (i = 0; i < length_haystack; i++)
{
size_t j;
for (j = 0; j < length_needle; j++)
{
unsigned char c1;
unsigned char c2;
c1 = haystack[i+j];
c2 = needle[j];
if (toupper(c1) != toupper(c2))
goto next;
}
return (char *) haystack + i;
next:
;
}
return NULL;
}
#endif

View File

@@ -0,0 +1,372 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
/*
* 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)
#else
#define ALIGN(n) (n << 16)
#endif
// automatically select between floating point (preferred) and fixed point libraries:
// NOTE: works with Tremor version here: http://svn.xiph.org/trunk/Tremor, not vorbisidec.1.0.2 currently in ubuntu
// we take common definations from <vorbis/vorbisfile.h> even though we can use tremor at run time
// 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 <ivorbisfile.h>
#else
#include <vorbis/vorbisfile.h>
#endif
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);
int (* ov_clear)(OggVorbis_File *vf);
long (* ov_read)(OggVorbis_File *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream);
long (* ov_read_tremor)(OggVorbis_File *vf, char *buffer, int length, int *bitstream);
int (* ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks);
#endif
};
static struct vorbis *v;
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
#if LINKALL
#define OV(h, fn, ...) (ov_ ## fn)(__VA_ARGS__)
#define TREMOR(h) 0
#if !WIN
extern int ov_read_tremor(); // needed to enable compilation, not linked
#endif
#else
#define OV(h, fn, ...) (h)->ov_##fn(__VA_ARGS__)
#define TREMOR(h) (h)->ov_read_tremor
#endif
// called with mutex locked within vorbis_decode to avoid locking O before S
static size_t _read_cb(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes;
LOCK_S;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(bytes, size * nmemb);
memcpy(ptr, streambuf->readp, bytes);
_buf_inc_readp(streambuf, bytes);
UNLOCK_S;
return bytes / size;
}
// these are needed for older versions of tremor, later versions and libvorbis allow NULL to be used
static int _seek_cb(void *datasource, ogg_int64_t offset, int whence) { return -1; }
static int _close_cb(void *datasource) { return 0; }
static long _tell_cb(void *datasource) { return 0; }
static decode_state vorbis_decode(void) {
static int channels;
frames_t frames;
int bytes, s, n;
u8_t *write_buf;
LOCK_S;
if (stream.state <= DISCONNECT && !_buf_used(streambuf)) {
UNLOCK_S;
return DECODE_COMPLETE;
}
UNLOCK_S;
if (decode.new_stream) {
ov_callbacks cbs;
int err;
struct vorbis_info *info;
cbs.read_func = _read_cb;
if (TREMOR(v)) {
cbs.seek_func = _seek_cb; cbs.close_func = _close_cb; cbs.tell_func = _tell_cb;
} else {
cbs.seek_func = NULL; cbs.close_func = NULL; cbs.tell_func = NULL;
}
if ((err = OV(v, open_callbacks, streambuf, v->vf, NULL, 0, cbs)) < 0) {
LOG_WARN("open_callbacks error: %d", err);
return DECODE_COMPLETE;
}
v->opened = true;
info = OV(v, info, v->vf, -1);
LOG_INFO("setting track_start");
LOCK_O;
output.next_sample_rate = decode_newstream(info->rate, 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;
channels = info->channels;
if (channels > 2) {
LOG_WARN("too many channels: %d", channels);
return DECODE_ERROR;
}
}
#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;
);
#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
#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);
#else
n = OV(v, read, v->vf, (char *)write_buf, bytes, 1, 2, 1, &s);
#endif
#if !WIN
} else {
n = OV(v, read_tremor, v->vf, (char *)write_buf, bytes, &s);
#endif
}
#endif
#if FRAME_BUF
LOCK_O_direct;
#endif
if (n > 0) {
frames_t count;
s16_t *iptr;
ISAMPLE_T *optr;
frames = n / 2 / channels;
count = frames * channels;
iptr = (s16_t *)write_buf + count;
optr = (ISAMPLE_T *)write_buf + frames * 2;
if (channels == 2) {
#if BYTES_PER_FRAME == 4
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
#else
while (count--) {
*--optr = *--iptr << 16;
}
#endif
} else if (channels == 1) {
while (count--) {
*--optr = ALIGN(*--iptr);
*--optr = ALIGN(*iptr);
}
}
IF_DIRECT(
_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = frames;
);
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) {
// recoverable hole in stream, seen when skipping
LOG_DEBUG("hole in stream");
} else {
LOG_INFO("ov_read error: %d", n);
UNLOCK_O_direct;
return DECODE_COMPLETE;
}
UNLOCK_O_direct;
return DECODE_RUNNING;
}
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);
v->opened = false;
}
}
}
static void vorbis_close(void) {
if (v->opened) {
OV(v, clear, v->vf);
v->opened = false;
}
free(v->vf);
#if FRAME_BUF
free(v->write_buf);
v->write_buf = NULL;
#endif
v->vf = NULL;
}
static bool load_vorbis() {
#if !LINKALL
void *handle = dlopen(LIBVORBIS, RTLD_NOW);
char *err;
bool tremor = false;
if (!handle) {
handle = dlopen(LIBTREMOR, RTLD_NOW);
if (handle) {
tremor = true;
} else {
LOG_INFO("dlerror: %s", dlerror());
return false;
}
}
v->ov_read = tremor ? NULL : dlsym(handle, "ov_read");
v->ov_read_tremor = tremor ? dlsym(handle, "ov_read") : NULL;
v->ov_info = dlsym(handle, "ov_info");
v->ov_clear = dlsym(handle, "ov_clear");
v->ov_open_callbacks = dlsym(handle, "ov_open_callbacks");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
return false;
}
LOG_INFO("loaded %s", tremor ? LIBTREMOR : LIBVORBIS);
#endif
return true;
}
struct codec *register_vorbis(void) {
static struct codec ret = {
'o', // id
"ogg", // types
4096, // min read
20480, // min space
vorbis_open, // open
vorbis_close, // close
vorbis_decode,// decode
};
v = malloc(sizeof(struct vorbis));
if (!v) {
return NULL;
}
v->vf = NULL;
v->opened = false;
if (!load_vorbis()) {
return NULL;
}
LOG_INFO("using vorbis to decode ogg");
return &ret;
}