* aggregating their buffers for raw audio
* @see_also: #GstAggregator
*
+ * #GstAudioAggregator will perform conversion on the data arriving
+ * on its sink pads, based on the format expected downstream.
+ *
+ * Subclasses can opt out of the conversion behaviour by setting
+ * #GstAudioAggregator.convert_buffer() to %NULL.
+ *
+ * Subclasses that wish to use the default conversion implementation
+ * should use a (subclass of) #GstAudioAggregatorConvertPad as their
+ * #GstAggregatorClass.sinkpads_type, as it will cache the created
+ * #GstAudioConverter and install a property allowing to configure it,
+ * #GstAudioAggregatorPadClass:converter-config.
+ *
+ * Subclasses that wish to perform custom conversion should override
+ * #GstAudioAggregator.convert_buffer().
+ *
+ * When conversion is enabled, #GstAudioAggregator will accept
+ * any type of raw audio caps and perform conversion
+ * on the data arriving on its sink pads, with whatever downstream
+ * expects as the target format.
+ *
+ * In case downstream caps are not fully fixated, it will use
+ * the first configured sink pad to finish fixating its source pad
+ * caps.
+ *
+ * Additionally, handling audio conversion directly in the element
+ * means that this base class supports safely reconfiguring its
+ * source pad.
+ *
+ * A notable exception for now is the sample rate, sink pads must
+ * have the same sample rate as either the downstream requirement,
+ * or the first configured pad, or a combination of both (when
+ * downstream specifies a range or a set of acceptable rates).
*/
{
/* All members are protected by the pad object lock */
- GstBuffer *buffer; /* current input buffer we're mixing, for
+ GstBuffer *buffer; /* current buffer we're mixing, for
comparison with a new input buffer from
aggregator to see if we need to update our
cached values. */
guint position, size; /* position in the input buffer and size of the
input buffer in number of samples */
+ GstBuffer *input_buffer;
+
guint64 output_offset; /* Sample offset in output segment relative to
pad.segment.start that position refers to
in the current buffer. */
G_DEFINE_TYPE (GstAudioAggregatorPad, gst_audio_aggregator_pad,
GST_TYPE_AGGREGATOR_PAD);
+enum
+{
+ PROP_PAD_0,
+ PROP_PAD_CONVERTER_CONFIG,
+};
+
static GstFlowReturn
gst_audio_aggregator_pad_flush_pad (GstAggregatorPad * aggpad,
GstAggregator * aggregator);
GstAudioAggregatorPad *pad = (GstAudioAggregatorPad *) object;
gst_buffer_replace (&pad->priv->buffer, NULL);
+ gst_buffer_replace (&pad->priv->input_buffer, NULL);
G_OBJECT_CLASS (gst_audio_aggregator_pad_parent_class)->finalize (object);
}
gst_audio_info_init (&pad->info);
pad->priv->buffer = NULL;
+ pad->priv->input_buffer = NULL;
pad->priv->position = 0;
pad->priv->size = 0;
pad->priv->output_offset = -1;
pad->priv->output_offset = pad->priv->next_offset = -1;
pad->priv->discont_time = GST_CLOCK_TIME_NONE;
gst_buffer_replace (&pad->priv->buffer, NULL);
+ gst_buffer_replace (&pad->priv->input_buffer, NULL);
GST_OBJECT_UNLOCK (aggpad);
return GST_FLOW_OK;
}
+struct _GstAudioAggregatorConvertPadPrivate
+{
+ /* All members are protected by the pad object lock */
+ GstAudioConverter *converter;
+ GstStructure *converter_config;
+ gboolean converter_config_changed;
+};
+
+G_DEFINE_TYPE (GstAudioAggregatorConvertPad, gst_audio_aggregator_convert_pad,
+ GST_TYPE_AUDIO_AGGREGATOR_PAD);
+
+static void
+gst_audio_aggregator_convert_pad_update_converter (GstAudioAggregatorConvertPad
+ * aaggcpad, GstAudioInfo * in_info, GstAudioInfo * out_info)
+{
+ if (!aaggcpad->priv->converter_config_changed)
+ return;
+
+ if (aaggcpad->priv->converter) {
+ gst_audio_converter_free (aaggcpad->priv->converter);
+ aaggcpad->priv->converter = NULL;
+ }
+
+ if (gst_audio_info_is_equal (in_info, out_info) ||
+ in_info->finfo->format == GST_AUDIO_FORMAT_UNKNOWN) {
+ if (aaggcpad->priv->converter) {
+ gst_audio_converter_free (aaggcpad->priv->converter);
+ aaggcpad->priv->converter = NULL;
+ }
+ } else {
+ /* If we haven't received caps yet, this pad should not have
+ * a buffer to convert anyway */
+ aaggcpad->priv->converter =
+ gst_audio_converter_new (GST_AUDIO_CONVERTER_FLAG_NONE,
+ in_info, out_info,
+ aaggcpad->priv->converter_config ? gst_structure_copy (aaggcpad->
+ priv->converter_config) : NULL);
+ }
+
+ aaggcpad->priv->converter_config_changed = FALSE;
+}
+
+static GstBuffer *
+gst_audio_aggregator_convert_pad_convert_buffer (GstAudioAggregatorConvertPad *
+ aaggcpad, GstAudioInfo * in_info, GstAudioInfo * out_info,
+ GstBuffer * input_buffer)
+{
+ GstBuffer *res;
+
+ gst_audio_aggregator_convert_pad_update_converter (aaggcpad, in_info,
+ out_info);
+
+ if (aaggcpad->priv->converter) {
+ gint insize = gst_buffer_get_size (input_buffer);
+ gsize insamples = insize / in_info->bpf;
+ gsize outsamples =
+ gst_audio_converter_get_out_frames (aaggcpad->priv->converter,
+ insamples);
+ gint outsize = outsamples * out_info->bpf;
+ GstMapInfo inmap, outmap;
+
+ res = gst_buffer_new_allocate (NULL, outsize, NULL);
+
+ /* We create a perfectly similar buffer, except obviously for
+ * its converted contents */
+ gst_buffer_copy_into (res, input_buffer,
+ GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
+ GST_BUFFER_COPY_META, 0, -1);
+
+ gst_buffer_map (input_buffer, &inmap, GST_MAP_READ);
+ gst_buffer_map (res, &outmap, GST_MAP_WRITE);
+
+ gst_audio_converter_samples (aaggcpad->priv->converter,
+ GST_AUDIO_CONVERTER_FLAG_NONE,
+ (gpointer *) & inmap.data, insamples,
+ (gpointer *) & outmap.data, outsamples);
+
+ gst_buffer_unmap (input_buffer, &inmap);
+ gst_buffer_unmap (res, &outmap);
+ } else {
+ res = gst_buffer_ref (input_buffer);
+ }
+
+ return res;
+}
+
+static void
+gst_audio_aggregator_convert_pad_finalize (GObject * object)
+{
+ GstAudioAggregatorConvertPad *pad = (GstAudioAggregatorConvertPad *) object;
+
+ if (pad->priv->converter)
+ gst_audio_converter_free (pad->priv->converter);
+
+ if (pad->priv->converter_config)
+ gst_structure_free (pad->priv->converter_config);
+
+ G_OBJECT_CLASS (gst_audio_aggregator_convert_pad_parent_class)->finalize
+ (object);
+}
+
+static void
+gst_audio_aggregator_convert_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstAudioAggregatorConvertPad *pad = GST_AUDIO_AGGREGATOR_CONVERT_PAD (object);
+
+ switch (prop_id) {
+ case PROP_PAD_CONVERTER_CONFIG:
+ GST_OBJECT_LOCK (pad);
+ if (pad->priv->converter_config)
+ g_value_set_boxed (value, pad->priv->converter_config);
+ GST_OBJECT_UNLOCK (pad);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_audio_aggregator_convert_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstAudioAggregatorConvertPad *pad = GST_AUDIO_AGGREGATOR_CONVERT_PAD (object);
+
+ switch (prop_id) {
+ case PROP_PAD_CONVERTER_CONFIG:
+ GST_OBJECT_LOCK (pad);
+ if (pad->priv->converter_config)
+ gst_structure_free (pad->priv->converter_config);
+ pad->priv->converter_config = g_value_dup_boxed (value);
+ pad->priv->converter_config_changed = TRUE;
+ GST_OBJECT_UNLOCK (pad);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_audio_aggregator_convert_pad_class_init (GstAudioAggregatorConvertPadClass *
+ klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ g_type_class_add_private (klass,
+ sizeof (GstAudioAggregatorConvertPadPrivate));
+
+ gobject_class->set_property = gst_audio_aggregator_convert_pad_set_property;
+ gobject_class->get_property = gst_audio_aggregator_convert_pad_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_PAD_CONVERTER_CONFIG,
+ g_param_spec_boxed ("converter-config", "Converter configuration",
+ "A GstStructure describing the configuration that should be used "
+ "when converting this pad's audio buffers",
+ GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gobject_class->finalize = gst_audio_aggregator_convert_pad_finalize;
+}
+
+static void
+gst_audio_aggregator_convert_pad_init (GstAudioAggregatorConvertPad * pad)
+{
+ pad->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (pad, GST_TYPE_AUDIO_AGGREGATOR_CONVERT_PAD,
+ GstAudioAggregatorConvertPadPrivate);
+}
/**************************************
* GstAudioAggregator implementation *
GstAggregatorPad * aggpad, GstEvent * event);
static gboolean gst_audio_aggregator_src_query (GstAggregator * agg,
GstQuery * query);
+static gboolean
+gst_audio_aggregator_sink_query (GstAggregator * agg, GstAggregatorPad * aggpad,
+ GstQuery * query);
static gboolean gst_audio_aggregator_start (GstAggregator * agg);
static gboolean gst_audio_aggregator_stop (GstAggregator * agg);
static GstFlowReturn gst_audio_aggregator_flush (GstAggregator * agg);
static gboolean sync_pad_values (GstElement * aagg, GstPad * pad, gpointer ud);
static gboolean gst_audio_aggregator_negotiated_src_caps (GstAggregator * agg,
GstCaps * caps);
+static GstFlowReturn
+gst_audio_aggregator_update_src_caps (GstAggregator * agg,
+ GstCaps * caps, GstCaps ** ret);
+static GstCaps *gst_audio_aggregator_fixate_src_caps (GstAggregator * agg,
+ GstCaps * caps);
#define DEFAULT_OUTPUT_BUFFER_DURATION (10 * GST_MSECOND)
#define DEFAULT_ALIGNMENT_THRESHOLD (40 * GST_MSECOND)
return next_time;
}
+static GstBuffer *
+gst_audio_aggregator_convert_once (GstAudioAggregator * aagg, GstPad * pad,
+ GstAudioInfo * in_info, GstAudioInfo * out_info, GstBuffer * buffer)
+{
+ GstAudioConverter *converter =
+ gst_audio_converter_new (GST_AUDIO_CONVERTER_FLAG_NONE,
+ in_info, out_info, NULL);
+ gint insize = gst_buffer_get_size (buffer);
+ gsize insamples = insize / in_info->bpf;
+ gsize outsamples = gst_audio_converter_get_out_frames (converter,
+ insamples);
+ gint outsize = outsamples * out_info->bpf;
+ GstMapInfo inmap, outmap;
+ GstBuffer *converted = gst_buffer_new_allocate (NULL, outsize, NULL);
+
+ gst_buffer_copy_into (converted, buffer,
+ GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
+ GST_BUFFER_COPY_META, 0, -1);
+
+ gst_buffer_map (buffer, &inmap, GST_MAP_READ);
+ gst_buffer_map (converted, &outmap, GST_MAP_WRITE);
+
+ gst_audio_converter_samples (converter,
+ GST_AUDIO_CONVERTER_FLAG_NONE,
+ (gpointer *) & inmap.data, insamples,
+ (gpointer *) & outmap.data, outsamples);
+
+ gst_buffer_unmap (buffer, &inmap);
+ gst_buffer_unmap (converted, &outmap);
+ gst_audio_converter_free (converter);
+
+ return converted;
+}
+
+static GstBuffer *
+gst_audio_aggregator_default_convert_buffer (GstAudioAggregator * aagg,
+ GstPad * pad, GstAudioInfo * in_info, GstAudioInfo * out_info,
+ GstBuffer * buffer)
+{
+ if (GST_IS_AUDIO_AGGREGATOR_CONVERT_PAD (pad))
+ return
+ gst_audio_aggregator_convert_pad_convert_buffer
+ (GST_AUDIO_AGGREGATOR_CONVERT_PAD (pad),
+ &GST_AUDIO_AGGREGATOR_PAD (pad)->info, out_info, buffer);
+ else
+ return gst_audio_aggregator_convert_once (aagg, pad, in_info, out_info,
+ buffer);
+}
+
+static GstBuffer *
+gst_audio_aggregator_convert_buffer (GstAudioAggregator * aagg, GstPad * pad,
+ GstAudioInfo * in_info, GstAudioInfo * out_info, GstBuffer * buffer)
+{
+ GstAudioAggregatorClass *klass = GST_AUDIO_AGGREGATOR_GET_CLASS (aagg);
+
+ g_assert (klass->convert_buffer);
+
+ return klass->convert_buffer (aagg, pad, in_info, out_info, buffer);
+}
+
static void
gst_audio_aggregator_class_init (GstAudioAggregatorClass * klass)
{
GST_DEBUG_FUNCPTR (gst_audio_aggregator_sink_event);
gstaggregator_class->src_query =
GST_DEBUG_FUNCPTR (gst_audio_aggregator_src_query);
+ gstaggregator_class->sink_query = gst_audio_aggregator_sink_query;
gstaggregator_class->start = gst_audio_aggregator_start;
gstaggregator_class->stop = gst_audio_aggregator_stop;
gstaggregator_class->flush = gst_audio_aggregator_flush;
GST_DEBUG_FUNCPTR (gst_audio_aggregator_aggregate);
gstaggregator_class->clip = GST_DEBUG_FUNCPTR (gst_audio_aggregator_do_clip);
gstaggregator_class->get_next_time = gst_audio_aggregator_get_next_time;
+ gstaggregator_class->update_src_caps =
+ GST_DEBUG_FUNCPTR (gst_audio_aggregator_update_src_caps);
+ gstaggregator_class->fixate_src_caps = gst_audio_aggregator_fixate_src_caps;
gstaggregator_class->negotiated_src_caps =
gst_audio_aggregator_negotiated_src_caps;
klass->create_output_buffer = gst_audio_aggregator_create_output_buffer;
+ klass->convert_buffer = gst_audio_aggregator_default_convert_buffer;
GST_DEBUG_CATEGORY_INIT (audio_aggregator_debug, "audioaggregator",
GST_DEBUG_FG_MAGENTA, "GstAudioAggregator");
}
}
+/* Caps negotiation */
+
+/* Unref after usage */
+static GstAudioAggregatorPad *
+gst_audio_aggregator_get_first_configured_pad (GstAggregator * agg)
+{
+ GstAudioAggregatorPad *res = NULL;
+ GList *l;
+
+ GST_OBJECT_LOCK (agg);
+ for (l = GST_ELEMENT (agg)->sinkpads; l; l = l->next) {
+ GstAudioAggregatorPad *aaggpad = l->data;
+
+ if (GST_AUDIO_INFO_FORMAT (&aaggpad->info) != GST_AUDIO_FORMAT_UNKNOWN) {
+ res = gst_object_ref (aaggpad);
+ break;
+ }
+ }
+ GST_OBJECT_UNLOCK (agg);
+
+ return res;
+}
+
+static GstCaps *
+gst_audio_aggregator_sink_getcaps (GstPad * pad, GstAggregator * agg,
+ GstCaps * filter)
+{
+ GstAudioAggregatorPad *first_configured_pad =
+ gst_audio_aggregator_get_first_configured_pad (agg);
+ GstCaps *sink_template_caps = gst_pad_get_pad_template_caps (pad);
+ GstCaps *downstream_caps = gst_pad_get_allowed_caps (agg->srcpad);
+ GstCaps *sink_caps;
+ GstStructure *s, *s2;
+ gint downstream_rate;
+
+ sink_template_caps = gst_caps_make_writable (sink_template_caps);
+ s = gst_caps_get_structure (sink_template_caps, 0);
+
+ if (downstream_caps && !gst_caps_is_empty (downstream_caps))
+ s2 = gst_caps_get_structure (downstream_caps, 0);
+ else
+ s2 = NULL;
+
+ if (s2 && gst_structure_get_int (s2, "rate", &downstream_rate)) {
+ gst_structure_fixate_field_nearest_int (s, "rate", downstream_rate);
+ } else if (first_configured_pad) {
+ gst_structure_fixate_field_nearest_int (s, "rate",
+ first_configured_pad->info.rate);
+ }
+
+ if (first_configured_pad)
+ gst_object_unref (first_configured_pad);
+
+ sink_caps = filter ? gst_caps_intersect (sink_template_caps,
+ filter) : gst_caps_ref (sink_template_caps);
+
+ GST_INFO_OBJECT (pad, "Getting caps with filter %" GST_PTR_FORMAT, filter);
+ GST_DEBUG_OBJECT (pad, "sink template caps : %" GST_PTR_FORMAT,
+ sink_template_caps);
+ GST_DEBUG_OBJECT (pad, "downstream caps %" GST_PTR_FORMAT, downstream_caps);
+ GST_INFO_OBJECT (pad, "returned sink caps : %" GST_PTR_FORMAT, sink_caps);
+
+ gst_caps_unref (sink_template_caps);
+
+ if (downstream_caps)
+ gst_caps_unref (downstream_caps);
+
+ return sink_caps;
+}
+
+static gboolean
+gst_audio_aggregator_sink_setcaps (GstAudioAggregatorPad * aaggpad,
+ GstAggregator * agg, GstCaps * caps)
+{
+ GstAudioAggregatorPad *first_configured_pad =
+ gst_audio_aggregator_get_first_configured_pad (agg);
+ GstCaps *downstream_caps = gst_pad_get_allowed_caps (agg->srcpad);
+ GstAudioInfo info;
+ gboolean ret = TRUE;
+ gint downstream_rate;
+ GstStructure *s;
+
+ if (!downstream_caps || gst_caps_is_empty (downstream_caps)) {
+ ret = FALSE;
+ goto done;
+ }
+
+ gst_audio_info_from_caps (&info, caps);
+ s = gst_caps_get_structure (downstream_caps, 0);
+
+ /* TODO: handle different rates on sinkpads, a bit complex
+ * because offsets will have to be updated, and audio resampling
+ * has a latency to take into account
+ */
+ if ((gst_structure_get_int (s, "rate", &downstream_rate)
+ && info.rate != downstream_rate) || (first_configured_pad
+ && info.rate != first_configured_pad->info.rate)) {
+ gst_pad_push_event (GST_PAD (aaggpad), gst_event_new_reconfigure ());
+ gst_object_unref (first_configured_pad);
+ ret = FALSE;
+ } else {
+ GST_OBJECT_LOCK (aaggpad);
+ gst_audio_info_from_caps (&aaggpad->info, caps);
+ if (GST_IS_AUDIO_AGGREGATOR_CONVERT_PAD (aaggpad))
+ GST_AUDIO_AGGREGATOR_CONVERT_PAD (aaggpad)->
+ priv->converter_config_changed = TRUE;
+ GST_OBJECT_UNLOCK (aaggpad);
+ }
+
+done:
+ if (downstream_caps)
+ gst_caps_unref (downstream_caps);
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_audio_aggregator_update_src_caps (GstAggregator * agg,
+ GstCaps * caps, GstCaps ** ret)
+{
+ GstCaps *src_template_caps = gst_pad_get_pad_template_caps (agg->srcpad);
+ GstCaps *downstream_caps =
+ gst_pad_peer_query_caps (agg->srcpad, src_template_caps);
+
+ gst_caps_unref (src_template_caps);
+
+ *ret = gst_caps_intersect (caps, downstream_caps);
+
+ GST_INFO ("Updated src caps to %" GST_PTR_FORMAT, *ret);
+
+ if (downstream_caps)
+ gst_caps_unref (downstream_caps);
+
+ return GST_FLOW_OK;
+}
+
+/* At that point if the caps are not fixed, this means downstream
+ * didn't have fully specified requirements, we'll just go ahead
+ * and fixate raw audio fields using our first configured pad, we don't for
+ * now need a more complicated heuristic
+ */
+static GstCaps *
+gst_audio_aggregator_fixate_src_caps (GstAggregator * agg, GstCaps * caps)
+{
+ GstAudioAggregatorClass *aaggclass = GST_AUDIO_AGGREGATOR_GET_CLASS (agg);
+ GstAudioAggregatorPad *first_configured_pad;
+
+ if (!aaggclass->convert_buffer)
+ return
+ GST_AGGREGATOR_CLASS
+ (gst_audio_aggregator_parent_class)->fixate_src_caps (agg, caps);
+
+ first_configured_pad = gst_audio_aggregator_get_first_configured_pad (agg);
+
+ if (first_configured_pad) {
+ GstStructure *s, *s2;
+ GstCaps *first_configured_caps =
+ gst_audio_info_to_caps (&first_configured_pad->info);
+ gint first_configured_rate, first_configured_channels;
+
+ caps = gst_caps_make_writable (caps);
+ s = gst_caps_get_structure (caps, 0);
+ s2 = gst_caps_get_structure (first_configured_caps, 0);
+
+ gst_structure_get_int (s2, "rate", &first_configured_rate);
+ gst_structure_get_int (s2, "channels", &first_configured_channels);
+
+ gst_structure_fixate_field_string (s, "format",
+ gst_structure_get_string (s2, "format"));
+ gst_structure_fixate_field_string (s, "layout",
+ gst_structure_get_string (s2, "layout"));
+ gst_structure_fixate_field_nearest_int (s, "rate", first_configured_rate);
+ gst_structure_fixate_field_nearest_int (s, "channels",
+ first_configured_channels);
+
+ gst_caps_unref (first_configured_caps);
+ gst_object_unref (first_configured_pad);
+ }
+
+ if (!gst_caps_is_fixed (caps))
+ caps = gst_caps_fixate (caps);
+
+ GST_INFO_OBJECT (agg, "Fixated src caps to %" GST_PTR_FORMAT, caps);
+
+ return caps;
+}
+
+/* Must be called with OBJECT_LOCK taken */
+static void
+gst_audio_aggregator_update_converters (GstAudioAggregator * aagg,
+ GstAudioInfo * new_info)
+{
+ GList *l;
+
+ for (l = GST_ELEMENT (aagg)->sinkpads; l; l = l->next) {
+ GstAudioAggregatorPad *aaggpad = l->data;
+
+ if (GST_IS_AUDIO_AGGREGATOR_CONVERT_PAD (aaggpad))
+ GST_AUDIO_AGGREGATOR_CONVERT_PAD (aaggpad)->
+ priv->converter_config_changed = TRUE;
+
+ /* If we currently were mixing a buffer, we need to convert it to the new
+ * format */
+ if (aaggpad->priv->buffer) {
+ GstBuffer *new_converted_buffer =
+ gst_audio_aggregator_convert_buffer (aagg, GST_PAD (aaggpad),
+ &aaggpad->info, new_info, aaggpad->priv->input_buffer);
+ gst_buffer_replace (&aaggpad->priv->buffer, new_converted_buffer);
+ }
+ }
+}
+
+/* We now have our final output caps, we can create the required converters */
+static gboolean
+gst_audio_aggregator_negotiated_src_caps (GstAggregator * agg, GstCaps * caps)
+{
+ GstAudioAggregator *aagg = GST_AUDIO_AGGREGATOR (agg);
+ GstAudioAggregatorClass *aaggclass = GST_AUDIO_AGGREGATOR_GET_CLASS (agg);
+ GstAudioInfo info;
+
+ GST_INFO_OBJECT (agg, "src caps negotiated %" GST_PTR_FORMAT, caps);
+
+ if (!gst_audio_info_from_caps (&info, caps)) {
+ GST_WARNING_OBJECT (aagg, "Rejecting invalid caps: %" GST_PTR_FORMAT, caps);
+ return FALSE;
+ }
+
+ GST_AUDIO_AGGREGATOR_LOCK (aagg);
+ GST_OBJECT_LOCK (aagg);
+
+ if (aaggclass->convert_buffer) {
+ gst_audio_aggregator_update_converters (aagg, &info);
+
+ if (aagg->priv->current_buffer
+ && !gst_audio_info_is_equal (&aagg->info, &info)) {
+ GstBuffer *converted =
+ gst_audio_aggregator_convert_buffer (aagg, agg->srcpad, &aagg->info,
+ &info, aagg->priv->current_buffer);
+ gst_buffer_unref (aagg->priv->current_buffer);
+ aagg->priv->current_buffer = converted;
+ }
+ }
+
+ if (!gst_audio_info_is_equal (&info, &aagg->info)) {
+ GST_INFO_OBJECT (aagg, "setting caps to %" GST_PTR_FORMAT, caps);
+ gst_caps_replace (&aagg->current_caps, caps);
+
+ memcpy (&aagg->info, &info, sizeof (info));
+ }
+
+ GST_OBJECT_UNLOCK (aagg);
+ GST_AUDIO_AGGREGATOR_UNLOCK (aagg);
+
+ return
+ GST_AGGREGATOR_CLASS
+ (gst_audio_aggregator_parent_class)->negotiated_src_caps (agg, caps);
+}
/* event handling */
gst_audio_aggregator_sink_event (GstAggregator * agg,
GstAggregatorPad * aggpad, GstEvent * event)
{
+ GstAudioAggregatorPad *aaggpad = GST_AUDIO_AGGREGATOR_PAD (aggpad);
gboolean res = TRUE;
GST_DEBUG_OBJECT (aggpad, "Got %s event on sink pad",
break;
}
+ case GST_EVENT_CAPS:
+ {
+ GstCaps *caps;
+
+ gst_event_parse_caps (event, &caps);
+ GST_INFO_OBJECT (aggpad, "Got caps %" GST_PTR_FORMAT, caps);
+ res = gst_audio_aggregator_sink_setcaps (aaggpad, agg, caps);
+ gst_event_unref (event);
+ event = NULL;
+ break;
+ }
default:
break;
}
return res;
}
+static gboolean
+gst_audio_aggregator_sink_query (GstAggregator * agg, GstAggregatorPad * aggpad,
+ GstQuery * query)
+{
+ gboolean res = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_CAPS:
+ {
+ GstCaps *filter, *caps;
+
+ gst_query_parse_caps (query, &filter);
+ caps = gst_audio_aggregator_sink_getcaps (GST_PAD (aggpad), agg, filter);
+ gst_query_set_caps_result (query, caps);
+ gst_caps_unref (caps);
+ res = TRUE;
+ break;
+ }
+ default:
+ res =
+ GST_AGGREGATOR_CLASS (gst_audio_aggregator_parent_class)->sink_query
+ (agg, aggpad, query);
+ break;
+ }
+
+ return res;
+}
+
+
/* FIXME, the duration query should reflect how long you will produce
* data, that is the amount of stream time until you will emit EOS.
*
#endif
}
-
-static gboolean
-gst_audio_aggregator_negotiated_src_caps (GstAggregator * agg, GstCaps * caps)
-{
- GstAudioAggregator *aagg = GST_AUDIO_AGGREGATOR (agg);
- GstAudioInfo info;
-
- if (!gst_audio_info_from_caps (&info, caps)) {
- GST_WARNING_OBJECT (aagg, "Rejecting invalid caps: %" GST_PTR_FORMAT, caps);
- return FALSE;
- }
-
- GST_AUDIO_AGGREGATOR_LOCK (aagg);
- GST_OBJECT_LOCK (aagg);
-
- if (!gst_audio_info_is_equal (&info, &aagg->info)) {
- GST_INFO_OBJECT (aagg, "setting caps to %" GST_PTR_FORMAT, caps);
- gst_caps_replace (&aagg->current_caps, caps);
-
- memcpy (&aagg->info, &info, sizeof (info));
- }
-
- GST_OBJECT_UNLOCK (aagg);
- GST_AUDIO_AGGREGATOR_UNLOCK (aagg);
-
- /* send caps event later, after stream-start event */
-
- return
- GST_AGGREGATOR_CLASS
- (gst_audio_aggregator_parent_class)->negotiated_src_caps (agg, caps);
-}
-
-
/* Must hold object lock and aagg lock to call */
static void
* values.
*/
static gboolean
-gst_audio_aggregator_queue_new_buffer (GstAudioAggregator * aagg,
- GstAudioAggregatorPad * pad, GstBuffer * inbuf)
+gst_audio_aggregator_fill_buffer (GstAudioAggregator * aagg,
+ GstAudioAggregatorPad * pad)
{
+ GstAudioAggregatorClass *aaggclass = GST_AUDIO_AGGREGATOR_GET_CLASS (aagg);
GstClockTime start_time, end_time;
gboolean discont = FALSE;
guint64 start_offset, end_offset;
GstAggregator *agg = GST_AGGREGATOR (aagg);
GstAggregatorPad *aggpad = GST_AGGREGATOR_PAD (pad);
- g_assert (pad->priv->buffer == NULL);
-
- rate = GST_AUDIO_INFO_RATE (&pad->info);
- bpf = GST_AUDIO_INFO_BPF (&pad->info);
+ if (aaggclass->convert_buffer) {
+ rate = GST_AUDIO_INFO_RATE (&aagg->info);
+ bpf = GST_AUDIO_INFO_BPF (&aagg->info);
+ } else {
+ rate = GST_AUDIO_INFO_RATE (&pad->info);
+ bpf = GST_AUDIO_INFO_BPF (&pad->info);
+ }
pad->priv->position = 0;
- pad->priv->size = gst_buffer_get_size (inbuf) / bpf;
+ pad->priv->size = gst_buffer_get_size (pad->priv->buffer) / bpf;
if (pad->priv->size == 0) {
- if (!GST_BUFFER_DURATION_IS_VALID (inbuf) ||
- !GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP)) {
+ if (!GST_BUFFER_DURATION_IS_VALID (pad->priv->buffer) ||
+ !GST_BUFFER_FLAG_IS_SET (pad->priv->buffer, GST_BUFFER_FLAG_GAP)) {
GST_WARNING_OBJECT (pad, "Dropping 0-sized buffer missing either a"
- " duration or a GAP flag: %" GST_PTR_FORMAT, inbuf);
+ " duration or a GAP flag: %" GST_PTR_FORMAT, pad->priv->buffer);
return FALSE;
}
- pad->priv->size = gst_util_uint64_scale (GST_BUFFER_DURATION (inbuf), rate,
+ pad->priv->size =
+ gst_util_uint64_scale (GST_BUFFER_DURATION (pad->priv->buffer), rate,
GST_SECOND);
}
- if (!GST_BUFFER_PTS_IS_VALID (inbuf)) {
+ if (!GST_BUFFER_PTS_IS_VALID (pad->priv->buffer)) {
if (pad->priv->output_offset == -1)
pad->priv->output_offset = aagg->priv->offset;
if (pad->priv->next_offset == -1)
goto done;
}
- start_time = GST_BUFFER_PTS (inbuf);
+ start_time = GST_BUFFER_PTS (pad->priv->buffer);
end_time =
start_time + gst_util_uint64_scale_ceil (pad->priv->size, GST_SECOND,
rate);
GST_SECOND);
end_offset = start_offset + pad->priv->size;
- if (GST_BUFFER_IS_DISCONT (inbuf)
- || GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_RESYNC)
+ if (GST_BUFFER_IS_DISCONT (pad->priv->buffer)
+ || GST_BUFFER_FLAG_IS_SET (pad->priv->buffer, GST_BUFFER_FLAG_RESYNC)
|| pad->priv->new_segment || pad->priv->next_offset == -1) {
discont = TRUE;
pad->priv->new_segment = FALSE;
if (start_output_offset == -1 && end_output_offset == -1) {
/* Outside output segment, drop */
- gst_buffer_unref (inbuf);
- pad->priv->buffer = NULL;
pad->priv->position = 0;
pad->priv->size = 0;
pad->priv->output_offset = -1;
end_output_offset = start_output_offset + pad->priv->size;
if (end_output_offset < aagg->priv->offset) {
- /* Before output segment, drop */
- gst_buffer_unref (inbuf);
- pad->priv->buffer = NULL;
pad->priv->position = 0;
pad->priv->size = 0;
pad->priv->output_offset = -1;
pad->priv->position += diff;
if (pad->priv->position >= pad->priv->size) {
/* Empty buffer, drop */
- gst_buffer_unref (inbuf);
- pad->priv->buffer = NULL;
pad->priv->position = 0;
pad->priv->size = 0;
pad->priv->output_offset = -1;
GST_LOG_OBJECT (pad,
"Queued new buffer at offset %" G_GUINT64_FORMAT,
pad->priv->output_offset);
- pad->priv->buffer = inbuf;
return TRUE;
}
pad->priv->position = pad->priv->size;
gst_buffer_replace (&pad->priv->buffer, NULL);
+ gst_buffer_replace (&pad->priv->input_buffer, NULL);
return FALSE;
}
if (pad->priv->position == pad->priv->size) {
/* Buffer done, drop it */
gst_buffer_replace (&pad->priv->buffer, NULL);
+ gst_buffer_replace (&pad->priv->input_buffer, NULL);
GST_LOG_OBJECT (pad, "Finished mixing buffer, waiting for next");
return FALSE;
}
gst_aggregator_get_allocator (GST_AGGREGATOR (aagg), &allocator, ¶ms);
+ GST_DEBUG ("Creating output buffer with size %d",
+ num_frames * GST_AUDIO_INFO_BPF (&aagg->info));
+
outbuf = gst_buffer_new_allocate (allocator, num_frames *
GST_AUDIO_INFO_BPF (&aagg->info), ¶ms);
aagg->priv->offset, GST_TIME_ARGS (agg->segment.position));
for (iter = element->sinkpads; iter; iter = iter->next) {
- GstBuffer *inbuf;
GstAudioAggregatorPad *pad = (GstAudioAggregatorPad *) iter->data;
GstAggregatorPad *aggpad = (GstAggregatorPad *) iter->data;
gboolean pad_eos = gst_aggregator_pad_is_eos (aggpad);
if (!pad_eos)
is_eos = FALSE;
- inbuf = gst_aggregator_pad_get_buffer (aggpad);
+ pad->priv->input_buffer = gst_aggregator_pad_get_buffer (aggpad);
GST_OBJECT_LOCK (pad);
- if (!inbuf) {
+ if (!pad->priv->input_buffer) {
if (timeout) {
if (pad->priv->output_offset < next_offset) {
gint64 diff = next_offset - pad->priv->output_offset;
continue;
}
- g_assert (!pad->priv->buffer || pad->priv->buffer == inbuf);
-
/* New buffer? */
if (!pad->priv->buffer) {
- /* Takes ownership of buffer */
- if (!gst_audio_aggregator_queue_new_buffer (aagg, pad, inbuf)) {
+ if (GST_IS_AUDIO_AGGREGATOR_CONVERT_PAD (pad))
+ pad->priv->buffer =
+ gst_audio_aggregator_convert_buffer
+ (aagg, GST_PAD (pad), &pad->info, &aagg->info,
+ pad->priv->input_buffer);
+ else
+ pad->priv->buffer = gst_buffer_ref (pad->priv->input_buffer);
+
+ if (!gst_audio_aggregator_fill_buffer (aagg, pad)) {
+ gst_buffer_replace (&pad->priv->buffer, NULL);
+ gst_buffer_replace (&pad->priv->input_buffer, NULL);
+ pad->priv->buffer = NULL;
dropped = TRUE;
GST_OBJECT_UNLOCK (pad);
+
gst_aggregator_pad_drop_buffer (aggpad);
continue;
}
} else {
- gst_buffer_unref (inbuf);
+ gst_buffer_unref (pad->priv->input_buffer);
}
if (!pad->priv->buffer && !dropped && pad_eos) {
GST_AUDIO_INFO_RATE (&aagg->info))), pad->priv->buffer);
/* Buffer done, drop it */
gst_buffer_replace (&pad->priv->buffer, NULL);
+ gst_buffer_replace (&pad->priv->input_buffer, NULL);
dropped = TRUE;
GST_OBJECT_UNLOCK (pad);
gst_aggregator_pad_drop_buffer (aggpad);
* Unlike the adder element audiomixer properly synchronises all input streams
* and also handles live inputs such as capture sources or RTP properly.
*
- * Caps negotiation is inherently racy with the audiomixer element. You can set
- * the "caps" property to force audiomixer to operate in a specific audio
- * format, sample rate and channel count. In this case you may also need
- * audioconvert and/or audioresample elements for each input stream before the
- * audiomixer element to make sure the input branch can produce the forced
- * format.
+ * The audiomixer element can accept any sort of raw audio data, it will
+ * be converted to the target format if necessary, with the exception
+ * of the sample rate, which has to be identical to either what downstream
+ * expects, or the sample rate of the first configured pad. Use a capsfilter
+ * after the audiomixer element if you want to precisely control the format
+ * that comes out of the audiomixer, which supports changing the format of
+ * its output while playing.
+ *
+ * If you want to control the manner in which incoming data gets converted,
+ * see the #GstAudioAggregatorPad:converter-config property, which will let
+ * you for example change the way in which channels may get remapped.
*
* The input pads are from a GstPad subclass and have additional
* properties to mute each pad individually and set the volume:
};
G_DEFINE_TYPE (GstAudioMixerPad, gst_audiomixer_pad,
- GST_TYPE_AUDIO_AGGREGATOR_PAD);
+ GST_TYPE_AUDIO_AGGREGATOR_CONVERT_PAD);
static void
gst_audiomixer_pad_get_property (GObject * object, guint prop_id,
enum
{
- PROP_0,
- PROP_FILTER_CAPS
+ PROP_0
};
-/* elementfactory information */
+/* These are the formats we can mix natively */
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define CAPS \
GST_AUDIO_CAPS_MAKE ("{ S32LE, U32LE, S16LE, U16LE, S8, U8, F32LE, F64LE }") \
- ", layout = (string) { interleaved, non-interleaved }"
+ ", layout = interleaved"
#else
#define CAPS \
GST_AUDIO_CAPS_MAKE ("{ S32BE, U32BE, S16BE, U16BE, S8, U8, F32BE, F64BE }") \
- ", layout = (string) { interleaved, non-interleaved }"
+ ", layout = interleaved"
#endif
static GstStaticPadTemplate gst_audiomixer_src_template =
GST_STATIC_CAPS (CAPS)
);
+#define SINK_CAPS \
+ GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL) \
+ ", layout=interleaved")
+
static GstStaticPadTemplate gst_audiomixer_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink_%u",
GST_PAD_SINK,
GST_PAD_REQUEST,
- GST_STATIC_CAPS (CAPS)
- );
+ SINK_CAPS);
static void gst_audiomixer_child_proxy_init (gpointer g_iface,
gpointer iface_data);
GST_TYPE_AUDIO_AGGREGATOR, G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
gst_audiomixer_child_proxy_init));
-static void gst_audiomixer_dispose (GObject * object);
-static void gst_audiomixer_set_property (GObject * object, guint prop_id,
- const GValue * value, GParamSpec * pspec);
-static void gst_audiomixer_get_property (GObject * object, guint prop_id,
- GValue * value, GParamSpec * pspec);
-
-static gboolean gst_audiomixer_setcaps (GstAudioMixer * audiomixer,
- GstPad * pad, GstCaps * caps);
static GstPad *gst_audiomixer_request_new_pad (GstElement * element,
GstPadTemplate * temp, const gchar * req_name, const GstCaps * caps);
static void gst_audiomixer_release_pad (GstElement * element, GstPad * pad);
GstBuffer * outbuf, guint out_offset, guint num_samples);
-/* we can only accept caps that we and downstream can handle.
- * if we have filtercaps set, use those to constrain the target caps.
- */
-static GstCaps *
-gst_audiomixer_sink_getcaps (GstAggregator * agg, GstPad * pad,
- GstCaps * filter)
-{
- GstAudioAggregator *aagg;
- GstAudioMixer *audiomixer;
- GstCaps *result, *peercaps, *current_caps, *filter_caps;
- GstStructure *s;
- gint i, n;
-
- audiomixer = GST_AUDIO_MIXER (agg);
- aagg = GST_AUDIO_AGGREGATOR (agg);
-
- GST_OBJECT_LOCK (audiomixer);
- /* take filter */
- if ((filter_caps = audiomixer->filter_caps)) {
- if (filter)
- filter_caps =
- gst_caps_intersect_full (filter, filter_caps,
- GST_CAPS_INTERSECT_FIRST);
- else
- gst_caps_ref (filter_caps);
- } else {
- filter_caps = filter ? gst_caps_ref (filter) : NULL;
- }
- GST_OBJECT_UNLOCK (audiomixer);
-
- if (filter_caps && gst_caps_is_empty (filter_caps)) {
- GST_WARNING_OBJECT (pad, "Empty filter caps");
- return filter_caps;
- }
-
- /* get the downstream possible caps */
- peercaps = gst_pad_peer_query_caps (agg->srcpad, filter_caps);
-
- /* get the allowed caps on this sinkpad */
- GST_OBJECT_LOCK (audiomixer);
- current_caps = aagg->current_caps ? gst_caps_ref (aagg->current_caps) : NULL;
- if (current_caps == NULL) {
- current_caps = gst_pad_get_pad_template_caps (pad);
- if (!current_caps)
- current_caps = gst_caps_new_any ();
- }
- GST_OBJECT_UNLOCK (audiomixer);
-
- if (peercaps) {
- /* if the peer has caps, intersect */
- GST_DEBUG_OBJECT (audiomixer, "intersecting peer and our caps");
- result =
- gst_caps_intersect_full (peercaps, current_caps,
- GST_CAPS_INTERSECT_FIRST);
- gst_caps_unref (peercaps);
- gst_caps_unref (current_caps);
- } else {
- /* the peer has no caps (or there is no peer), just use the allowed caps
- * of this sinkpad. */
- /* restrict with filter-caps if any */
- if (filter_caps) {
- GST_DEBUG_OBJECT (audiomixer, "no peer caps, using filtered caps");
- result =
- gst_caps_intersect_full (filter_caps, current_caps,
- GST_CAPS_INTERSECT_FIRST);
- gst_caps_unref (current_caps);
- } else {
- GST_DEBUG_OBJECT (audiomixer, "no peer caps, using our caps");
- result = current_caps;
- }
- }
-
- result = gst_caps_make_writable (result);
-
- n = gst_caps_get_size (result);
- for (i = 0; i < n; i++) {
- GstStructure *sref;
-
- s = gst_caps_get_structure (result, i);
- sref = gst_structure_copy (s);
- gst_structure_set (sref, "channels", GST_TYPE_INT_RANGE, 0, 2, NULL);
- if (gst_structure_is_subset (s, sref)) {
- /* This field is irrelevant when in mono or stereo */
- gst_structure_remove_field (s, "channel-mask");
- }
- gst_structure_free (sref);
- }
-
- if (filter_caps)
- gst_caps_unref (filter_caps);
-
- GST_LOG_OBJECT (audiomixer, "getting caps on pad %p,%s to %" GST_PTR_FORMAT,
- pad, GST_PAD_NAME (pad), result);
-
- return result;
-}
-
-static gboolean
-gst_audiomixer_sink_query (GstAggregator * agg, GstAggregatorPad * aggpad,
- GstQuery * query)
-{
- gboolean res = FALSE;
-
- switch (GST_QUERY_TYPE (query)) {
- case GST_QUERY_CAPS:
- {
- GstCaps *filter, *caps;
-
- gst_query_parse_caps (query, &filter);
- caps = gst_audiomixer_sink_getcaps (agg, GST_PAD (aggpad), filter);
- gst_query_set_caps_result (query, caps);
- gst_caps_unref (caps);
- res = TRUE;
- break;
- }
- default:
- res =
- GST_AGGREGATOR_CLASS (parent_class)->sink_query (agg, aggpad, query);
- break;
- }
-
- return res;
-}
-
-/* the first caps we receive on any of the sinkpads will define the caps for all
- * the other sinkpads because we can only mix streams with the same caps.
- */
-static gboolean
-gst_audiomixer_setcaps (GstAudioMixer * audiomixer, GstPad * pad,
- GstCaps * orig_caps)
-{
- GstAggregator *agg = GST_AGGREGATOR (audiomixer);
- GstAudioAggregator *aagg = GST_AUDIO_AGGREGATOR (audiomixer);
- GstCaps *caps;
- GstAudioInfo info;
- GstStructure *s;
- gint channels = 0;
-
- caps = gst_caps_copy (orig_caps);
-
- s = gst_caps_get_structure (caps, 0);
- if (gst_structure_get_int (s, "channels", &channels))
- if (channels <= 2)
- gst_structure_remove_field (s, "channel-mask");
-
- if (!gst_audio_info_from_caps (&info, caps))
- goto invalid_format;
-
- if (channels == 1) {
- GstCaps *filter;
- GstCaps *downstream_caps;
-
- if (audiomixer->filter_caps)
- filter = gst_caps_intersect_full (caps, audiomixer->filter_caps,
- GST_CAPS_INTERSECT_FIRST);
- else
- filter = gst_caps_ref (caps);
-
- downstream_caps = gst_pad_peer_query_caps (agg->srcpad, filter);
- gst_caps_unref (filter);
-
- if (downstream_caps) {
- gst_caps_unref (caps);
- caps = downstream_caps;
-
- if (gst_caps_is_empty (caps)) {
- gst_caps_unref (caps);
- return FALSE;
- }
- caps = gst_caps_fixate (caps);
- }
- }
-
- GST_OBJECT_LOCK (audiomixer);
- /* don't allow reconfiguration for now; there's still a race between the
- * different upstream threads doing query_caps + accept_caps + sending
- * (possibly different) CAPS events, but there's not much we can do about
- * that, upstream needs to deal with it. */
- if (aagg->current_caps != NULL) {
- if (gst_audio_info_is_equal (&info, &aagg->info)) {
- GST_OBJECT_UNLOCK (audiomixer);
- gst_caps_unref (caps);
- gst_audio_aggregator_set_sink_caps (aagg, GST_AUDIO_AGGREGATOR_PAD (pad),
- orig_caps);
- return TRUE;
- } else {
- GST_DEBUG_OBJECT (pad, "got input caps %" GST_PTR_FORMAT ", but "
- "current caps are %" GST_PTR_FORMAT, caps, aagg->current_caps);
- GST_OBJECT_UNLOCK (audiomixer);
- gst_pad_push_event (pad, gst_event_new_reconfigure ());
- gst_caps_unref (caps);
- return FALSE;
- }
- } else {
- gst_caps_replace (&aagg->current_caps, caps);
- aagg->info = info;
- gst_pad_mark_reconfigure (GST_AGGREGATOR_SRC_PAD (agg));
- }
- GST_OBJECT_UNLOCK (audiomixer);
-
- gst_audio_aggregator_set_sink_caps (aagg, GST_AUDIO_AGGREGATOR_PAD (pad),
- orig_caps);
-
- GST_INFO_OBJECT (pad, "handle caps change to %" GST_PTR_FORMAT, caps);
-
- gst_caps_unref (caps);
-
- return TRUE;
-
- /* ERRORS */
-invalid_format:
- {
- gst_caps_unref (caps);
- GST_WARNING_OBJECT (audiomixer, "invalid format set as caps");
- return FALSE;
- }
-}
-
-static GstFlowReturn
-gst_audiomixer_update_src_caps (GstAggregator * agg, GstCaps * caps,
- GstCaps ** ret)
-{
- GstAudioAggregator *aagg = GST_AUDIO_AGGREGATOR (agg);
-
- if (aagg->current_caps == NULL)
- return GST_AGGREGATOR_FLOW_NEED_DATA;
-
- *ret = gst_caps_ref (aagg->current_caps);
-
- return GST_FLOW_OK;
-}
-
-static gboolean
-gst_audiomixer_sink_event (GstAggregator * agg, GstAggregatorPad * aggpad,
- GstEvent * event)
-{
- GstAudioMixer *audiomixer = GST_AUDIO_MIXER (agg);
- gboolean res = TRUE;
-
- GST_DEBUG_OBJECT (aggpad, "Got %s event on sink pad",
- GST_EVENT_TYPE_NAME (event));
-
- switch (GST_EVENT_TYPE (event)) {
- case GST_EVENT_CAPS:
- {
- GstCaps *caps;
-
- gst_event_parse_caps (event, &caps);
- res = gst_audiomixer_setcaps (audiomixer, GST_PAD_CAST (aggpad), caps);
- gst_event_unref (event);
- event = NULL;
- break;
- }
- default:
- break;
- }
-
- if (event != NULL)
- return GST_AGGREGATOR_CLASS (parent_class)->sink_event (agg, aggpad, event);
-
- return res;
-}
-
static void
gst_audiomixer_class_init (GstAudioMixerClass * klass)
{
- GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
- GstAggregatorClass *agg_class = (GstAggregatorClass *) klass;
GstAudioAggregatorClass *aagg_class = (GstAudioAggregatorClass *) klass;
- gobject_class->set_property = gst_audiomixer_set_property;
- gobject_class->get_property = gst_audiomixer_get_property;
- gobject_class->dispose = gst_audiomixer_dispose;
-
- g_object_class_install_property (gobject_class, PROP_FILTER_CAPS,
- g_param_spec_boxed ("caps", "Target caps",
- "Set target format for mixing (NULL means ANY). "
- "Setting this property takes a reference to the supplied GstCaps "
- "object", GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
gst_element_class_add_static_pad_template (gstelement_class,
&gst_audiomixer_src_template);
gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
gstelement_class->release_pad =
GST_DEBUG_FUNCPTR (gst_audiomixer_release_pad);
- agg_class->sink_query = GST_DEBUG_FUNCPTR (gst_audiomixer_sink_query);
- agg_class->sink_event = GST_DEBUG_FUNCPTR (gst_audiomixer_sink_event);
- agg_class->update_src_caps =
- GST_DEBUG_FUNCPTR (gst_audiomixer_update_src_caps);
-
aagg_class->aggregate_one_buffer = gst_audiomixer_aggregate_one_buffer;
}
static void
gst_audiomixer_init (GstAudioMixer * audiomixer)
{
- audiomixer->filter_caps = NULL;
-}
-
-static void
-gst_audiomixer_dispose (GObject * object)
-{
- GstAudioMixer *audiomixer = GST_AUDIO_MIXER (object);
-
- gst_caps_replace (&audiomixer->filter_caps, NULL);
-
- G_OBJECT_CLASS (parent_class)->dispose (object);
-}
-
-static void
-gst_audiomixer_set_property (GObject * object, guint prop_id,
- const GValue * value, GParamSpec * pspec)
-{
- GstAudioMixer *audiomixer = GST_AUDIO_MIXER (object);
-
- switch (prop_id) {
- case PROP_FILTER_CAPS:{
- GstCaps *new_caps = NULL;
- GstCaps *old_caps;
- const GstCaps *new_caps_val = gst_value_get_caps (value);
-
- if (new_caps_val != NULL) {
- new_caps = (GstCaps *) new_caps_val;
- gst_caps_ref (new_caps);
- }
-
- GST_OBJECT_LOCK (audiomixer);
- old_caps = audiomixer->filter_caps;
- audiomixer->filter_caps = new_caps;
- GST_OBJECT_UNLOCK (audiomixer);
-
- if (old_caps)
- gst_caps_unref (old_caps);
-
- GST_DEBUG_OBJECT (audiomixer, "set new caps %" GST_PTR_FORMAT, new_caps);
- break;
- }
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-gst_audiomixer_get_property (GObject * object, guint prop_id, GValue * value,
- GParamSpec * pspec)
-{
- GstAudioMixer *audiomixer = GST_AUDIO_MIXER (object);
-
- switch (prop_id) {
- case PROP_FILTER_CAPS:
- GST_OBJECT_LOCK (audiomixer);
- gst_value_set_caps (value, audiomixer->filter_caps);
- GST_OBJECT_UNLOCK (audiomixer);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
}
static GstPad *
/* some test helpers */
static GstElement *
-setup_pipeline (GstElement * audiomixer, gint num_srcs)
+setup_pipeline (GstElement * audiomixer, gint num_srcs, GstElement * capsfilter)
{
GstElement *pipeline, *src, *sink;
gint i;
sink = gst_element_factory_make ("fakesink", "sink");
gst_bin_add_many (GST_BIN (pipeline), audiomixer, sink, NULL);
- gst_element_link (audiomixer, sink);
+
+ if (capsfilter) {
+ gst_bin_add (GST_BIN (pipeline), capsfilter);
+ gst_element_link_many (audiomixer, capsfilter, sink, NULL);
+ } else {
+ gst_element_link (audiomixer, sink);
+ }
for (i = 0; i < num_srcs; i++) {
src = gst_element_factory_make ("audiotestsrc", NULL);
GstCaps *caps;
/* build pipeline */
- pipeline = setup_pipeline (NULL, 1);
+ pipeline = setup_pipeline (NULL, 1, NULL);
/* prepare playing */
set_state_and_wait (pipeline, GST_STATE_PAUSED);
/* check that caps set on the property are honoured */
GST_START_TEST (test_filter_caps)
{
- GstElement *pipeline, *audiomixer;
+ GstElement *pipeline, *audiomixer, *capsfilter;
GstCaps *filter_caps, *caps;
filter_caps = gst_caps_new_simple ("audio/x-raw",
"rate", G_TYPE_INT, 44100, "channels", G_TYPE_INT, 1,
"channel-mask", GST_TYPE_BITMASK, (guint64) 0x04, NULL);
+ capsfilter = gst_element_factory_make ("capsfilter", NULL);
+
/* build pipeline */
audiomixer = gst_element_factory_make ("audiomixer", NULL);
- g_object_set (audiomixer, "caps", filter_caps, NULL);
- pipeline = setup_pipeline (audiomixer, 1);
+ g_object_set (capsfilter, "caps", filter_caps, NULL);
+ pipeline = setup_pipeline (audiomixer, 1, capsfilter);
/* prepare playing */
set_state_and_wait (pipeline, GST_STATE_PAUSED);
/* build pipeline */
audiomixer = gst_element_factory_make ("audiomixer", "audiomixer");
- bin = setup_pipeline (audiomixer, 2);
+ bin = setup_pipeline (audiomixer, 2, NULL);
bus = gst_element_get_bus (bin);
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
/* build pipeline */
audiomixer = gst_element_factory_make ("audiomixer", "audiomixer");
- bin = setup_pipeline (audiomixer, 2);
+ bin = setup_pipeline (audiomixer, 2, NULL);
bus = gst_element_get_bus (bin);
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
GST_INFO ("preparing test");
/* build pipeline */
- bin = setup_pipeline (NULL, 2);
+ bin = setup_pipeline (NULL, 2, NULL);
bus = gst_element_get_bus (bin);
gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
GST_END_TEST;
+static void
+change_src_caps (GstElement * fakesink, GstBuffer * buffer, GstPad * pad,
+ GstElement * capsfilter)
+{
+ GstCaps *caps = gst_caps_new_simple ("audio/x-raw",
+ "format", G_TYPE_STRING, GST_AUDIO_NE (S32),
+ "layout", G_TYPE_STRING, "interleaved",
+ "rate", G_TYPE_INT, 10, "channels", G_TYPE_INT, 1, NULL);
+
+ g_object_set (capsfilter, "caps", caps, NULL);
+ g_signal_connect (fakesink, "handoff", (GCallback) handoff_buffer_cb, NULL);
+ g_signal_handlers_disconnect_by_func (fakesink, change_src_caps, capsfilter);
+}
+
+/* In this test, we create an input buffer with a duration of 2 seconds,
+ * and require the audiomixer to output 1 second long buffers.
+ * The input buffer will thus be mixed twice, and the audiomixer will
+ * output two buffers.
+ *
+ * After audiomixer has output a first buffer, we change its output format
+ * from S8 to S32. As our sample rate stays the same at 10 fps, and we use
+ * mono, the first buffer should be 10 bytes long, and the second 40.
+ *
+ * The input buffer is made up of 15 0-valued bytes, and 5 1-valued bytes.
+ * We verify that the second buffer contains 5 0-valued integers, and
+ * 5 1 << 24 valued integers.
+ */
+GST_START_TEST (test_change_output_caps)
+{
+ GstSegment segment;
+ GstElement *bin, *audiomixer, *capsfilter, *sink;
+ GstBus *bus;
+ GstPad *sinkpad;
+ gboolean res;
+ GstStateChangeReturn state_res;
+ GstFlowReturn ret;
+ GstEvent *event;
+ GstBuffer *buffer;
+ GstCaps *caps;
+ GstQuery *drain = gst_query_new_drain ();
+ GstMapInfo inmap;
+ GstMapInfo outmap;
+ gsize i;
+
+ bin = gst_pipeline_new ("pipeline");
+ bus = gst_element_get_bus (bin);
+ gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH);
+
+ g_signal_connect (bus, "message::error", (GCallback) message_received, bin);
+ g_signal_connect (bus, "message::warning", (GCallback) message_received, bin);
+ g_signal_connect (bus, "message::eos", (GCallback) message_received, bin);
+
+ audiomixer = gst_element_factory_make ("audiomixer", "audiomixer");
+ g_object_set (audiomixer, "output-buffer-duration", GST_SECOND, NULL);
+ capsfilter = gst_element_factory_make ("capsfilter", NULL);
+ sink = gst_element_factory_make ("fakesink", "sink");
+ g_object_set (sink, "signal-handoffs", TRUE, NULL);
+ g_signal_connect (sink, "handoff", (GCallback) change_src_caps, capsfilter);
+ gst_bin_add_many (GST_BIN (bin), audiomixer, capsfilter, sink, NULL);
+
+ res = gst_element_link_many (audiomixer, capsfilter, sink, NULL);
+ fail_unless (res == TRUE, NULL);
+
+ state_res = gst_element_set_state (bin, GST_STATE_PLAYING);
+ ck_assert_int_ne (state_res, GST_STATE_CHANGE_FAILURE);
+
+ sinkpad = gst_element_get_request_pad (audiomixer, "sink_%u");
+ fail_if (sinkpad == NULL, NULL);
+
+ gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test"));
+
+ caps = gst_caps_new_simple ("audio/x-raw",
+ "format", G_TYPE_STRING, "S8",
+ "layout", G_TYPE_STRING, "interleaved",
+ "rate", G_TYPE_INT, 10, "channels", G_TYPE_INT, 1, NULL);
+
+ gst_pad_set_caps (sinkpad, caps);
+ g_object_set (capsfilter, "caps", caps, NULL);
+ gst_caps_unref (caps);
+
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 0;
+ segment.stop = 2 * GST_SECOND;
+ segment.time = 0;
+ event = gst_event_new_segment (&segment);
+ gst_pad_send_event (sinkpad, event);
+
+ gst_buffer_replace (&handoff_buffer, NULL);
+
+ buffer = new_buffer (20, 0, 0, 2 * GST_SECOND, 0);
+ gst_buffer_map (buffer, &inmap, GST_MAP_WRITE);
+ memset (inmap.data + 15, 1, 5);
+ gst_buffer_unmap (buffer, &inmap);
+ ret = gst_pad_chain (sinkpad, buffer);
+ ck_assert_int_eq (ret, GST_FLOW_OK);
+ gst_pad_query (sinkpad, drain);
+ fail_unless (handoff_buffer != NULL);
+ fail_unless_equals_int (gst_buffer_get_size (handoff_buffer), 40);
+
+ gst_buffer_map (handoff_buffer, &outmap, GST_MAP_READ);
+ for (i = 0; i < 10; i++) {
+ guint32 sample;
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ sample = GUINT32_FROM_LE (((guint32 *) outmap.data)[i]);
+#else
+ sample = GUINT32_FROM_BE (((guint32 *) outmap.data)[i]);
+#endif
+
+ if (i < 5) {
+ fail_unless_equals_int (sample, 0);
+ } else {
+ fail_unless_equals_int (sample, 1 << 24);
+ }
+ }
+ gst_buffer_unmap (handoff_buffer, &outmap);
+
+ gst_element_release_request_pad (audiomixer, sinkpad);
+ gst_object_unref (sinkpad);
+ gst_element_set_state (bin, GST_STATE_NULL);
+ gst_bus_remove_signal_watch (bus);
+ gst_object_unref (bus);
+ gst_object_unref (bin);
+ gst_query_unref (drain);
+}
+
+GST_END_TEST;
+
static Suite *
audiomixer_suite (void)
{
tcase_add_test (tc_chain, test_segment_base_handling);
tcase_add_test (tc_chain, test_sinkpad_property_controller);
tcase_add_checked_fixture (tc_chain, test_setup, test_teardown);
+ tcase_add_test (tc_chain, test_change_output_caps);
/* Use a longer timeout */
#ifdef HAVE_VALGRIND