Merge branch 'master' into 0.11

Conflicts:
	ext/opus/gstopusdec.c
	ext/opus/gstopusenc.c
	ext/opus/gstopusparse.c
	gst/audiovisualizers/gstwavescope.c
	gst/filter/Makefile.am
	gst/filter/gstfilter.c
	gst/filter/gstiir.c
	gst/playondemand/gstplayondemand.c
This commit is contained in:
Wim Taymans 2011-11-23 11:08:39 +01:00
commit 296786c011
8 changed files with 794 additions and 270 deletions

View file

@ -1,6 +1,6 @@
plugin_LTLIBRARIES = libgstopus.la 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 = \ libgstopus_la_CFLAGS = \
-DGST_USE_UNSTABLE_API \ -DGST_USE_UNSTABLE_API \
$(GST_PLUGINS_BASE_CFLAGS) \ $(GST_PLUGINS_BASE_CFLAGS) \
@ -15,4 +15,4 @@ libgstopus_la_LIBADD = \
libgstopus_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM) libgstopus_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM)
libgstopus_la_LIBTOOLFLAGS = --tag=disable-static libgstopus_la_LIBTOOLFLAGS = --tag=disable-static
noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h gstopusheader.h

View file

@ -41,9 +41,10 @@
# include "config.h" # include "config.h"
#endif #endif
#include "gstopusdec.h"
#include <string.h> #include <string.h>
#include <gst/tag/tag.h> #include <gst/tag/tag.h>
#include "gstopusheader.h"
#include "gstopusdec.h"
GST_DEBUG_CATEGORY_STATIC (opusdec_debug); GST_DEBUG_CATEGORY_STATIC (opusdec_debug);
#define GST_CAT_DEFAULT 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->streamheader, NULL);
gst_buffer_replace (&dec->vorbiscomment, NULL); gst_buffer_replace (&dec->vorbiscomment, NULL);
dec->pre_skip = 0;
} }
static void static void
@ -151,9 +154,17 @@ gst_opus_dec_stop (GstAudioDecoder * dec)
static GstFlowReturn static GstFlowReturn
gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf) 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; return GST_FLOW_OK;
} }
static GstFlowReturn static GstFlowReturn
gst_opus_dec_parse_comments (GstOpusDec * dec, GstBuffer * buf) gst_opus_dec_parse_comments (GstOpusDec * dec, GstBuffer * buf)
{ {
@ -235,11 +246,18 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buf)
size = 0; size = 0;
} }
samples = if (data) {
opus_packet_get_samples_per_frame (data, samples =
dec->sample_rate) * opus_packet_get_nb_frames (data, size); opus_packet_get_samples_per_frame (data,
GST_DEBUG ("bandwidth %d", opus_packet_get_bandwidth (data)); dec->sample_rate) * opus_packet_get_nb_frames (data, size);
GST_DEBUG ("samples %d", samples); 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; packet_size = samples * dec->n_channels * 2;
outbuf = gst_buffer_new_and_alloc (packet_size); 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); 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); n = opus_decode (dec->state, data, size, out_data, samples, 0);
gst_buffer_unmap (buf, data, size); gst_buffer_unmap (buf, data, size);
gst_buffer_unmap (outbuf, out_data, out_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; return GST_FLOW_ERROR;
} }
GST_DEBUG_OBJECT (dec, "decoded %d samples", n); 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); res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1);
@ -337,21 +372,6 @@ memcmp_buffers (GstBuffer * buf1, GstBuffer * buf2)
return res; 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 static GstFlowReturn
gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf) gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf)
{ {
@ -384,10 +404,10 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf)
} }
} else { } else {
/* Otherwise fall back to packet counting and assume that the /* 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) { switch (dec->packetno) {
case 0: 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"); GST_DEBUG_OBJECT (dec, "found streamheader");
res = gst_opus_dec_parse_header (dec, buf); res = gst_opus_dec_parse_header (dec, buf);
gst_audio_decoder_finish_frame (adec, NULL, 1); gst_audio_decoder_finish_frame (adec, NULL, 1);
@ -396,7 +416,7 @@ gst_opus_dec_handle_frame (GstAudioDecoder * adec, GstBuffer * buf)
} }
break; break;
case 1: 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"); GST_DEBUG_OBJECT (dec, "counted vorbiscomments");
res = gst_opus_dec_parse_comments (dec, buf); res = gst_opus_dec_parse_comments (dec, buf);
gst_audio_decoder_finish_frame (adec, NULL, 1); gst_audio_decoder_finish_frame (adec, NULL, 1);

View file

@ -53,6 +53,7 @@ struct _GstOpusDec {
int sample_rate; int sample_rate;
int n_channels; int n_channels;
guint32 pre_skip;
}; };
struct _GstOpusDecClass { struct _GstOpusDecClass {

View file

@ -1,6 +1,7 @@
/* GStreamer Opus Encoder /* GStreamer Opus Encoder
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk> * Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
* Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public * modify it under the terms of the GNU Library General Public
@ -46,14 +47,19 @@
#include <opus/opus.h> #include <opus/opus.h>
#include <gst/gsttagsetter.h> #include <gst/gsttagsetter.h>
#include <gst/tag/tag.h>
#include <gst/base/gstbytewriter.h>
#include <gst/audio/audio.h> #include <gst/audio/audio.h>
#include "gstopusheader.h"
#include "gstopusenc.h" #include "gstopusenc.h"
GST_DEBUG_CATEGORY_STATIC (opusenc_debug); GST_DEBUG_CATEGORY_STATIC (opusenc_debug);
#define GST_CAT_DEFAULT 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()) #define GST_OPUS_ENC_TYPE_BANDWIDTH (gst_opus_enc_bandwidth_get_type())
static GType static GType
gst_opus_enc_bandwidth_get_type (void) gst_opus_enc_bandwidth_get_type (void)
@ -80,8 +86,33 @@ gst_opus_enc_bandwidth_get_type (void)
return id; 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", static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK, GST_PAD_SINK,
GST_PAD_ALWAYS, GST_PAD_ALWAYS,
@ -107,6 +138,7 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
#define DEFAULT_INBAND_FEC FALSE #define DEFAULT_INBAND_FEC FALSE
#define DEFAULT_DTX FALSE #define DEFAULT_DTX FALSE
#define DEFAULT_PACKET_LOSS_PERCENT 0 #define DEFAULT_PACKET_LOSS_PERCENT 0
#define DEFAULT_MAX_PAYLOAD_SIZE 1024
enum enum
{ {
@ -120,7 +152,8 @@ enum
PROP_COMPLEXITY, PROP_COMPLEXITY,
PROP_INBAND_FEC, PROP_INBAND_FEC,
PROP_DTX, PROP_DTX,
PROP_PACKET_LOSS_PERCENT PROP_PACKET_LOSS_PERCENT,
PROP_MAX_PAYLOAD_SIZE
}; };
static void gst_opus_enc_finalize (GObject * object); static void gst_opus_enc_finalize (GObject * object);
@ -140,8 +173,6 @@ static gboolean gst_opus_enc_set_format (GstAudioEncoder * benc,
GstAudioInfo * info); GstAudioInfo * info);
static GstFlowReturn gst_opus_enc_handle_frame (GstAudioEncoder * benc, static GstFlowReturn gst_opus_enc_handle_frame (GstAudioEncoder * benc,
GstBuffer * buf); GstBuffer * buf);
static GstFlowReturn gst_opus_enc_pre_push (GstAudioEncoder * benc,
GstBuffer ** buffer);
static gint64 gst_opus_enc_get_latency (GstOpusEnc * enc); static gint64 gst_opus_enc_get_latency (GstOpusEnc * enc);
static GstFlowReturn gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buffer); 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->stop = GST_DEBUG_FUNCPTR (gst_opus_enc_stop);
base_class->set_format = GST_DEBUG_FUNCPTR (gst_opus_enc_set_format); 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->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); base_class->event = GST_DEBUG_FUNCPTR (gst_opus_enc_sink_event);
g_object_class_install_property (gobject_class, PROP_AUDIO, 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_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BITRATE,
g_param_spec_int ("bitrate", "Encoding Bit-rate", g_param_spec_int ("bitrate", "Encoding Bit-rate",
"Specify an encoding bit-rate (in bps).", "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_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
GST_PARAM_MUTABLE_PLAYING));
g_object_class_install_property (gobject_class, PROP_BANDWIDTH, g_object_class_install_property (gobject_class, PROP_BANDWIDTH,
g_param_spec_enum ("bandwidth", "Band Width", g_param_spec_enum ("bandwidth", "Band Width", "Audio Band Width",
"Audio Band Width", GST_OPUS_ENC_TYPE_BANDWIDTH, DEFAULT_BANDWIDTH, GST_OPUS_ENC_TYPE_BANDWIDTH, DEFAULT_BANDWIDTH,
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_FRAME_SIZE, g_object_class_install_property (gobject_class, PROP_FRAME_SIZE,
g_param_spec_int ("frame-size", "Frame Size", g_param_spec_enum ("frame-size", "Frame Size",
"The duration of an audio frame, in ms", 2, 60, DEFAULT_FRAMESIZE, "The duration of an audio frame, in ms", GST_OPUS_ENC_TYPE_FRAME_SIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); DEFAULT_FRAMESIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
GST_PARAM_MUTABLE_PLAYING));
g_object_class_install_property (gobject_class, PROP_CBR, g_object_class_install_property (gobject_class, PROP_CBR,
g_param_spec_boolean ("cbr", "Constant bit rate", g_param_spec_boolean ("cbr", "Constant bit rate", "Constant bit rate",
"Constant bit rate", DEFAULT_CBR, DEFAULT_CBR,
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_CONSTRAINED_VBR, g_object_class_install_property (gobject_class, PROP_CONSTRAINED_VBR,
g_param_spec_boolean ("constrained-vbr", "Constrained VBR", g_param_spec_boolean ("constrained-vbr", "Constrained VBR",
"Constrained VBR", DEFAULT_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_object_class_install_property (gobject_class, PROP_COMPLEXITY,
g_param_spec_int ("complexity", "Complexity", g_param_spec_int ("complexity", "Complexity", "Complexity", 0, 10,
"Complexity", 0, 10, DEFAULT_COMPLEXITY, DEFAULT_COMPLEXITY,
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_INBAND_FEC, g_object_class_install_property (gobject_class, PROP_INBAND_FEC,
g_param_spec_boolean ("inband-fec", "In-band FEC", g_param_spec_boolean ("inband-fec", "In-band FEC",
"Enable forward error correction", DEFAULT_INBAND_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_object_class_install_property (gobject_class, PROP_DTX,
g_param_spec_boolean ("dtx", "DTX", g_param_spec_boolean ("dtx", "DTX", "DTX", DEFAULT_DTX,
"DTX", DEFAULT_DTX, 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), g_object_class_install_property (G_OBJECT_CLASS (klass),
PROP_PACKET_LOSS_PERCENT, g_param_spec_int ("packet-loss-percentage", PROP_PACKET_LOSS_PERCENT, g_param_spec_int ("packet-loss-percentage",
"Loss percentage", "Packet loss percentage", 0, 100, "Loss percentage", "Packet loss percentage", 0, 100,
DEFAULT_PACKET_LOSS_PERCENT, 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); 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); enc = GST_OPUS_ENC (object);
g_mutex_free (enc->property_lock);
G_OBJECT_CLASS (parent_class)->finalize (object); G_OBJECT_CLASS (parent_class)->finalize (object);
} }
@ -248,6 +296,8 @@ gst_opus_enc_init (GstOpusEnc * enc, GstOpusEncClass * klass)
GST_DEBUG_OBJECT (enc, "init"); GST_DEBUG_OBJECT (enc, "init");
enc->property_lock = g_mutex_new ();
enc->n_channels = -1; enc->n_channels = -1;
enc->sample_rate = -1; enc->sample_rate = -1;
enc->frame_samples = 0; enc->frame_samples = 0;
@ -261,6 +311,7 @@ gst_opus_enc_init (GstOpusEnc * enc, GstOpusEncClass * klass)
enc->inband_fec = DEFAULT_INBAND_FEC; enc->inband_fec = DEFAULT_INBAND_FEC;
enc->dtx = DEFAULT_DTX; enc->dtx = DEFAULT_DTX;
enc->packet_loss_percentage = DEFAULT_PACKET_LOSS_PERCENT; enc->packet_loss_percentage = DEFAULT_PACKET_LOSS_PERCENT;
enc->max_payload_size = DEFAULT_MAX_PAYLOAD_SIZE;
/* arrange granulepos marking (and required perfect ts) */ /* arrange granulepos marking (and required perfect ts) */
gst_audio_encoder_set_mark_granule (benc, TRUE); gst_audio_encoder_set_mark_granule (benc, TRUE);
@ -275,6 +326,7 @@ gst_opus_enc_start (GstAudioEncoder * benc)
GST_DEBUG_OBJECT (enc, "start"); GST_DEBUG_OBJECT (enc, "start");
enc->tags = gst_tag_list_new (); enc->tags = gst_tag_list_new ();
enc->header_sent = FALSE; enc->header_sent = FALSE;
return TRUE; return TRUE;
} }
@ -293,6 +345,7 @@ gst_opus_enc_stop (GstAudioEncoder * benc)
enc->tags = NULL; enc->tags = NULL;
g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL); g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL);
enc->headers = NULL; enc->headers = NULL;
gst_tag_setter_reset_tags (GST_TAG_SETTER (enc));
return TRUE; return TRUE;
} }
@ -306,6 +359,18 @@ gst_opus_enc_get_latency (GstOpusEnc * enc)
return latency; return latency;
} }
static void
gst_opus_enc_setup_base_class (GstOpusEnc * enc, GstAudioEncoder * benc)
{
gst_audio_encoder_set_latency (benc,
gst_opus_enc_get_latency (enc), gst_opus_enc_get_latency (enc));
gst_audio_encoder_set_frame_samples_min (benc,
enc->frame_samples * 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 static gint
gst_opus_enc_get_frame_samples (GstOpusEnc * enc) 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); enc = GST_OPUS_ENC (benc);
g_mutex_lock (enc->property_lock);
enc->n_channels = GST_AUDIO_INFO_CHANNELS (info); enc->n_channels = GST_AUDIO_INFO_CHANNELS (info);
enc->sample_rate = GST_AUDIO_INFO_RATE (info); enc->sample_rate = GST_AUDIO_INFO_RATE (info);
GST_DEBUG_OBJECT (benc, "Setup with %d channels, %d Hz", enc->n_channels, 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); enc->frame_samples = gst_opus_enc_get_frame_samples (enc);
/* feedback to base class */ /* feedback to base class */
gst_audio_encoder_set_latency (benc, gst_opus_enc_setup_base_class (enc, benc);
gst_opus_enc_get_latency (enc), gst_opus_enc_get_latency (enc));
gst_audio_encoder_set_frame_samples_min (benc, g_mutex_unlock (enc->property_lock);
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);
return TRUE; 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 static gboolean
gst_opus_enc_setup (GstOpusEnc * enc) gst_opus_enc_setup (GstOpusEnc * enc)
{ {
@ -490,78 +498,15 @@ gst_opus_enc_sink_event (GstAudioEncoder * benc, GstEvent * event)
return FALSE; 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 static GstFlowReturn
gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf)
{ {
guint8 *bdata, *data, *mdata = NULL; guint8 *bdata, *data, *mdata = NULL;
gsize bsize, size; gsize bsize, size;
gsize bytes = enc->frame_samples * enc->n_channels * 2; 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; gint ret = GST_FLOW_OK;
if (G_LIKELY (buf)) { g_mutex_lock (enc->property_lock);
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;
if (G_LIKELY (buf)) { if (G_LIKELY (buf)) {
bdata = gst_buffer_map (buf, &bsize, NULL, GST_MAP_READ); 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; gsize out_size;
GstBuffer *outbuf; GstBuffer *outbuf;
outbuf = gst_buffer_new_and_alloc (bytes_per_packet); outbuf = gst_buffer_new_and_alloc (enc->max_payload_size);
if (!outbuf) if (!outbuf)
goto done; goto done;
GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes) to %d bytes", GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)",
enc->frame_samples, bytes, bytes_per_packet); enc->frame_samples);
out_data = gst_buffer_map (outbuf, &out_size, NULL, GST_MAP_WRITE); out_data = gst_buffer_map (outbuf, &out_size, NULL, GST_MAP_WRITE);
encoded_size = encoded_size =
opus_encode (enc->state, (const gint16 *) data, enc->frame_samples, 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); gst_buffer_unmap (outbuf, out_data, out_size);
if (encoded_size < 0) { if (encoded_size < 0) {
GST_ERROR_OBJECT (enc, "Encoding failed: %d", encoded_size); GST_ERROR_OBJECT (enc, "Encoding failed: %d", encoded_size);
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
goto done; goto done;
} else if (encoded_size != bytes_per_packet) { } else if (encoded_size > enc->max_payload_size) {
GST_WARNING_OBJECT (enc, GST_WARNING_OBJECT (enc,
"Encoded size %d is different from %d bytes per packet", encoded_size, "Encoded size %d is higher than max payload size (%d bytes)",
bytes_per_packet); outsize, enc->max_payload_size);
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
goto done; goto done;
} }
GST_BUFFER_SIZE (outbuf) = outsize;
ret = ret =
gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (enc), outbuf, gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER (enc), outbuf,
enc->frame_samples); enc->frame_samples);
@ -630,69 +577,14 @@ done:
if (bdata) if (bdata)
gst_buffer_unmap (buf, bdata, bsize); gst_buffer_unmap (buf, bdata, bsize);
g_mutex_unlock (enc->property_lock);
if (mdata) if (mdata)
g_free (mdata); g_free (mdata);
return ret; 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 static GstFlowReturn
gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf) 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"); GST_DEBUG_OBJECT (enc, "handle_frame");
if (!enc->header_sent) { 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; GstCaps *caps;
/* create header buffers */ g_slist_foreach (enc->headers, (GFunc) gst_buffer_unref, NULL);
buf1 = gst_opus_enc_create_id_buffer (enc); enc->headers = NULL;
buf2 = gst_opus_enc_create_metadata_buffer (enc);
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 */ /* negotiate with these caps */
GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps); GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps);
gst_pad_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), 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; 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); enc = GST_OPUS_ENC (object);
g_mutex_lock (enc->property_lock);
switch (prop_id) { switch (prop_id) {
case PROP_AUDIO: case PROP_AUDIO:
g_value_set_boolean (value, enc->audio_or_voip); 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); g_value_set_enum (value, enc->bandwidth);
break; break;
case PROP_FRAME_SIZE: case PROP_FRAME_SIZE:
g_value_set_int (value, enc->frame_size); g_value_set_enum (value, enc->frame_size);
break; break;
case PROP_CBR: case PROP_CBR:
g_value_set_boolean (value, enc->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: case PROP_PACKET_LOSS_PERCENT:
g_value_set_int (value, enc->packet_loss_percentage); g_value_set_int (value, enc->packet_loss_percentage);
break; break;
case PROP_MAX_PAYLOAD_SIZE:
g_value_set_uint (value, enc->max_payload_size);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
} }
g_mutex_unlock (enc->property_lock);
} }
static void static void
@ -793,39 +680,64 @@ gst_opus_enc_set_property (GObject * object, guint prop_id,
enc = GST_OPUS_ENC (object); 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) { switch (prop_id) {
case PROP_AUDIO: case PROP_AUDIO:
enc->audio_or_voip = g_value_get_boolean (value); enc->audio_or_voip = g_value_get_boolean (value);
break; break;
case PROP_BITRATE: case PROP_BITRATE:
enc->bitrate = g_value_get_int (value); GST_OPUS_UPDATE_PROPERTY (bitrate, int, BITRATE);
break; break;
case PROP_BANDWIDTH: case PROP_BANDWIDTH:
enc->bandwidth = g_value_get_enum (value); GST_OPUS_UPDATE_PROPERTY (bandwidth, enum, BANDWIDTH);
break; break;
case PROP_FRAME_SIZE: 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; break;
case PROP_CBR: 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); enc->cbr = g_value_get_boolean (value);
opus_encoder_ctl (enc->state, OPUS_SET_VBR (!enc->cbr));
g_mutex_unlock (enc->property_lock);
break; break;
case PROP_CONSTRAINED_VBR: case PROP_CONSTRAINED_VBR:
enc->constrained_vbr = g_value_get_boolean (value); GST_OPUS_UPDATE_PROPERTY (constrained_vbr, boolean, VBR_CONSTRAINT);
break; break;
case PROP_COMPLEXITY: case PROP_COMPLEXITY:
enc->complexity = g_value_get_int (value); GST_OPUS_UPDATE_PROPERTY (complexity, int, COMPLEXITY);
break; break;
case PROP_INBAND_FEC: case PROP_INBAND_FEC:
enc->inband_fec = g_value_get_boolean (value); GST_OPUS_UPDATE_PROPERTY (inband_fec, boolean, INBAND_FEC);
break; break;
case PROP_DTX: case PROP_DTX:
enc->dtx = g_value_get_boolean (value); GST_OPUS_UPDATE_PROPERTY (dtx, boolean, DTX);
break; break;
case PROP_PACKET_LOSS_PERCENT: 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; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
} }
#undef GST_OPUS_UPDATE_PROPERTY
} }

