|
@@ -30,9 +30,6 @@
|
|
|
* thread has a higher priority. Using an interim buffer where opus 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
|
|
|
*/
|
|
|
-#if EMBEDDED
|
|
|
-#define FRAME_BUF 2048
|
|
|
-#endif
|
|
|
|
|
|
#if BYTES_PER_FRAME == 4
|
|
|
#define ALIGN(n) (n)
|
|
@@ -40,22 +37,52 @@
|
|
|
#define ALIGN(n) (n << 16)
|
|
|
#endif
|
|
|
|
|
|
-#include <opusfile.h>
|
|
|
+#include <ogg/ogg.h>
|
|
|
+#include <opus.h>
|
|
|
+
|
|
|
+// opus maximum output frames is 120ms @ 48kHz
|
|
|
+#define MAX_OPUS_FRAMES 5760
|
|
|
|
|
|
struct opus {
|
|
|
- struct OggOpusFile *of;
|
|
|
- bool end;
|
|
|
-#if FRAME_BUF
|
|
|
- u8_t *write_buf;
|
|
|
-#endif
|
|
|
+ enum {OGG_SYNC, OGG_HEADER, OGG_PCM, OGG_DECODE} status;
|
|
|
+ ogg_stream_state state;
|
|
|
+ ogg_packet packet;
|
|
|
+ ogg_sync_state sync;
|
|
|
+ ogg_page page;
|
|
|
+ OpusDecoder* decoder;
|
|
|
+ int rate, gain, pre_skip;
|
|
|
+ bool fetch;
|
|
|
+ size_t overframes;
|
|
|
+ u8_t *overbuf;
|
|
|
+ int channels;
|
|
|
+};
|
|
|
+
|
|
|
#if !LINKALL
|
|
|
- // opus symbols to be dynamically loaded
|
|
|
- void (*op_free)(OggOpusFile *_of);
|
|
|
- int (*op_read)(OggOpusFile *_of, opus_int16 *_pcm, int _buf_size, int *_li);
|
|
|
- const OpusHead* (*op_head)(OggOpusFile *_of, int _li);
|
|
|
- OggOpusFile* (*op_open_callbacks) (void *_source, OpusFileCallbacks *_cb, unsigned char *_initial_data, size_t _initial_bytes, int *_error);
|
|
|
+static struct {
|
|
|
+ void *handle;
|
|
|
+ int (*ogg_stream_init)(ogg_stream_state* os, int serialno);
|
|
|
+ int (*ogg_stream_clear)(ogg_stream_state* os);
|
|
|
+ int (*ogg_stream_reset)(ogg_stream_state* os);
|
|
|
+ int (*ogg_stream_eos)(ogg_stream_state* os);
|
|
|
+ int (*ogg_stream_reset_serialno)(ogg_stream_state* os, int serialno);
|
|
|
+ int (*ogg_sync_clear)(ogg_sync_state* oy);
|
|
|
+ void (*ogg_packet_clear)(ogg_packet* op);
|
|
|
+ char* (*ogg_sync_buffer)(ogg_sync_state* oy, long size);
|
|
|
+ int (*ogg_sync_wrote)(ogg_sync_state* oy, long bytes);
|
|
|
+ long (*ogg_sync_pageseek)(ogg_sync_state* oy, ogg_page* og);
|
|
|
+ int (*ogg_sync_pageout)(ogg_sync_state* oy, ogg_page* og);
|
|
|
+ int (*ogg_stream_pagein)(ogg_stream_state* os, ogg_page* og);
|
|
|
+ int (*ogg_stream_packetout)(ogg_stream_state* os, ogg_packet* op);
|
|
|
+ int (*ogg_page_packets)(const ogg_page* og);
|
|
|
+} go;
|
|
|
+
|
|
|
+static struct {
|
|
|
+ void* handle;
|
|
|
+ OpusDecoder* (*opus_decoder_create)(opus_int32 Fs, int channels, int* error);
|
|
|
+ int (*opus_decode)(OpusDecoder* st, const unsigned char* data, opus_int32 len, opus_int16* pcm, int frame_size, int decode_fec);
|
|
|
+ void (*opus_decoder_destroy)(OpusDecoder* st);
|
|
|
+} gu;
|
|
|
#endif
|
|
|
-};
|
|
|
|
|
|
static struct opus *u;
|
|
|
|
|
@@ -89,58 +116,130 @@ extern struct processstate process;
|
|
|
#endif
|
|
|
|
|
|
#if LINKALL
|
|
|
-#define OP(h, fn, ...) (op_ ## fn)(__VA_ARGS__)
|
|
|
+#define OG(h, fn, ...) (ogg_ ## fn)(__VA_ARGS__)
|
|
|
+#define OP(h, fn, ...) (opus_ ## fn)(__VA_ARGS__)
|
|
|
#else
|
|
|
-#define OP(h, fn, ...) (h)->op_ ## fn(__VA_ARGS__)
|
|
|
+#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
|
|
|
+#define OP(h, fn, ...) (h)->opus_ ## fn(__VA_ARGS__)
|
|
|
#endif
|
|
|
|
|
|
-// called with mutex locked within vorbis_decode to avoid locking O before S
|
|
|
-static int _read_cb(void *datasource, char *ptr, int size) {
|
|
|
- size_t bytes;
|
|
|
+static unsigned parse_uint16(const unsigned char* _data) {
|
|
|
+ return _data[0] | _data[1] << 8;
|
|
|
+}
|
|
|
+
|
|
|
+static int parse_int16(const unsigned char* _data) {
|
|
|
+ return ((_data[0] | _data[1] << 8) ^ 0x8000) - 0x8000;
|
|
|
+}
|
|
|
+
|
|
|
+static opus_uint32 parse_uint32(const unsigned char* _data) {
|
|
|
+ return _data[0] | (opus_uint32)_data[1] << 8 |
|
|
|
+ (opus_uint32)_data[2] << 16 | (opus_uint32)_data[3] << 24;
|
|
|
+}
|
|
|
+
|
|
|
+static int get_opus_packet(void) {
|
|
|
+ int status = 0;
|
|
|
|
|
|
LOCK_S;
|
|
|
+ size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
|
|
+
|
|
|
+ while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
|
|
|
+ do {
|
|
|
+ size_t consumed = min(bytes, 4096);
|
|
|
+ char* buffer = OG(&gu, sync_buffer, &u->sync, consumed);
|
|
|
+ memcpy(buffer, streambuf->readp, consumed);
|
|
|
+ OG(&gu, sync_wrote, &u->sync, consumed);
|
|
|
|
|
|
- bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
|
|
- bytes = min(bytes, size);
|
|
|
+ _buf_inc_readp(streambuf, consumed);
|
|
|
+ bytes -= consumed;
|
|
|
+ } while (!(status = OG(&gu, sync_pageseek, &u->sync, &u->page)) && bytes);
|
|
|
|
|
|
- memcpy(ptr, streambuf->readp, bytes);
|
|
|
- _buf_inc_readp(streambuf, bytes);
|
|
|
+ // if we have a new page, put it in
|
|
|
+ if (status) OG(&go, stream_pagein, &u->state, &u->page);
|
|
|
+ }
|
|
|
|
|
|
UNLOCK_S;
|
|
|
-
|
|
|
- return bytes;
|
|
|
+ return status;
|
|
|
}
|
|
|
|
|
|
-static decode_state opus_decompress(void) {
|
|
|
- frames_t frames;
|
|
|
- int n;
|
|
|
- static int channels;
|
|
|
- u8_t *write_buf;
|
|
|
+static int read_opus_header(void) {
|
|
|
+ int status = 0;
|
|
|
|
|
|
LOCK_S;
|
|
|
+ size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
|
|
|
|
|
- if (stream.state <= DISCONNECT && u->end) {
|
|
|
- UNLOCK_S;
|
|
|
- return DECODE_COMPLETE;
|
|
|
- }
|
|
|
+ while (bytes && !status) {
|
|
|
|
|
|
- UNLOCK_S;
|
|
|
+ // first fetch a page if we need one
|
|
|
+ if (u->fetch) {
|
|
|
+ size_t consumed = min(bytes, 4096);
|
|
|
+ char* buffer = OG(&gu, sync_buffer, &u->sync, consumed);
|
|
|
+ memcpy(buffer, streambuf->readp, consumed);
|
|
|
+ OG(&gu, sync_wrote, &u->sync, consumed);
|
|
|
|
|
|
- if (decode.new_stream) {
|
|
|
- struct OpusFileCallbacks cbs;
|
|
|
- const struct OpusHead *info;
|
|
|
- int err;
|
|
|
+ _buf_inc_readp(streambuf, consumed);
|
|
|
+ bytes -= consumed;
|
|
|
|
|
|
- cbs.read = (op_read_func) _read_cb;
|
|
|
- cbs.seek = NULL; cbs.tell = NULL; cbs.close = NULL;
|
|
|
+ if (!OG(&gu, sync_pageseek, &u->sync, &u->page)) continue;
|
|
|
+ u->fetch = false;
|
|
|
+ }
|
|
|
|
|
|
- if ((u->of = OP(u, open_callbacks, streambuf, &cbs, NULL, 0, &err)) == NULL) {
|
|
|
- LOG_WARN("open_callbacks error: %d", err);
|
|
|
- return DECODE_COMPLETE;
|
|
|
+ //bytes = min(bytes, size);
|
|
|
+ switch (u->status) {
|
|
|
+ case OGG_SYNC:
|
|
|
+ u->status = OGG_HEADER;
|
|
|
+ //OG(&gu, sync_pageout, &u->sync, &u->page);
|
|
|
+ OG(&gu, stream_reset_serialno, &u->state, OG(&gu, page_serialno, &u->page));
|
|
|
+ break;
|
|
|
+ case OGG_HEADER:
|
|
|
+ status = OG(&gu, stream_pagein, &u->state, &u->page);
|
|
|
+ if (OG(&gu, stream_packetout, &u->state, &u->packet)) {
|
|
|
+ u->status = OGG_PCM;
|
|
|
+ if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) {
|
|
|
+ LOG_ERROR("wrong opus header packet (size:%u)", u->packet.bytes);
|
|
|
+ status = -100;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ u->channels = u->packet.packet[9];
|
|
|
+ u->pre_skip = parse_uint16(u->packet.packet + 10);
|
|
|
+ u->rate = parse_uint32(u->packet.packet + 12);
|
|
|
+ u->gain = parse_int16(u->packet.packet + 16);
|
|
|
+ u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status);
|
|
|
+ if (!u->decoder || status != OPUS_OK) {
|
|
|
+ LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ u->fetch = true;
|
|
|
+ break;
|
|
|
+ case OGG_PCM:
|
|
|
+ // loop until we have consumed VorbisComment and get ready for a new packet
|
|
|
+ u->fetch = true;
|
|
|
+ status = OG(&gu, page_packets, &u->page);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ UNLOCK_S;
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+static decode_state opus_decompress(void) {
|
|
|
+ frames_t frames;
|
|
|
+ int n;
|
|
|
+ static int channels;
|
|
|
+ u8_t *write_buf;
|
|
|
|
|
|
- info = OP(u, head, u->of, -1);
|
|
|
+ if (decode.new_stream) {
|
|
|
+ int status = read_opus_header();
|
|
|
|
|
|
+ if (status == 0) {
|
|
|
+ return DECODE_RUNNING;
|
|
|
+ } else if (status < 0) {
|
|
|
+ LOG_WARN("can't create codec");
|
|
|
+ return DECODE_ERROR;
|
|
|
+ }
|
|
|
+
|
|
|
LOCK_O;
|
|
|
output.next_sample_rate = decode_newstream(48000, output.supported_rates);
|
|
|
IF_DSD( output.next_fmt = PCM; )
|
|
@@ -148,39 +247,49 @@ static decode_state opus_decompress(void) {
|
|
|
if (output.fade_mode) _checkfade(true);
|
|
|
decode.new_stream = false;
|
|
|
UNLOCK_O;
|
|
|
-
|
|
|
- channels = info->channel_count;
|
|
|
+
|
|
|
+ channels = u->channels;
|
|
|
|
|
|
LOG_INFO("setting track_start");
|
|
|
}
|
|
|
|
|
|
-#if FRAME_BUF
|
|
|
- IF_DIRECT(
|
|
|
- frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
|
|
|
- frames = min(frames, FRAME_BUF);
|
|
|
- write_buf = u->write_buf;
|
|
|
- );
|
|
|
-#else
|
|
|
LOCK_O_direct;
|
|
|
IF_DIRECT(
|
|
|
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
|
|
|
write_buf = outputbuf->writep;
|
|
|
);
|
|
|
-#endif
|
|
|
IF_PROCESS(
|
|
|
frames = process.max_in_frames;
|
|
|
write_buf = process.inbuf;
|
|
|
);
|
|
|
|
|
|
- u->end = frames == 0;
|
|
|
-
|
|
|
- // write the decoded frames into outputbuf then unpack them (they are 16 bits)
|
|
|
- n = OP(u, read, u->of, (opus_int16*) write_buf, frames * channels, NULL);
|
|
|
+ // get some packets and decode them, or use the leftover from previous pass
|
|
|
+ if (u->overframes) {
|
|
|
+ /* use potential leftover from previous encoding. We know that it will fit this time
|
|
|
+ * as min_space is >=MAX_OPUS_FRAMES and we start from the beginning of the buffer */
|
|
|
+ memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME);
|
|
|
+ n = u->overframes;
|
|
|
+ u->overframes = 0;
|
|
|
+ } else if (get_opus_packet() > 0) {
|
|
|
+ if (frames < MAX_OPUS_FRAMES) {
|
|
|
+ // don't have enough contiguous space, use the overflow buffer (still works if n < 0)
|
|
|
+ n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) u->overbuf, MAX_OPUS_FRAMES, 0);
|
|
|
+ if (n > 0) {
|
|
|
+ u->overframes = n - min(n, frames);
|
|
|
+ n = min(n, frames);
|
|
|
+ memcpy(write_buf, u->overbuf, n * BYTES_PER_FRAME);
|
|
|
+ memmove(u->overbuf, u->overbuf + n, u->overframes);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /* we just do one packet at a time, although we could loop on packets but that means locking the
|
|
|
+ * outputbuf and streambuf for maybe a long time while we process it all, so don't do that */
|
|
|
+ n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) write_buf, frames, 0);
|
|
|
+ }
|
|
|
+ } else if (!OG(&go, page_eos, &u->page)) {
|
|
|
+ UNLOCK_O_direct;
|
|
|
+ return DECODE_RUNNING;
|
|
|
+ }
|
|
|
|
|
|
-#if FRAME_BUF
|
|
|
- LOCK_O_direct;
|
|
|
-#endif
|
|
|
-
|
|
|
if (n > 0) {
|
|
|
frames_t count;
|
|
|
s16_t *iptr;
|
|
@@ -199,14 +308,7 @@ static decode_state opus_decompress(void) {
|
|
|
)
|
|
|
|
|
|
if (channels == 2) {
|
|
|
-#if BYTES_PER_FRAME == 4
|
|
|
-#if FRAME_BUF
|
|
|
- // copy needed only when DIRECT and FRAME_BUF
|
|
|
- IF_DIRECT(
|
|
|
- memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
|
|
|
- )
|
|
|
-#endif
|
|
|
-#else
|
|
|
+#if BYTES_PER_FRAME == 8
|
|
|
while (count--) {
|
|
|
*--optr = ALIGN(*--iptr);
|
|
|
}
|
|
@@ -230,21 +332,16 @@ static decode_state opus_decompress(void) {
|
|
|
} else if (n == 0) {
|
|
|
|
|
|
if (stream.state <= DISCONNECT) {
|
|
|
- LOG_INFO("partial decode");
|
|
|
+ LOG_INFO("end of decode");
|
|
|
UNLOCK_O_direct;
|
|
|
return DECODE_COMPLETE;
|
|
|
} else {
|
|
|
LOG_INFO("no frame decoded");
|
|
|
}
|
|
|
|
|
|
- } else if (n == OP_HOLE) {
|
|
|
-
|
|
|
- // recoverable hole in stream, seen when skipping
|
|
|
- LOG_DEBUG("hole in stream");
|
|
|
-
|
|
|
} else {
|
|
|
|
|
|
- LOG_INFO("op_read error: %d", n);
|
|
|
+ LOG_INFO("opus decode error: %d", n);
|
|
|
UNLOCK_O_direct;
|
|
|
return DECODE_COMPLETE;
|
|
|
}
|
|
@@ -254,44 +351,52 @@ static decode_state opus_decompress(void) {
|
|
|
return DECODE_RUNNING;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
|
|
|
- if (!u->of) {
|
|
|
-#if FRAME_BUF
|
|
|
- if (!u->write_buf) u->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
|
|
|
-#endif
|
|
|
- } else {
|
|
|
- OP(u, free, u->of);
|
|
|
- u->of = NULL;
|
|
|
- }
|
|
|
- u->end = false;
|
|
|
+static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
|
|
|
+ if (u->decoder) OP(&gu, decoder_destroy, u->decoder);
|
|
|
+
|
|
|
+ if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME);
|
|
|
+ u->status = OGG_SYNC;
|
|
|
+ u->fetch = true;
|
|
|
+ u->overframes = 0;
|
|
|
+
|
|
|
+ OG(&gu, sync_init, &u->sync);
|
|
|
+ OG(&gu, stream_init, &u->state, -1);
|
|
|
}
|
|
|
|
|
|
-static void opus_close(void) {
|
|
|
- if (u->of) {
|
|
|
- OP(u, free, u->of);
|
|
|
- u->of = NULL;
|
|
|
- }
|
|
|
-#if FRAME_BUF
|
|
|
- free(u->write_buf);
|
|
|
- u->write_buf = NULL;
|
|
|
-#endif
|
|
|
+static void opus_close(void) {
|
|
|
+ if (u->decoder) OP(&gu, decoder_destroy, u->decoder);
|
|
|
+ free(u->overbuf);
|
|
|
+ OG(&gu, stream_clear, &u->state);
|
|
|
+ OG(&gu, sync_clear, &u->sync);
|
|
|
}
|
|
|
|
|
|
static bool load_opus(void) {
|
|
|
#if !LINKALL
|
|
|
- void *handle = dlopen(LIBOPUS, RTLD_NOW);
|
|
|
char *err;
|
|
|
-
|
|
|
- if (!handle) {
|
|
|
+ void *g_handle = dlopen(LIBOGG, RTLD_NOW);
|
|
|
+ void *u.handle = dlopen(LIBOPUS, RTLD_NOW);
|
|
|
+
|
|
|
+ if (!g_handle || !u_handle) {
|
|
|
LOG_INFO("dlerror: %s", dlerror());
|
|
|
return false;
|
|
|
}
|
|
|
-
|
|
|
- u->op_free = dlsym(handle, "op_free");
|
|
|
- u->op_read = dlsym(handle, "op_read");
|
|
|
- u->op_head = dlsym(handle, "op_head");
|
|
|
- u->op_open_callbacks = dlsym(handle, "op_open_callbacks");
|
|
|
+
|
|
|
+ g_handle->ogg_stream_clear = dlsym(g_handle->handle, "ogg_stream_clear");
|
|
|
+ g_handle->.ogg_stream_reset = dlsym(g_handle->handle, "ogg_stream_reset");
|
|
|
+ g_handle->ogg_stream_eos = dlsym(g_handle->handle, "ogg_stream_eos");
|
|
|
+ g_handle->ogg_stream_reset_serialno = dlsym(g_handle->handle, "ogg_stream_reset_serialno");
|
|
|
+ g_handle->ogg_sync_clear = dlsym(g_handle->handle, "ogg_sync_clear");
|
|
|
+ g_handle->ogg_packet_clear = dlsym(g_handle->handle, "ogg_packet_clear");
|
|
|
+ g_handle->ogg_sync_buffer = dlsym(g_handle->handle, "ogg_sync_buffer");
|
|
|
+ g_handle->ogg_sync_wrote = dlsym(g_handle->handle, "ogg_sync_wrote");
|
|
|
+ g_handle->ogg_sync_pageseek = dlsym(g_handle->handle, "ogg_sync_pageseek");
|
|
|
+ g_handle->ogg_sync_pageout = dlsym(g_handle->handle, "ogg_sync_pageout");
|
|
|
+ g_handle->ogg_stream_pagein = dlsym(g_handle->handle, "ogg_stream_pagein");
|
|
|
+ g_handle->ogg_stream_packetout = dlsym(g_handle->handle, "ogg_stream_packetout");
|
|
|
+ g_handle->ogg_page_packets = dlsym(g_handle->handle, "ogg_page_packets");
|
|
|
+ u_handle->opus_decoder_create = dlsym(u_handle->handle, "opus_decoder_create");
|
|
|
+ u_handle->opus_decoder_destroy = dlsym(u_handle->handle, "opus_decoder_destroy");
|
|
|
+ u_handle->opus_decode = dlsym(u_handle->handle, "opus_decode");
|
|
|
|
|
|
if ((err = dlerror()) != NULL) {
|
|
|
LOG_INFO("dlerror: %s", err);
|
|
@@ -308,23 +413,17 @@ struct codec *register_opus(void) {
|
|
|
static struct codec ret = {
|
|
|
'u', // id
|
|
|
"ops", // types
|
|
|
- 4*1024, // min read
|
|
|
- 32*1024, // min space
|
|
|
+ 8*1024, // min read
|
|
|
+ MAX_OPUS_FRAMES*BYTES_PER_FRAME*2, // min space
|
|
|
opus_open, // open
|
|
|
opus_close, // close
|
|
|
opus_decompress, // decode
|
|
|
};
|
|
|
|
|
|
- u = malloc(sizeof(struct opus));
|
|
|
- if (!u) {
|
|
|
+ if ((u = calloc(1, sizeof(struct opus))) == NULL) {
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
- u->of = NULL;
|
|
|
-#if FRAME_BUF
|
|
|
- u->write_buf = NULL;
|
|
|
-#endif
|
|
|
-
|
|
|
if (!load_opus()) {
|
|
|
return NULL;
|
|
|
}
|