From d2aba01428a5a0c4aa49dee1816a22dfd312523a Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Wed, 16 Nov 2011 19:22:44 +0000 Subject: [PATCH 01/16] opusenc: the encoder might not make use of all the bytes --- ext/opus/gstopusenc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index f632a72056..327c5fae80 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -588,7 +588,7 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) GST_ERROR_OBJECT (enc, "Encoding failed: %d", outsize); ret = GST_FLOW_ERROR; goto done; - } else if (outsize != bytes_per_packet) { + } else if (outsize > bytes_per_packet) { GST_WARNING_OBJECT (enc, "Encoded size %d is different from %d bytes per packet", outsize, bytes_per_packet); @@ -596,6 +596,8 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) goto done; } + GST_BUFFER_SIZE (outbuf) = outsize; + ret = gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (enc), outbuf, enc->frame_samples); From 7c0c221fa4f015671743fc5706ab7cb0283d0081 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Wed, 16 Nov 2011 19:40:20 +0000 Subject: [PATCH 02/16] opusenc: make frame-size an enum It only supports a set number of specific values (including a non integer one). --- ext/opus/gstopusenc.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index 327c5fae80..ed3e82ebd6 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -80,6 +80,32 @@ gst_opus_enc_bandwidth_get_type (void) 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; +} + static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -216,8 +242,9 @@ gst_opus_enc_class_init (GstOpusEncClass * klass) "Audio Band Width", GST_OPUS_ENC_TYPE_BANDWIDTH, DEFAULT_BANDWIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_FRAME_SIZE, - g_param_spec_int ("frame-size", "Frame Size", - "The duration of an audio frame, in ms", 2, 60, DEFAULT_FRAMESIZE, + 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)); g_object_class_install_property (gobject_class, PROP_CBR, g_param_spec_boolean ("cbr", "Constant bit rate", @@ -740,7 +767,7 @@ gst_opus_enc_get_property (GObject * object, guint prop_id, GValue * value, g_value_set_enum (value, enc->bandwidth); break; case PROP_FRAME_SIZE: - g_value_set_int (value, enc->frame_size); + g_value_set_enum (value, enc->frame_size); break; case PROP_CBR: g_value_set_boolean (value, enc->cbr); @@ -785,7 +812,7 @@ gst_opus_enc_set_property (GObject * object, guint prop_id, enc->bandwidth = g_value_get_enum (value); break; case PROP_FRAME_SIZE: - enc->frame_size = g_value_get_int (value); + enc->frame_size = g_value_get_enum (value); break; case PROP_CBR: enc->cbr = g_value_get_boolean (value); From efcd0383460a3bcee9d6f83a12888468148c2e70 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Sat, 19 Nov 2011 15:58:09 +0000 Subject: [PATCH 03/16] opusenc: fix terminating NUL being written in signature --- ext/opus/gstopusenc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index ed3e82ebd6..2f8dc5bfbe 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -423,7 +423,7 @@ gst_opus_enc_create_id_buffer (GstOpusEnc * enc) gst_byte_writer_init (&bw); /* See http://wiki.xiph.org/OggOpus */ - gst_byte_writer_put_string_utf8 (&bw, "OpusHead"); + gst_byte_writer_put_data (&bw, (const guint8 *) "OpusHead", 8); gst_byte_writer_put_uint8 (&bw, 0); /* version number */ gst_byte_writer_put_uint8 (&bw, enc->n_channels); gst_byte_writer_put_uint16_le (&bw, 0); /* pre-skip *//* TODO: endianness ? */ From 7030b04f5e1af896f25aa7de974089de25351172 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Sun, 20 Nov 2011 09:52:46 +0000 Subject: [PATCH 04/16] opus: make opusparse set headers on caps Header-on-caps code moved to a new shared location to avoid duplicating the code. --- ext/opus/Makefile.am | 4 +- ext/opus/gstopusenc.c | 138 ++------------------------------- ext/opus/gstopusheader.c | 163 +++++++++++++++++++++++++++++++++++++++ ext/opus/gstopusheader.h | 32 ++++++++ 4 files changed, 204 insertions(+), 133 deletions(-) create mode 100644 ext/opus/gstopusheader.c create mode 100644 ext/opus/gstopusheader.h diff --git a/ext/opus/Makefile.am b/ext/opus/Makefile.am index 6fe723ecce..88845a3cab 100644 --- a/ext/opus/Makefile.am +++ b/ext/opus/Makefile.am @@ -1,6 +1,6 @@ plugin_LTLIBRARIES = libgstopus.la -libgstopus_la_SOURCES = gstopus.c gstopusdec.c gstopusenc.c gstopusparse.c +libgstopus_la_SOURCES = gstopus.c gstopusdec.c gstopusenc.c gstopusparse.c gstopusheader.c libgstopus_la_CFLAGS = \ -DGST_USE_UNSTABLE_API \ $(GST_PLUGINS_BASE_CFLAGS) \ @@ -15,4 +15,4 @@ libgstopus_la_LIBADD = \ libgstopus_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM) libgstopus_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h +noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h gstopusheader.h diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index 2f8dc5bfbe..f13490ee74 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -1,6 +1,7 @@ /* GStreamer Opus Encoder * Copyright (C) <1999> Erik Walthinsen * Copyright (C) <2008> Sebastian Dröge + * Copyright (C) <2011> Vincent Penquerc'h * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -46,9 +47,8 @@ #include #include -#include -#include #include +#include "gstopusheader.h" #include "gstopusenc.h" GST_DEBUG_CATEGORY_STATIC (opusenc_debug); @@ -414,61 +414,6 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info) return TRUE; } -static GstBuffer * -gst_opus_enc_create_id_buffer (GstOpusEnc * enc) -{ - GstBuffer *buffer; - GstByteWriter bw; - - gst_byte_writer_init (&bw); - - /* See http://wiki.xiph.org/OggOpus */ - gst_byte_writer_put_data (&bw, (const guint8 *) "OpusHead", 8); - gst_byte_writer_put_uint8 (&bw, 0); /* version number */ - gst_byte_writer_put_uint8 (&bw, enc->n_channels); - gst_byte_writer_put_uint16_le (&bw, 0); /* pre-skip *//* TODO: endianness ? */ - gst_byte_writer_put_uint32_le (&bw, enc->sample_rate); - gst_byte_writer_put_uint16_le (&bw, 0); /* output gain *//* TODO: endianness ? */ - gst_byte_writer_put_uint8 (&bw, 0); /* channel mapping *//* TODO: what is this ? */ - - buffer = gst_byte_writer_reset_and_get_buffer (&bw); - - GST_BUFFER_OFFSET (buffer) = 0; - GST_BUFFER_OFFSET_END (buffer) = 0; - - return buffer; -} - -static GstBuffer * -gst_opus_enc_create_metadata_buffer (GstOpusEnc * enc) -{ - const GstTagList *tags; - GstTagList *empty_tags = NULL; - GstBuffer *comments = NULL; - - tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc)); - - GST_DEBUG_OBJECT (enc, "tags = %" GST_PTR_FORMAT, tags); - - if (tags == NULL) { - /* FIXME: better fix chain of callers to not write metadata at all, - * if there is none */ - empty_tags = gst_tag_list_new (); - tags = empty_tags; - } - comments = - gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags", - 8, "Encoded with GStreamer Opusenc"); - - GST_BUFFER_OFFSET (comments) = 0; - GST_BUFFER_OFFSET_END (comments) = 0; - - if (empty_tags) - gst_tag_list_free (empty_tags); - - return comments; -} - static gboolean gst_opus_enc_setup (GstOpusEnc * enc) { @@ -644,63 +589,6 @@ done: return ret; } -/* - * (really really) FIXME: move into core (dixit tpm) - */ -/** - * _gst_caps_set_buffer_array: - * @caps: a #GstCaps - * @field: field in caps to set - * @buf: header buffers - * - * Adds given buffers to an array of buffers set as the given @field - * on the given @caps. List of buffer arguments must be NULL-terminated. - * - * Returns: input caps with a streamheader field added, or NULL if some error - */ -static GstCaps * -_gst_caps_set_buffer_array (GstCaps * caps, const gchar * field, - GstBuffer * buf, ...) -{ - GstStructure *structure = NULL; - va_list va; - GValue array = { 0 }; - GValue value = { 0 }; - - g_return_val_if_fail (caps != NULL, NULL); - g_return_val_if_fail (gst_caps_is_fixed (caps), NULL); - g_return_val_if_fail (field != NULL, NULL); - - caps = gst_caps_make_writable (caps); - structure = gst_caps_get_structure (caps, 0); - - g_value_init (&array, GST_TYPE_ARRAY); - - va_start (va, buf); - /* put buffers in a fixed list */ - while (buf) { - g_assert (gst_buffer_is_writable (buf)); - - /* mark buffer */ - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); - - g_value_init (&value, GST_TYPE_BUFFER); - buf = gst_buffer_copy (buf); - GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); - gst_value_set_buffer (&value, buf); - gst_buffer_unref (buf); - gst_value_array_append_value (&array, &value); - g_value_unset (&value); - - buf = va_arg (va, GstBuffer *); - } - - gst_structure_set_value (structure, field, &array); - g_value_unset (&array); - - return caps; -} - static GstFlowReturn gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf) { @@ -711,32 +599,20 @@ gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf) GST_DEBUG_OBJECT (enc, "handle_frame"); if (!enc->header_sent) { - /* Opus streams in Ogg begin with two headers; the initial header (with - most of the codec setup parameters) which is mandated by the Ogg - bitstream spec. The second header holds any comment fields. */ - GstBuffer *buf1, *buf2; GstCaps *caps; - /* create header buffers */ - buf1 = gst_opus_enc_create_id_buffer (enc); - buf2 = gst_opus_enc_create_metadata_buffer (enc); + g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL); + enc->headers = NULL; + + gst_opus_header_create_caps (&caps, &enc->headers, enc->n_channels, + enc->sample_rate, gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc))); - /* mark and put on caps */ - caps = gst_caps_from_string ("audio/x-opus"); - caps = _gst_caps_set_buffer_array (caps, "streamheader", buf1, buf2, NULL); /* negotiate with these caps */ GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps); gst_pad_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), caps); - /* push out buffers */ - /* store buffers for later pre_push sending */ - g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL); - enc->headers = NULL; - GST_DEBUG_OBJECT (enc, "storing header buffers"); - enc->headers = g_slist_prepend (enc->headers, buf2); - enc->headers = g_slist_prepend (enc->headers, buf1); enc->header_sent = TRUE; } diff --git a/ext/opus/gstopusheader.c b/ext/opus/gstopusheader.c new file mode 100644 index 0000000000..b430b7df47 --- /dev/null +++ b/ext/opus/gstopusheader.c @@ -0,0 +1,163 @@ +/* GStreamer Opus Encoder + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2008> Sebastian Dröge + * Copyright (C) <2011> Vincent Penquerc'h + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include "gstopusheader.h" + +static GstBuffer * +gst_opus_enc_create_id_buffer (gint nchannels, gint sample_rate) +{ + GstBuffer *buffer; + GstByteWriter bw; + + gst_byte_writer_init (&bw); + + /* See http://wiki.xiph.org/OggOpus */ + gst_byte_writer_put_data (&bw, (const guint8 *) "OpusHead", 8); + gst_byte_writer_put_uint8 (&bw, 0); /* version number */ + gst_byte_writer_put_uint8 (&bw, nchannels); + gst_byte_writer_put_uint16_le (&bw, 0); /* pre-skip *//* TODO: endianness ? */ + gst_byte_writer_put_uint32_le (&bw, sample_rate); + gst_byte_writer_put_uint16_le (&bw, 0); /* output gain *//* TODO: endianness ? */ + gst_byte_writer_put_uint8 (&bw, 0); /* channel mapping *//* TODO: what is this ? */ + + buffer = gst_byte_writer_reset_and_get_buffer (&bw); + + GST_BUFFER_OFFSET (buffer) = 0; + GST_BUFFER_OFFSET_END (buffer) = 0; + + return buffer; +} + +static GstBuffer * +gst_opus_enc_create_metadata_buffer (const GstTagList * tags) +{ + GstTagList *empty_tags = NULL; + GstBuffer *comments = NULL; + + GST_DEBUG ("tags = %" GST_PTR_FORMAT, tags); + + if (tags == NULL) { + /* FIXME: better fix chain of callers to not write metadata at all, + * if there is none */ + empty_tags = gst_tag_list_new (); + tags = empty_tags; + } + comments = + gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags", + 8, "Encoded with GStreamer Opusenc"); + + GST_BUFFER_OFFSET (comments) = 0; + GST_BUFFER_OFFSET_END (comments) = 0; + + if (empty_tags) + gst_tag_list_free (empty_tags); + + return comments; +} + +/* + * (really really) FIXME: move into core (dixit tpm) + */ +/** + * _gst_caps_set_buffer_array: + * @caps: a #GstCaps + * @field: field in caps to set + * @buf: header buffers + * + * Adds given buffers to an array of buffers set as the given @field + * on the given @caps. List of buffer arguments must be NULL-terminated. + * + * Returns: input caps with a streamheader field added, or NULL if some error + */ +static GstCaps * +_gst_caps_set_buffer_array (GstCaps * caps, const gchar * field, + GstBuffer * buf, ...) +{ + GstStructure *structure = NULL; + va_list va; + GValue array = { 0 }; + GValue value = { 0 }; + + g_return_val_if_fail (caps != NULL, NULL); + g_return_val_if_fail (gst_caps_is_fixed (caps), NULL); + g_return_val_if_fail (field != NULL, NULL); + + caps = gst_caps_make_writable (caps); + structure = gst_caps_get_structure (caps, 0); + + g_value_init (&array, GST_TYPE_ARRAY); + + va_start (va, buf); + /* put buffers in a fixed list */ + while (buf) { + g_assert (gst_buffer_is_writable (buf)); + + /* mark buffer */ + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); + + g_value_init (&value, GST_TYPE_BUFFER); + buf = gst_buffer_copy (buf); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); + gst_value_set_buffer (&value, buf); + gst_buffer_unref (buf); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + + buf = va_arg (va, GstBuffer *); + } + + gst_structure_set_value (structure, field, &array); + g_value_unset (&array); + + return caps; +} + +void +gst_opus_header_create_caps (GstCaps ** caps, GSList ** headers, gint nchannels, + gint sample_rate, const GstTagList * tags) +{ + GstBuffer *buf1, *buf2; + + g_return_if_fail (caps); + g_return_if_fail (headers && !*headers); + g_return_if_fail (nchannels > 0); + g_return_if_fail (sample_rate >= 0); /* 0 -> unset */ + + /* Opus streams in Ogg begin with two headers; the initial header (with + most of the codec setup parameters) which is mandated by the Ogg + bitstream spec. The second header holds any comment fields. */ + + /* create header buffers */ + buf1 = gst_opus_enc_create_id_buffer (nchannels, sample_rate); + buf2 = gst_opus_enc_create_metadata_buffer (tags); + + /* mark and put on caps */ + *caps = gst_caps_from_string ("audio/x-opus"); + *caps = _gst_caps_set_buffer_array (*caps, "streamheader", buf1, buf2, NULL); + + *headers = g_slist_prepend (*headers, buf2); + *headers = g_slist_prepend (*headers, buf1); +} diff --git a/ext/opus/gstopusheader.h b/ext/opus/gstopusheader.h new file mode 100644 index 0000000000..4679083661 --- /dev/null +++ b/ext/opus/gstopusheader.h @@ -0,0 +1,32 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2008> Sebastian Dröge + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_OPUS_HEADER_H__ +#define __GST_OPUS_HEADER_H__ + +#include + +G_BEGIN_DECLS + +extern void gst_opus_header_create_caps (GstCaps **caps, GSList **headers, gint nchannels, gint sample_rate, const GstTagList *tags); + +G_END_DECLS + +#endif /* __GST_OPUS_HEADER_H__ */ From 95ae14f8b453e6f9fab2954629565094e65c17fb Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Sun, 20 Nov 2011 09:58:06 +0000 Subject: [PATCH 05/16] opusenc: do not push header buffers Opus headers appear only when muxed in Ogg, so only place them on the caps, where oggmux will find them, but other elements will be blithely unaware of them. --- ext/opus/gstopusenc.c | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index f13490ee74..c343968c5b 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -165,8 +165,6 @@ static gboolean gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info); static GstFlowReturn gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf); -static GstFlowReturn gst_opus_enc_pre_push (GstAudioEncoder * benc, - GstBuffer ** buffer); static gint64 gst_opus_enc_get_latency (GstOpusEnc * enc); static GstFlowReturn gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buffer); @@ -225,7 +223,6 @@ gst_opus_enc_class_init (GstOpusEncClass * klass) 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->pre_push = GST_DEBUG_FUNCPTR (gst_opus_enc_pre_push); base_class->event = GST_DEBUG_FUNCPTR (gst_opus_enc_sink_event); g_object_class_install_property (gobject_class, PROP_AUDIO, @@ -477,36 +474,6 @@ gst_opus_enc_sink_event (GstAudioEncoder * benc, GstEvent * event) return FALSE; } -static GstFlowReturn -gst_opus_enc_pre_push (GstAudioEncoder * benc, GstBuffer ** buffer) -{ - GstFlowReturn ret = GST_FLOW_OK; - GstOpusEnc *enc; - - enc = GST_OPUS_ENC (benc); - - /* FIXME 0.11 ? get rid of this special ogg stuff and have it - * put and use 'codec data' in caps like anything else, - * with all the usual out-of-band advantage etc */ - if (G_UNLIKELY (enc->headers)) { - GSList *header = enc->headers; - - /* try to push all of these, if we lose one, might as well lose all */ - while (header) { - if (ret == GST_FLOW_OK) - ret = gst_pad_push (GST_AUDIO_ENCODER_SRC_PAD (enc), header->data); - else - gst_pad_push (GST_AUDIO_ENCODER_SRC_PAD (enc), header->data); - header = g_slist_next (header); - } - - g_slist_free (enc->headers); - enc->headers = NULL; - } - - return ret; -} - static GstFlowReturn gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) { From de87d061fcde9d59a3acc0f00c73e41a124a63e2 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 21 Nov 2011 11:28:10 +0000 Subject: [PATCH 06/16] opusdec: light cleanup --- ext/opus/gstopusdec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/opus/gstopusdec.c b/ext/opus/gstopusdec.c index 624ac81e42..c924e80171 100644 --- a/ext/opus/gstopusdec.c +++ b/ext/opus/gstopusdec.c @@ -248,8 +248,8 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf) opus_packet_get_samples_per_frame (data, dec->sample_rate) * opus_packet_get_nb_frames (data, size); packet_size = samples * dec->n_channels * 2; - GST_DEBUG ("bandwidth %d", opus_packet_get_bandwidth (data)); - GST_DEBUG ("samples %d", samples); + GST_DEBUG_OBJECT (dec, "bandwidth %d", opus_packet_get_bandwidth (data)); + GST_DEBUG_OBJECT (dec, "samples %d", samples); res = gst_pad_alloc_buffer_and_set_caps (GST_AUDIO_DECODER_SRC_PAD (dec), GST_BUFFER_OFFSET_NONE, packet_size, @@ -377,7 +377,7 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf) } } else { /* Otherwise fall back to packet counting and assume that the - * first two packets are the headers. */ + * first two packets might be the headers, checking magic. */ switch (dec->packetno) { case 0: if (gst_opus_dec_is_header (buf, "OpusHead", 8)) { From a6ca3673b4ff427890534083e75225f5b00a3c7e Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 21 Nov 2011 11:44:01 +0000 Subject: [PATCH 07/16] opusdec: handle NULL packets (used for PLC) --- ext/opus/gstopusdec.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ext/opus/gstopusdec.c b/ext/opus/gstopusdec.c index c924e80171..54d25d56cd 100644 --- a/ext/opus/gstopusdec.c +++ b/ext/opus/gstopusdec.c @@ -244,12 +244,19 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf) size = 0; } - samples = - opus_packet_get_samples_per_frame (data, - dec->sample_rate) * opus_packet_get_nb_frames (data, size); + if (data) { + samples = + opus_packet_get_samples_per_frame (data, + dec->sample_rate) * opus_packet_get_nb_frames (data, size); + packet_size = samples * dec->n_channels * 2; + GST_DEBUG_OBJECT (dec, "bandwidth %d", opus_packet_get_bandwidth (data)); + GST_DEBUG_OBJECT (dec, "samples %d", samples); + } else { + /* use maximum size (120 ms) as we do now know in advance how many samples + will be returned */ + samples = 120 * dec->sample_rate / 1000; + } packet_size = samples * dec->n_channels * 2; - GST_DEBUG_OBJECT (dec, "bandwidth %d", opus_packet_get_bandwidth (data)); - GST_DEBUG_OBJECT (dec, "samples %d", samples); res = gst_pad_alloc_buffer_and_set_caps (GST_AUDIO_DECODER_SRC_PAD (dec), GST_BUFFER_OFFSET_NONE, packet_size, @@ -262,14 +269,13 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf) out_data = (gint16 *) GST_BUFFER_DATA (outbuf); - GST_LOG_OBJECT (dec, "decoding %d samples, in size %u", samples, size); - n = opus_decode (dec->state, data, size, out_data, samples, 0); if (n < 0) { GST_ELEMENT_ERROR (dec, STREAM, DECODE, ("Decoding error: %d", n), (NULL)); return GST_FLOW_ERROR; } GST_DEBUG_OBJECT (dec, "decoded %d samples", n); + GST_BUFFER_SIZE (outbuf) = n * 2 * dec->n_channels; res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1); From fbfc870343e9993c9f1b256edcbbf77268149fde Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 21 Nov 2011 12:02:28 +0000 Subject: [PATCH 08/16] opusenc: reset tagsetter interface on stop --- ext/opus/gstopusenc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index c343968c5b..276e1ddaa1 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -333,6 +333,7 @@ gst_opus_enc_stop (GstAudioEncoder * benc) enc->tags = NULL; g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL); enc->headers = NULL; + gst_tag_setter_reset_tags (GST_TAG_SETTER (enc)); return TRUE; } From 1e81bc4b64a9d383e5396869d14da6c2dff31f4d Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 21 Nov 2011 12:50:22 +0000 Subject: [PATCH 09/16] opusdec: read pre-skip from first header if available --- ext/opus/gstopusdec.c | 12 ++++++++++++ ext/opus/gstopusdec.h | 1 + 2 files changed, 13 insertions(+) diff --git a/ext/opus/gstopusdec.c b/ext/opus/gstopusdec.c index 54d25d56cd..f0a9a5144e 100644 --- a/ext/opus/gstopusdec.c +++ b/ext/opus/gstopusdec.c @@ -77,6 +77,8 @@ static GstFlowReturn gst_opus_dec_handle_frame (GstAudioDecoder * dec, GstBuffer * buffer); static gboolean gst_opus_dec_set_format (GstAudioDecoder * bdec, GstCaps * caps); +static gboolean gst_opus_dec_is_header (GstBuffer * buf, const char *magic, + guint magic_size); static void gst_opus_dec_base_init (gpointer g_class) @@ -122,6 +124,8 @@ gst_opus_dec_reset (GstOpusDec * dec) gst_buffer_replace (&dec->streamheader, NULL); gst_buffer_replace (&dec->vorbiscomment, NULL); + + dec->pre_skip = 0; } static void @@ -159,9 +163,17 @@ gst_opus_dec_stop (GstAudioDecoder * dec) static GstFlowReturn gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf) { + g_return_val_if_fail (gst_opus_dec_is_header (buf, "OpusHead", 8), + GST_FLOW_ERROR); + g_return_val_if_fail (GST_BUFFER_SIZE (buf) >= 19, GST_FLOW_ERROR); + + dec->pre_skip = GST_READ_UINT16_LE (GST_BUFFER_DATA (buf) + 10); + GST_DEBUG_OBJECT (dec, "Found pre-skip of %u samples", dec->pre_skip); + return GST_FLOW_OK; } + static GstFlowReturn gst_opus_dec_parse_comments (GstOpusDec * dec, GstBuffer * buf) { diff --git a/ext/opus/gstopusdec.h b/ext/opus/gstopusdec.h index 9e78330e1f..eee27dc554 100644 --- a/ext/opus/gstopusdec.h +++ b/ext/opus/gstopusdec.h @@ -53,6 +53,7 @@ struct _GstOpusDec { int sample_rate; int n_channels; + guint32 pre_skip; }; struct _GstOpusDecClass { From 354c7824aab009896f5894f787ee89bd903afcdf Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 21 Nov 2011 17:01:49 +0000 Subject: [PATCH 10/16] opusdec: skip pre-skip samples --- ext/opus/gstopusdec.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/ext/opus/gstopusdec.c b/ext/opus/gstopusdec.c index f0a9a5144e..8bb3a047c9 100644 --- a/ext/opus/gstopusdec.c +++ b/ext/opus/gstopusdec.c @@ -168,7 +168,7 @@ gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf) g_return_val_if_fail (GST_BUFFER_SIZE (buf) >= 19, GST_FLOW_ERROR); dec->pre_skip = GST_READ_UINT16_LE (GST_BUFFER_DATA (buf) + 10); - GST_DEBUG_OBJECT (dec, "Found pre-skip of %u samples", dec->pre_skip); + GST_INFO_OBJECT (dec, "Found pre-skip of %u samples", dec->pre_skip); return GST_FLOW_OK; } @@ -289,6 +289,24 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf) GST_DEBUG_OBJECT (dec, "decoded %d samples", n); GST_BUFFER_SIZE (outbuf) = n * 2 * dec->n_channels; + /* Skip any samples that need skipping */ + if (dec->pre_skip > 0) { + guint scaled_pre_skip = dec->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_SIZE (outbuf) -= skip * 2 * dec->n_channels; + GST_BUFFER_DATA (outbuf) += skip * 2 * dec->n_channels; + dec->pre_skip -= scaled_skip; + GST_INFO_OBJECT (dec, + "Skipping %u samples (%u at 48000 Hz, %u left to skip)", skip, + scaled_skip, dec->pre_skip); + + if (GST_BUFFER_SIZE (outbuf) == 0) { + gst_buffer_unref (outbuf); + outbuf = NULL; + } + } + res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1); if (res != GST_FLOW_OK) From 5be77031ca2f105df78bef18f4bd29abb588bdfe Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 21 Nov 2011 17:48:54 +0000 Subject: [PATCH 11/16] opus: move header magic testing to gstopusheader --- ext/opus/gstopusdec.c | 18 +++++------------- ext/opus/gstopusheader.c | 7 +++++++ ext/opus/gstopusheader.h | 2 ++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ext/opus/gstopusdec.c b/ext/opus/gstopusdec.c index 8bb3a047c9..89eec6941e 100644 --- a/ext/opus/gstopusdec.c +++ b/ext/opus/gstopusdec.c @@ -41,9 +41,10 @@ # include "config.h" #endif -#include "gstopusdec.h" #include #include +#include "gstopusheader.h" +#include "gstopusdec.h" GST_DEBUG_CATEGORY_STATIC (opusdec_debug); #define GST_CAT_DEFAULT opusdec_debug @@ -77,8 +78,6 @@ static GstFlowReturn gst_opus_dec_handle_frame (GstAudioDecoder * dec, GstBuffer * buffer); static gboolean gst_opus_dec_set_format (GstAudioDecoder * bdec, GstCaps * caps); -static gboolean gst_opus_dec_is_header (GstBuffer * buf, const char *magic, - guint magic_size); static void gst_opus_dec_base_init (gpointer g_class) @@ -163,7 +162,7 @@ gst_opus_dec_stop (GstAudioDecoder * dec) static GstFlowReturn gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf) { - g_return_val_if_fail (gst_opus_dec_is_header (buf, "OpusHead", 8), + g_return_val_if_fail (gst_opus_header_is_header (buf, "OpusHead", 8), GST_FLOW_ERROR); g_return_val_if_fail (GST_BUFFER_SIZE (buf) >= 19, GST_FLOW_ERROR); @@ -374,13 +373,6 @@ memcmp_buffers (GstBuffer * buf1, GstBuffer * buf2) return !memcmp (GST_BUFFER_DATA (buf1), GST_BUFFER_DATA (buf2), size1); } -static gboolean -gst_opus_dec_is_header (GstBuffer * buf, const char *magic, guint magic_size) -{ - return (GST_BUFFER_SIZE (buf) >= magic_size - && !memcmp (magic, GST_BUFFER_DATA (buf), magic_size)); -} - static GstFlowReturn gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf) { @@ -416,7 +408,7 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf) * first two packets might be the headers, checking magic. */ switch (dec->packetno) { case 0: - if (gst_opus_dec_is_header (buf, "OpusHead", 8)) { + 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); @@ -425,7 +417,7 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf) } break; case 1: - if (gst_opus_dec_is_header (buf, "OpusTags", 8)) { + 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); diff --git a/ext/opus/gstopusheader.c b/ext/opus/gstopusheader.c index b430b7df47..3551055840 100644 --- a/ext/opus/gstopusheader.c +++ b/ext/opus/gstopusheader.c @@ -161,3 +161,10 @@ gst_opus_header_create_caps (GstCaps ** caps, GSList ** headers, gint nchannels, *headers = g_slist_prepend (*headers, buf2); *headers = g_slist_prepend (*headers, buf1); } + +gboolean +gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size) +{ + return (GST_BUFFER_SIZE (buf) >= magic_size + && !memcmp (magic, GST_BUFFER_DATA (buf), magic_size)); +} diff --git a/ext/opus/gstopusheader.h b/ext/opus/gstopusheader.h index 4679083661..4594264ccd 100644 --- a/ext/opus/gstopusheader.h +++ b/ext/opus/gstopusheader.h @@ -26,6 +26,8 @@ G_BEGIN_DECLS extern void gst_opus_header_create_caps (GstCaps **caps, GSList **headers, gint nchannels, gint sample_rate, const GstTagList *tags); +extern gboolean gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size); + G_END_DECLS From bb39c86fdfff4674a023de3ae193db68141a80e4 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Tue, 22 Nov 2011 15:33:20 +0000 Subject: [PATCH 12/16] opusenc: fix crash on pathological parameters Asking for 1 bit/s would select a 0 byte buffer, leading to a crash. Buffer size is now controlled by a max-payload-size property, which can't be less than 2. --- ext/opus/gstopusenc.c | 32 ++++++++++++++++++++++---------- ext/opus/gstopusenc.h | 1 + 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index 276e1ddaa1..6a7d549bd6 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -132,6 +132,7 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", #define DEFAULT_INBAND_FEC FALSE #define DEFAULT_DTX FALSE #define DEFAULT_PACKET_LOSS_PERCENT 0 +#define DEFAULT_MAX_PAYLOAD_SIZE 1024 enum { @@ -145,7 +146,8 @@ enum PROP_COMPLEXITY, PROP_INBAND_FEC, PROP_DTX, - PROP_PACKET_LOSS_PERCENT + PROP_PACKET_LOSS_PERCENT, + PROP_MAX_PAYLOAD_SIZE }; static void gst_opus_enc_finalize (GObject * object); @@ -267,6 +269,11 @@ gst_opus_enc_class_init (GstOpusEncClass * klass) "Loss percentage", "Packet loss percentage", 0, 100, DEFAULT_PACKET_LOSS_PERCENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + 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, 1275, + DEFAULT_MAX_PAYLOAD_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_opus_enc_finalize); } @@ -301,6 +308,7 @@ gst_opus_enc_init (GstOpusEnc * enc, GstOpusEncClass * klass) 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; /* arrange granulepos marking (and required perfect ts) */ gst_audio_encoder_set_mark_granule (benc, TRUE); @@ -481,8 +489,6 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) guint8 *bdata, *data, *mdata = NULL; gsize bsize, size; gsize bytes = enc->frame_samples * enc->n_channels * 2; - gsize bytes_per_packet = - (enc->bitrate * enc->frame_samples / enc->sample_rate + 4) / 8; gint ret = GST_FLOW_OK; if (G_LIKELY (buf)) { @@ -511,27 +517,27 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) GstBuffer *outbuf; ret = gst_pad_alloc_buffer_and_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), - GST_BUFFER_OFFSET_NONE, bytes_per_packet, + GST_BUFFER_OFFSET_NONE, enc->max_payload_size, GST_PAD_CAPS (GST_AUDIO_ENCODER_SRC_PAD (enc)), &outbuf); if (GST_FLOW_OK != ret) goto done; - GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes) to %d bytes", - enc->frame_samples, bytes, bytes_per_packet); + GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", + enc->frame_samples); outsize = opus_encode (enc->state, (const gint16 *) data, enc->frame_samples, - GST_BUFFER_DATA (outbuf), bytes_per_packet); + GST_BUFFER_DATA (outbuf), enc->max_payload_size); if (outsize < 0) { GST_ERROR_OBJECT (enc, "Encoding failed: %d", outsize); ret = GST_FLOW_ERROR; goto done; - } else if (outsize > bytes_per_packet) { + } else if (outsize > enc->max_payload_size) { GST_WARNING_OBJECT (enc, - "Encoded size %d is different from %d bytes per packet", outsize, - bytes_per_packet); + "Encoded size %d is higher than max payload size (%d bytes)", + outsize, enc->max_payload_size); ret = GST_FLOW_ERROR; goto done; } @@ -631,6 +637,9 @@ gst_opus_enc_get_property (GObject * object, guint prop_id, GValue * value, 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; @@ -676,6 +685,9 @@ gst_opus_enc_set_property (GObject * object, guint prop_id, case PROP_PACKET_LOSS_PERCENT: enc->packet_loss_percentage = g_value_get_int (value); break; + case PROP_MAX_PAYLOAD_SIZE: + enc->max_payload_size = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/ext/opus/gstopusenc.h b/ext/opus/gstopusenc.h index fe7b94e68d..d645ce8d07 100644 --- a/ext/opus/gstopusenc.h +++ b/ext/opus/gstopusenc.h @@ -63,6 +63,7 @@ struct _GstOpusEnc { gboolean inband_fec; gboolean dtx; gint packet_loss_percentage; + guint max_payload_size; gint frame_samples; gint n_channels; From ee723996dbac3f5f310cbfe12dad9f09951ea67b Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Tue, 22 Nov 2011 16:14:06 +0000 Subject: [PATCH 13/16] opusenc: bound the bitrate to more sensible values Go from the bounds mentioned in the spec, and allow some more variation. In particular, don't allow silly low bitrates, and allow reaching the maximum useful bitrate. --- ext/opus/gstopusenc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index 6a7d549bd6..df11c5ff77 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -54,6 +54,12 @@ 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) @@ -234,7 +240,7 @@ gst_opus_enc_class_init (GstOpusEncClass * klass) 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).", - 1, 320000, DEFAULT_BITRATE, + LOWEST_BITRATE, HIGHEST_BITRATE, DEFAULT_BITRATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_BANDWIDTH, g_param_spec_enum ("bandwidth", "Band Width", From b226e8a0857dd7a947565a375a0610ef2a6838c7 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Tue, 22 Nov 2011 17:04:09 +0000 Subject: [PATCH 14/16] opusenc: allow setting most properties at PLAYING time Opus allows these to be changed during encoding, transparently to the decoder. --- ext/opus/gstopusenc.c | 76 ++++++++++++++++++++++++++++++++++--------- ext/opus/gstopusenc.h | 3 ++ 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index df11c5ff77..cdcb298629 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -291,6 +291,8 @@ gst_opus_enc_finalize (GObject * object) enc = GST_OPUS_ENC (object); + g_mutex_free (enc->property_lock); + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -301,6 +303,8 @@ gst_opus_enc_init (GstOpusEnc * enc, GstOpusEncClass * klass) GST_DEBUG_OBJECT (enc, "init"); + enc->property_lock = g_mutex_new (); + enc->n_channels = -1; enc->sample_rate = -1; enc->frame_samples = 0; @@ -329,6 +333,7 @@ gst_opus_enc_start (GstAudioEncoder * benc) GST_DEBUG_OBJECT (enc, "start"); enc->tags = gst_tag_list_new (); enc->header_sent = FALSE; + return TRUE; } @@ -361,6 +366,18 @@ gst_opus_enc_get_latency (GstOpusEnc * enc) 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 * enc->n_channels * 2); + gst_audio_encoder_set_frame_samples_max (benc, + enc->frame_samples * enc->n_channels * 2); + gst_audio_encoder_set_frame_max (benc, 0); +} + static gint gst_opus_enc_get_frame_samples (GstOpusEnc * enc) { @@ -399,6 +416,8 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info) 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_DEBUG_OBJECT (benc, "Setup with %d channels, %d Hz", enc->n_channels, @@ -415,13 +434,9 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info) enc->frame_samples = gst_opus_enc_get_frame_samples (enc); /* feedback to base class */ - 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 * enc->n_channels * 2); - gst_audio_encoder_set_frame_samples_max (benc, - enc->frame_samples * enc->n_channels * 2); - gst_audio_encoder_set_frame_max (benc, 0); + gst_opus_enc_setup_base_class (enc, benc); + + g_mutex_unlock (enc->property_lock); return TRUE; } @@ -494,9 +509,12 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) { guint8 *bdata, *data, *mdata = NULL; gsize bsize, size; - gsize bytes = enc->frame_samples * enc->n_channels * 2; + gsize bytes; gint ret = GST_FLOW_OK; + g_mutex_lock (enc->property_lock); + + bytes = enc->frame_samples * enc->n_channels * 2; if (G_LIKELY (buf)) { bdata = GST_BUFFER_DATA (buf); bsize = GST_BUFFER_SIZE (buf); @@ -563,6 +581,8 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) done: + g_mutex_unlock (enc->property_lock); + if (mdata) g_free (mdata); @@ -612,6 +632,8 @@ gst_opus_enc_get_property (GObject * object, guint prop_id, GValue * value, enc = GST_OPUS_ENC (object); + g_mutex_lock (enc->property_lock); + switch (prop_id) { case PROP_AUDIO: g_value_set_boolean (value, enc->audio_or_voip); @@ -650,6 +672,8 @@ gst_opus_enc_get_property (GObject * object, guint prop_id, GValue * value, G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + + g_mutex_unlock (enc->property_lock); } static void @@ -660,42 +684,64 @@ gst_opus_enc_set_property (GObject * object, guint prop_id, 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_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_or_voip = g_value_get_boolean (value); break; case PROP_BITRATE: - enc->bitrate = g_value_get_int (value); + GST_OPUS_UPDATE_PROPERTY (bitrate, int, BITRATE); break; case PROP_BANDWIDTH: - enc->bandwidth = g_value_get_enum (value); + 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: + /* this one has an opposite meaning to the opus ctl... */ + g_mutex_lock (enc->property_lock); enc->cbr = g_value_get_boolean (value); + opus_encoder_ctl (enc->state, OPUS_SET_VBR (!enc->cbr)); + g_mutex_unlock (enc->property_lock); break; case PROP_CONSTRAINED_VBR: - enc->constrained_vbr = g_value_get_boolean (value); + GST_OPUS_UPDATE_PROPERTY (constrained_vbr, boolean, VBR_CONSTRAINT); break; case PROP_COMPLEXITY: - enc->complexity = g_value_get_int (value); + GST_OPUS_UPDATE_PROPERTY (complexity, int, COMPLEXITY); break; case PROP_INBAND_FEC: - enc->inband_fec = g_value_get_boolean (value); + GST_OPUS_UPDATE_PROPERTY (inband_fec, boolean, INBAND_FEC); break; case PROP_DTX: - enc->dtx = g_value_get_boolean (value); + GST_OPUS_UPDATE_PROPERTY (dtx, boolean, DTX); break; case PROP_PACKET_LOSS_PERCENT: - enc->packet_loss_percentage = g_value_get_int (value); + 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 index d645ce8d07..772f6f4cc7 100644 --- a/ext/opus/gstopusenc.h +++ b/ext/opus/gstopusenc.h @@ -52,6 +52,9 @@ struct _GstOpusEnc { OpusEncoder *state; + /* Locks those properties which may be changed at play time */ + GMutex *property_lock; + /* properties */ gboolean audio_or_voip; gint bitrate; From e7228fc0b344edf5c412ea2cf7f8f806abe0a215 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Tue, 22 Nov 2011 18:33:17 +0000 Subject: [PATCH 15/16] opus: add test --- tests/check/elements/opus.c | 383 ++++++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 tests/check/elements/opus.c diff --git a/tests/check/elements/opus.c b/tests/check/elements/opus.c new file mode 100644 index 0000000000..66c12a3237 --- /dev/null +++ b/tests/check/elements/opus.c @@ -0,0 +1,383 @@ +/* GStreamer + * + * unit test for opus + * + * Copyright (C) <2011> Vincent Penquerc'h + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include + +static const guint8 opus_ogg_id_header[19] = { + 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const guint8 opus_ogg_comments_header[] = { + 0x4f, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, 0x1e, 0x00, 0x00, 0x00, 0x45, + 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x20, 0x4f, 0x70, 0x75, 0x73, + 0x65, 0x6e, 0x63, 0x00, 0x00, 0x00, 0x00 +}; + +/* 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, NULL); + mydecsinkpad = gst_check_setup_sink_pad (opusdec, &sinktemplate, NULL); + 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, NULL); + myencsinkpad = gst_check_setup_sink_pad (opusenc, &sinktemplate, NULL); + 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, gboolean headers_in_caps) +{ + 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_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_id_header) +{ + GstElement *opusdec; + GstBuffer *inbuffer; + + opusdec = setup_opusdec (); + fail_unless (gst_element_set_state (opusdec, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (sizeof (opus_ogg_id_header)); + memcpy (GST_BUFFER_DATA (inbuffer), opus_ogg_id_header, + sizeof (opus_ogg_id_header)); + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_ref (inbuffer); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mydecsrcpad, inbuffer) == GST_FLOW_OK); + /* ... and nothing ends up on the global buffer list */ + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + gst_buffer_unref (inbuffer); + fail_unless (g_list_length (buffers) == 0); + + /* cleanup */ + cleanup_opusdec (opusdec); +} + +GST_END_TEST; + +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; + guint16 *samples; + unsigned int n; + + 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); + samples = (guint16 *) GST_BUFFER_DATA (inbuffer); + for (n = 0; n < nsamples; ++n) { + samples[n] = 0; + } + + 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/x-raw-int,rate=48000,channels=1,signed=true,width=16,depth=16,endianness=1234"); + fail_unless (caps != NULL); + gst_buffer_set_caps (inbuffer, caps); + 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, FALSE); + + /* 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; + guint16 *samples; + unsigned int n, 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/x-raw-int,rate=48000,channels=1,signed=true,width=16,depth=16,endianness=1234"); + fail_unless (caps != NULL); + + for (step = 0; step < steps; ++step) { + inbuffer = gst_buffer_new_and_alloc (nsamples * 2); + samples = (guint16 *) GST_BUFFER_DATA (inbuffer); + for (n = 0; n < nsamples; ++n) { + samples[n] = 0; + } + + 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_set_caps (inbuffer, 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); + + /* change random parameters */ + g_object_set (opusenc, param_changes[step].param, param_changes[step].value, + NULL); + } + + 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_id_header); + 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; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = opus_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} From 2befc00dea12f103024bdd4ee8bc3fdc825f1e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 22 Nov 2011 20:27:50 +0000 Subject: [PATCH 16/16] opusenc: mark properties changeable at runtime with GST_PARAM_MUTABLE_PLAYING --- ext/opus/gstopusenc.c | 48 ++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index cdcb298629..29b9faf756 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -241,45 +241,55 @@ gst_opus_enc_class_init (GstOpusEncClass * klass) 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)); + 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)); + 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)); + "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", DEFAULT_CBR, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_param_spec_boolean ("cbr", "Constant bit rate", "Constant bit rate", + DEFAULT_CBR, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_PLAYING)); g_object_class_install_property (gobject_class, PROP_CONSTRAINED_VBR, g_param_spec_boolean ("constrained-vbr", "Constrained VBR", "Constrained VBR", DEFAULT_CONSTRAINED_VBR, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + 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)); + 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)); + 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)); + 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)); + 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, 1275, DEFAULT_MAX_PAYLOAD_SIZE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_PLAYING)); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_opus_enc_finalize); }