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/gstopusdec.c b/ext/opus/gstopusdec.c index 8b2c9ee179..4e1a3a4e07 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 @@ -114,6 +115,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 @@ -151,9 +154,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_header_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_INFO_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) { @@ -235,11 +246,18 @@ 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); - GST_DEBUG ("bandwidth %d", opus_packet_get_bandwidth (data)); - GST_DEBUG ("samples %d", samples); + 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; outbuf = gst_buffer_new_and_alloc (packet_size); @@ -249,8 +267,6 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf) out_data = (gint16 *) gst_buffer_map (outbuf, &out_size, NULL, GST_MAP_WRITE); - GST_LOG_OBJECT (dec, "decoding %d samples, in size %u", samples, size); - n = opus_decode (dec->state, data, size, out_data, samples, 0); gst_buffer_unmap (buf, data, size); gst_buffer_unmap (outbuf, out_data, out_size); @@ -259,6 +275,25 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf) return GST_FLOW_ERROR; } 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); @@ -337,21 +372,6 @@ memcmp_buffers (GstBuffer * buf1, GstBuffer * buf2) return res; } -static gboolean -gst_opus_dec_is_header (GstBuffer * buf, const char *magic, guint magic_size) -{ - guint8 *data; - gsize size; - gboolean ret; - - data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ); - if (!data) - return FALSE; - ret = (size >= magic_size && !memcmp (magic, data, magic_size)); - gst_buffer_unmap (buf, data, size); - return ret; -} - static GstFlowReturn gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf) { @@ -384,10 +404,10 @@ 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)) { + 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); @@ -396,7 +416,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/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 { diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index cb9615a650..9dec51ee52 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,14 +47,19 @@ #include #include -#include -#include #include +#include "gstopusheader.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) @@ -80,8 +86,33 @@ gst_opus_enc_bandwidth_get_type (void) return id; } -#define FORMAT_STR GST_AUDIO_NE(S16) +#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 FORMAT_STR GST_AUDIO_NE(S16) static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -107,6 +138,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 { @@ -120,7 +152,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); @@ -140,8 +173,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); @@ -181,7 +212,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, @@ -191,40 +221,56 @@ 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, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + 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)); + 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_int ("frame-size", "Frame Size", - "The duration of an audio frame, in ms", 2, 60, DEFAULT_FRAMESIZE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + 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", 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 | + GST_PARAM_MUTABLE_PLAYING)); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_opus_enc_finalize); @@ -238,6 +284,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); } @@ -248,6 +296,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; @@ -261,6 +311,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); @@ -275,6 +326,7 @@ gst_opus_enc_start (GstAudioEncoder * benc) GST_DEBUG_OBJECT (enc, "start"); enc->tags = gst_tag_list_new (); enc->header_sent = FALSE; + return TRUE; } @@ -293,6 +345,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; } @@ -306,6 +359,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) { @@ -344,6 +409,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, @@ -360,72 +427,13 @@ 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; } -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_string_utf8 (&bw, "OpusHead"); - 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_empty (); - 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) { @@ -490,78 +498,15 @@ 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) { 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)) { - bdata = GST_BUFFER_DATA (buf); - bsize = GST_BUFFER_SIZE (buf); - if (G_UNLIKELY (bsize % bytes)) { - GST_DEBUG_OBJECT (enc, "draining; adding silence samples"); - - size = ((bsize / bytes) + 1) * bytes; - mdata = g_malloc0 (size); - memcpy (mdata, bdata, bsize); - bdata = NULL; - data = mdata; - } else { - data = bdata; - size = bsize; - } - } else { - GST_DEBUG_OBJECT (enc, "nothing to drain"); - goto done; - } - - return ret; -} - -static GstFlowReturn -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; + g_mutex_lock (enc->property_lock); if (G_LIKELY (buf)) { bdata = gst_buffer_map (buf, &bsize, NULL, GST_MAP_READ); @@ -590,31 +535,33 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) gsize out_size; GstBuffer *outbuf; - outbuf = gst_buffer_new_and_alloc (bytes_per_packet); + outbuf = gst_buffer_new_and_alloc (enc->max_payload_size); if (!outbuf) 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); out_data = gst_buffer_map (outbuf, &out_size, NULL, GST_MAP_WRITE); encoded_size = opus_encode (enc->state, (const gint16 *) data, enc->frame_samples, - out_data, bytes_per_packet); + out_data, enc->max_payload_size); gst_buffer_unmap (outbuf, out_data, out_size); if (encoded_size < 0) { GST_ERROR_OBJECT (enc, "Encoding failed: %d", encoded_size); ret = GST_FLOW_ERROR; goto done; - } else if (encoded_size != bytes_per_packet) { + } else if (encoded_size > enc->max_payload_size) { GST_WARNING_OBJECT (enc, - "Encoded size %d is different from %d bytes per packet", encoded_size, - 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; } + GST_BUFFER_SIZE (outbuf) = outsize; + ret = gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (enc), outbuf, enc->frame_samples); @@ -630,69 +577,14 @@ done: if (bdata) gst_buffer_unmap (buf, bdata, bsize); + g_mutex_unlock (enc->property_lock); + if (mdata) g_free (mdata); 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) { @@ -703,32 +595,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; } @@ -748,6 +628,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); @@ -759,7 +641,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); @@ -779,10 +661,15 @@ 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; } + + g_mutex_unlock (enc->property_lock); } static void @@ -793,39 +680,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: - enc->frame_size = g_value_get_int (value); + 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 fe7b94e68d..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; @@ -63,6 +66,7 @@ struct _GstOpusEnc { gboolean inband_fec; gboolean dtx; gint packet_loss_percentage; + guint max_payload_size; gint frame_samples; gint n_channels; diff --git a/ext/opus/gstopusheader.c b/ext/opus/gstopusheader.c new file mode 100644 index 0000000000..3551055840 --- /dev/null +++ b/ext/opus/gstopusheader.c @@ -0,0 +1,170 @@ +/* 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); +} + +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 new file mode 100644 index 0000000000..4594264ccd --- /dev/null +++ b/ext/opus/gstopusheader.h @@ -0,0 +1,34 @@ +/* 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); +extern gboolean gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size); + + +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 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; +}