diff options
author | Tim-Philipp Müller <tim@centricular.com> | 2016-02-25 23:51:42 +0000 |
---|---|---|
committer | Tim-Philipp Müller <tim@centricular.com> | 2016-02-25 23:51:42 +0000 |
commit | a2eb43001049d1f45d62f48c9a1b68da6703117f (patch) | |
tree | 35beb6c5f326d77bef4c1bbda4ad122ed743154a | |
parent | a153804566c5fa08a98d63bb70ded73e1f0a71d6 (diff) | |
parent | 1ef601e7d3c5689ef6832c21e05b1c75865d9194 (diff) | |
download | gstreamer-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.am | 20 | ||||
-rw-r--r-- | ext/opus/gstopus.c | 66 | ||||
-rw-r--r-- | ext/opus/gstopuscommon.c | 111 | ||||
-rw-r--r-- | ext/opus/gstopuscommon.h | 37 | ||||
-rw-r--r-- | ext/opus/gstopusdec.c | 819 | ||||
-rw-r--r-- | ext/opus/gstopusdec.h | 86 | ||||
-rw-r--r-- | ext/opus/gstopusenc.c | 1282 | ||||
-rw-r--r-- | ext/opus/gstopusenc.h | 102 | ||||
-rw-r--r-- | ext/opus/gstopusheader.c | 93 | ||||
-rw-r--r-- | ext/opus/gstopusheader.h | 37 | ||||
-rw-r--r-- | tests/check/elements/opus.c | 338 |
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); |