/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2002-2007 Jean-Marc Valin * Copyright (c) 2008 CSIRO * Copyright (c) 2007-2012 Xiph.Org Foundation */ /** \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 "para.h" #include "list.h" #include "sched.h" #include "buffer_tree.h" #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; uint16_t preskip; bool have_opus_stream; bool have_more; ogg_int32_t opus_serialno; }; static int opusdec_execute(const 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 = zalloc(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 num_frames, bytes; uint16_t tmp_skip; tmp_skip = PARA_MIN((int)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 = alloc(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 = arr_alloc(sizeof(short) * ctx->channels, MAX_FRAME_SIZE); 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_monitor(__a_unused struct sched *s, void *context) { struct filter_node *fn = context; 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_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_monitor(struct sched *s, void *context) { struct filter_node *fn = context; 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); } /** \cond doxygen_ignore */ const struct filter lsg_filter_cmd_com_opusdec_user_data = { .open = opusdec_open, .close = opusdec_close, .pre_monitor = opusdec_pre_monitor, .post_monitor = opusdec_post_monitor, .execute = opusdec_execute, }; /** \endcond */