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

@@ -1,5 +1,5 @@
idf_component_register( idf_component_register(
INCLUDE_DIRS . ./inc inc/alac inc/helix-aac inc/mad inc/resample16 inc/soxr inc/vorbis inc/opus inc/opusfile INCLUDE_DIRS . ./inc inc/alac inc/helix-aac inc/mad inc/resample16 inc/soxr inc/vorbis inc/opus
) )
if (DEFINED AAC_DISABLE_SBR) if (DEFINED AAC_DISABLE_SBR)
@@ -14,7 +14,6 @@ add_prebuilt_library(libvorbisidec lib/libvorbisidec.a )
add_prebuilt_library(libogg lib/libogg.a ) add_prebuilt_library(libogg lib/libogg.a )
add_prebuilt_library(libalac lib/libalac.a ) add_prebuilt_library(libalac lib/libalac.a )
add_prebuilt_library(libresample16 lib/libresample16.a ) add_prebuilt_library(libresample16 lib/libresample16.a )
add_prebuilt_library(libopusfile lib/libopusfile.a )
add_prebuilt_library(libopus lib/libopus.a ) add_prebuilt_library(libopus lib/libopus.a )
target_link_libraries(${COMPONENT_LIB} INTERFACE libmad) target_link_libraries(${COMPONENT_LIB} INTERFACE libmad)
@@ -24,5 +23,4 @@ target_link_libraries(${COMPONENT_LIB} INTERFACE libvorbisidec)
target_link_libraries(${COMPONENT_LIB} INTERFACE libogg) target_link_libraries(${COMPONENT_LIB} INTERFACE libogg)
target_link_libraries(${COMPONENT_LIB} INTERFACE libalac) target_link_libraries(${COMPONENT_LIB} INTERFACE libalac)
target_link_libraries(${COMPONENT_LIB} INTERFACE libresample16) target_link_libraries(${COMPONENT_LIB} INTERFACE libresample16)
target_link_libraries(${COMPONENT_LIB} INTERFACE libopusfile)
target_link_libraries(${COMPONENT_LIB} INTERFACE libopus) target_link_libraries(${COMPONENT_LIB} INTERFACE libopus)

