new opus decoder

This commit is contained in:
philippe44
2023-04-04 22:13:31 -07:00
parent 18b830eaa3
commit 8d1888a198
3 changed files with 218 additions and 121 deletions

View File

@@ -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,23 +37,53 @@
#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
#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);
#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
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;
extern log_level loglevel;
@@ -89,26 +116,112 @@ 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 status;
}
return bytes;
static int read_opus_header(void) {
int status = 0;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (bytes && !status) {
// 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);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
if (!OG(&gu, sync_pageseek, &u->sync, &u->page)) continue;
u->fetch = false;
}
//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) {
@@ -117,30 +230,16 @@ static decode_state opus_decompress(void) {
static int channels;
u8_t *write_buf;
LOCK_S;
if (decode.new_stream) {
int status = read_opus_header();
if (stream.state <= DISCONNECT && u->end) {
UNLOCK_S;
return DECODE_COMPLETE;
}
UNLOCK_S;
if (decode.new_stream) {
struct OpusFileCallbacks cbs;
const struct OpusHead *info;
int err;
cbs.read = (op_read_func) _read_cb;
cbs.seek = NULL; cbs.tell = NULL; cbs.close = NULL;
if ((u->of = OP(u, open_callbacks, streambuf, &cbs, NULL, 0, &err)) == NULL) {
LOG_WARN("open_callbacks error: %d", err);
return DECODE_COMPLETE;
if (status == 0) {
return DECODE_RUNNING;
} else if (status < 0) {
LOG_WARN("can't create codec");
return DECODE_ERROR;
}
info = OP(u, head, u->of, -1);
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;
}