From 273756ef36a4090d2c702e14b1ee30ca245186e8 Mon Sep 17 00:00:00 2001
From: Andre Noll <maan@systemlinux.org>
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 <maan@systemlinux.org>
+ * Copyright (c) 2002-2007 Jean-Marc Valin
+ * Copyright (c) 2008 CSIRO
+ * Copyright (c) 2007-2012 Xiph.Org Foundation
+ * Copyright (C) 2012-2013 Andre Noll <maan@systemlinux.org>
  *
  * 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 <regex.h>
+#include <opus/opus.h>
+#include <opus/opus_multistream.h>
+#include <ogg/ogg.h>
+#include <math.h>
 
 #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