mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 17:50:36 +00:00
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:
commit
296786c011
8 changed files with 794 additions and 270 deletions
|
@ -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
|
||||
|
|
|
@ -41,9 +41,10 @@
|
|||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstopusdec.h"
|
||||
#include <string.h>
|
||||
#include <gst/tag/tag.h>
|
||||
#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);
|
||||
|
|
|
@ -53,6 +53,7 @@ struct _GstOpusDec {
|
|||
|
||||
int sample_rate;
|
||||
int n_channels;
|
||||
guint32 pre_skip;
|
||||
};
|
||||
|
||||
struct _GstOpusDecClass {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* 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
|
||||
|
@ -46,14 +47,19 @@
|
|||
#include <opus/opus.h>
|
||||
|
||||
#include <gst/gsttagsetter.h>
|
||||
#include <gst/tag/tag.h>
|
||||
#include <gst/base/gstbytewriter.h>
|
||||
#include <gst/audio/audio.h>
|
||||
#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
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
170
ext/opus/gstopusheader.c
Normal file
170
ext/opus/gstopusheader.c
Normal 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
34
ext/opus/gstopusheader.h
Normal 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
383
tests/check/elements/opus.c
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue