From 273756ef36a4090d2c702e14b1ee30ca245186e8 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 14 Oct 2012 21:18:58 +0200 Subject: [PATCH] The opus decoder. Implementation is similar to the speex decoder, in particular meta data handling is almost identical. --- error.h | 6 +- opusdec_filter.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 259 insertions(+), 2 deletions(-) diff --git a/error.h b/error.h index 55089c46..873ce280 100644 --- a/error.h +++ b/error.h @@ -34,7 +34,6 @@ DEFINE_ERRLIST_OBJECT_ENUM; #define STDIN_ERRORS #define WRITE_ERRORS #define CHECK_WAV_ERRORS -#define OPUSDEC_FILTER_ERRORS extern const char **para_errlist[]; @@ -62,6 +61,11 @@ extern const char **para_errlist[]; PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \ +#define OPUSDEC_FILTER_ERRORS \ + PARA_ERROR(CREATE_OPUS_DECODER, "could not create opus decoder"), \ + PARA_ERROR(OPUS_SET_GAIN, "opus: could not set gain"), \ + PARA_ERROR(OPUS_DECODE, "opus decode error"), \ + #define SIDEBAND_ERRORS \ PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \ PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \ diff --git a/opusdec_filter.c b/opusdec_filter.c index fdcb486e..482108a6 100644 --- a/opusdec_filter.c +++ b/opusdec_filter.c @@ -1,12 +1,45 @@ /* - * Copyright (C) 2012 Andre Noll + * Copyright (c) 2002-2007 Jean-Marc Valin + * Copyright (c) 2008 CSIRO + * Copyright (c) 2007-2012 Xiph.Org Foundation + * Copyright (C) 2012-2013 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file opusdec_filter.c The ogg/opus decoder. */ +/* This file is based on opusdec.c, by Jean-Marc Valin, see below. */ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + #include +#include +#include +#include +#include #include "para.h" #include "list.h" @@ -16,6 +49,220 @@ #include "filter.h" #include "error.h" #include "string.h" +#include "opus_common.h" + +/** 120ms at 48000 */ +#define MAX_FRAME_SIZE (960*6) + +struct opusdec_context { + OpusMSDecoder *st; + opus_int64 packet_count; + int total_links; + bool stream_init; + ogg_sync_state oy; + ogg_stream_state os; + ogg_page ogg_page; + bool eos; + int channels; + int preskip; + bool have_opus_stream; + ogg_int32_t opus_serialno; +}; + +static int opusdec_execute(struct btr_node *btrn, const char *cmd, + char **result) +{ + struct filter_node *fn = btr_context(btrn); + struct opusdec_context *ctx = fn->private_data; + + return decoder_execute(cmd, 48000, ctx->channels, result); +} + +static void opusdec_open(struct filter_node *fn) +{ + struct opusdec_context *ctx = para_calloc(sizeof(*ctx)); + + ogg_sync_init(&ctx->oy); + fn->private_data = ctx; +} + +static void opusdec_close(struct filter_node *fn) +{ + struct opusdec_context *ctx = fn->private_data; + + if (ctx->st) { + opus_multistream_decoder_destroy(ctx->st); + if (ctx->stream_init) + ogg_stream_clear(&ctx->os); + ogg_sync_clear(&ctx->oy); + } + free(ctx); + fn->private_data = NULL; +} + +/* Process an Opus header and setup the opus decoder based on it. */ +static int opusdec_init(ogg_packet *op, struct opusdec_context *ctx) +{ + int ret; + struct opus_header header; + + ctx->st = NULL; + ret = opus_parse_header((char *)op->packet, op->bytes, &header); + if (ret < 0) + return ret; + PARA_INFO_LOG("detected header v%d\n", header.version); + ctx->channels = header.channels; + ctx->preskip = header.preskip; + ctx->st = opus_multistream_decoder_create(48000, header.channels, + header.nb_streams, header.nb_coupled, header.stream_map, &ret); + if (ret != OPUS_OK || !ctx->st) { + PARA_ERROR_LOG("%s\n", opus_strerror(ret)); + return -E_CREATE_OPUS_DECODER; + } + if (header.gain != 0) { + ret = opus_multistream_decoder_ctl(ctx->st, + OPUS_SET_GAIN(header.gain)); + if (ret != OPUS_OK) { + PARA_ERROR_LOG("%s\n", opus_strerror(ret)); + return -E_OPUS_SET_GAIN; + } + PARA_INFO_LOG("playback gain: %fdB\n", header.gain / 256.); + } + PARA_INFO_LOG("%d channel(s), 48KHz\n", ctx->channels); + return 1; +} + +static void opusdec_add_output(short *pcm, int frames_available, + struct btr_node *btrn, struct opusdec_context *ctx) +{ + int tmp_skip, num_frames, bytes; + + tmp_skip = PARA_MIN(ctx->preskip, frames_available); + ctx->preskip -= tmp_skip; + num_frames = frames_available - tmp_skip; + if (num_frames <= 0) + return; + bytes = sizeof(short) * num_frames * ctx->channels; + + if (tmp_skip > 0) { + short *in = pcm + ctx->channels * tmp_skip; + short *out = para_malloc(bytes); + memcpy(out, in, bytes); + free(pcm); + pcm = out; + } + btr_add_output((char *)pcm, bytes, btrn); +} + +/* returns > 1 if packet was decoded, 0 if it was ignored, negative on errors. */ +static int decode_packet(struct opusdec_context *ctx, ogg_packet *op, + struct btr_node *btrn) +{ + int ret; + short *output; + + /* + * OggOpus streams are identified by a magic string in the initial + * stream header. + */ + if (op->b_o_s && op->bytes >= 8 && !memcmp(op->packet, "OpusHead", 8)) { + if (!ctx->have_opus_stream) { + ctx->opus_serialno = ctx->os.serialno; + ctx->have_opus_stream = true; + ctx->packet_count = 0; + ctx->eos = false; + ctx->total_links++; + } else + PARA_NOTICE_LOG("ignoring opus stream %llu\n", + (long long unsigned)ctx->os.serialno); + } + if (!ctx->have_opus_stream || ctx->os.serialno != ctx->opus_serialno) + return 0; + /* If first packet in a logical stream, process the Opus header. */ + if (ctx->packet_count == 0) + return opusdec_init(op, ctx); + if (ctx->packet_count == 1) + return 1; + /* don't care for anything except opus eos */ + if (op->e_o_s && ctx->os.serialno == ctx->opus_serialno) + ctx->eos = true; + output = para_malloc(sizeof(short) * MAX_FRAME_SIZE * ctx->channels); + ret = opus_multistream_decode(ctx->st, (unsigned char *)op->packet, + op->bytes, output, MAX_FRAME_SIZE, 0); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", opus_strerror(ret)); + free(output); + return -E_OPUS_DECODE; + } + opusdec_add_output(output, ret, btrn, ctx); + return 1; +} + +static void opusdec_pre_select(struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct btr_node *btrn = fn->btrn; + int ns; + + ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ns != 0) + return sched_min_delay(s); + sched_request_timeout_ms(100, s); +} + +static int opusdec_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct opusdec_context *ctx = fn->private_data; + struct btr_node *btrn = fn->btrn; + int ret; + char *btr_buf, *data; + size_t nbytes; + ogg_packet op; + + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret <= 0) + goto out; + btr_merge(btrn, fn->min_iqs); + nbytes = btr_next_buffer(btrn, &btr_buf); + nbytes = PARA_MIN(nbytes, (size_t)32768); + ret = 0; + if (nbytes == 0) + goto out; + data = ogg_sync_buffer(&ctx->oy, nbytes); + memcpy(data, btr_buf, nbytes); + btr_consume(btrn, nbytes); + ogg_sync_wrote(&ctx->oy, nbytes); + for (;;) { /* loop over all ogg pages we got */ + ret = 0; + if (ogg_sync_pageout(&ctx->oy, &ctx->ogg_page) != 1) + goto out; + if (!ctx->stream_init) { + ogg_stream_init(&ctx->os, ogg_page_serialno(&ctx->ogg_page)); + ctx->stream_init = true; + } + if (ogg_page_serialno(&ctx->ogg_page) != ctx->os.serialno) + ogg_stream_reset_serialno(&ctx->os, + ogg_page_serialno(&ctx->ogg_page)); + /* Add page to the bitstream */ + ogg_stream_pagein(&ctx->os, &ctx->ogg_page); + for (;;) { /* loop over all opus packets */ + ret = ogg_stream_packetout(&ctx->os, &op); + if (ret != 1) + break; + ret = decode_packet(ctx, &op, btrn); + if (ret < 0) + goto out; + ctx->packet_count++; + if (ctx->eos) + ctx->have_opus_stream = false; + } + } +out: + if (ret < 0) + btr_remove_node(&fn->btrn); + return ret; +} /** * The init function of the opusdec filter. @@ -26,4 +273,10 @@ */ void opusdec_filter_init(struct filter *f) { + f->open = opusdec_open; + f->close = opusdec_close; + f->pre_select = generic_filter_pre_select; + f->pre_select = opusdec_pre_select; + f->post_select = opusdec_post_select; + f->execute = opusdec_execute; } -- 2.39.5