Binary file not shown.

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 * 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 * 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 #if BYTES_PER_FRAME == 4
#define ALIGN(n) (n) #define ALIGN(n) (n)
@@ -40,23 +37,53 @@
#define ALIGN(n) (n << 16) #define ALIGN(n) (n << 16)
#endif #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 opus {
struct OggOpusFile *of; enum {OGG_SYNC, OGG_HEADER, OGG_PCM, OGG_DECODE} status;
bool end; ogg_stream_state state;
#if FRAME_BUF ogg_packet packet;
u8_t *write_buf; ogg_sync_state sync;
#endif ogg_page page;
#if !LINKALL OpusDecoder* decoder;
// opus symbols to be dynamically loaded int rate, gain, pre_skip;
void (*op_free)(OggOpusFile *_of); bool fetch;
int (*op_read)(OggOpusFile *_of, opus_int16 *_pcm, int _buf_size, int *_li); size_t overframes;
const OpusHead* (*op_head)(OggOpusFile *_of, int _li); u8_t *overbuf;
OggOpusFile* (*op_open_callbacks) (void *_source, OpusFileCallbacks *_cb, unsigned char *_initial_data, size_t _initial_bytes, int *_error); int channels;
#endif
}; };
#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; static struct opus *u;
extern log_level loglevel; extern log_level loglevel;
@@ -89,26 +116,112 @@ extern struct processstate process;
#endif #endif
#if LINKALL #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 #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 #endif
// called with mutex locked within vorbis_decode to avoid locking O before S static unsigned parse_uint16(const unsigned char* _data) {
static int _read_cb(void *datasource, char *ptr, int size) { return _data[0] | _data[1] << 8;
size_t bytes; }
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; LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
bytes = min(bytes, size); 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);
memcpy(ptr, streambuf->readp, bytes); _buf_inc_readp(streambuf, consumed);
_buf_inc_readp(streambuf, bytes); bytes -= consumed;
} while (!(status = OG(&gu, sync_pageseek, &u->sync, &u->page)) && bytes);
// if we have a new page, put it in
if (status) OG(&go, stream_pagein, &u->state, &u->page);
}
UNLOCK_S; 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) { static decode_state opus_decompress(void) {
@@ -117,30 +230,16 @@ static decode_state opus_decompress(void) {
static int channels; static int channels;
u8_t *write_buf; u8_t *write_buf;
LOCK_S;
if (stream.state <= DISCONNECT && u->end) {
UNLOCK_S;
return DECODE_COMPLETE;
}
UNLOCK_S;
if (decode.new_stream) { if (decode.new_stream) {
struct OpusFileCallbacks cbs; int status = read_opus_header();
const struct OpusHead *info;
int err;
cbs.read = (op_read_func) _read_cb; if (status == 0) {
cbs.seek = NULL; cbs.tell = NULL; cbs.close = NULL; return DECODE_RUNNING;
} else if (status < 0) {
if ((u->of = OP(u, open_callbacks, streambuf, &cbs, NULL, 0, &err)) == NULL) { LOG_WARN("can't create codec");
LOG_WARN("open_callbacks error: %d", err); return DECODE_ERROR;
return DECODE_COMPLETE;
} }
info = OP(u, head, u->of, -1);
LOCK_O; LOCK_O;
output.next_sample_rate = decode_newstream(48000, output.supported_rates); output.next_sample_rate = decode_newstream(48000, output.supported_rates);
IF_DSD( output.next_fmt = PCM; ) IF_DSD( output.next_fmt = PCM; )
@@ -149,37 +248,47 @@ static decode_state opus_decompress(void) {
decode.new_stream = false; decode.new_stream = false;
UNLOCK_O; UNLOCK_O;
channels = info->channel_count; channels = u->channels;
LOG_INFO("setting track_start"); 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; LOCK_O_direct;
IF_DIRECT( IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
write_buf = outputbuf->writep; write_buf = outputbuf->writep;
); );
#endif
IF_PROCESS( IF_PROCESS(
frames = process.max_in_frames; frames = process.max_in_frames;
write_buf = process.inbuf; write_buf = process.inbuf;
); );
u->end = frames == 0; // get some packets and decode them, or use the leftover from previous pass
if (u->overframes) {
// write the decoded frames into outputbuf then unpack them (they are 16 bits) /* use potential leftover from previous encoding. We know that it will fit this time
n = OP(u, read, u->of, (opus_int16*) write_buf, frames * channels, NULL); * 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);
#if FRAME_BUF n = u->overframes;
LOCK_O_direct; u->overframes = 0;
#endif } 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 (n > 0) { if (n > 0) {
frames_t count; frames_t count;
@@ -199,14 +308,7 @@ static decode_state opus_decompress(void) {
) )
if (channels == 2) { if (channels == 2) {
#if BYTES_PER_FRAME == 4 #if BYTES_PER_FRAME == 8
#if FRAME_BUF
// copy needed only when DIRECT and FRAME_BUF
IF_DIRECT(
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
)
#endif
#else
while (count--) { while (count--) {
*--optr = ALIGN(*--iptr); *--optr = ALIGN(*--iptr);
} }
@@ -230,21 +332,16 @@ static decode_state opus_decompress(void) {
} else if (n == 0) { } else if (n == 0) {
if (stream.state <= DISCONNECT) { if (stream.state <= DISCONNECT) {
LOG_INFO("partial decode"); LOG_INFO("end of decode");
UNLOCK_O_direct; UNLOCK_O_direct;
return DECODE_COMPLETE; return DECODE_COMPLETE;
} else { } else {
LOG_INFO("no frame decoded"); LOG_INFO("no frame decoded");
} }
} else if (n == OP_HOLE) {
// recoverable hole in stream, seen when skipping
LOG_DEBUG("hole in stream");
} else { } else {
LOG_INFO("op_read error: %d", n); LOG_INFO("opus decode error: %d", n);
UNLOCK_O_direct; UNLOCK_O_direct;
return DECODE_COMPLETE; return DECODE_COMPLETE;
} }
@@ -254,44 +351,52 @@ static decode_state opus_decompress(void) {
return DECODE_RUNNING; return DECODE_RUNNING;
} }
static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (!u->of) { if (u->decoder) OP(&gu, decoder_destroy, u->decoder);
#if FRAME_BUF
if (!u->write_buf) u->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME); if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME);
#endif u->status = OGG_SYNC;
} else { u->fetch = true;
OP(u, free, u->of); u->overframes = 0;
u->of = NULL;
} OG(&gu, sync_init, &u->sync);
u->end = false; OG(&gu, stream_init, &u->state, -1);
} }
static void opus_close(void) { static void opus_close(void) {
if (u->of) { if (u->decoder) OP(&gu, decoder_destroy, u->decoder);
OP(u, free, u->of); free(u->overbuf);
u->of = NULL; OG(&gu, stream_clear, &u->state);
} OG(&gu, sync_clear, &u->sync);
#if FRAME_BUF
free(u->write_buf);
u->write_buf = NULL;
#endif
} }
static bool load_opus(void) { static bool load_opus(void) {
#if !LINKALL #if !LINKALL
void *handle = dlopen(LIBOPUS, RTLD_NOW);
char *err; char *err;
void *g_handle = dlopen(LIBOGG, RTLD_NOW);
void *u.handle = dlopen(LIBOPUS, RTLD_NOW);
if (!handle) { if (!g_handle || !u_handle) {
LOG_INFO("dlerror: %s", dlerror()); LOG_INFO("dlerror: %s", dlerror());
return false; return false;
} }
u->op_free = dlsym(handle, "op_free"); g_handle->ogg_stream_clear = dlsym(g_handle->handle, "ogg_stream_clear");
u->op_read = dlsym(handle, "op_read"); g_handle->.ogg_stream_reset = dlsym(g_handle->handle, "ogg_stream_reset");
u->op_head = dlsym(handle, "op_head"); g_handle->ogg_stream_eos = dlsym(g_handle->handle, "ogg_stream_eos");
u->op_open_callbacks = dlsym(handle, "op_open_callbacks"); 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) { if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err); LOG_INFO("dlerror: %s", err);
@@ -308,23 +413,17 @@ struct codec *register_opus(void) {
static struct codec ret = { static struct codec ret = {
'u', // id 'u', // id
"ops", // types "ops", // types
4*1024, // min read 8*1024, // min read
32*1024, // min space MAX_OPUS_FRAMES*BYTES_PER_FRAME*2, // min space
opus_open, // open opus_open, // open
opus_close, // close opus_close, // close
opus_decompress, // decode opus_decompress, // decode
}; };
u = malloc(sizeof(struct opus)); if ((u = calloc(1, sizeof(struct opus))) == NULL) {
if (!u) {
return NULL; return NULL;
} }
u->of = NULL;
#if FRAME_BUF
u->write_buf = NULL;
#endif
if (!load_opus()) { if (!load_opus()) {
return NULL; return NULL;
} }