summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim-Philipp Müller <tim@centricular.com>2016-02-25 23:51:42 +0000
committerTim-Philipp Müller <tim@centricular.com>2016-02-25 23:51:42 +0000
commita2eb43001049d1f45d62f48c9a1b68da6703117f (patch)
tree35beb6c5f326d77bef4c1bbda4ad122ed743154a
parenta153804566c5fa08a98d63bb70ded73e1f0a71d6 (diff)
parent1ef601e7d3c5689ef6832c21e05b1c75865d9194 (diff)
downloadgstreamer-plugins-base-a2eb43001049d1f45d62f48c9a1b68da6703117f.tar.gz
Merge branch 'plugin-move-opus'
Move Opus decoder and encoder from -bad to -base. https://bugzilla.gnome.org/show_bug.cgi?id=756282
-rw-r--r--ext/opus/Makefile.am20
-rw-r--r--ext/opus/gstopus.c66
-rw-r--r--ext/opus/gstopuscommon.c111
-rw-r--r--ext/opus/gstopuscommon.h37
-rw-r--r--ext/opus/gstopusdec.c819
-rw-r--r--ext/opus/gstopusdec.h86
-rw-r--r--ext/opus/gstopusenc.c1282
-rw-r--r--ext/opus/gstopusenc.h102
-rw-r--r--ext/opus/gstopusheader.c93
-rw-r--r--ext/opus/gstopusheader.h37
-rw-r--r--tests/check/elements/opus.c338
11 files changed, 2991 insertions, 0 deletions
diff --git a/ext/opus/Makefile.am b/ext/opus/Makefile.am
new file mode 100644
index 000000000..c64369252
--- /dev/null
+++ b/ext/opus/Makefile.am
@@ -0,0 +1,20 @@
+plugin_LTLIBRARIES = libgstopus.la
+
+libgstopus_la_SOURCES = gstopus.c gstopusdec.c gstopusenc.c gstopusparse.c gstopusheader.c gstopuscommon.c gstrtpopuspay.c gstrtpopusdepay.c
+libgstopus_la_CFLAGS = \
+ -DGST_USE_UNSTABLE_API \
+ $(GST_PLUGINS_BAD_CFLAGS) \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(OPUS_CFLAGS)
+libgstopus_la_LIBADD = \
+ $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \
+ -lgsttag-$(GST_API_VERSION) -lgstrtp-$(GST_API_VERSION) \
+ -lgstpbutils-$(GST_API_VERSION) \
+ $(GST_BASE_LIBS) \
+ $(GST_LIBS) \
+ $(OPUS_LIBS)
+libgstopus_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM)
+libgstopus_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h gstopusheader.h gstopuscommon.h gstrtpopuspay.h gstrtpopusdepay.h
diff --git a/ext/opus/gstopus.c b/ext/opus/gstopus.c
new file mode 100644
index 000000000..a3427b284
--- /dev/null
+++ b/ext/opus/gstopus.c
@@ -0,0 +1,66 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstopusdec.h"
+#include "gstopusenc.h"
+#include "gstopusparse.h"
+
+#include "gstrtpopuspay.h"
+#include "gstrtpopusdepay.h"
+
+#include <gst/tag/tag.h>
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+
+ if (!gst_element_register (plugin, "opusenc", GST_RANK_PRIMARY,
+ GST_TYPE_OPUS_ENC))
+ return FALSE;
+
+ if (!gst_element_register (plugin, "opusdec", GST_RANK_PRIMARY,
+ GST_TYPE_OPUS_DEC))
+ return FALSE;
+
+ if (!gst_element_register (plugin, "opusparse", GST_RANK_NONE,
+ GST_TYPE_OPUS_PARSE))
+ return FALSE;
+
+ if (!gst_element_register (plugin, "rtpopusdepay", GST_RANK_SECONDARY,
+ GST_TYPE_RTP_OPUS_DEPAY))
+ return FALSE;
+
+ if (!gst_element_register (plugin, "rtpopuspay", GST_RANK_SECONDARY,
+ GST_TYPE_RTP_OPUS_PAY))
+ return FALSE;
+
+ gst_tag_register_musicbrainz_tags ();
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ opus,
+ "OPUS plugin library",
+ plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/ext/opus/gstopuscommon.c b/ext/opus/gstopuscommon.c
new file mode 100644
index 000000000..febccd85f
--- /dev/null
+++ b/ext/opus/gstopuscommon.c
@@ -0,0 +1,111 @@
+/* GStreamer
+ * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "gstopuscommon.h"
+
+/* http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9 */
+/* copy of the same structure in the vorbis plugin */
+const GstAudioChannelPosition gst_opus_channel_positions[][8] = {
+ { /* Mono */
+ GST_AUDIO_CHANNEL_POSITION_MONO},
+ { /* Stereo */
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT},
+ { /* Stereo + Centre */
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT},
+ { /* Quadraphonic */
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+ },
+ { /* Stereo + Centre + rear stereo */
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+ },
+ { /* Full 5.1 Surround */
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_LFE1,
+ },
+ { /* 6.1 Surround, in Vorbis spec since 2010-01-13 */
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_LFE1},
+ { /* 7.1 Surround, in Vorbis spec since 2010-01-13 */
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
+ GST_AUDIO_CHANNEL_POSITION_LFE1},
+};
+
+const char *gst_opus_channel_names[] = {
+ "mono",
+ "front left",
+ "front right",
+ "rear center",
+ "rear left",
+ "rear right",
+ "lfe",
+ "front center",
+ "front left of center",
+ "front right of center",
+ "side left",
+ "side right",
+ "none"
+};
+
+void
+gst_opus_common_log_channel_mapping_table (GstElement * element,
+ GstDebugCategory * category, const char *msg, int n_channels,
+ const guint8 * table)
+{
+ int n;
+ GString *s;
+
+ if (gst_debug_category_get_threshold (category) < GST_LEVEL_INFO)
+ return;
+
+ s = g_string_new ("[ ");
+ for (n = 0; n < n_channels; ++n) {
+ g_string_append_printf (s, "%d ", table[n]);
+ }
+ g_string_append (s, "]");
+
+ GST_CAT_LEVEL_LOG (category, GST_LEVEL_INFO, element, "%s: %s", msg, s->str);
+ g_string_free (s, TRUE);
+}
diff --git a/ext/opus/gstopuscommon.h b/ext/opus/gstopuscommon.h
new file mode 100644
index 000000000..71771ae74
--- /dev/null
+++ b/ext/opus/gstopuscommon.h
@@ -0,0 +1,37 @@
+/* GStreamer Opus Encoder
+ * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GST_OPUS_COMMON_H__
+#define __GST_OPUS_COMMON_H__
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+
+G_BEGIN_DECLS
+
+extern const GstAudioChannelPosition gst_opus_channel_positions[][8];
+extern const char *gst_opus_channel_names[];
+extern void gst_opus_common_log_channel_mapping_table (GstElement *element,
+ GstDebugCategory * category, const char *msg,
+ int n_channels, const guint8 *table);
+
+G_END_DECLS
+
+#endif /* __GST_OPUS_COMMON_H__ */
diff --git a/ext/opus/gstopusdec.c b/ext/opus/gstopusdec.c
new file mode 100644
index 000000000..1470ea321
--- /dev/null
+++ b/ext/opus/gstopusdec.c
@@ -0,0 +1,819 @@
+/* GStreamer
+ * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
+ * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
+ * Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) 2011-2012 Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * Based on the speexdec element.
+ */
+
+/**
+ * SECTION:element-opusdec
+ * @see_also: opusenc, oggdemux
+ *
+ * This element decodes a OPUS stream to raw integer audio.
+ *
+ * <refsect2>
+ * <title>Example pipelines</title>
+ * |[
+ * gst-launch-1.0 -v filesrc location=opus.ogg ! oggdemux ! opusdec ! audioconvert ! audioresample ! alsasink
+ * ]| Decode an Ogg/Opus file. To create an Ogg/Opus file refer to the documentation of opusenc.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <string.h>
+#include "gstopusheader.h"
+#include "gstopuscommon.h"
+#include "gstopusdec.h"
+#include <gst/pbutils/pbutils.h>
+
+GST_DEBUG_CATEGORY_STATIC (opusdec_debug);
+#define GST_CAT_DEFAULT opusdec_debug
+
+static GstStaticPadTemplate opus_dec_src_factory =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-raw, "
+ "format = (string) " GST_AUDIO_NE (S16) ", "
+ "layout = (string) interleaved, "
+ "rate = (int) { 48000, 24000, 16000, 12000, 8000 }, "
+ "channels = (int) [ 1, 8 ] ")
+ );
+
+static GstStaticPadTemplate opus_dec_sink_factory =
+ GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-opus, "
+ "channel-mapping-family = (int) 0; "
+ "audio/x-opus, "
+ "channel-mapping-family = (int) [1, 255], "
+ "channels = (int) [1, 255], "
+ "stream-count = (int) [1, 255], " "coupled-count = (int) [0, 255]")
+ );
+
+G_DEFINE_TYPE (GstOpusDec, gst_opus_dec, GST_TYPE_AUDIO_DECODER);
+
+#define DB_TO_LINEAR(x) pow (10., (x) / 20.)
+
+#define DEFAULT_USE_INBAND_FEC FALSE
+#define DEFAULT_APPLY_GAIN TRUE
+
+enum
+{
+ PROP_0,
+ PROP_USE_INBAND_FEC,
+ PROP_APPLY_GAIN
+};
+
+
+static GstFlowReturn gst_opus_dec_parse_header (GstOpusDec * dec,
+ GstBuffer * buf);
+static gboolean gst_opus_dec_start (GstAudioDecoder * dec);
+static gboolean gst_opus_dec_stop (GstAudioDecoder * dec);
+static GstFlowReturn gst_opus_dec_handle_frame (GstAudioDecoder * dec,
+ GstBuffer * buffer);
+static gboolean gst_opus_dec_set_format (GstAudioDecoder * bdec,
+ GstCaps * caps);
+static void gst_opus_dec_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_opus_dec_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+
+static void
+gst_opus_dec_class_init (GstOpusDecClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstAudioDecoderClass *adclass;
+ GstElementClass *element_class;
+
+ gobject_class = (GObjectClass *) klass;
+ adclass = (GstAudioDecoderClass *) klass;
+ element_class = (GstElementClass *) klass;
+
+ gobject_class->set_property = gst_opus_dec_set_property;
+ gobject_class->get_property = gst_opus_dec_get_property;
+
+ adclass->start = GST_DEBUG_FUNCPTR (gst_opus_dec_start);
+ adclass->stop = GST_DEBUG_FUNCPTR (gst_opus_dec_stop);
+ adclass->handle_frame = GST_DEBUG_FUNCPTR (gst_opus_dec_handle_frame);
+ adclass->set_format = GST_DEBUG_FUNCPTR (gst_opus_dec_set_format);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&opus_dec_src_factory));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&opus_dec_sink_factory));
+ gst_element_class_set_static_metadata (element_class, "Opus audio decoder",
+ "Codec/Decoder/Audio",
+ "decode opus streams to audio",
+ "Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>");
+ g_object_class_install_property (gobject_class, PROP_USE_INBAND_FEC,
+ g_param_spec_boolean ("use-inband-fec", "Use in-band FEC",
+ "Use forward error correction if available (needs PLC enabled)",
+ DEFAULT_USE_INBAND_FEC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_APPLY_GAIN,
+ g_param_spec_boolean ("apply-gain", "Apply gain",
+ "Apply gain if any is specified in the header", DEFAULT_APPLY_GAIN,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ GST_DEBUG_CATEGORY_INIT (opusdec_debug, "opusdec", 0,
+ "opus decoding element");
+}
+
+static void
+gst_opus_dec_reset (GstOpusDec * dec)
+{
+ dec->packetno = 0;
+ if (dec->state) {
+ opus_multistream_decoder_destroy (dec->state);
+ dec->state = NULL;
+ }
+
+ gst_buffer_replace (&dec->streamheader, NULL);
+ gst_buffer_replace (&dec->vorbiscomment, NULL);
+ gst_buffer_replace (&dec->last_buffer, NULL);
+ dec->primed = FALSE;
+
+ dec->pre_skip = 0;
+ dec->r128_gain = 0;
+ dec->sample_rate = 0;
+ dec->n_channels = 0;
+ dec->leftover_plc_duration = 0;
+}
+
+static void
+gst_opus_dec_init (GstOpusDec * dec)
+{
+ dec->use_inband_fec = FALSE;
+ dec->apply_gain = DEFAULT_APPLY_GAIN;
+
+ gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (dec), TRUE);
+ gst_audio_decoder_set_use_default_pad_acceptcaps (GST_AUDIO_DECODER_CAST
+ (dec), TRUE);
+ GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_DECODER_SINK_PAD (dec));
+
+ gst_opus_dec_reset (dec);
+}
+
+static gboolean
+gst_opus_dec_start (GstAudioDecoder * dec)
+{
+ GstOpusDec *odec = GST_OPUS_DEC (dec);
+
+ gst_opus_dec_reset (odec);
+
+ /* we know about concealment */
+ gst_audio_decoder_set_plc_aware (dec, TRUE);
+
+ if (odec->use_inband_fec) {
+ /* opusdec outputs samples directly from an input buffer, except if
+ * FEC is on, in which case it buffers one buffer in case one buffer
+ * goes missing.
+ */
+ gst_audio_decoder_set_latency (dec, 120 * GST_MSECOND, 120 * GST_MSECOND);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gst_opus_dec_stop (GstAudioDecoder * dec)
+{
+ GstOpusDec *odec = GST_OPUS_DEC (dec);
+
+ gst_opus_dec_reset (odec);
+
+ return TRUE;
+}
+
+static double
+gst_opus_dec_get_r128_gain (gint16 r128_gain)
+{
+ return r128_gain / (double) (1 << 8);
+}
+
+static double
+gst_opus_dec_get_r128_volume (gint16 r128_gain)
+{
+ return DB_TO_LINEAR (gst_opus_dec_get_r128_gain (r128_gain));
+}
+
+static void
+gst_opus_dec_negotiate (GstOpusDec * dec, const GstAudioChannelPosition * pos)
+{
+ GstCaps *caps = gst_pad_get_allowed_caps (GST_AUDIO_DECODER_SRC_PAD (dec));
+ GstStructure *s;
+ GstAudioInfo info;
+
+ if (caps) {
+ gint rate, channels;
+
+ caps = gst_caps_truncate (caps);
+ caps = gst_caps_make_writable (caps);
+ s = gst_caps_get_structure (caps, 0);
+
+ if (gst_structure_has_field (s, "rate"))
+ gst_structure_fixate_field_nearest_int (s, "rate", dec->sample_rate);
+ else
+ gst_structure_set (s, "rate", G_TYPE_INT, dec->sample_rate, NULL);
+ gst_structure_get_int (s, "rate", &rate);
+ dec->sample_rate = rate;
+
+ if (gst_structure_has_field (s, "channels"))
+ gst_structure_fixate_field_nearest_int (s, "channels", dec->n_channels);
+ else
+ gst_structure_set (s, "channels", G_TYPE_INT, dec->n_channels, NULL);
+ gst_structure_get_int (s, "channels", &channels);
+ dec->n_channels = channels;
+
+ gst_caps_unref (caps);
+ }
+
+ if (dec->n_channels == 0) {
+ GST_DEBUG_OBJECT (dec, "Using a default of 2 channels");
+ dec->n_channels = 2;
+ pos = NULL;
+ }
+
+ if (dec->sample_rate == 0) {
+ GST_DEBUG_OBJECT (dec, "Using a default of 48kHz sample rate");
+ dec->sample_rate = 48000;
+ }
+
+ GST_INFO_OBJECT (dec, "Negotiated %d channels, %d Hz", dec->n_channels,
+ dec->sample_rate);
+
+ /* pass valid order to audio info */
+ if (pos) {
+ memcpy (dec->opus_pos, pos, sizeof (pos[0]) * dec->n_channels);
+ gst_audio_channel_positions_to_valid_order (dec->opus_pos, dec->n_channels);
+ }
+
+ /* set up source format */
+ gst_audio_info_init (&info);
+ gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16,
+ dec->sample_rate, dec->n_channels, pos ? dec->opus_pos : NULL);
+ gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (dec), &info);
+
+ /* but we still need the opus order for later reordering */
+ if (pos) {
+ memcpy (dec->opus_pos, pos, sizeof (pos[0]) * dec->n_channels);
+ } else {
+ dec->opus_pos[0] = GST_AUDIO_CHANNEL_POSITION_INVALID;
+ }
+
+ dec->info = info;
+}
+
+static GstFlowReturn
+gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf)
+{
+ GstAudioChannelPosition pos[64];
+ const GstAudioChannelPosition *posn = NULL;
+
+ if (!gst_opus_header_is_id_header (buf)) {
+ GST_ERROR_OBJECT (dec, "Header is not an Opus ID header");
+ return GST_FLOW_ERROR;
+ }
+
+ if (!gst_codec_utils_opus_parse_header (buf,
+ &dec->sample_rate,
+ &dec->n_channels,
+ &dec->channel_mapping_family,
+ &dec->n_streams,
+ &dec->n_stereo_streams,
+ dec->channel_mapping, &dec->pre_skip, &dec->r128_gain)) {
+ GST_ERROR_OBJECT (dec, "Failed to parse Opus ID header");
+ return GST_FLOW_ERROR;
+ }
+ dec->r128_gain_volume = gst_opus_dec_get_r128_volume (dec->r128_gain);
+
+ GST_INFO_OBJECT (dec,
+ "Found pre-skip of %u samples, R128 gain %d (volume %f)",
+ dec->pre_skip, dec->r128_gain, dec->r128_gain_volume);
+
+ if (dec->channel_mapping_family == 1) {
+ GST_INFO_OBJECT (dec, "Channel mapping family 1, Vorbis mapping");
+ switch (dec->n_channels) {
+ case 1:
+ case 2:
+ /* nothing */
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ posn = gst_opus_channel_positions[dec->n_channels - 1];
+ break;
+ default:{
+ gint i;
+
+ GST_ELEMENT_WARNING (GST_ELEMENT (dec), STREAM, DECODE,
+ (NULL), ("Using NONE channel layout for more than 8 channels"));
+
+ for (i = 0; i < dec->n_channels; i++)
+ pos[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
+
+ posn = pos;
+ }
+ }
+ } else {
+ GST_INFO_OBJECT (dec, "Channel mapping family %d",
+ dec->channel_mapping_family);
+ }
+
+ gst_opus_dec_negotiate (dec, posn);
+
+ return GST_FLOW_OK;
+}
+
+
+static GstFlowReturn
+gst_opus_dec_parse_comments (GstOpusDec * dec, GstBuffer * buf)
+{
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buffer)
+{
+ GstFlowReturn res = GST_FLOW_OK;
+ gsize size;
+ guint8 *data;
+ GstBuffer *outbuf, *bufd;
+ gint16 *out_data;
+ int n, err;
+ int samples;
+ unsigned int packet_size;
+ GstBuffer *buf;
+ GstMapInfo map, omap;
+ GstAudioClippingMeta *cmeta = NULL;
+
+ if (dec->state == NULL) {
+ /* If we did not get any headers, default to 2 channels */
+ if (dec->n_channels == 0) {
+ GST_INFO_OBJECT (dec, "No header, assuming single stream");
+ dec->n_channels = 2;
+ dec->sample_rate = 48000;
+ /* default stereo mapping */
+ dec->channel_mapping_family = 0;
+ dec->channel_mapping[0] = 0;
+ dec->channel_mapping[1] = 1;
+ dec->n_streams = 1;
+ dec->n_stereo_streams = 1;
+
+ gst_opus_dec_negotiate (dec, NULL);
+ }
+
+ GST_DEBUG_OBJECT (dec, "Creating decoder with %d channels, %d Hz",
+ dec->n_channels, dec->sample_rate);
+#ifndef GST_DISABLE_GST_DEBUG
+ gst_opus_common_log_channel_mapping_table (GST_ELEMENT (dec), opusdec_debug,
+ "Mapping table", dec->n_channels, dec->channel_mapping);
+#endif
+
+ GST_DEBUG_OBJECT (dec, "%d streams, %d stereo", dec->n_streams,
+ dec->n_stereo_streams);
+ dec->state =
+ opus_multistream_decoder_create (dec->sample_rate, dec->n_channels,
+ dec->n_streams, dec->n_stereo_streams, dec->channel_mapping, &err);
+ if (!dec->state || err != OPUS_OK)
+ goto creation_failed;
+ }
+
+ if (buffer) {
+ GST_DEBUG_OBJECT (dec, "Received buffer of size %" G_GSIZE_FORMAT,
+ gst_buffer_get_size (buffer));
+ } else {
+ GST_DEBUG_OBJECT (dec, "Received missing buffer");
+ }
+
+ /* if using in-band FEC, we introdude one extra frame's delay as we need
+ to potentially wait for next buffer to decode a missing buffer */
+ if (dec->use_inband_fec && !dec->primed) {
+ GST_DEBUG_OBJECT (dec, "First buffer received in FEC mode, early out");
+ gst_buffer_replace (&dec->last_buffer, buffer);
+ dec->primed = TRUE;
+ goto done;
+ }
+
+ /* That's the buffer we'll be sending to the opus decoder. */
+ buf = (dec->use_inband_fec
+ && gst_buffer_get_size (dec->last_buffer) >
+ 0) ? dec->last_buffer : buffer;
+
+ /* That's the buffer we get duration from */
+ bufd = dec->use_inband_fec ? dec->last_buffer : buffer;
+
+ if (buf && gst_buffer_get_size (buf) > 0) {
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+ data = map.data;
+ size = map.size;
+ GST_DEBUG_OBJECT (dec, "Using buffer of size %" G_GSIZE_FORMAT, size);
+ } else {
+ /* concealment data, pass NULL as the bits parameters */
+ GST_DEBUG_OBJECT (dec, "Using NULL buffer");
+ data = NULL;
+ size = 0;
+ }
+
+ if (gst_buffer_get_size (bufd) == 0) {
+ GstClockTime const opus_plc_alignment = 2500 * GST_USECOND;
+ GstClockTime aligned_missing_duration;
+ GstClockTime missing_duration = GST_BUFFER_DURATION (bufd);
+
+ GST_DEBUG_OBJECT (dec,
+ "missing buffer, doing PLC duration %" GST_TIME_FORMAT
+ " plus leftover %" GST_TIME_FORMAT, GST_TIME_ARGS (missing_duration),
+ GST_TIME_ARGS (dec->leftover_plc_duration));
+
+ /* add the leftover PLC duration to that of the buffer */
+ missing_duration += dec->leftover_plc_duration;
+
+ /* align the combined buffer and leftover PLC duration to multiples
+ * of 2.5ms, rounding to nearest, and store excess duration for later */
+ aligned_missing_duration =
+ ((missing_duration +
+ opus_plc_alignment / 2) / opus_plc_alignment) * opus_plc_alignment;
+ dec->leftover_plc_duration = missing_duration - aligned_missing_duration;
+
+ /* Opus' PLC cannot operate with less than 2.5ms; skip PLC
+ * and accumulate the missing duration in the leftover_plc_duration
+ * for the next PLC attempt */
+ if (aligned_missing_duration < opus_plc_alignment) {
+ GST_DEBUG_OBJECT (dec,
+ "current duration %" GST_TIME_FORMAT
+ " of missing data not enough for PLC (minimum needed: %"
+ GST_TIME_FORMAT ") - skipping", GST_TIME_ARGS (missing_duration),
+ GST_TIME_ARGS (opus_plc_alignment));
+ goto done;
+ }
+
+ /* convert the duration (in nanoseconds) to sample count */
+ samples =
+ gst_util_uint64_scale_int (aligned_missing_duration, dec->sample_rate,
+ GST_SECOND);
+
+ GST_DEBUG_OBJECT (dec,
+ "calculated PLC frame length: %" GST_TIME_FORMAT
+ " num frame samples: %d new leftover: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (aligned_missing_duration), samples,
+ GST_TIME_ARGS (dec->leftover_plc_duration));
+ } else {
+ /* use maximum size (120 ms) as the number of returned samples is
+ not constant over the stream. */
+ samples = 120 * dec->sample_rate / 1000;
+ }
+
+ packet_size = samples * dec->n_channels * 2;
+
+ outbuf =
+ gst_audio_decoder_allocate_output_buffer (GST_AUDIO_DECODER (dec),
+ packet_size);
+ if (!outbuf) {
+ goto buffer_failed;
+ }
+
+ gst_buffer_map (outbuf, &omap, GST_MAP_WRITE);
+ out_data = (gint16 *) omap.data;
+
+ if (dec->use_inband_fec) {
+ if (gst_buffer_get_size (dec->last_buffer) > 0) {
+ /* normal delayed decode */
+ GST_LOG_OBJECT (dec, "FEC enabled, decoding last delayed buffer");
+ n = opus_multistream_decode (dec->state, data, size, out_data, samples,
+ 0);
+ } else {
+ /* FEC reconstruction decode */
+ GST_LOG_OBJECT (dec, "FEC enabled, reconstructing last buffer");
+ n = opus_multistream_decode (dec->state, data, size, out_data, samples,
+ 1);
+ }
+ } else {
+ /* normal decode */
+ GST_LOG_OBJECT (dec, "FEC disabled, decoding buffer");
+ n = opus_multistream_decode (dec->state, data, size, out_data, samples, 0);
+ }
+ gst_buffer_unmap (outbuf, &omap);
+ if (data != NULL)
+ gst_buffer_unmap (buf, &map);
+
+ if (n < 0) {
+ GST_ELEMENT_ERROR (dec, STREAM, DECODE, ("Decoding error: %d", n), (NULL));
+ gst_buffer_unref (outbuf);
+ return GST_FLOW_ERROR;
+ }
+ GST_DEBUG_OBJECT (dec, "decoded %d samples", n);
+ gst_buffer_set_size (outbuf, n * 2 * dec->n_channels);
+
+ cmeta = gst_buffer_get_audio_clipping_meta (buf);
+
+ g_assert (!cmeta || cmeta->format == GST_FORMAT_DEFAULT);
+
+ /* Skip any samples that need skipping */
+ if (cmeta && cmeta->start) {
+ guint pre_skip = cmeta->start;
+ guint scaled_pre_skip = pre_skip * dec->sample_rate / 48000;
+ guint skip = scaled_pre_skip > n ? n : scaled_pre_skip;
+ guint scaled_skip = skip * 48000 / dec->sample_rate;
+
+ gst_buffer_resize (outbuf, skip * 2 * dec->n_channels, -1);
+
+ GST_INFO_OBJECT (dec,
+ "Skipping %u samples at the beginning (%u at 48000 Hz)",
+ skip, scaled_skip);
+ }
+
+ if (cmeta && cmeta->end) {
+ guint post_skip = cmeta->end;
+ guint scaled_post_skip = post_skip * dec->sample_rate / 48000;
+ guint skip = scaled_post_skip > n ? n : scaled_post_skip;
+ guint scaled_skip = skip * 48000 / dec->sample_rate;
+ guint outsize = gst_buffer_get_size (outbuf);
+ guint skip_bytes = skip * 2 * dec->n_channels;
+
+ if (outsize > skip_bytes)
+ outsize -= skip_bytes;
+ else
+ outsize = 0;
+
+ gst_buffer_resize (outbuf, 0, outsize);
+
+ GST_INFO_OBJECT (dec,
+ "Skipping %u samples at the end (%u at 48000 Hz)", skip, scaled_skip);
+ }
+
+ if (gst_buffer_get_size (outbuf) == 0) {
+ gst_buffer_unref (outbuf);
+ outbuf = NULL;
+ } else if (dec->opus_pos[0] != GST_AUDIO_CHANNEL_POSITION_INVALID) {
+ gst_audio_buffer_reorder_channels (outbuf, GST_AUDIO_FORMAT_S16,
+ dec->n_channels, dec->opus_pos, dec->info.position);
+ }
+
+ /* Apply gain */
+ /* Would be better off leaving this to a volume element, as this is
+ a naive conversion that does too many int/float conversions.
+ However, we don't have control over the pipeline...
+ So make it optional if the user program wants to use a volume,
+ but do it by default so the correct volume goes out by default */
+ if (dec->apply_gain && outbuf && dec->r128_gain) {
+ gsize rsize;
+ unsigned int i, nsamples;
+ double volume = dec->r128_gain_volume;
+ gint16 *samples;
+
+ gst_buffer_map (outbuf, &omap, GST_MAP_READWRITE);
+ samples = (gint16 *) omap.data;
+ rsize = omap.size;
+ GST_DEBUG_OBJECT (dec, "Applying gain: volume %f", volume);
+ nsamples = rsize / 2;
+ for (i = 0; i < nsamples; ++i) {
+ int sample = (int) (samples[i] * volume + 0.5);
+ samples[i] = sample < -32768 ? -32768 : sample > 32767 ? 32767 : sample;
+ }
+ gst_buffer_unmap (outbuf, &omap);
+ }
+
+ if (dec->use_inband_fec) {
+ gst_buffer_replace (&dec->last_buffer, buffer);
+ }
+
+ res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1);
+
+ if (res != GST_FLOW_OK)
+ GST_DEBUG_OBJECT (dec, "flow: %s", gst_flow_get_name (res));
+
+done:
+ return res;
+
+creation_failed:
+ GST_ERROR_OBJECT (dec, "Failed to create Opus decoder: %d", err);
+ return GST_FLOW_ERROR;
+
+buffer_failed:
+ GST_ERROR_OBJECT (dec, "Failed to create %u byte buffer", packet_size);
+ return GST_FLOW_ERROR;
+}
+
+static gboolean
+gst_opus_dec_set_format (GstAudioDecoder * bdec, GstCaps * caps)
+{
+ GstOpusDec *dec = GST_OPUS_DEC (bdec);
+ gboolean ret = TRUE;
+ GstStructure *s;
+ const GValue *streamheader;
+ GstCaps *old_caps;
+
+ GST_DEBUG_OBJECT (dec, "set_format: %" GST_PTR_FORMAT, caps);
+
+ if ((old_caps = gst_pad_get_current_caps (GST_AUDIO_DECODER_SINK_PAD (bdec)))) {
+ if (gst_caps_is_equal (caps, old_caps)) {
+ gst_caps_unref (old_caps);
+ GST_DEBUG_OBJECT (dec, "caps didn't change");
+ goto done;
+ }
+
+ GST_DEBUG_OBJECT (dec, "caps have changed, resetting decoder");
+ gst_opus_dec_reset (dec);
+ gst_caps_unref (old_caps);
+ }
+
+ s = gst_caps_get_structure (caps, 0);
+ if ((streamheader = gst_structure_get_value (s, "streamheader")) &&
+ G_VALUE_HOLDS (streamheader, GST_TYPE_ARRAY) &&
+ gst_value_array_get_size (streamheader) >= 2) {
+ const GValue *header, *vorbiscomment;
+ GstBuffer *buf;
+ GstFlowReturn res = GST_FLOW_OK;
+
+ header = gst_value_array_get_value (streamheader, 0);
+ if (header && G_VALUE_HOLDS (header, GST_TYPE_BUFFER)) {
+ buf = gst_value_get_buffer (header);
+ res = gst_opus_dec_parse_header (dec, buf);
+ if (res != GST_FLOW_OK) {
+ ret = FALSE;
+ goto done;
+ }
+ gst_buffer_replace (&dec->streamheader, buf);
+ }
+
+ vorbiscomment = gst_value_array_get_value (streamheader, 1);
+ if (vorbiscomment && G_VALUE_HOLDS (vorbiscomment, GST_TYPE_BUFFER)) {
+ buf = gst_value_get_buffer (vorbiscomment);
+ res = gst_opus_dec_parse_comments (dec, buf);
+ if (res != GST_FLOW_OK) {
+ ret = FALSE;
+ goto done;
+ }
+ gst_buffer_replace (&dec->vorbiscomment, buf);
+ }
+ } else {
+ const GstAudioChannelPosition *posn = NULL;
+
+ if (!gst_codec_utils_opus_parse_caps (caps, &dec->sample_rate,
+ &dec->n_channels, &dec->channel_mapping_family, &dec->n_streams,
+ &dec->n_stereo_streams, dec->channel_mapping)) {
+ ret = FALSE;
+ goto done;
+ }
+
+ if (dec->channel_mapping_family == 1 && dec->n_channels <= 8)
+ posn = gst_opus_channel_positions[dec->n_channels - 1];
+
+ gst_opus_dec_negotiate (dec, posn);
+ }
+
+done:
+ return ret;
+}
+
+static gboolean
+memcmp_buffers (GstBuffer * buf1, GstBuffer * buf2)
+{
+ gsize size1, size2;
+ gboolean res;
+ GstMapInfo map;
+
+ size1 = gst_buffer_get_size (buf1);
+ size2 = gst_buffer_get_size (buf2);
+
+ if (size1 != size2)
+ return FALSE;
+
+ gst_buffer_map (buf1, &map, GST_MAP_READ);
+ res = gst_buffer_memcmp (buf2, 0, map.data, map.size) == 0;
+ gst_buffer_unmap (buf1, &map);
+
+ return res;
+}
+
+static GstFlowReturn
+gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf)
+{
+ GstFlowReturn res;
+ GstOpusDec *dec;
+
+ /* no fancy draining */
+ if (G_UNLIKELY (!buf))
+ return GST_FLOW_OK;
+
+ dec = GST_OPUS_DEC (adec);
+ GST_LOG_OBJECT (dec,
+ "Got buffer ts %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
+
+ /* If we have the streamheader and vorbiscomment from the caps already
+ * ignore them here */
+ if (dec->streamheader && dec->vorbiscomment) {
+ if (memcmp_buffers (dec->streamheader, buf)) {
+ GST_DEBUG_OBJECT (dec, "found streamheader");
+ gst_audio_decoder_finish_frame (adec, NULL, 1);
+ res = GST_FLOW_OK;
+ } else if (memcmp_buffers (dec->vorbiscomment, buf)) {
+ GST_DEBUG_OBJECT (dec, "found vorbiscomments");
+ gst_audio_decoder_finish_frame (adec, NULL, 1);
+ res = GST_FLOW_OK;
+ } else {
+ res = opus_dec_chain_parse_data (dec, buf);
+ }
+ } else {
+ /* Otherwise fall back to packet counting and assume that the
+ * first two packets might be the headers, checking magic. */
+ switch (dec->packetno) {
+ case 0:
+ if (gst_opus_header_is_header (buf, "OpusHead", 8)) {
+ GST_DEBUG_OBJECT (dec, "found streamheader");
+ res = gst_opus_dec_parse_header (dec, buf);
+ gst_audio_decoder_finish_frame (adec, NULL, 1);
+ } else {
+ res = opus_dec_chain_parse_data (dec, buf);
+ }
+ break;
+ case 1:
+ if (gst_opus_header_is_header (buf, "OpusTags", 8)) {
+ GST_DEBUG_OBJECT (dec, "counted vorbiscomments");
+ res = gst_opus_dec_parse_comments (dec, buf);
+ gst_audio_decoder_finish_frame (adec, NULL, 1);
+ } else {
+ res = opus_dec_chain_parse_data (dec, buf);
+ }
+ break;
+ default:
+ {
+ res = opus_dec_chain_parse_data (dec, buf);
+ break;
+ }
+ }
+ }
+
+ dec->packetno++;
+
+ return res;
+}
+
+static void
+gst_opus_dec_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstOpusDec *dec = GST_OPUS_DEC (object);
+
+ switch (prop_id) {
+ case PROP_USE_INBAND_FEC:
+ g_value_set_boolean (value, dec->use_inband_fec);
+ break;
+ case PROP_APPLY_GAIN:
+ g_value_set_boolean (value, dec->apply_gain);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_opus_dec_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstOpusDec *dec = GST_OPUS_DEC (object);
+
+ switch (prop_id) {
+ case PROP_USE_INBAND_FEC:
+ dec->use_inband_fec = g_value_get_boolean (value);
+ break;
+ case PROP_APPLY_GAIN:
+ dec->apply_gain = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
diff --git a/ext/opus/gstopusdec.h b/ext/opus/gstopusdec.h
new file mode 100644
index 000000000..df52cfb6f
--- /dev/null
+++ b/ext/opus/gstopusdec.h
@@ -0,0 +1,86 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011-2012> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_OPUS_DEC_H__
+#define __GST_OPUS_DEC_H__
+
+#include <gst/gst.h>
+#include <gst/audio/gstaudiodecoder.h>
+#include <opus_multistream.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_OPUS_DEC \
+ (gst_opus_dec_get_type())
+#define GST_OPUS_DEC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OPUS_DEC,GstOpusDec))
+#define GST_OPUS_DEC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OPUS_DEC,GstOpusDecClass))
+#define GST_IS_OPUS_DEC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OPUS_DEC))
+#define GST_IS_OPUS_DEC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OPUS_DEC))
+
+typedef struct _GstOpusDec GstOpusDec;
+typedef struct _GstOpusDecClass GstOpusDecClass;
+
+struct _GstOpusDec {
+ GstAudioDecoder element;
+
+ OpusMSDecoder *state;
+
+ guint64 packetno;
+
+ GstBuffer *streamheader;
+ GstBuffer *vorbiscomment;
+
+ guint32 sample_rate;
+ guint8 n_channels;
+ guint16 pre_skip;
+ gint16 r128_gain;
+
+ GstAudioChannelPosition opus_pos[64];
+ GstAudioInfo info;
+
+ guint8 n_streams;
+ guint8 n_stereo_streams;
+ guint8 channel_mapping_family;
+ guint8 channel_mapping[256];
+
+ gboolean apply_gain;
+ double r128_gain_volume;
+
+ gboolean use_inband_fec;
+ GstBuffer *last_buffer;
+ gboolean primed;
+
+ guint64 leftover_plc_duration;
+};
+
+struct _GstOpusDecClass {
+ GstAudioDecoderClass parent_class;
+};
+
+GType gst_opus_dec_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_OPUS_DEC_H__ */
diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c
new file mode 100644
index 000000000..7737bf575
--- /dev/null
+++ b/ext/opus/gstopusenc.c
@@ -0,0 +1,1282 @@
+/* GStreamer Opus Encoder
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * Based on the speexenc element
+ */
+
+/**
+ * SECTION:element-opusenc
+ * @see_also: opusdec, oggmux
+ *
+ * This element encodes raw audio to OPUS.
+ *
+ * <refsect2>
+ * <title>Example pipelines</title>
+ * |[
+ * gst-launch-1.0 -v audiotestsrc wave=sine num-buffers=100 ! audioconvert ! opusenc ! oggmux ! filesink location=sine.ogg
+ * ]| Encode a test sine signal to Ogg/OPUS.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include <opus.h>
+
+#include <gst/gsttagsetter.h>
+#include <gst/audio/audio.h>
+#include <gst/pbutils/pbutils.h>
+#include <gst/tag/tag.h>
+#include <gst/glib-compat-private.h>
+#include "gstopusheader.h"
+#include "gstopuscommon.h"
+#include "gstopusenc.h"
+
+GST_DEBUG_CATEGORY_STATIC (opusenc_debug);
+#define GST_CAT_DEFAULT opusenc_debug
+
+/* Some arbitrary bounds beyond which it really doesn't make sense.
+ The spec mentions 6 kb/s to 510 kb/s, so 4000 and 650000 ought to be
+ safe as property bounds. */
+#define LOWEST_BITRATE 4000
+#define HIGHEST_BITRATE 650000
+
+#define GST_OPUS_ENC_TYPE_BANDWIDTH (gst_opus_enc_bandwidth_get_type())
+static GType
+gst_opus_enc_bandwidth_get_type (void)
+{
+ static const GEnumValue values[] = {
+ {OPUS_BANDWIDTH_NARROWBAND, "Narrow band", "narrowband"},
+ {OPUS_BANDWIDTH_MEDIUMBAND, "Medium band", "mediumband"},
+ {OPUS_BANDWIDTH_WIDEBAND, "Wide band", "wideband"},
+ {OPUS_BANDWIDTH_SUPERWIDEBAND, "Super wide band", "superwideband"},
+ {OPUS_BANDWIDTH_FULLBAND, "Full band", "fullband"},
+ {OPUS_AUTO, "Auto", "auto"},
+ {0, NULL, NULL}
+ };
+ static volatile GType id = 0;
+
+ if (g_once_init_enter ((gsize *) & id)) {
+ GType _id;
+
+ _id = g_enum_register_static ("GstOpusEncBandwidth", values);
+
+ g_once_init_leave ((gsize *) & id, _id);
+ }
+
+ return id;
+}
+
+#define GST_OPUS_ENC_TYPE_FRAME_SIZE (gst_opus_enc_frame_size_get_type())
+static GType
+gst_opus_enc_frame_size_get_type (void)
+{
+ static const GEnumValue values[] = {
+ {2, "2.5", "2.5"},
+ {5, "5", "5"},
+ {10, "10", "10"},
+ {20, "20", "20"},
+ {40, "40", "40"},
+ {60, "60", "60"},
+ {0, NULL, NULL}
+ };
+ static volatile GType id = 0;
+
+ if (g_once_init_enter ((gsize *) & id)) {
+ GType _id;
+
+ _id = g_enum_register_static ("GstOpusEncFrameSize", values);
+
+ g_once_init_leave ((gsize *) & id, _id);
+ }
+
+ return id;
+}
+
+#define GST_OPUS_ENC_TYPE_AUDIO_TYPE (gst_opus_enc_audio_type_get_type())
+static GType
+gst_opus_enc_audio_type_get_type (void)
+{
+ static const GEnumValue values[] = {
+ {OPUS_APPLICATION_AUDIO, "Generic audio", "generic"},
+ {OPUS_APPLICATION_VOIP, "Voice", "voice"},
+ {0, NULL, NULL}
+ };
+ static volatile GType id = 0;
+
+ if (g_once_init_enter ((gsize *) & id)) {
+ GType _id;
+
+ _id = g_enum_register_static ("GstOpusEncAudioType", values);
+
+ g_once_init_leave ((gsize *) & id, _id);
+ }
+
+ return id;
+}
+
+#define GST_OPUS_ENC_TYPE_BITRATE_TYPE (gst_opus_enc_bitrate_type_get_type())
+static GType
+gst_opus_enc_bitrate_type_get_type (void)
+{
+ static const GEnumValue values[] = {
+ {BITRATE_TYPE_CBR, "CBR", "cbr"},
+ {BITRATE_TYPE_VBR, "VBR", "vbr"},
+ {BITRATE_TYPE_CONSTRAINED_VBR, "Constrained VBR", "constrained-vbr"},
+ {0, NULL, NULL}
+ };
+ static volatile GType id = 0;
+
+ if (g_once_init_enter ((gsize *) & id)) {
+ GType _id;
+
+ _id = g_enum_register_static ("GstOpusEncBitrateType", values);
+
+ g_once_init_leave ((gsize *) & id, _id);
+ }
+
+ return id;
+}
+
+#define FORMAT_STR GST_AUDIO_NE(S16)
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-raw, "
+ "format = (string) " FORMAT_STR ", "
+ "layout = (string) interleaved, "
+ "rate = (int) 48000, "
+ "channels = (int) [ 1, 8 ]; "
+ "audio/x-raw, "
+ "format = (string) " FORMAT_STR ", "
+ "layout = (string) interleaved, "
+ "rate = (int) { 8000, 12000, 16000, 24000 }, "
+ "channels = (int) [ 1, 8 ] ")
+ );
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-opus")
+ );
+
+#define DEFAULT_AUDIO TRUE
+#define DEFAULT_AUDIO_TYPE OPUS_APPLICATION_AUDIO
+#define DEFAULT_BITRATE 64000
+#define DEFAULT_BANDWIDTH OPUS_BANDWIDTH_FULLBAND
+#define DEFAULT_FRAMESIZE 20
+#define DEFAULT_CBR TRUE
+#define DEFAULT_CONSTRAINED_VBR TRUE
+#define DEFAULT_BITRATE_TYPE BITRATE_TYPE_CBR
+#define DEFAULT_COMPLEXITY 10
+#define DEFAULT_INBAND_FEC FALSE
+#define DEFAULT_DTX FALSE
+#define DEFAULT_PACKET_LOSS_PERCENT 0
+#define DEFAULT_MAX_PAYLOAD_SIZE 4000
+
+enum
+{
+ PROP_0,
+ PROP_AUDIO,
+ PROP_AUDIO_TYPE,
+ PROP_BITRATE,
+ PROP_BANDWIDTH,
+ PROP_FRAME_SIZE,
+ PROP_CBR,
+ PROP_CONSTRAINED_VBR,
+ PROP_BITRATE_TYPE,
+ PROP_COMPLEXITY,
+ PROP_INBAND_FEC,
+ PROP_DTX,
+ PROP_PACKET_LOSS_PERCENT,
+ PROP_MAX_PAYLOAD_SIZE
+};
+
+static void gst_opus_enc_finalize (GObject * object);
+
+static gboolean gst_opus_enc_sink_event (GstAudioEncoder * benc,
+ GstEvent * event);
+static GstCaps *gst_opus_enc_sink_getcaps (GstAudioEncoder * benc,
+ GstCaps * filter);
+static gboolean gst_opus_enc_setup (GstOpusEnc * enc);
+
+static void gst_opus_enc_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_opus_enc_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+
+static void gst_opus_enc_set_tags (GstOpusEnc * enc);
+static gboolean gst_opus_enc_start (GstAudioEncoder * benc);
+static gboolean gst_opus_enc_stop (GstAudioEncoder * benc);
+static gboolean gst_opus_enc_set_format (GstAudioEncoder * benc,
+ GstAudioInfo * info);
+static GstFlowReturn gst_opus_enc_handle_frame (GstAudioEncoder * benc,
+ GstBuffer * buf);
+static gint64 gst_opus_enc_get_latency (GstOpusEnc * enc);
+
+static GstFlowReturn gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buffer);
+
+#define gst_opus_enc_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstOpusEnc, gst_opus_enc, GST_TYPE_AUDIO_ENCODER,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL);
+ G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL));
+
+static void
+gst_opus_enc_set_tags (GstOpusEnc * enc)
+{
+ GstTagList *taglist;
+
+ /* create a taglist and add a bitrate tag to it */
+ taglist = gst_tag_list_new_empty ();
+ gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_BITRATE, enc->bitrate, NULL);
+
+ gst_audio_encoder_merge_tags (GST_AUDIO_ENCODER (enc), taglist,
+ GST_TAG_MERGE_REPLACE);
+
+ gst_tag_list_unref (taglist);
+}
+
+static void
+gst_opus_enc_class_init (GstOpusEncClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstAudioEncoderClass *base_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = (GObjectClass *) klass;
+ base_class = (GstAudioEncoderClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+
+ gobject_class->set_property = gst_opus_enc_set_property;
+ gobject_class->get_property = gst_opus_enc_get_property;
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&src_factory));
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&sink_factory));
+ gst_element_class_set_static_metadata (gstelement_class, "Opus audio encoder",
+ "Codec/Encoder/Audio",
+ "Encodes audio in Opus format",
+ "Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>");
+
+ base_class->start = GST_DEBUG_FUNCPTR (gst_opus_enc_start);
+ base_class->stop = GST_DEBUG_FUNCPTR (gst_opus_enc_stop);
+ base_class->set_format = GST_DEBUG_FUNCPTR (gst_opus_enc_set_format);
+ base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_opus_enc_handle_frame);
+ base_class->sink_event = GST_DEBUG_FUNCPTR (gst_opus_enc_sink_event);
+ base_class->getcaps = GST_DEBUG_FUNCPTR (gst_opus_enc_sink_getcaps);
+
+ g_object_class_install_property (gobject_class, PROP_AUDIO,
+ g_param_spec_boolean ("audio",
+ "Audio or voice",
+ "Audio or voice (DEPRECATED: use audio-type)", DEFAULT_AUDIO,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED));
+ g_object_class_install_property (gobject_class, PROP_AUDIO_TYPE,
+ g_param_spec_enum ("audio-type", "What type of audio to optimize for",
+ "What type of audio to optimize for", GST_OPUS_ENC_TYPE_AUDIO_TYPE,
+ DEFAULT_AUDIO_TYPE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BITRATE,
+ g_param_spec_int ("bitrate", "Encoding Bit-rate",
+ "Specify an encoding bit-rate (in bps).", LOWEST_BITRATE,
+ HIGHEST_BITRATE, DEFAULT_BITRATE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (gobject_class, PROP_BANDWIDTH,
+ g_param_spec_enum ("bandwidth", "Band Width", "Audio Band Width",
+ GST_OPUS_ENC_TYPE_BANDWIDTH, DEFAULT_BANDWIDTH,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (gobject_class, PROP_FRAME_SIZE,
+ g_param_spec_enum ("frame-size", "Frame Size",
+ "The duration of an audio frame, in ms", GST_OPUS_ENC_TYPE_FRAME_SIZE,
+ DEFAULT_FRAMESIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (gobject_class, PROP_CBR,
+ g_param_spec_boolean ("cbr", "Constant bit rate",
+ "Constant bit rate (DEPRECATED: use bitrate-type)", DEFAULT_CBR,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PLAYING
+ | G_PARAM_DEPRECATED));
+ g_object_class_install_property (gobject_class, PROP_CONSTRAINED_VBR,
+ g_param_spec_boolean ("constrained-vbr", "Constrained VBR",
+ "Constrained VBR (DEPRECATED: use bitrate-type)",
+ DEFAULT_CONSTRAINED_VBR,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PLAYING
+ | G_PARAM_DEPRECATED));
+ g_object_class_install_property (gobject_class, PROP_BITRATE_TYPE,
+ g_param_spec_enum ("bitrate-type", "Bitrate type", "Bitrate type",
+ GST_OPUS_ENC_TYPE_BITRATE_TYPE, DEFAULT_BITRATE_TYPE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (gobject_class, PROP_COMPLEXITY,
+ g_param_spec_int ("complexity", "Complexity", "Complexity", 0, 10,
+ DEFAULT_COMPLEXITY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (gobject_class, PROP_INBAND_FEC,
+ g_param_spec_boolean ("inband-fec", "In-band FEC",
+ "Enable forward error correction", DEFAULT_INBAND_FEC,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (gobject_class, PROP_DTX,
+ g_param_spec_boolean ("dtx", "DTX", "DTX", DEFAULT_DTX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_PACKET_LOSS_PERCENT, g_param_spec_int ("packet-loss-percentage",
+ "Loss percentage", "Packet loss percentage", 0, 100,
+ DEFAULT_PACKET_LOSS_PERCENT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_MAX_PAYLOAD_SIZE, g_param_spec_uint ("max-payload-size",
+ "Max payload size", "Maximum payload size in bytes", 2, 4000,
+ DEFAULT_MAX_PAYLOAD_SIZE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_opus_enc_finalize);
+
+ GST_DEBUG_CATEGORY_INIT (opusenc_debug, "opusenc", 0, "Opus encoder");
+}
+
+static void
+gst_opus_enc_finalize (GObject * object)
+{
+ GstOpusEnc *enc;
+
+ enc = GST_OPUS_ENC (object);
+
+ g_mutex_clear (&enc->property_lock);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_opus_enc_init (GstOpusEnc * enc)
+{
+ GST_DEBUG_OBJECT (enc, "init");
+
+ GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_ENCODER_SINK_PAD (enc));
+
+ g_mutex_init (&enc->property_lock);
+
+ enc->n_channels = -1;
+ enc->sample_rate = -1;
+ enc->frame_samples = 0;
+
+ enc->bitrate = DEFAULT_BITRATE;
+ enc->bandwidth = DEFAULT_BANDWIDTH;
+ enc->frame_size = DEFAULT_FRAMESIZE;
+ enc->bitrate_type = DEFAULT_BITRATE_TYPE;
+ enc->complexity = DEFAULT_COMPLEXITY;
+ enc->inband_fec = DEFAULT_INBAND_FEC;
+ enc->dtx = DEFAULT_DTX;
+ enc->packet_loss_percentage = DEFAULT_PACKET_LOSS_PERCENT;
+ enc->max_payload_size = DEFAULT_MAX_PAYLOAD_SIZE;
+ enc->audio_type = DEFAULT_AUDIO_TYPE;
+}
+
+static gboolean
+gst_opus_enc_start (GstAudioEncoder * benc)
+{
+ GstOpusEnc *enc = GST_OPUS_ENC (benc);
+
+ GST_DEBUG_OBJECT (enc, "start");
+ enc->encoded_samples = 0;
+ enc->consumed_samples = 0;
+
+ return TRUE;
+}
+
+static gboolean
+gst_opus_enc_stop (GstAudioEncoder * benc)
+{
+ GstOpusEnc *enc = GST_OPUS_ENC (benc);
+
+ GST_DEBUG_OBJECT (enc, "stop");
+ if (enc->state) {
+ opus_multistream_encoder_destroy (enc->state);
+ enc->state = NULL;
+ }
+ gst_tag_setter_reset_tags (GST_TAG_SETTER (enc));
+
+ return TRUE;
+}
+
+static gint64
+gst_opus_enc_get_latency (GstOpusEnc * enc)
+{
+ gint64 latency = gst_util_uint64_scale (enc->frame_samples, GST_SECOND,
+ enc->sample_rate);
+ GST_DEBUG_OBJECT (enc, "Latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (latency));
+ return latency;
+}
+
+static void
+gst_opus_enc_setup_base_class (GstOpusEnc * enc, GstAudioEncoder * benc)
+{
+ gst_audio_encoder_set_latency (benc,
+ gst_opus_enc_get_latency (enc), gst_opus_enc_get_latency (enc));
+ gst_audio_encoder_set_frame_samples_min (benc, enc->frame_samples);
+ gst_audio_encoder_set_frame_samples_max (benc, enc->frame_samples);
+ gst_audio_encoder_set_frame_max (benc, 1);
+}
+
+static gint
+gst_opus_enc_get_frame_samples (GstOpusEnc * enc)
+{
+ gint frame_samples = 0;
+ switch (enc->frame_size) {
+ case 2:
+ frame_samples = enc->sample_rate / 400;
+ break;
+ case 5:
+ frame_samples = enc->sample_rate / 200;
+ break;
+ case 10:
+ frame_samples = enc->sample_rate / 100;
+ break;
+ case 20:
+ frame_samples = enc->sample_rate / 50;
+ break;
+ case 40:
+ frame_samples = enc->sample_rate / 25;
+ break;
+ case 60:
+ frame_samples = 3 * enc->sample_rate / 50;
+ break;
+ default:
+ GST_WARNING_OBJECT (enc, "Unsupported frame size: %d", enc->frame_size);
+ frame_samples = 0;
+ break;
+ }
+ return frame_samples;
+}
+
+static void
+gst_opus_enc_setup_trivial_mapping (GstOpusEnc * enc, guint8 mapping[256])
+{
+ int n;
+
+ for (n = 0; n < 255; ++n)
+ mapping[n] = n;
+}
+
+static int
+gst_opus_enc_find_channel_position (GstOpusEnc * enc, const GstAudioInfo * info,
+ GstAudioChannelPosition position)
+{
+ int n;
+ for (n = 0; n < enc->n_channels; ++n) {
+ if (GST_AUDIO_INFO_POSITION (info, n) == position) {
+ return n;
+ }
+ }
+ return -1;
+}
+
+static int
+gst_opus_enc_find_channel_position_in_vorbis_order (GstOpusEnc * enc,
+ GstAudioChannelPosition position)
+{
+ int c;
+
+ for (c = 0; c < enc->n_channels; ++c) {
+ if (gst_opus_channel_positions[enc->n_channels - 1][c] == position) {
+ GST_INFO_OBJECT (enc,
+ "Channel position %s maps to index %d in Vorbis order",
+ gst_opus_channel_names[position], c);
+ return c;
+ }
+ }
+ GST_WARNING_OBJECT (enc,
+ "Channel position %s is not representable in Vorbis order",
+ gst_opus_channel_names[position]);
+ return -1;
+}
+
+static void
+gst_opus_enc_setup_channel_mappings (GstOpusEnc * enc,
+ const GstAudioInfo * info)
+{
+#define MAPS(idx,pos) (GST_AUDIO_INFO_POSITION (info, (idx)) == GST_AUDIO_CHANNEL_POSITION_##pos)
+
+ int n;
+
+ GST_DEBUG_OBJECT (enc, "Setting up channel mapping for %d channels",
+ enc->n_channels);
+
+ /* Start by setting up a default trivial mapping */
+ enc->n_stereo_streams = 0;
+ gst_opus_enc_setup_trivial_mapping (enc, enc->encoding_channel_mapping);
+ gst_opus_enc_setup_trivial_mapping (enc, enc->decoding_channel_mapping);
+
+ /* For one channel, use the basic RTP mapping */
+ if (enc->n_channels == 1) {
+ GST_INFO_OBJECT (enc, "Mono, trivial RTP mapping");
+ enc->channel_mapping_family = 0;
+ /* implicit mapping for family 0 */
+ return;
+ }
+
+ /* For two channels, use the basic RTP mapping if the channels are
+ mapped as left/right. */
+ if (enc->n_channels == 2) {
+ GST_INFO_OBJECT (enc, "Stereo, trivial RTP mapping");
+ enc->channel_mapping_family = 0;
+ enc->n_stereo_streams = 1;
+ /* implicit mapping for family 0 */
+ return;
+ }
+
+ /* For channels between 3 and 8, we use the Vorbis mapping if we can
+ find a permutation that matches it. Mono and stereo will have been taken
+ care of earlier, but this code also handles it. There are two mappings.
+ One maps the input channels to an ordering which has the natural pairs
+ first so they can benefit from the Opus stereo channel coupling, and the
+ other maps this ordering to the Vorbis ordering. */
+ if (enc->n_channels >= 3 && enc->n_channels <= 8) {
+ int c0, c1, c0v, c1v;
+ int mapped;
+ gboolean positions_done[256];
+ static const GstAudioChannelPosition pairs[][2] = {
+ {GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT},
+ {GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT},
+ {GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER},
+ {GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER},
+ {GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
+ GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT},
+ {GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_REAR_CENTER},
+ };
+ size_t pair;
+
+ GST_DEBUG_OBJECT (enc,
+ "In range for the Vorbis mapping, building channel mapping tables");
+
+ enc->n_stereo_streams = 0;
+ mapped = 0;
+ for (n = 0; n < 256; ++n)
+ positions_done[n] = FALSE;
+
+ /* First, find any natural pairs, and move them to the front */
+ for (pair = 0; pair < G_N_ELEMENTS (pairs); ++pair) {
+ GstAudioChannelPosition p0 = pairs[pair][0];
+ GstAudioChannelPosition p1 = pairs[pair][1];
+ c0 = gst_opus_enc_find_channel_position (enc, info, p0);
+ c1 = gst_opus_enc_find_channel_position (enc, info, p1);
+ if (c0 >= 0 && c1 >= 0) {
+ /* We found a natural pair */
+ GST_DEBUG_OBJECT (enc, "Natural pair '%s/%s' found at %d %d",
+ gst_opus_channel_names[p0], gst_opus_channel_names[p1], c0, c1);
+ /* Find where they map in Vorbis order */
+ c0v = gst_opus_enc_find_channel_position_in_vorbis_order (enc, p0);
+ c1v = gst_opus_enc_find_channel_position_in_vorbis_order (enc, p1);
+ if (c0v < 0 || c1v < 0) {
+ GST_WARNING_OBJECT (enc,
+ "Cannot map channel positions to Vorbis order, using unknown mapping");
+ enc->channel_mapping_family = 255;
+ enc->n_stereo_streams = 0;
+ return;
+ }
+
+ enc->encoding_channel_mapping[mapped] = c0;
+ enc->encoding_channel_mapping[mapped + 1] = c1;
+ enc->decoding_channel_mapping[c0v] = mapped;
+ enc->decoding_channel_mapping[c1v] = mapped + 1;
+ enc->n_stereo_streams++;
+ mapped += 2;
+ positions_done[p0] = positions_done[p1] = TRUE;
+ }
+ }
+
+ /* Now add all other input channels as mono streams */
+ for (n = 0; n < enc->n_channels; ++n) {
+ GstAudioChannelPosition position = GST_AUDIO_INFO_POSITION (info, n);
+
+ /* if we already mapped it while searching for pairs, nothing else
+ needs to be done */
+ if (!positions_done[position]) {
+ int cv;
+ GST_DEBUG_OBJECT (enc, "Channel position %s is not mapped yet, adding",
+ gst_opus_channel_names[position]);
+ cv = gst_opus_enc_find_channel_position_in_vorbis_order (enc, position);
+ if (cv < 0)
+ g_assert_not_reached ();
+ enc->encoding_channel_mapping[mapped] = n;
+ enc->decoding_channel_mapping[cv] = mapped;
+ mapped++;
+ }
+ }
+
+#ifndef GST_DISABLE_GST_DEBUG
+ GST_INFO_OBJECT (enc,
+ "Mapping tables built: %d channels, %d stereo streams", enc->n_channels,
+ enc->n_stereo_streams);
+ gst_opus_common_log_channel_mapping_table (GST_ELEMENT (enc), opusenc_debug,
+ "Encoding mapping table", enc->n_channels,
+ enc->encoding_channel_mapping);
+ gst_opus_common_log_channel_mapping_table (GST_ELEMENT (enc), opusenc_debug,
+ "Decoding mapping table", enc->n_channels,
+ enc->decoding_channel_mapping);
+#endif
+
+ enc->channel_mapping_family = 1;
+ return;
+ }
+
+ /* More than 8 channels, if future mappings are added for those */
+
+ /* For other cases, we use undefined, with the default trivial mapping
+ and all mono streams */
+ GST_WARNING_OBJECT (enc, "Unknown mapping");
+ enc->channel_mapping_family = 255;
+ enc->n_stereo_streams = 0;
+
+#undef MAPS
+}
+
+static gboolean
+gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info)
+{
+ GstOpusEnc *enc;
+
+ enc = GST_OPUS_ENC (benc);
+
+ g_mutex_lock (&enc->property_lock);
+
+ enc->n_channels = GST_AUDIO_INFO_CHANNELS (info);
+ enc->sample_rate = GST_AUDIO_INFO_RATE (info);
+ gst_opus_enc_setup_channel_mappings (enc, info);
+ GST_DEBUG_OBJECT (benc, "Setup with %d channels, %d Hz", enc->n_channels,
+ enc->sample_rate);
+
+ /* handle reconfigure */
+ if (enc->state) {
+ opus_multistream_encoder_destroy (enc->state);
+ enc->state = NULL;
+ }
+ if (!gst_opus_enc_setup (enc)) {
+ g_mutex_unlock (&enc->property_lock);
+ return FALSE;
+ }
+
+ /* update the tags */
+ gst_opus_enc_set_tags (enc);
+
+ enc->frame_samples = gst_opus_enc_get_frame_samples (enc);
+
+ /* feedback to base class */
+ gst_opus_enc_setup_base_class (enc, benc);
+
+ g_mutex_unlock (&enc->property_lock);
+
+ return TRUE;
+}
+
+static gboolean
+gst_opus_enc_setup (GstOpusEnc * enc)
+{
+ int error = OPUS_OK;
+ GstCaps *caps;
+ gboolean ret;
+ gint32 lookahead;
+ const GstTagList *tags;
+ GstTagList *empty_tags = NULL;
+ GstBuffer *header, *comments;
+
+#ifndef GST_DISABLE_GST_DEBUG
+ GST_DEBUG_OBJECT (enc,
+ "setup: %d Hz, %d channels, %d stereo streams, family %d",
+ enc->sample_rate, enc->n_channels, enc->n_stereo_streams,
+ enc->channel_mapping_family);
+ GST_INFO_OBJECT (enc, "Mapping tables built: %d channels, %d stereo streams",
+ enc->n_channels, enc->n_stereo_streams);
+ gst_opus_common_log_channel_mapping_table (GST_ELEMENT (enc), opusenc_debug,
+ "Encoding mapping table", enc->n_channels, enc->encoding_channel_mapping);
+ gst_opus_common_log_channel_mapping_table (GST_ELEMENT (enc), opusenc_debug,
+ "Decoding mapping table", enc->n_channels, enc->decoding_channel_mapping);
+#endif
+
+ enc->state = opus_multistream_encoder_create (enc->sample_rate,
+ enc->n_channels, enc->n_channels - enc->n_stereo_streams,
+ enc->n_stereo_streams, enc->encoding_channel_mapping,
+ enc->audio_type, &error);
+ if (!enc->state || error != OPUS_OK)
+ goto encoder_creation_failed;
+
+ opus_multistream_encoder_ctl (enc->state, OPUS_SET_BITRATE (enc->bitrate), 0);
+ opus_multistream_encoder_ctl (enc->state, OPUS_SET_BANDWIDTH (enc->bandwidth),
+ 0);
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_VBR (enc->bitrate_type != BITRATE_TYPE_CBR), 0);
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_VBR_CONSTRAINT (enc->bitrate_type ==
+ BITRATE_TYPE_CONSTRAINED_VBR), 0);
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_COMPLEXITY (enc->complexity), 0);
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_INBAND_FEC (enc->inband_fec), 0);
+ opus_multistream_encoder_ctl (enc->state, OPUS_SET_DTX (enc->dtx), 0);
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_PACKET_LOSS_PERC (enc->packet_loss_percentage), 0);
+
+ opus_multistream_encoder_ctl (enc->state, OPUS_GET_LOOKAHEAD (&lookahead), 0);
+
+ GST_LOG_OBJECT (enc, "we have frame size %d, lookahead %d", enc->frame_size,
+ lookahead);
+
+ /* lookahead is samples, the Opus header wants it in 48kHz samples */
+ lookahead = lookahead * 48000 / enc->sample_rate;
+ enc->lookahead = enc->pending_lookahead = lookahead;
+
+ header = gst_codec_utils_opus_create_header (enc->sample_rate,
+ enc->n_channels, enc->channel_mapping_family,
+ enc->n_channels - enc->n_stereo_streams, enc->n_stereo_streams,
+ enc->decoding_channel_mapping, lookahead, 0);
+ tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc));
+ if (!tags)
+ tags = empty_tags = gst_tag_list_new_empty ();
+ comments =
+ gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags",
+ 8, "Encoded with GStreamer opusenc");
+ caps = gst_codec_utils_opus_create_caps_from_header (header, comments);
+ if (empty_tags)
+ gst_tag_list_unref (empty_tags);
+ gst_buffer_unref (header);
+ gst_buffer_unref (comments);
+
+ /* negotiate with these caps */
+ GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps);
+
+ ret = gst_audio_encoder_set_output_format (GST_AUDIO_ENCODER (enc), caps);
+ gst_caps_unref (caps);
+
+ return ret;
+
+encoder_creation_failed:
+ GST_ERROR_OBJECT (enc, "Encoder creation failed");
+ return FALSE;
+}
+
+static gboolean
+gst_opus_enc_sink_event (GstAudioEncoder * benc, GstEvent * event)
+{
+ GstOpusEnc *enc;
+
+ enc = GST_OPUS_ENC (benc);
+
+ GST_DEBUG_OBJECT (enc, "sink event: %s", GST_EVENT_TYPE_NAME (event));
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_TAG:
+ {
+ GstTagList *list;
+ GstTagSetter *setter = GST_TAG_SETTER (enc);
+ const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
+
+ gst_event_parse_tag (event, &list);
+ gst_tag_setter_merge_tags (setter, list, mode);
+ break;
+ }
+ case GST_EVENT_SEGMENT:
+ enc->encoded_samples = 0;
+ enc->consumed_samples = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ return GST_AUDIO_ENCODER_CLASS (parent_class)->sink_event (benc, event);
+}
+
+static GstCaps *
+gst_opus_enc_get_sink_template_caps (void)
+{
+ static volatile gsize init = 0;
+ static GstCaps *caps = NULL;
+
+ if (g_once_init_enter (&init)) {
+ GValue rate_array = G_VALUE_INIT;
+ GValue v = G_VALUE_INIT;
+ GstStructure *s1, *s2, *s;
+ gint i, c;
+
+ caps = gst_caps_new_empty ();
+
+ /* Generate our two template structures */
+ g_value_init (&rate_array, GST_TYPE_LIST);
+ g_value_init (&v, G_TYPE_INT);
+ g_value_set_int (&v, 8000);
+ gst_value_list_append_value (&rate_array, &v);
+ g_value_set_int (&v, 12000);
+ gst_value_list_append_value (&rate_array, &v);
+ g_value_set_int (&v, 16000);
+ gst_value_list_append_value (&rate_array, &v);
+ g_value_set_int (&v, 24000);
+ gst_value_list_append_value (&rate_array, &v);
+
+ s1 = gst_structure_new ("audio/x-raw",
+ "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
+ "layout", G_TYPE_STRING, "interleaved",
+ "rate", G_TYPE_INT, 48000, NULL);
+ s2 = gst_structure_new ("audio/x-raw",
+ "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
+ "layout", G_TYPE_STRING, "interleaved", NULL);
+ gst_structure_set_value (s2, "rate", &rate_array);
+ g_value_unset (&rate_array);
+ g_value_unset (&v);
+
+ /* Mono */
+ s = gst_structure_copy (s1);
+ gst_structure_set (s, "channels", G_TYPE_INT, 1, NULL);
+ gst_caps_append_structure (caps, s);
+
+ s = gst_structure_copy (s2);
+ gst_structure_set (s, "channels", G_TYPE_INT, 1, NULL);
+ gst_caps_append_structure (caps, s);
+
+ /* Stereo and further */
+ for (i = 2; i <= 8; i++) {
+ guint64 channel_mask = 0;
+ const GstAudioChannelPosition *pos = gst_opus_channel_positions[i - 1];
+
+ for (c = 0; c < i; c++) {
+ channel_mask |= G_GUINT64_CONSTANT (1) << pos[c];
+ }
+
+ s = gst_structure_copy (s1);
+ gst_structure_set (s, "channels", G_TYPE_INT, i, "channel-mask",
+ GST_TYPE_BITMASK, channel_mask, NULL);
+ gst_caps_append_structure (caps, s);
+
+ s = gst_structure_copy (s2);
+ gst_structure_set (s, "channels", G_TYPE_INT, i, "channel-mask",
+ GST_TYPE_BITMASK, channel_mask, NULL);
+ gst_caps_append_structure (caps, s);
+ }
+
+ gst_structure_free (s1);
+ gst_structure_free (s2);
+
+ g_once_init_leave (&init, 1);
+ }
+
+ return caps;
+}
+
+static GstCaps *
+gst_opus_enc_sink_getcaps (GstAudioEncoder * benc, GstCaps * filter)
+{
+ GstOpusEnc *enc;
+ GstCaps *caps;
+
+ enc = GST_OPUS_ENC (benc);
+
+ GST_DEBUG_OBJECT (enc, "sink getcaps");
+
+ caps = gst_opus_enc_get_sink_template_caps ();
+ caps = gst_audio_encoder_proxy_getcaps (benc, caps, filter);
+
+ GST_DEBUG_OBJECT (enc, "Returning caps: %" GST_PTR_FORMAT, caps);
+
+ return caps;
+}
+
+static GstFlowReturn
+gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf)
+{
+ guint8 *bdata = NULL, *data, *mdata = NULL;
+ gsize bsize, size;
+ gsize bytes;
+ gint ret = GST_FLOW_OK;
+ GstMapInfo map;
+ GstMapInfo omap;
+ gint outsize;
+ GstBuffer *outbuf;
+ GstSegment *segment;
+ GstClockTime duration;
+ guint64 trim_start = 0, trim_end = 0;
+
+ guint max_payload_size;
+ gint frame_samples, input_samples, output_samples;
+
+ g_mutex_lock (&enc->property_lock);
+
+ bytes = enc->frame_samples * enc->n_channels * 2;
+ max_payload_size = enc->max_payload_size;
+ frame_samples = input_samples = enc->frame_samples;
+
+ g_mutex_unlock (&enc->property_lock);
+
+ if (G_LIKELY (buf)) {
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+ bdata = map.data;
+ bsize = map.size;
+
+ if (G_UNLIKELY (bsize % bytes)) {
+ gint64 diff;
+
+ GST_DEBUG_OBJECT (enc, "draining; adding silence samples");
+ g_assert (bsize < bytes);
+
+ /* If encoding part of a frame, and we have no set stop time on
+ * the output segment, we update the segment stop time to reflect
+ * the last sample. This will let oggmux set the last page's
+ * granpos to tell a decoder the dummy samples should be clipped.
+ */
+ input_samples = bsize / (enc->n_channels * 2);
+ segment = &GST_AUDIO_ENCODER_OUTPUT_SEGMENT (enc);
+ if (!GST_CLOCK_TIME_IS_VALID (segment->stop)) {
+ GST_DEBUG_OBJECT (enc,
+ "No stop time and partial frame, updating segment");
+ duration =
+ gst_util_uint64_scale_ceil (enc->consumed_samples + input_samples,
+ GST_SECOND, enc->sample_rate);
+ segment->stop = segment->start + duration;
+ GST_DEBUG_OBJECT (enc, "new output segment %" GST_SEGMENT_FORMAT,
+ segment);
+ gst_pad_push_event (GST_AUDIO_ENCODER_SRC_PAD (enc),
+ gst_event_new_segment (segment));
+ }
+
+ diff =
+ (enc->encoded_samples + frame_samples) - (enc->consumed_samples +
+ input_samples);
+ if (diff >= 0) {
+ GST_DEBUG_OBJECT (enc,
+ "%" G_GINT64_FORMAT " extra samples of padding in this frame",
+ diff);
+ output_samples = frame_samples - diff;
+ trim_end = diff * 48000 / enc->sample_rate;
+ } else {
+ GST_DEBUG_OBJECT (enc,
+ "Need to add %" G_GINT64_FORMAT " extra samples in the next frame",
+ -diff);
+ output_samples = frame_samples;
+ }
+
+ size = ((bsize / bytes) + 1) * bytes;
+ mdata = g_malloc0 (size);
+ /* FIXME: Instead of silence, use LPC with the last real samples.
+ * Otherwise we will create a discontinuity here, which will distort the
+ * last few encoded samples
+ */
+ memcpy (mdata, bdata, bsize);
+ data = mdata;
+ } else {
+ data = bdata;
+ size = bsize;
+
+ /* Adjust for lookahead here */
+ if (enc->pending_lookahead) {
+ guint scaled_lookahead =
+ enc->pending_lookahead * enc->sample_rate / 48000;
+
+ if (input_samples > scaled_lookahead) {
+ output_samples = input_samples - scaled_lookahead;
+ trim_start = enc->pending_lookahead;
+ enc->pending_lookahead = 0;
+ } else {
+ trim_start = ((guint64) input_samples) * 48000 / enc->sample_rate;
+ enc->pending_lookahead -= trim_start;
+ output_samples = 0;
+ }
+ } else {
+ output_samples = input_samples;
+ }
+ }
+ } else {
+ if (enc->encoded_samples < enc->consumed_samples) {
+ /* FIXME: Instead of silence, use LPC with the last real samples.
+ * Otherwise we will create a discontinuity here, which will distort the
+ * last few encoded samples
+ */
+ data = mdata = g_malloc0 (bytes);
+ size = bytes;
+ output_samples = enc->consumed_samples - enc->encoded_samples;
+ input_samples = 0;
+ GST_DEBUG_OBJECT (enc, "draining %d samples", output_samples);
+ trim_end =
+ ((guint64) frame_samples - output_samples) * 48000 / enc->sample_rate;
+ } else if (enc->encoded_samples == enc->consumed_samples) {
+ GST_DEBUG_OBJECT (enc, "nothing to drain");
+ goto done;
+ } else {
+ g_assert_not_reached ();
+ goto done;
+ }
+ }
+
+ g_assert (size == bytes);
+
+ outbuf =
+ gst_audio_encoder_allocate_output_buffer (GST_AUDIO_ENCODER (enc),
+ max_payload_size * enc->n_channels);
+ if (!outbuf)
+ goto done;
+
+ GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)",
+ frame_samples, (int) bytes);
+
+ if (trim_start || trim_end) {
+ GST_DEBUG_OBJECT (enc,
+ "Adding trim-start %" G_GUINT64_FORMAT " trim-end %" G_GUINT64_FORMAT,
+ trim_start, trim_end);
+ gst_buffer_add_audio_clipping_meta (outbuf, GST_FORMAT_DEFAULT, trim_start,
+ trim_end);
+ }
+
+ gst_buffer_map (outbuf, &omap, GST_MAP_WRITE);
+
+ outsize =
+ opus_multistream_encode (enc->state, (const gint16 *) data,
+ frame_samples, omap.data, max_payload_size * enc->n_channels);
+
+ gst_buffer_unmap (outbuf, &omap);
+
+ if (outsize < 0) {
+ GST_ERROR_OBJECT (enc, "Encoding failed: %d", outsize);
+ ret = GST_FLOW_ERROR;
+ goto done;
+ } else if (outsize > max_payload_size) {
+ GST_WARNING_OBJECT (enc,
+ "Encoded size %d is higher than max payload size (%d bytes)",
+ outsize, max_payload_size);
+ ret = GST_FLOW_ERROR;
+ goto done;
+ }
+
+ GST_DEBUG_OBJECT (enc, "Output packet is %u bytes", outsize);
+ gst_buffer_set_size (outbuf, outsize);
+
+
+ ret =
+ gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (enc), outbuf,
+ output_samples);
+ enc->encoded_samples += output_samples;
+ enc->consumed_samples += input_samples;
+
+done:
+
+ if (bdata)
+ gst_buffer_unmap (buf, &map);
+
+ g_free (mdata);
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf)
+{
+ GstOpusEnc *enc;
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ enc = GST_OPUS_ENC (benc);
+ GST_DEBUG_OBJECT (enc, "handle_frame");
+ GST_DEBUG_OBJECT (enc, "received buffer %p of %" G_GSIZE_FORMAT " bytes", buf,
+ buf ? gst_buffer_get_size (buf) : 0);
+
+ ret = gst_opus_enc_encode (enc, buf);
+
+ return ret;
+}
+
+static void
+gst_opus_enc_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstOpusEnc *enc;
+
+ enc = GST_OPUS_ENC (object);
+
+ g_mutex_lock (&enc->property_lock);
+
+ switch (prop_id) {
+ case PROP_AUDIO:
+ g_value_set_boolean (value,
+ enc->audio_type == OPUS_APPLICATION_AUDIO ? TRUE : FALSE);
+ break;
+ case PROP_AUDIO_TYPE:
+ g_value_set_enum (value, enc->audio_type);
+ break;
+ case PROP_BITRATE:
+ g_value_set_int (value, enc->bitrate);
+ break;
+ case PROP_BANDWIDTH:
+ g_value_set_enum (value, enc->bandwidth);
+ break;
+ case PROP_FRAME_SIZE:
+ g_value_set_enum (value, enc->frame_size);
+ break;
+ case PROP_CBR:
+ GST_WARNING_OBJECT (enc,
+ "cbr property is deprecated; use bitrate-type instead");
+ g_value_set_boolean (value, enc->bitrate_type == BITRATE_TYPE_CBR);
+ break;
+ case PROP_CONSTRAINED_VBR:
+ GST_WARNING_OBJECT (enc,
+ "constrained-vbr property is deprecated; use bitrate-type instead");
+ g_value_set_boolean (value,
+ enc->bitrate_type == BITRATE_TYPE_CONSTRAINED_VBR);
+ break;
+ case PROP_BITRATE_TYPE:
+ g_value_set_enum (value, enc->bitrate_type);
+ break;
+ case PROP_COMPLEXITY:
+ g_value_set_int (value, enc->complexity);
+ break;
+ case PROP_INBAND_FEC:
+ g_value_set_boolean (value, enc->inband_fec);
+ break;
+ case PROP_DTX:
+ g_value_set_boolean (value, enc->dtx);
+ break;
+ case PROP_PACKET_LOSS_PERCENT:
+ g_value_set_int (value, enc->packet_loss_percentage);
+ break;
+ case PROP_MAX_PAYLOAD_SIZE:
+ g_value_set_uint (value, enc->max_payload_size);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+ g_mutex_unlock (&enc->property_lock);
+}
+
+static void
+gst_opus_enc_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstOpusEnc *enc;
+
+ enc = GST_OPUS_ENC (object);
+
+#define GST_OPUS_UPDATE_PROPERTY(prop,type,ctl) do { \
+ g_mutex_lock (&enc->property_lock); \
+ enc->prop = g_value_get_##type (value); \
+ if (enc->state) { \
+ opus_multistream_encoder_ctl (enc->state, OPUS_SET_##ctl (enc->prop)); \
+ } \
+ g_mutex_unlock (&enc->property_lock); \
+} while(0)
+
+ switch (prop_id) {
+ case PROP_AUDIO:
+ enc->audio_type =
+ g_value_get_boolean (value) ? OPUS_APPLICATION_AUDIO :
+ OPUS_APPLICATION_VOIP;
+ break;
+ case PROP_AUDIO_TYPE:
+ enc->audio_type = g_value_get_enum (value);
+ break;
+ case PROP_BITRATE:
+ GST_OPUS_UPDATE_PROPERTY (bitrate, int, BITRATE);
+ break;
+ case PROP_BANDWIDTH:
+ GST_OPUS_UPDATE_PROPERTY (bandwidth, enum, BANDWIDTH);
+ break;
+ case PROP_FRAME_SIZE:
+ g_mutex_lock (&enc->property_lock);
+ enc->frame_size = g_value_get_enum (value);
+ enc->frame_samples = gst_opus_enc_get_frame_samples (enc);
+ gst_opus_enc_setup_base_class (enc, GST_AUDIO_ENCODER (enc));
+ g_mutex_unlock (&enc->property_lock);
+ break;
+ case PROP_CBR:
+ GST_WARNING_OBJECT (enc,
+ "cbr property is deprecated; use bitrate-type instead");
+ g_warning ("cbr property is deprecated; use bitrate-type instead");
+ g_mutex_lock (&enc->property_lock);
+ enc->bitrate_type = BITRATE_TYPE_CBR;
+ if (enc->state) {
+ opus_multistream_encoder_ctl (enc->state, OPUS_SET_VBR (FALSE));
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_VBR_CONSTRAINT (FALSE), 0);
+ }
+ g_mutex_unlock (&enc->property_lock);
+ break;
+ case PROP_CONSTRAINED_VBR:
+ GST_WARNING_OBJECT (enc,
+ "constrained-vbr property is deprecated; use bitrate-type instead");
+ g_warning
+ ("constrained-vbr property is deprecated; use bitrate-type instead");
+ g_mutex_lock (&enc->property_lock);
+ enc->bitrate_type = BITRATE_TYPE_CONSTRAINED_VBR;
+ if (enc->state) {
+ opus_multistream_encoder_ctl (enc->state, OPUS_SET_VBR (TRUE));
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_VBR_CONSTRAINT (TRUE), 0);
+ }
+ g_mutex_unlock (&enc->property_lock);
+ break;
+ case PROP_BITRATE_TYPE:
+ /* this one has an opposite meaning to the opus ctl... */
+ g_mutex_lock (&enc->property_lock);
+ enc->bitrate_type = g_value_get_enum (value);
+ if (enc->state) {
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_VBR (enc->bitrate_type != BITRATE_TYPE_CBR));
+ opus_multistream_encoder_ctl (enc->state,
+ OPUS_SET_VBR_CONSTRAINT (enc->bitrate_type ==
+ BITRATE_TYPE_CONSTRAINED_VBR), 0);
+ }
+ g_mutex_unlock (&enc->property_lock);
+ break;
+ case PROP_COMPLEXITY:
+ GST_OPUS_UPDATE_PROPERTY (complexity, int, COMPLEXITY);
+ break;
+ case PROP_INBAND_FEC:
+ GST_OPUS_UPDATE_PROPERTY (inband_fec, boolean, INBAND_FEC);
+ break;
+ case PROP_DTX:
+ GST_OPUS_UPDATE_PROPERTY (dtx, boolean, DTX);
+ break;
+ case PROP_PACKET_LOSS_PERCENT:
+ GST_OPUS_UPDATE_PROPERTY (packet_loss_percentage, int, PACKET_LOSS_PERC);
+ break;
+ case PROP_MAX_PAYLOAD_SIZE:
+ g_mutex_lock (&enc->property_lock);
+ enc->max_payload_size = g_value_get_uint (value);
+ g_mutex_unlock (&enc->property_lock);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+
+#undef GST_OPUS_UPDATE_PROPERTY
+
+}
diff --git a/ext/opus/gstopusenc.h b/ext/opus/gstopusenc.h
new file mode 100644
index 000000000..f447292af
--- /dev/null
+++ b/ext/opus/gstopusenc.h
@@ -0,0 +1,102 @@
+/* GStreamer Opus Encoder
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011-2012> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GST_OPUS_ENC_H__
+#define __GST_OPUS_ENC_H__
+
+
+#include <gst/gst.h>
+#include <gst/audio/gstaudioencoder.h>
+
+#include <opus_multistream.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_OPUS_ENC \
+ (gst_opus_enc_get_type())
+#define GST_OPUS_ENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OPUS_ENC,GstOpusEnc))
+#define GST_OPUS_ENC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OPUS_ENC,GstOpusEncClass))
+#define GST_IS_OPUS_ENC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OPUS_ENC))
+#define GST_IS_OPUS_ENC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OPUS_ENC))
+
+#define MAX_FRAME_SIZE 2000*2
+#define MAX_FRAME_BYTES 2000
+
+typedef enum
+{
+ BITRATE_TYPE_CBR,
+ BITRATE_TYPE_VBR,
+ BITRATE_TYPE_CONSTRAINED_VBR,
+} GstOpusEncBitrateType;
+
+typedef struct _GstOpusEnc GstOpusEnc;
+typedef struct _GstOpusEncClass GstOpusEncClass;
+
+struct _GstOpusEnc {
+ GstAudioEncoder element;
+
+ OpusMSEncoder *state;
+
+ /* Locks those properties which may be changed at play time */
+ GMutex property_lock;
+
+ /* properties */
+ gint audio_type;
+ gint bitrate;
+ gint bandwidth;
+ gint frame_size;
+ GstOpusEncBitrateType bitrate_type;
+ gint complexity;
+ gboolean inband_fec;
+ gboolean dtx;
+ gint packet_loss_percentage;
+ guint max_payload_size;
+
+ gint frame_samples;
+ gint n_channels;
+ gint sample_rate;
+
+ guint64 encoded_samples, consumed_samples;
+ guint16 lookahead, pending_lookahead;
+
+ guint8 channel_mapping_family;
+ guint8 encoding_channel_mapping[256];
+ guint8 decoding_channel_mapping[256];
+ guint8 n_stereo_streams;
+};
+
+struct _GstOpusEncClass {
+ GstAudioEncoderClass parent_class;
+
+ /* signals */
+ void (*frame_encoded) (GstElement *element);
+};
+
+GType gst_opus_enc_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_OPUS_ENC_H__ */
diff --git a/ext/opus/gstopusheader.c b/ext/opus/gstopusheader.c
new file mode 100644
index 000000000..dcc85897d
--- /dev/null
+++ b/ext/opus/gstopusheader.c
@@ -0,0 +1,93 @@
+/* GStreamer Opus Encoder
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <gst/tag/tag.h>
+#include <gst/base/gstbytewriter.h>
+#include "gstopusheader.h"
+
+gboolean
+gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size)
+{
+ return (gst_buffer_get_size (buf) >= magic_size
+ && !gst_buffer_memcmp (buf, 0, magic, magic_size));
+}
+
+gboolean
+gst_opus_header_is_id_header (GstBuffer * buf)
+{
+ gsize size = gst_buffer_get_size (buf);
+ guint8 *data = NULL;
+ guint8 version, channels, channel_mapping_family, n_streams, n_stereo_streams;
+ gboolean ret = FALSE;
+ GstMapInfo map;
+
+ if (size < 19)
+ goto beach;
+ if (!gst_opus_header_is_header (buf, "OpusHead", 8))
+ goto beach;
+
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+ data = map.data;
+ size = map.size;
+
+ version = data[8];
+ if (version >= 0x0f) /* major version >=0 is what we grok */
+ goto beach;
+
+ channels = data[9];
+
+ if (channels == 0)
+ goto beach;
+
+ channel_mapping_family = data[18];
+
+ if (channel_mapping_family == 0) {
+ if (channels > 2)
+ goto beach;
+ } else {
+ channels = data[9];
+ if (size < 21 + channels)
+ goto beach;
+ n_streams = data[19];
+ n_stereo_streams = data[20];
+ if (n_streams == 0)
+ goto beach;
+ if (n_stereo_streams > n_streams)
+ goto beach;
+ if (n_streams + n_stereo_streams > 255)
+ goto beach;
+ }
+ ret = TRUE;
+
+beach:
+ if (data)
+ gst_buffer_unmap (buf, &map);
+ return ret;
+}
+
+gboolean
+gst_opus_header_is_comment_header (GstBuffer * buf)
+{
+ return gst_opus_header_is_header (buf, "OpusTags", 8);
+}
diff --git a/ext/opus/gstopusheader.h b/ext/opus/gstopusheader.h
new file mode 100644
index 000000000..2a91c03f7
--- /dev/null
+++ b/ext/opus/gstopusheader.h
@@ -0,0 +1,37 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) <2011-2012> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_OPUS_HEADER_H__
+#define __GST_OPUS_HEADER_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+extern gboolean gst_opus_header_is_header (GstBuffer * buf,
+ const char *magic, guint magic_size);
+extern gboolean gst_opus_header_is_id_header (GstBuffer * buf);
+extern gboolean gst_opus_header_is_comment_header (GstBuffer * buf);
+
+
+G_END_DECLS
+
+#endif /* __GST_OPUS_HEADER_H__ */
diff --git a/tests/check/elements/opus.c b/tests/check/elements/opus.c
new file mode 100644
index 000000000..c4b4b84e0
--- /dev/null
+++ b/tests/check/elements/opus.c
@@ -0,0 +1,338 @@
+/* GStreamer
+ *
+ * unit test for opus
+ *
+ * Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collbaora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <unistd.h>
+
+#include <gst/check/gstcheck.h>
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+#define AFORMAT "S16BE"
+#else
+#define AFORMAT "S16LE"
+#endif
+
+#define AUDIO_CAPS_STRING "audio/x-raw, " \
+ "format = (string) " AFORMAT ", "\
+ "layout = (string) interleaved, " \
+ "rate = (int) 48000, " \
+ "channels = (int) 1 "
+
+/* A lot of these taken from the vorbisdec test */
+
+/* For ease of programming we use globals to keep refs for our floating
+ * src and sink pads we create; otherwise we always have to do get_pad,
+ * get_peer, and then remove references in every test function */
+static GstPad *mydecsrcpad, *mydecsinkpad;
+static GstPad *myencsrcpad, *myencsinkpad;
+
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstElement *
+setup_opusdec (void)
+{
+ GstElement *opusdec;
+
+ GST_DEBUG ("setup_opusdec");
+ opusdec = gst_check_setup_element ("opusdec");
+ mydecsrcpad = gst_check_setup_src_pad (opusdec, &srctemplate);
+ mydecsinkpad = gst_check_setup_sink_pad (opusdec, &sinktemplate);
+ gst_pad_set_active (mydecsrcpad, TRUE);
+ gst_pad_set_active (mydecsinkpad, TRUE);
+
+ return opusdec;
+}
+
+static void
+cleanup_opusdec (GstElement * opusdec)
+{
+ GST_DEBUG ("cleanup_opusdec");
+ gst_element_set_state (opusdec, GST_STATE_NULL);
+
+ gst_pad_set_active (mydecsrcpad, FALSE);
+ gst_pad_set_active (mydecsinkpad, FALSE);
+ gst_check_teardown_src_pad (opusdec);
+ gst_check_teardown_sink_pad (opusdec);
+ gst_check_teardown_element (opusdec);
+}
+
+static GstElement *
+setup_opusenc (void)
+{
+ GstElement *opusenc;
+
+ GST_DEBUG ("setup_opusenc");
+ opusenc = gst_check_setup_element ("opusenc");
+ myencsrcpad = gst_check_setup_src_pad (opusenc, &srctemplate);
+ myencsinkpad = gst_check_setup_sink_pad (opusenc, &sinktemplate);
+ gst_pad_set_active (myencsrcpad, TRUE);
+ gst_pad_set_active (myencsinkpad, TRUE);
+
+ return opusenc;
+}
+
+static void
+cleanup_opusenc (GstElement * opusenc)
+{
+ GST_DEBUG ("cleanup_opusenc");
+ gst_element_set_state (opusenc, GST_STATE_NULL);
+
+ gst_pad_set_active (myencsrcpad, FALSE);
+ gst_pad_set_active (myencsinkpad, FALSE);
+ gst_check_teardown_src_pad (opusenc);
+ gst_check_teardown_sink_pad (opusenc);
+ gst_check_teardown_element (opusenc);
+}
+
+static void
+check_buffers (guint expected)
+{
+ GstBuffer *outbuffer;
+ guint i, num_buffers;
+
+ /* check buffers are the type we expect */
+ num_buffers = g_list_length (buffers);
+ fail_unless (num_buffers >= expected);
+ for (i = 0; i < num_buffers; ++i) {
+ outbuffer = GST_BUFFER (buffers->data);
+ fail_if (outbuffer == NULL);
+ fail_if (gst_buffer_get_size (outbuffer) == 0);
+
+ buffers = g_list_remove (buffers, outbuffer);
+
+ ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1);
+ gst_buffer_unref (outbuffer);
+ outbuffer = NULL;
+ }
+}
+
+GST_START_TEST (test_opus_encode_nothing)
+{
+ GstElement *opusenc;
+
+ opusenc = setup_opusenc ();
+ fail_unless (gst_element_set_state (opusenc,
+ GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to playing");
+
+ fail_unless (gst_pad_push_event (myencsrcpad, gst_event_new_eos ()) == TRUE);
+
+ fail_unless (gst_element_set_state (opusenc,
+ GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to ready");
+
+ /* cleanup */
+ cleanup_opusenc (opusenc);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_opus_decode_nothing)
+{
+ GstElement *opusdec;
+
+ opusdec = setup_opusdec ();
+ fail_unless (gst_element_set_state (opusdec,
+ GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to playing");
+
+ fail_unless (gst_pad_push_event (mydecsrcpad, gst_event_new_eos ()) == TRUE);
+
+ fail_unless (gst_element_set_state (opusdec,
+ GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to ready");
+
+ /* cleanup */
+ cleanup_opusdec (opusdec);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_opus_encode_samples)
+{
+ const unsigned int nsamples = 4096;
+ GstElement *opusenc;
+ GstBuffer *inbuffer;
+ GstCaps *caps;
+
+ opusenc = setup_opusenc ();
+
+ fail_unless (gst_element_set_state (opusenc,
+ GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to playing");
+
+ inbuffer = gst_buffer_new_and_alloc (nsamples * 2);
+ gst_buffer_memset (inbuffer, 0, 0, nsamples * 2);
+
+ GST_BUFFER_TIMESTAMP (inbuffer) = GST_BUFFER_OFFSET (inbuffer) = 0;
+ GST_BUFFER_DURATION (inbuffer) = GST_CLOCK_TIME_NONE;
+ ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
+
+ caps = gst_caps_from_string (AUDIO_CAPS_STRING);
+ fail_unless (caps != NULL);
+ gst_check_setup_events (myencsrcpad, opusenc, caps, GST_FORMAT_TIME);
+ gst_caps_unref (caps);
+ gst_buffer_ref (inbuffer);
+
+ /* pushing gives away my reference ... */
+ fail_unless (gst_pad_push (myencsrcpad, inbuffer) == GST_FLOW_OK);
+ /* ... and nothing ends up on the global buffer list */
+ fail_unless (gst_pad_push_event (myencsrcpad, gst_event_new_eos ()) == TRUE);
+
+ ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
+ gst_buffer_unref (inbuffer);
+
+ fail_unless (gst_element_set_state (opusenc,
+ GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to ready");
+
+ /* default frame size is 20 ms, at 48000 Hz that's 960 samples */
+ check_buffers ((nsamples + 959) / 960);
+
+ /* cleanup */
+ cleanup_opusenc (opusenc);
+ g_list_free (buffers);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_opus_encode_properties)
+{
+ const unsigned int nsamples = 4096;
+ enum
+ { steps = 20 };
+ GstElement *opusenc;
+ GstBuffer *inbuffer;
+ GstCaps *caps;
+ unsigned int step;
+ static const struct
+ {
+ const char *param;
+ int value;
+ } param_changes[steps] = {
+ {
+ "frame-size", 40}, {
+ "inband-fec", 1}, {
+ "complexity", 5}, {
+ "bandwidth", 1104}, {
+ "frame-size", 2}, {
+ "max-payload-size", 80}, {
+ "frame-size", 60}, {
+ "max-payload-size", 900}, {
+ "complexity", 1}, {
+ "bitrate", 30000}, {
+ "frame-size", 10}, {
+ "bitrate", 300000}, {
+ "inband-fec", 0}, {
+ "frame-size", 5}, {
+ "bandwidth", 1101}, {
+ "frame-size", 10}, {
+ "bitrate", 500000}, {
+ "frame-size", 5}, {
+ "bitrate", 80000}, {
+ "complexity", 8},};
+
+ opusenc = setup_opusenc ();
+
+ fail_unless (gst_element_set_state (opusenc,
+ GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to playing");
+
+ caps = gst_caps_from_string (AUDIO_CAPS_STRING);
+ fail_unless (caps != NULL);
+
+ gst_check_setup_events (myencsrcpad, opusenc, caps, GST_FORMAT_TIME);
+
+ for (step = 0; step < steps; ++step) {
+ GstSegment segment;
+
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ gst_pad_push_event (myencsrcpad, gst_event_new_segment (&segment));
+
+ inbuffer = gst_buffer_new_and_alloc (nsamples * 2);
+ gst_buffer_memset (inbuffer, 0, 0, nsamples * 2);
+
+ GST_BUFFER_TIMESTAMP (inbuffer) = GST_BUFFER_OFFSET (inbuffer) = 0;
+ GST_BUFFER_DURATION (inbuffer) = GST_CLOCK_TIME_NONE;
+ ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
+
+ gst_buffer_ref (inbuffer);
+
+ /* pushing gives away my reference ... */
+ fail_unless (gst_pad_push (myencsrcpad, inbuffer) == GST_FLOW_OK);
+ /* ... and nothing ends up on the global buffer list */
+ fail_unless (gst_pad_push_event (myencsrcpad,
+ gst_event_new_eos ()) == TRUE);
+
+ ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
+ gst_buffer_unref (inbuffer);
+
+ /* change random parameters */
+ g_object_set (opusenc, param_changes[step].param, param_changes[step].value,
+ NULL);
+
+ check_buffers (1);
+
+ fail_unless (gst_pad_push_event (myencsrcpad,
+ gst_event_new_flush_start ()) == TRUE);
+ fail_unless (gst_pad_push_event (myencsrcpad,
+ gst_event_new_flush_stop (TRUE)) == TRUE);
+ }
+
+ gst_caps_unref (caps);
+
+ fail_unless (gst_element_set_state (opusenc,
+ GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS,
+ "could not set to ready");
+
+ /* cleanup */
+ cleanup_opusenc (opusenc);
+ g_list_free (buffers);
+}
+
+GST_END_TEST;
+
+static Suite *
+opus_suite (void)
+{
+ Suite *s = suite_create ("opus");
+ TCase *tc_chain = tcase_create ("general");
+
+ suite_add_tcase (s, tc_chain);
+
+#define X if (0)
+ tcase_add_test (tc_chain, test_opus_encode_nothing);
+ tcase_add_test (tc_chain, test_opus_decode_nothing);
+ tcase_add_test (tc_chain, test_opus_encode_samples);
+ tcase_add_test (tc_chain, test_opus_encode_properties);
+#undef X
+
+ return s;
+}
+
+GST_CHECK_MAIN (opus);