View file

@ -52,6 +52,9 @@ struct _GstOpusEnc {
OpusEncoder *state; OpusEncoder *state;
/* Locks those properties which may be changed at play time */
GMutex *property_lock;
/* properties */ /* properties */
gboolean audio_or_voip; gboolean audio_or_voip;
gint bitrate; gint bitrate;
@ -63,6 +66,7 @@ struct _GstOpusEnc {
gboolean inband_fec; gboolean inband_fec;
gboolean dtx; gboolean dtx;
gint packet_loss_percentage; gint packet_loss_percentage;
guint max_payload_size;
gint frame_samples; gint frame_samples;
gint n_channels; gint n_channels;

170
ext/opus/gstopusheader.c Normal file
View file

@ -0,0 +1,170 @@
/* GStreamer Opus Encoder
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
* Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/tag/tag.h>
#include <gst/base/gstbytewriter.h>
#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));
}

34
ext/opus/gstopusheader.h Normal file
View file

@ -0,0 +1,34 @@
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2008> Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_OPUS_HEADER_H__
#define __GST_OPUS_HEADER_H__
#include <gst/gst.h>
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__ */

383
tests/check/elements/opus.c Normal file
View file

@ -0,0 +1,383 @@
/* GStreamer
*
* unit test for opus
*
* Copyright (C) <2011> Vincent Penquerc'h <vincent.penquerch@collbaora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <unistd.h>
#include <gst/check/gstcheck.h>
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;
}