X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=opusdec_filter.c;h=9022fbab63e0c3e2626528bcfa752ff6fde8faa3;hp=fdcb486e097930fb771fa00644a03d3a5b0e5847;hb=72e9bdf7c5c09ca4c20cad56c68a9a155cceff43;hpb=8bcf75a3921d15e03c4351b3efa609eac67c3817 diff --git a/opusdec_filter.c b/opusdec_filter.c index fdcb486e..9022fbab 100644 --- a/opusdec_filter.c +++ b/opusdec_filter.c @@ -1,12 +1,51 @@ /* - * 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-2014 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. + */ + + +/* Silence gcc warning caused by including opus.h */ +#if !defined(__STDC_VERSION__) +#define __STDC_VERSION__ 0 +#endif + #include +#include +#include +#include +#include #include "para.h" #include "list.h" @@ -16,6 +55,233 @@ #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; + bool have_more; + 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; +} + +#define OPUSDEC_MAX_OUTPUT_SIZE (1024 * 1024) + +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; + ogg_packet op; + + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret < 0) { + if (ret != -E_BTR_EOF) /* fatal error */ + goto out; + if (!ctx->have_more) /* EOF */ + goto out; + } else if (ret == 0 && !ctx->have_more) /* nothing to do */ + goto out; + if (btr_get_output_queue_size(btrn) > OPUSDEC_MAX_OUTPUT_SIZE) + return 0; + for (;;) { + int serial; + if (ctx->stream_init) { + ret = ogg_stream_packetout(&ctx->os, &op); + if (ret == 1) + break; + } + while (ogg_sync_pageout(&ctx->oy, &ctx->ogg_page) != 1) { + char *btr_buf, *data; + size_t nbytes = btr_next_buffer(btrn, &btr_buf); + nbytes = PARA_MIN(nbytes, (size_t)24 * 1024); + //PARA_CRIT_LOG("nbytes: %d\n", nbytes); + ctx->have_more = false; + if (nbytes == 0) + return 0; + data = ogg_sync_buffer(&ctx->oy, nbytes); + memcpy(data, btr_buf, nbytes); + btr_consume(btrn, nbytes); + ogg_sync_wrote(&ctx->oy, nbytes); + } + ctx->have_more = true; + serial = ogg_page_serialno(&ctx->ogg_page); + if (ctx->stream_init) { + if (serial != ctx->os.serialno) + ogg_stream_reset_serialno(&ctx->os, serial); + } else { + ogg_stream_init(&ctx->os, serial); + ctx->stream_init = true; + } + /* Add page to the bitstream */ + ogg_stream_pagein(&ctx->os, &ctx->ogg_page); + } + 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; +} + +static void opusdec_pre_select(struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct opusdec_context *ctx = fn->private_data; + int ret = btr_node_status(fn->btrn, fn->min_iqs, BTR_NT_INTERNAL); + + if (ret != 0) + return sched_min_delay(s); + if (!ctx->have_more) + return; + if (btr_get_output_queue_size(fn->btrn) <= OPUSDEC_MAX_OUTPUT_SIZE) + return sched_min_delay(s); +} /** * The init function of the opusdec filter. @@ -26,4 +292,9 @@ */ void opusdec_filter_init(struct filter *f) { + f->open = opusdec_open; + f->close = opusdec_close; + f->pre_select = opusdec_pre_select; + f->post_select = opusdec_post_select; + f->execute = opusdec_execute; }