gstreamer/gst-libs/gst/audio/gstaudiodecoder.c
Jan Schmidt b3053925ac audiodecoder: Don't send pending events before decode
Make sure to update the output segment to track the segment
we're decoding in, but don't actually push it downstream until
after buffers are decoded.

https://bugzilla.gnome.org/show_bug.cgi?id=744806
2015-02-24 01:36:44 +11:00

3460 lines
98 KiB
C

/* GStreamer
* Copyright (C) 2009 Igalia S.L.
* Author: Iago Toral Quiroga <itoral@igalia.com>
* Copyright (C) 2011 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>.
* Copyright (C) 2011 Nokia Corporation. All rights reserved.
* Contact: Stefan Kost <stefan.kost@nokia.com>
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gstaudiodecoder
* @short_description: Base class for audio decoders
* @see_also: #GstBaseTransform
*
* This base class is for audio decoders turning encoded data into
* raw audio samples.
*
* GstAudioDecoder and subclass should cooperate as follows.
* <orderedlist>
* <listitem>
* <itemizedlist><title>Configuration</title>
* <listitem><para>
* Initially, GstAudioDecoder calls @start when the decoder element
* is activated, which allows subclass to perform any global setup.
* Base class (context) parameters can already be set according to subclass
* capabilities (or possibly upon receive more information in subsequent
* @set_format).
* </para></listitem>
* <listitem><para>
* GstAudioDecoder calls @set_format to inform subclass of the format
* of input audio data that it is about to receive.
* While unlikely, it might be called more than once, if changing input
* parameters require reconfiguration.
* </para></listitem>
* <listitem><para>
* GstAudioDecoder calls @stop at end of all processing.
* </para></listitem>
* </itemizedlist>
* </listitem>
* As of configuration stage, and throughout processing, GstAudioDecoder
* provides various (context) parameters, e.g. describing the format of
* output audio data (valid when output caps have been set) or current parsing state.
* Conversely, subclass can and should configure context to inform
* base class of its expectation w.r.t. buffer handling.
* <listitem>
* <itemizedlist>
* <title>Data processing</title>
* <listitem><para>
* Base class gathers input data, and optionally allows subclass
* to parse this into subsequently manageable (as defined by subclass)
* chunks. Such chunks are subsequently referred to as 'frames',
* though they may or may not correspond to 1 (or more) audio format frame.
* </para></listitem>
* <listitem><para>
* Input frame is provided to subclass' @handle_frame.
* </para></listitem>
* <listitem><para>
* If codec processing results in decoded data, subclass should call
* @gst_audio_decoder_finish_frame to have decoded data pushed
* downstream.
* </para></listitem>
* <listitem><para>
* Just prior to actually pushing a buffer downstream,
* it is passed to @pre_push. Subclass should either use this callback
* to arrange for additional downstream pushing or otherwise ensure such
* custom pushing occurs after at least a method call has finished since
* setting src pad caps.
* </para></listitem>
* <listitem><para>
* During the parsing process GstAudioDecoderClass will handle both
* srcpad and sinkpad events. Sink events will be passed to subclass
* if @event callback has been provided.
* </para></listitem>
* </itemizedlist>
* </listitem>
* <listitem>
* <itemizedlist><title>Shutdown phase</title>
* <listitem><para>
* GstAudioDecoder class calls @stop to inform the subclass that data
* parsing will be stopped.
* </para></listitem>
* </itemizedlist>
* </listitem>
* </orderedlist>
*
* Subclass is responsible for providing pad template caps for
* source and sink pads. The pads need to be named "sink" and "src". It also
* needs to set the fixed caps on srcpad, when the format is ensured. This
* is typically when base class calls subclass' @set_format function, though
* it might be delayed until calling @gst_audio_decoder_finish_frame.
*
* In summary, above process should have subclass concentrating on
* codec data processing while leaving other matters to base class,
* such as most notably timestamp handling. While it may exert more control
* in this area (see e.g. @pre_push), it is very much not recommended.
*
* In particular, base class will try to arrange for perfect output timestamps
* as much as possible while tracking upstream timestamps.
* To this end, if deviation between the next ideal expected perfect timestamp
* and upstream exceeds #GstAudioDecoder:tolerance, then resync to upstream
* occurs (which would happen always if the tolerance mechanism is disabled).
*
* In non-live pipelines, baseclass can also (configurably) arrange for
* output buffer aggregation which may help to redue large(r) numbers of
* small(er) buffers being pushed and processed downstream.
*
* On the other hand, it should be noted that baseclass only provides limited
* seeking support (upon explicit subclass request), as full-fledged support
* should rather be left to upstream demuxer, parser or alike. This simple
* approach caters for seeking and duration reporting using estimated input
* bitrates.
*
* Things that subclass need to take care of:
* <itemizedlist>
* <listitem><para>Provide pad templates</para></listitem>
* <listitem><para>
* Set source pad caps when appropriate
* </para></listitem>
* <listitem><para>
* Set user-configurable properties to sane defaults for format and
* implementing codec at hand, and convey some subclass capabilities and
* expectations in context.
* </para></listitem>
* <listitem><para>
* Accept data in @handle_frame and provide encoded results to
* @gst_audio_decoder_finish_frame. If it is prepared to perform
* PLC, it should also accept NULL data in @handle_frame and provide for
* data for indicated duration.
* </para></listitem>
* </itemizedlist>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstaudiodecoder.h"
#include "gstaudioutilsprivate.h"
#include <gst/pbutils/descriptions.h>
#include <string.h>
GST_DEBUG_CATEGORY (audiodecoder_debug);
#define GST_CAT_DEFAULT audiodecoder_debug
#define GST_AUDIO_DECODER_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_AUDIO_DECODER, \
GstAudioDecoderPrivate))
enum
{
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_LATENCY,
PROP_TOLERANCE,
PROP_PLC
};
#define DEFAULT_LATENCY 0
#define DEFAULT_TOLERANCE 0
#define DEFAULT_PLC FALSE
#define DEFAULT_DRAINABLE TRUE
#define DEFAULT_NEEDS_FORMAT FALSE
typedef struct _GstAudioDecoderContext
{
/* last negotiated input caps */
GstCaps *input_caps;
/* (output) audio format */
GstAudioInfo info;
gboolean output_format_changed;
/* parsing state */
gboolean eos;
gboolean sync;
gboolean had_output_data;
gboolean had_input_data;
/* misc */
gint delay;
/* output */
gboolean do_plc;
gboolean do_estimate_rate;
gint max_errors;
/* MT-protected (with LOCK) */
GstClockTime min_latency;
GstClockTime max_latency;
GstAllocator *allocator;
GstAllocationParams params;
} GstAudioDecoderContext;
struct _GstAudioDecoderPrivate
{
/* activation status */
gboolean active;
/* input base/first ts as basis for output ts */
GstClockTime base_ts;
/* input samples processed and sent downstream so far (w.r.t. base_ts) */
guint64 samples;
/* collected input data */
GstAdapter *adapter;
/* tracking input ts for changes */
GstClockTime prev_ts;
guint64 prev_distance;
/* frames obtained from input */
GQueue frames;
/* collected output data */
GstAdapter *adapter_out;
/* ts and duration for output data collected above */
GstClockTime out_ts, out_dur;
/* mark outgoing discont */
gboolean discont;
/* subclass gave all it could already */
gboolean drained;
/* subclass currently being forcibly drained */
gboolean force;
/* input bps estimatation */
/* global in bytes seen */
guint64 bytes_in;
/* global samples sent out */
guint64 samples_out;
/* bytes flushed during parsing */
guint sync_flush;
/* error count */
gint error_count;
/* codec id tag */
GstTagList *taglist;
gboolean taglist_changed;
/* whether circumstances allow output aggregation */
gint agg;
/* reverse playback queues */
/* collect input */
GList *gather;
/* to-be-decoded */
GList *decode;
/* reversed output */
GList *queued;
/* context storage */
GstAudioDecoderContext ctx;
/* properties */
GstClockTime latency;
GstClockTime tolerance;
gboolean plc;
gboolean drainable;
gboolean needs_format;
/* pending serialized sink events, will be sent from finish_frame() */
GList *pending_events;
};
//* Default channel layouts taken from audioconvert */
static const GstAudioChannelPosition default_positions[8][8] = {
/* 1 channel */
{
GST_AUDIO_CHANNEL_POSITION_MONO,
},
/* 2 channels */
{
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
},
/* 3 channels (2.1) */
{
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_LFE1,
},
/* 4 channels (4.0) */
{
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
},
/* 5 channels */
{
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
},
/* 6 channels (5.1) */
{
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
GST_AUDIO_CHANNEL_POSITION_LFE1,
},
/* 7 channels (6.1) */
{
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
GST_AUDIO_CHANNEL_POSITION_LFE1,
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
},
/* 8 channels (7.1) */
{
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
GST_AUDIO_CHANNEL_POSITION_LFE1,
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT,
}
};
static void gst_audio_decoder_finalize (GObject * object);
static void gst_audio_decoder_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_audio_decoder_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static void gst_audio_decoder_clear_queues (GstAudioDecoder * dec);
static GstFlowReturn gst_audio_decoder_chain_reverse (GstAudioDecoder *
dec, GstBuffer * buf);
static GstStateChangeReturn gst_audio_decoder_change_state (GstElement *
element, GstStateChange transition);
static gboolean gst_audio_decoder_sink_eventfunc (GstAudioDecoder * dec,
GstEvent * event);
static gboolean gst_audio_decoder_src_eventfunc (GstAudioDecoder * dec,
GstEvent * event);
static gboolean gst_audio_decoder_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_audio_decoder_src_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_audio_decoder_sink_setcaps (GstAudioDecoder * dec,
GstCaps * caps);
static GstFlowReturn gst_audio_decoder_chain (GstPad * pad, GstObject * parent,
GstBuffer * buf);
static gboolean gst_audio_decoder_src_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static gboolean gst_audio_decoder_sink_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static void gst_audio_decoder_reset (GstAudioDecoder * dec, gboolean full);
static gboolean gst_audio_decoder_decide_allocation_default (GstAudioDecoder *
dec, GstQuery * query);
static gboolean gst_audio_decoder_propose_allocation_default (GstAudioDecoder *
dec, GstQuery * query);
static gboolean gst_audio_decoder_negotiate_default (GstAudioDecoder * dec);
static gboolean gst_audio_decoder_negotiate_unlocked (GstAudioDecoder * dec);
static gboolean gst_audio_decoder_handle_gap (GstAudioDecoder * dec,
GstEvent * event);
static GstElementClass *parent_class = NULL;
static void gst_audio_decoder_class_init (GstAudioDecoderClass * klass);
static void gst_audio_decoder_init (GstAudioDecoder * dec,
GstAudioDecoderClass * klass);
GType
gst_audio_decoder_get_type (void)
{
static volatile gsize audio_decoder_type = 0;
if (g_once_init_enter (&audio_decoder_type)) {
GType _type;
static const GTypeInfo audio_decoder_info = {
sizeof (GstAudioDecoderClass),
NULL,
NULL,
(GClassInitFunc) gst_audio_decoder_class_init,
NULL,
NULL,
sizeof (GstAudioDecoder),
0,
(GInstanceInitFunc) gst_audio_decoder_init,
};
_type = g_type_register_static (GST_TYPE_ELEMENT,
"GstAudioDecoder", &audio_decoder_info, G_TYPE_FLAG_ABSTRACT);
g_once_init_leave (&audio_decoder_type, _type);
}
return audio_decoder_type;
}
static void
gst_audio_decoder_class_init (GstAudioDecoderClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
GstAudioDecoderClass *audiodecoder_class;
gobject_class = G_OBJECT_CLASS (klass);
element_class = GST_ELEMENT_CLASS (klass);
audiodecoder_class = GST_AUDIO_DECODER_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
g_type_class_add_private (klass, sizeof (GstAudioDecoderPrivate));
GST_DEBUG_CATEGORY_INIT (audiodecoder_debug, "audiodecoder", 0,
"audio decoder base class");
gobject_class->set_property = gst_audio_decoder_set_property;
gobject_class->get_property = gst_audio_decoder_get_property;
gobject_class->finalize = gst_audio_decoder_finalize;
element_class->change_state =
GST_DEBUG_FUNCPTR (gst_audio_decoder_change_state);
/* Properties */
g_object_class_install_property (gobject_class, PROP_LATENCY,
g_param_spec_int64 ("min-latency", "Minimum Latency",
"Aggregate output data to a minimum of latency time (ns)",
0, G_MAXINT64, DEFAULT_LATENCY,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_TOLERANCE,
g_param_spec_int64 ("tolerance", "Tolerance",
"Perfect ts while timestamp jitter/imperfection within tolerance (ns)",
0, G_MAXINT64, DEFAULT_TOLERANCE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PLC,
g_param_spec_boolean ("plc", "Packet Loss Concealment",
"Perform packet loss concealment (if supported)",
DEFAULT_PLC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
audiodecoder_class->sink_event =
GST_DEBUG_FUNCPTR (gst_audio_decoder_sink_eventfunc);
audiodecoder_class->src_event =
GST_DEBUG_FUNCPTR (gst_audio_decoder_src_eventfunc);
audiodecoder_class->propose_allocation =
GST_DEBUG_FUNCPTR (gst_audio_decoder_propose_allocation_default);
audiodecoder_class->decide_allocation =
GST_DEBUG_FUNCPTR (gst_audio_decoder_decide_allocation_default);
audiodecoder_class->negotiate =
GST_DEBUG_FUNCPTR (gst_audio_decoder_negotiate_default);
}
static void
gst_audio_decoder_init (GstAudioDecoder * dec, GstAudioDecoderClass * klass)
{
GstPadTemplate *pad_template;
GST_DEBUG_OBJECT (dec, "gst_audio_decoder_init");
dec->priv = GST_AUDIO_DECODER_GET_PRIVATE (dec);
/* Setup sink pad */
pad_template =
gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "sink");
g_return_if_fail (pad_template != NULL);
dec->sinkpad = gst_pad_new_from_template (pad_template, "sink");
gst_pad_set_event_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (gst_audio_decoder_sink_event));
gst_pad_set_chain_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (gst_audio_decoder_chain));
gst_pad_set_query_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (gst_audio_decoder_sink_query));
gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
GST_DEBUG_OBJECT (dec, "sinkpad created");
/* Setup source pad */
pad_template =
gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "src");
g_return_if_fail (pad_template != NULL);
dec->srcpad = gst_pad_new_from_template (pad_template, "src");
gst_pad_set_event_function (dec->srcpad,
GST_DEBUG_FUNCPTR (gst_audio_decoder_src_event));
gst_pad_set_query_function (dec->srcpad,
GST_DEBUG_FUNCPTR (gst_audio_decoder_src_query));
gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
GST_DEBUG_OBJECT (dec, "srcpad created");
dec->priv->adapter = gst_adapter_new ();
dec->priv->adapter_out = gst_adapter_new ();
g_queue_init (&dec->priv->frames);
g_rec_mutex_init (&dec->stream_lock);
/* property default */
dec->priv->latency = DEFAULT_LATENCY;
dec->priv->tolerance = DEFAULT_TOLERANCE;
dec->priv->plc = DEFAULT_PLC;
dec->priv->drainable = DEFAULT_DRAINABLE;
dec->priv->needs_format = DEFAULT_NEEDS_FORMAT;
/* init state */
dec->priv->ctx.min_latency = 0;
dec->priv->ctx.max_latency = 0;
gst_audio_decoder_reset (dec, TRUE);
GST_DEBUG_OBJECT (dec, "init ok");
}
static void
gst_audio_decoder_reset (GstAudioDecoder * dec, gboolean full)
{
GST_DEBUG_OBJECT (dec, "gst_audio_decoder_reset");
GST_AUDIO_DECODER_STREAM_LOCK (dec);
if (full) {
dec->priv->active = FALSE;
dec->priv->bytes_in = 0;
dec->priv->samples_out = 0;
dec->priv->agg = -1;
dec->priv->error_count = 0;
gst_audio_decoder_clear_queues (dec);
if (dec->priv->taglist) {
gst_tag_list_unref (dec->priv->taglist);
dec->priv->taglist = NULL;
}
dec->priv->taglist_changed = FALSE;
gst_segment_init (&dec->input_segment, GST_FORMAT_TIME);
gst_segment_init (&dec->output_segment, GST_FORMAT_TIME);
g_list_foreach (dec->priv->pending_events, (GFunc) gst_event_unref, NULL);
g_list_free (dec->priv->pending_events);
dec->priv->pending_events = NULL;
if (dec->priv->ctx.allocator)
gst_object_unref (dec->priv->ctx.allocator);
gst_caps_replace (&dec->priv->ctx.input_caps, NULL);
memset (&dec->priv->ctx, 0, sizeof (dec->priv->ctx));
gst_audio_info_init (&dec->priv->ctx.info);
dec->priv->ctx.max_errors = GST_AUDIO_DECODER_MAX_ERRORS;
dec->priv->ctx.had_output_data = FALSE;
dec->priv->ctx.had_input_data = FALSE;
}
g_queue_foreach (&dec->priv->frames, (GFunc) gst_buffer_unref, NULL);
g_queue_clear (&dec->priv->frames);
gst_adapter_clear (dec->priv->adapter);
gst_adapter_clear (dec->priv->adapter_out);
dec->priv->out_ts = GST_CLOCK_TIME_NONE;
dec->priv->out_dur = 0;
dec->priv->prev_ts = GST_CLOCK_TIME_NONE;
dec->priv->prev_distance = 0;
dec->priv->drained = TRUE;
dec->priv->base_ts = GST_CLOCK_TIME_NONE;
dec->priv->samples = 0;
dec->priv->discont = TRUE;
dec->priv->sync_flush = FALSE;
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
}
static void
gst_audio_decoder_finalize (GObject * object)
{
GstAudioDecoder *dec;
g_return_if_fail (GST_IS_AUDIO_DECODER (object));
dec = GST_AUDIO_DECODER (object);
if (dec->priv->adapter) {
g_object_unref (dec->priv->adapter);
}
if (dec->priv->adapter_out) {
g_object_unref (dec->priv->adapter_out);
}
g_rec_mutex_clear (&dec->stream_lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_audio_decoder_push_event (GstAudioDecoder * dec, GstEvent * event)
{
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEGMENT:{
GstSegment seg;
GST_AUDIO_DECODER_STREAM_LOCK (dec);
gst_event_copy_segment (event, &seg);
GST_DEBUG_OBJECT (dec, "starting segment %" GST_SEGMENT_FORMAT, &seg);
dec->output_segment = seg;
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
break;
}
default:
break;
}
return gst_pad_push_event (dec->srcpad, event);
}
static gboolean
gst_audio_decoder_negotiate_default (GstAudioDecoder * dec)
{
GstAudioDecoderClass *klass;
gboolean res = TRUE;
GstCaps *caps;
GstCaps *prevcaps;
GstQuery *query = NULL;
GstAllocator *allocator;
GstAllocationParams params;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), FALSE);
g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (&dec->priv->ctx.info), FALSE);
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
caps = gst_audio_info_to_caps (&dec->priv->ctx.info);
GST_DEBUG_OBJECT (dec, "setting src caps %" GST_PTR_FORMAT, caps);
if (dec->priv->pending_events) {
GList **pending_events, *l;
pending_events = &dec->priv->pending_events;
GST_DEBUG_OBJECT (dec, "Pushing pending events");
for (l = *pending_events; l;) {
GstEvent *event = GST_EVENT (l->data);
GList *tmp;
if (GST_EVENT_TYPE (event) < GST_EVENT_CAPS) {
gst_audio_decoder_push_event (dec, l->data);
tmp = l;
l = l->next;
*pending_events = g_list_delete_link (*pending_events, tmp);
} else {
l = l->next;
}
}
}
prevcaps = gst_pad_get_current_caps (dec->srcpad);
if (!prevcaps || !gst_caps_is_equal (prevcaps, caps))
res = gst_pad_set_caps (dec->srcpad, caps);
if (prevcaps)
gst_caps_unref (prevcaps);
if (!res)
goto done;
dec->priv->ctx.output_format_changed = FALSE;
query = gst_query_new_allocation (caps, TRUE);
if (!gst_pad_peer_query (dec->srcpad, query)) {
GST_DEBUG_OBJECT (dec, "didn't get downstream ALLOCATION hints");
}
g_assert (klass->decide_allocation != NULL);
res = klass->decide_allocation (dec, query);
GST_DEBUG_OBJECT (dec, "ALLOCATION (%d) params: %" GST_PTR_FORMAT, res,
query);
if (!res)
goto no_decide_allocation;
/* we got configuration from our peer or the decide_allocation method,
* parse them */
if (gst_query_get_n_allocation_params (query) > 0) {
gst_query_parse_nth_allocation_param (query, 0, &allocator, &params);
} else {
allocator = NULL;
gst_allocation_params_init (&params);
}
if (dec->priv->ctx.allocator)
gst_object_unref (dec->priv->ctx.allocator);
dec->priv->ctx.allocator = allocator;
dec->priv->ctx.params = params;
done:
if (query)
gst_query_unref (query);
gst_caps_unref (caps);
return res;
/* ERRORS */
no_decide_allocation:
{
GST_WARNING_OBJECT (dec, "Subclass failed to decide allocation");
goto done;
}
}
static gboolean
gst_audio_decoder_negotiate_unlocked (GstAudioDecoder * dec)
{
GstAudioDecoderClass *klass = GST_AUDIO_DECODER_GET_CLASS (dec);
gboolean ret = TRUE;
if (G_LIKELY (klass->negotiate))
ret = klass->negotiate (dec);
return ret;
}
/**
* gst_audio_decoder_negotiate:
* @dec: a #GstAudioDecoder
*
* Negotiate with downstream elements to currently configured #GstAudioInfo.
* Unmark GST_PAD_FLAG_NEED_RECONFIGURE in any case. But mark it again if
* negotiate fails.
*
* Returns: #TRUE if the negotiation succeeded, else #FALSE.
*/
gboolean
gst_audio_decoder_negotiate (GstAudioDecoder * dec)
{
GstAudioDecoderClass *klass;
gboolean res = TRUE;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), FALSE);
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GST_AUDIO_DECODER_STREAM_LOCK (dec);
gst_pad_check_reconfigure (dec->srcpad);
if (klass->negotiate) {
res = klass->negotiate (dec);
if (!res)
gst_pad_mark_reconfigure (dec->srcpad);
}
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
return res;
}
/**
* gst_audio_decoder_set_output_format:
* @dec: a #GstAudioDecoder
* @info: #GstAudioInfo
*
* Configure output info on the srcpad of @dec.
*
* Returns: %TRUE on success.
**/
gboolean
gst_audio_decoder_set_output_format (GstAudioDecoder * dec,
const GstAudioInfo * info)
{
gboolean res = TRUE;
guint old_rate;
GstCaps *caps = NULL;
GstCaps *templ_caps;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), FALSE);
g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (info), FALSE);
GST_DEBUG_OBJECT (dec, "Setting output format");
GST_AUDIO_DECODER_STREAM_LOCK (dec);
/* If the audio info can't be converted to caps,
* it was invalid */
caps = gst_audio_info_to_caps (info);
if (!caps)
goto refuse_caps;
/* Only allow caps that are a subset of the template caps */
templ_caps = gst_pad_get_pad_template_caps (dec->srcpad);
if (!gst_caps_is_subset (caps, templ_caps)) {
GST_WARNING_OBJECT (dec, "Requested output format %" GST_PTR_FORMAT
" do not match template %" GST_PTR_FORMAT, caps, templ_caps);
gst_caps_unref (templ_caps);
goto refuse_caps;
}
gst_caps_unref (templ_caps);
/* adjust ts tracking to new sample rate */
old_rate = GST_AUDIO_INFO_RATE (&dec->priv->ctx.info);
if (GST_CLOCK_TIME_IS_VALID (dec->priv->base_ts) && old_rate) {
dec->priv->base_ts +=
GST_FRAMES_TO_CLOCK_TIME (dec->priv->samples, old_rate);
dec->priv->samples = 0;
}
/* copy the GstAudioInfo */
dec->priv->ctx.info = *info;
dec->priv->ctx.output_format_changed = TRUE;
done:
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
if (caps)
gst_caps_unref (caps);
return res;
/* ERRORS */
refuse_caps:
{
GST_WARNING_OBJECT (dec, "invalid output format");
res = FALSE;
goto done;
}
}
static gboolean
gst_audio_decoder_sink_setcaps (GstAudioDecoder * dec, GstCaps * caps)
{
GstAudioDecoderClass *klass;
gboolean res = TRUE;
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GST_DEBUG_OBJECT (dec, "caps: %" GST_PTR_FORMAT, caps);
GST_AUDIO_DECODER_STREAM_LOCK (dec);
if (dec->priv->ctx.input_caps
&& gst_caps_is_equal (dec->priv->ctx.input_caps, caps)) {
GST_DEBUG_OBJECT (dec, "Caps did not change, not setting again");
goto done;
}
/* NOTE pbutils only needed here */
/* TODO maybe (only) upstream demuxer/parser etc should handle this ? */
#if 0
if (!dec->priv->taglist)
dec->priv->taglist = gst_tag_list_new ();
dec->priv->taglist = gst_tag_list_make_writable (dec->priv->taglist);
gst_pb_utils_add_codec_description_to_tag_list (dec->priv->taglist,
GST_TAG_AUDIO_CODEC, caps);
dec->priv->taglist_changed = TRUE;
#endif
if (klass->set_format)
res = klass->set_format (dec, caps);
if (res)
gst_caps_replace (&dec->priv->ctx.input_caps, caps);
done:
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
return res;
}
static void
gst_audio_decoder_setup (GstAudioDecoder * dec)
{
GstQuery *query;
gboolean res;
/* check if in live pipeline, then latency messing is no-no */
query = gst_query_new_latency ();
res = gst_pad_peer_query (dec->sinkpad, query);
if (res) {
gst_query_parse_latency (query, &res, NULL, NULL);
res = !res;
}
gst_query_unref (query);
/* normalize to bool */
dec->priv->agg = ! !res;
}
static GstFlowReturn
gst_audio_decoder_push_forward (GstAudioDecoder * dec, GstBuffer * buf)
{
GstAudioDecoderClass *klass;
GstAudioDecoderPrivate *priv;
GstAudioDecoderContext *ctx;
GstFlowReturn ret = GST_FLOW_OK;
GstClockTime ts;
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
priv = dec->priv;
ctx = &dec->priv->ctx;
g_return_val_if_fail (ctx->info.bpf != 0, GST_FLOW_ERROR);
if (G_UNLIKELY (!buf)) {
g_assert_not_reached ();
return GST_FLOW_OK;
}
ctx->had_output_data = TRUE;
ts = GST_BUFFER_TIMESTAMP (buf);
GST_LOG_OBJECT (dec,
"clipping buffer of size %" G_GSIZE_FORMAT " with ts %" GST_TIME_FORMAT
", duration %" GST_TIME_FORMAT, gst_buffer_get_size (buf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
/* clip buffer */
buf = gst_audio_buffer_clip (buf, &dec->output_segment, ctx->info.rate,
ctx->info.bpf);
if (G_UNLIKELY (!buf)) {
GST_DEBUG_OBJECT (dec, "no data after clipping to segment");
if (dec->output_segment.rate >= 0) {
if (ts >= dec->output_segment.stop)
ret = GST_FLOW_EOS;
} else if (ts < dec->output_segment.start) {
ret = GST_FLOW_EOS;
}
goto exit;
}
/* decorate */
if (G_UNLIKELY (priv->discont)) {
GST_LOG_OBJECT (dec, "marking discont");
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
priv->discont = FALSE;
}
/* track where we are */
if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buf))) {
/* duration should always be valid for raw audio */
g_assert (GST_BUFFER_DURATION_IS_VALID (buf));
dec->output_segment.position =
GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf);
}
if (klass->pre_push) {
/* last chance for subclass to do some dirty stuff */
ret = klass->pre_push (dec, &buf);
if (ret != GST_FLOW_OK || !buf) {
GST_DEBUG_OBJECT (dec, "subclass returned %s, buf %p",
gst_flow_get_name (ret), buf);
if (buf)
gst_buffer_unref (buf);
goto exit;
}
}
GST_LOG_OBJECT (dec,
"pushing buffer of size %" G_GSIZE_FORMAT " with ts %" GST_TIME_FORMAT
", duration %" GST_TIME_FORMAT, gst_buffer_get_size (buf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
ret = gst_pad_push (dec->srcpad, buf);
exit:
return ret;
}
/* mini aggregator combining output buffers into fewer larger ones,
* if so allowed/configured */
static GstFlowReturn
gst_audio_decoder_output (GstAudioDecoder * dec, GstBuffer * buf)
{
GstAudioDecoderPrivate *priv;
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *inbuf = NULL;
priv = dec->priv;
if (G_UNLIKELY (priv->agg < 0))
gst_audio_decoder_setup (dec);
if (G_LIKELY (buf)) {
GST_LOG_OBJECT (dec,
"output buffer of size %" G_GSIZE_FORMAT " with ts %" GST_TIME_FORMAT
", duration %" GST_TIME_FORMAT, gst_buffer_get_size (buf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
}
again:
inbuf = NULL;
if (priv->agg && dec->priv->latency > 0) {
gint av;
gboolean assemble = FALSE;
const GstClockTimeDiff tol = 10 * GST_MSECOND;
GstClockTimeDiff diff = -100 * GST_MSECOND;
av = gst_adapter_available (priv->adapter_out);
if (G_UNLIKELY (!buf)) {
/* forcibly send current */
assemble = TRUE;
GST_LOG_OBJECT (dec, "forcing fragment flush");
} else if (av && (!GST_BUFFER_TIMESTAMP_IS_VALID (buf) ||
!GST_CLOCK_TIME_IS_VALID (priv->out_ts) ||
((diff = GST_CLOCK_DIFF (GST_BUFFER_TIMESTAMP (buf),
priv->out_ts + priv->out_dur)) > tol) || diff < -tol)) {
assemble = TRUE;
GST_LOG_OBJECT (dec, "buffer %d ms apart from current fragment",
(gint) (diff / GST_MSECOND));
} else {
/* add or start collecting */
if (!av) {
GST_LOG_OBJECT (dec, "starting new fragment");
priv->out_ts = GST_BUFFER_TIMESTAMP (buf);
} else {
GST_LOG_OBJECT (dec, "adding to fragment");
}
gst_adapter_push (priv->adapter_out, buf);
priv->out_dur += GST_BUFFER_DURATION (buf);
av += gst_buffer_get_size (buf);
buf = NULL;
}
if (priv->out_dur > dec->priv->latency)
assemble = TRUE;
if (av && assemble) {
GST_LOG_OBJECT (dec, "assembling fragment");
inbuf = buf;
buf = gst_adapter_take_buffer (priv->adapter_out, av);
GST_BUFFER_TIMESTAMP (buf) = priv->out_ts;
GST_BUFFER_DURATION (buf) = priv->out_dur;
priv->out_ts = GST_CLOCK_TIME_NONE;
priv->out_dur = 0;
}
}
if (G_LIKELY (buf)) {
if (dec->output_segment.rate > 0.0) {
ret = gst_audio_decoder_push_forward (dec, buf);
GST_LOG_OBJECT (dec, "buffer pushed: %s", gst_flow_get_name (ret));
} else {
ret = GST_FLOW_OK;
priv->queued = g_list_prepend (priv->queued, buf);
GST_LOG_OBJECT (dec, "buffer queued");
}
if (inbuf) {
buf = inbuf;
goto again;
}
}
return ret;
}
static void
send_pending_events (GstAudioDecoder * dec)
{
GstAudioDecoderPrivate *priv = dec->priv;
GList *pending_events, *l;
pending_events = priv->pending_events;
priv->pending_events = NULL;
GST_DEBUG_OBJECT (dec, "Pushing pending events");
for (l = pending_events; l; l = l->next)
gst_audio_decoder_push_event (dec, l->data);
g_list_free (pending_events);
}
/* Iterate the list of pending events, and ensure
* the current output segment is up to date for
* decoding */
static void
apply_pending_events (GstAudioDecoder * dec)
{
GstAudioDecoderPrivate *priv = dec->priv;
GList *l;
GST_DEBUG_OBJECT (dec, "Applying pending segments");
for (l = priv->pending_events; l; l = l->next) {
GstEvent *event = GST_EVENT (l->data);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEGMENT:{
GstSegment seg;
GST_AUDIO_DECODER_STREAM_LOCK (dec);
gst_event_copy_segment (event, &seg);
GST_DEBUG_OBJECT (dec, "starting segment %" GST_SEGMENT_FORMAT, &seg);
dec->output_segment = seg;
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
break;
}
default:
break;
}
}
}
static GstFlowReturn
check_pending_reconfigure (GstAudioDecoder * dec)
{
GstFlowReturn ret = GST_FLOW_OK;
GstAudioDecoderContext *ctx;
gboolean needs_reconfigure;
ctx = &dec->priv->ctx;
needs_reconfigure = gst_pad_check_reconfigure (dec->srcpad);
if (G_UNLIKELY (ctx->output_format_changed ||
(GST_AUDIO_INFO_IS_VALID (&ctx->info)
&& needs_reconfigure))) {
if (!gst_audio_decoder_negotiate_unlocked (dec)) {
gst_pad_mark_reconfigure (dec->srcpad);
if (GST_PAD_IS_FLUSHING (dec->srcpad))
ret = GST_FLOW_FLUSHING;
else
ret = GST_FLOW_NOT_NEGOTIATED;
}
}
return ret;
}
/**
* gst_audio_decoder_finish_frame:
* @dec: a #GstAudioDecoder
* @buf: decoded data
* @frames: number of decoded frames represented by decoded data
*
* Collects decoded data and pushes it downstream.
*
* @buf may be NULL in which case the indicated number of frames
* are discarded and considered to have produced no output
* (e.g. lead-in or setup frames).
* Otherwise, source pad caps must be set when it is called with valid
* data in @buf.
*
* Note that a frame received in gst_audio_decoder_handle_frame() may be
* invalidated by a call to this function.
*
* Returns: a #GstFlowReturn that should be escalated to caller (of caller)
*/
GstFlowReturn
gst_audio_decoder_finish_frame (GstAudioDecoder * dec, GstBuffer * buf,
gint frames)
{
GstAudioDecoderPrivate *priv;
GstAudioDecoderContext *ctx;
gint samples = 0;
GstClockTime ts, next_ts;
gsize size;
GstFlowReturn ret = GST_FLOW_OK;
/* subclass should not hand us no data */
g_return_val_if_fail (buf == NULL || gst_buffer_get_size (buf) > 0,
GST_FLOW_ERROR);
/* no dummy calls please */
g_return_val_if_fail (frames != 0, GST_FLOW_ERROR);
priv = dec->priv;
ctx = &dec->priv->ctx;
size = buf ? gst_buffer_get_size (buf) : 0;
/* must know the output format by now */
g_return_val_if_fail (buf == NULL || GST_AUDIO_INFO_IS_VALID (&ctx->info),
GST_FLOW_ERROR);
GST_LOG_OBJECT (dec,
"accepting %" G_GSIZE_FORMAT " bytes == %" G_GSIZE_FORMAT
" samples for %d frames", buf ? size : -1,
buf ? size / ctx->info.bpf : -1, frames);
GST_AUDIO_DECODER_STREAM_LOCK (dec);
if (buf) {
ret = check_pending_reconfigure (dec);
if (ret == GST_FLOW_FLUSHING || ret == GST_FLOW_NOT_NEGOTIATED)
goto exit;
if (priv->pending_events)
send_pending_events (dec);
}
/* output shoud be whole number of sample frames */
if (G_LIKELY (buf && ctx->info.bpf)) {
if (size % ctx->info.bpf)
goto wrong_buffer;
/* per channel least */
samples = size / ctx->info.bpf;
}
/* frame and ts book-keeping */
if (G_UNLIKELY (frames < 0)) {
if (G_UNLIKELY (-frames - 1 > priv->frames.length)) {
GST_ELEMENT_WARNING (dec, STREAM, DECODE,
("received more decoded frames %d than provided %d", frames,
priv->frames.length), (NULL));
frames = 0;
} else {
frames = priv->frames.length + frames + 1;
}
} else if (G_UNLIKELY (frames > priv->frames.length)) {
if (G_LIKELY (!priv->force)) {
GST_ELEMENT_WARNING (dec, STREAM, DECODE,
("received more decoded frames %d than provided %d", frames,
priv->frames.length), (NULL));
}
frames = priv->frames.length;
}
if (G_LIKELY (priv->frames.length))
ts = GST_BUFFER_TIMESTAMP (priv->frames.head->data);
else
ts = GST_CLOCK_TIME_NONE;
GST_DEBUG_OBJECT (dec, "leading frame ts %" GST_TIME_FORMAT,
GST_TIME_ARGS (ts));
while (priv->frames.length && frames) {
gst_buffer_unref (g_queue_pop_head (&priv->frames));
dec->priv->ctx.delay = dec->priv->frames.length;
frames--;
}
if (G_UNLIKELY (!buf))
goto exit;
/* lock on */
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (priv->base_ts))) {
priv->base_ts = ts;
GST_DEBUG_OBJECT (dec, "base_ts now %" GST_TIME_FORMAT, GST_TIME_ARGS (ts));
}
/* still no valid ts, track the segment one */
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (priv->base_ts)) &&
dec->output_segment.rate > 0.0) {
priv->base_ts = dec->output_segment.start;
}
/* slightly convoluted approach caters for perfect ts if subclass desires */
if (GST_CLOCK_TIME_IS_VALID (ts)) {
if (dec->priv->tolerance > 0) {
GstClockTimeDiff diff;
g_assert (GST_CLOCK_TIME_IS_VALID (priv->base_ts));
next_ts = priv->base_ts +
gst_util_uint64_scale (priv->samples, GST_SECOND, ctx->info.rate);
GST_LOG_OBJECT (dec,
"buffer is %" G_GUINT64_FORMAT " samples past base_ts %"
GST_TIME_FORMAT ", expected ts %" GST_TIME_FORMAT, priv->samples,
GST_TIME_ARGS (priv->base_ts), GST_TIME_ARGS (next_ts));
diff = GST_CLOCK_DIFF (next_ts, ts);
GST_LOG_OBJECT (dec, "ts diff %d ms", (gint) (diff / GST_MSECOND));
/* if within tolerance,
* discard buffer ts and carry on producing perfect stream,
* otherwise resync to ts */
if (G_UNLIKELY (diff < (gint64) - dec->priv->tolerance ||
diff > (gint64) dec->priv->tolerance)) {
GST_DEBUG_OBJECT (dec, "base_ts resync");
priv->base_ts = ts;
priv->samples = 0;
}
} else {
GST_DEBUG_OBJECT (dec, "base_ts resync");
priv->base_ts = ts;
priv->samples = 0;
}
}
/* delayed one-shot stuff until confirmed data */
if (priv->taglist && priv->taglist_changed) {
GST_DEBUG_OBJECT (dec, "codec tag %" GST_PTR_FORMAT, priv->taglist);
if (!gst_tag_list_is_empty (priv->taglist))
gst_audio_decoder_push_event (dec,
gst_event_new_tag (gst_tag_list_ref (priv->taglist)));
priv->taglist_changed = FALSE;
}
buf = gst_buffer_make_writable (buf);
if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (priv->base_ts))) {
GST_BUFFER_TIMESTAMP (buf) =
priv->base_ts +
GST_FRAMES_TO_CLOCK_TIME (priv->samples, ctx->info.rate);
GST_BUFFER_DURATION (buf) = priv->base_ts +
GST_FRAMES_TO_CLOCK_TIME (priv->samples + samples, ctx->info.rate) -
GST_BUFFER_TIMESTAMP (buf);
} else {
GST_BUFFER_TIMESTAMP (buf) = GST_CLOCK_TIME_NONE;
GST_BUFFER_DURATION (buf) =
GST_FRAMES_TO_CLOCK_TIME (samples, ctx->info.rate);
}
priv->samples += samples;
priv->samples_out += samples;
/* we got data, so note things are looking up */
if (G_UNLIKELY (dec->priv->error_count))
dec->priv->error_count = 0;
ret = gst_audio_decoder_output (dec, buf);
exit:
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
return ret;
/* ERRORS */
wrong_buffer:
{
GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL),
("buffer size %" G_GSIZE_FORMAT " not a multiple of %d", size,
ctx->info.bpf));
gst_buffer_unref (buf);
ret = GST_FLOW_ERROR;
goto exit;
}
}
static GstFlowReturn
gst_audio_decoder_handle_frame (GstAudioDecoder * dec,
GstAudioDecoderClass * klass, GstBuffer * buffer)
{
/* Skip decoding and send a GAP instead if
* GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO is set and we have timestamps
* FIXME: We only do this for forward playback atm, because reverse
* playback would require accumulating GAP events and pushing them
* out in reverse order as for normal audio samples
*/
if (G_UNLIKELY (dec->input_segment.rate > 0.0
&& dec->input_segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO)) {
if (buffer) {
GstClockTime ts = GST_BUFFER_PTS (buffer);
if (GST_CLOCK_TIME_IS_VALID (ts)) {
GstEvent *event = gst_event_new_gap (ts, GST_BUFFER_DURATION (buffer));
gst_buffer_unref (buffer);
GST_LOG_OBJECT (dec, "Skipping decode in trickmode and sending gap");
gst_audio_decoder_handle_gap (dec, event);
return GST_FLOW_OK;
}
}
}
if (G_LIKELY (buffer)) {
gsize size = gst_buffer_get_size (buffer);
/* keep around for admin */
GST_LOG_OBJECT (dec,
"tracking frame size %" G_GSIZE_FORMAT ", ts %" GST_TIME_FORMAT, size,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
g_queue_push_tail (&dec->priv->frames, buffer);
dec->priv->ctx.delay = dec->priv->frames.length;
dec->priv->bytes_in += size;
} else {
GST_LOG_OBJECT (dec, "providing subclass with NULL frame");
}
return klass->handle_frame (dec, buffer);
}
/* maybe subclass configurable instead, but this allows for a whole lot of
* raw samples, so at least quite some encoded ... */
#define GST_AUDIO_DECODER_MAX_SYNC 10 * 8 * 2 * 1024
static GstFlowReturn
gst_audio_decoder_push_buffers (GstAudioDecoder * dec, gboolean force)
{
GstAudioDecoderClass *klass;
GstAudioDecoderPrivate *priv;
GstAudioDecoderContext *ctx;
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *buffer;
gint av, flush;
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
priv = dec->priv;
ctx = &dec->priv->ctx;
g_return_val_if_fail (klass->handle_frame != NULL, GST_FLOW_ERROR);
av = gst_adapter_available (priv->adapter);
GST_DEBUG_OBJECT (dec, "available: %d", av);
while (ret == GST_FLOW_OK) {
flush = 0;
ctx->eos = force;
if (G_LIKELY (av)) {
gint len;
GstClockTime ts;
guint64 distance;
/* parse if needed */
if (klass->parse) {
gint offset = 0;
/* limited (legacy) parsing; avoid whole of baseparse */
GST_DEBUG_OBJECT (dec, "parsing available: %d", av);
/* piggyback sync state on discont */
ctx->sync = !priv->discont;
ret = klass->parse (dec, priv->adapter, &offset, &len);
g_assert (offset <= av);
if (offset) {
/* jumped a bit */
GST_DEBUG_OBJECT (dec, "skipped %d; setting DISCONT", offset);
gst_adapter_flush (priv->adapter, offset);
flush = offset;
/* avoid parsing indefinitely */
priv->sync_flush += offset;
if (priv->sync_flush > GST_AUDIO_DECODER_MAX_SYNC)
goto parse_failed;
}
if (ret == GST_FLOW_EOS) {
GST_LOG_OBJECT (dec, "no frame yet");
ret = GST_FLOW_OK;
break;
} else if (ret == GST_FLOW_OK) {
GST_LOG_OBJECT (dec, "frame at offset %d of length %d", offset, len);
g_assert (len);
g_assert (offset + len <= av);
priv->sync_flush = 0;
} else {
break;
}
} else {
len = av;
}
/* track upstream ts, but do not get stuck if nothing new upstream */
ts = gst_adapter_prev_pts (priv->adapter, &distance);
if (ts != priv->prev_ts || distance <= priv->prev_distance) {
priv->prev_ts = ts;
priv->prev_distance = distance;
} else {
GST_LOG_OBJECT (dec, "ts == prev_ts; discarding");
ts = GST_CLOCK_TIME_NONE;
}
buffer = gst_adapter_take_buffer (priv->adapter, len);
buffer = gst_buffer_make_writable (buffer);
GST_BUFFER_TIMESTAMP (buffer) = ts;
flush += len;
priv->force = FALSE;
} else {
if (!force)
break;
if (!priv->drainable) {
priv->drained = TRUE;
break;
}
buffer = NULL;
priv->force = TRUE;
}
ret = gst_audio_decoder_handle_frame (dec, klass, buffer);
/* do not keep pushing it ... */
if (G_UNLIKELY (!av)) {
priv->drained = TRUE;
break;
}
av -= flush;
g_assert (av >= 0);
}
GST_LOG_OBJECT (dec, "done pushing to subclass");
return ret;
/* ERRORS */
parse_failed:
{
GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("failed to parse stream"));
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
gst_audio_decoder_drain (GstAudioDecoder * dec)
{
GstFlowReturn ret;
if (dec->priv->drained && !dec->priv->gather)
return GST_FLOW_OK;
/* Apply any pending events before draining, as that
* may update the pending segment info */
apply_pending_events (dec);
/* dispatch reverse pending buffers */
/* chain eventually calls upon drain as well, but by that time
* gather list should be clear, so ok ... */
if (dec->output_segment.rate < 0.0 && dec->priv->gather)
gst_audio_decoder_chain_reverse (dec, NULL);
/* have subclass give all it can */
ret = gst_audio_decoder_push_buffers (dec, TRUE);
if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (dec, "audio decoder push buffers failed");
goto drain_failed;
}
/* ensure all output sent */
ret = gst_audio_decoder_output (dec, NULL);
if (ret != GST_FLOW_OK)
GST_WARNING_OBJECT (dec, "audio decoder output failed");
drain_failed:
/* everything should be away now */
if (dec->priv->frames.length) {
/* not fatal/impossible though if subclass/codec eats stuff */
GST_WARNING_OBJECT (dec, "still %d frames left after draining",
dec->priv->frames.length);
g_queue_foreach (&dec->priv->frames, (GFunc) gst_buffer_unref, NULL);
g_queue_clear (&dec->priv->frames);
}
/* discard (unparsed) leftover */
gst_adapter_clear (dec->priv->adapter);
return ret;
}
/* hard == FLUSH, otherwise discont */
static GstFlowReturn
gst_audio_decoder_flush (GstAudioDecoder * dec, gboolean hard)
{
GstAudioDecoderClass *klass;
GstFlowReturn ret = GST_FLOW_OK;
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GST_LOG_OBJECT (dec, "flush hard %d", hard);
if (!hard) {
ret = gst_audio_decoder_drain (dec);
} else {
gst_audio_decoder_clear_queues (dec);
gst_segment_init (&dec->input_segment, GST_FORMAT_TIME);
gst_segment_init (&dec->output_segment, GST_FORMAT_TIME);
dec->priv->error_count = 0;
}
/* only bother subclass with flushing if known it is already alive
* and kicking out stuff */
if (klass->flush && dec->priv->samples_out > 0)
klass->flush (dec, hard);
/* and get (re)set for the sequel */
gst_audio_decoder_reset (dec, FALSE);
return ret;
}
static GstFlowReturn
gst_audio_decoder_chain_forward (GstAudioDecoder * dec, GstBuffer * buffer)
{
GstFlowReturn ret = GST_FLOW_OK;
/* discard silly case, though maybe ts may be of value ?? */
if (G_UNLIKELY (gst_buffer_get_size (buffer) == 0)) {
GST_DEBUG_OBJECT (dec, "discarding empty buffer");
gst_buffer_unref (buffer);
goto exit;
}
/* grab buffer */
gst_adapter_push (dec->priv->adapter, buffer);
buffer = NULL;
/* new stuff, so we can push subclass again */
dec->priv->drained = FALSE;
/* hand to subclass */
ret = gst_audio_decoder_push_buffers (dec, FALSE);
exit:
GST_LOG_OBJECT (dec, "chain-done");
return ret;
}
static void
gst_audio_decoder_clear_queues (GstAudioDecoder * dec)
{
GstAudioDecoderPrivate *priv = dec->priv;
g_list_foreach (priv->queued, (GFunc) gst_mini_object_unref, NULL);
g_list_free (priv->queued);
priv->queued = NULL;
g_list_foreach (priv->gather, (GFunc) gst_mini_object_unref, NULL);
g_list_free (priv->gather);
priv->gather = NULL;
g_list_foreach (priv->decode, (GFunc) gst_mini_object_unref, NULL);
g_list_free (priv->decode);
priv->decode = NULL;
}
/*
* Input:
* Buffer decoding order: 7 8 9 4 5 6 3 1 2 EOS
* Discont flag: D D D D
*
* - Each Discont marks a discont in the decoding order.
*
* for vorbis, each buffer is a keyframe when we have the previous
* buffer. This means that to decode buffer 7, we need buffer 6, which
* arrives out of order.
*
* we first gather buffers in the gather queue until we get a DISCONT. We
* prepend each incomming buffer so that they are in reversed order.
*
* gather queue: 9 8 7
* decode queue:
* output queue:
*
* When a DISCONT is received (buffer 4), we move the gather queue to the
* decode queue. This is simply done be taking the head of the gather queue
* and prepending it to the decode queue. This yields:
*
* gather queue:
* decode queue: 7 8 9
* output queue:
*
* Then we decode each buffer in the decode queue in order and put the output
* buffer in the output queue. The first buffer (7) will not produce any output
* because it needs the previous buffer (6) which did not arrive yet. This
* yields:
*
* gather queue:
* decode queue: 7 8 9
* output queue: 9 8
*
* Then we remove the consumed buffers from the decode queue. Buffer 7 is not
* completely consumed, we need to keep it around for when we receive buffer
* 6. This yields:
*
* gather queue:
* decode queue: 7
* output queue: 9 8
*
* Then we accumulate more buffers:
*
* gather queue: 6 5 4
* decode queue: 7
* output queue:
*
* prepending to the decode queue on DISCONT yields:
*
* gather queue:
* decode queue: 4 5 6 7
* output queue:
*
* after decoding and keeping buffer 4:
*
* gather queue:
* decode queue: 4
* output queue: 7 6 5
*
* Etc..
*/
static GstFlowReturn
gst_audio_decoder_flush_decode (GstAudioDecoder * dec)
{
GstAudioDecoderPrivate *priv = dec->priv;
GstFlowReturn res = GST_FLOW_OK;
GstClockTime timestamp;
GList *walk;
walk = priv->decode;
GST_DEBUG_OBJECT (dec, "flushing buffers to decoder");
/* clear buffer and decoder state */
gst_audio_decoder_flush (dec, FALSE);
while (walk) {
GList *next;
GstBuffer *buf = GST_BUFFER_CAST (walk->data);
GST_DEBUG_OBJECT (dec, "decoding buffer %p, ts %" GST_TIME_FORMAT,
buf, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
next = g_list_next (walk);
/* decode buffer, resulting data prepended to output queue */
gst_buffer_ref (buf);
res = gst_audio_decoder_chain_forward (dec, buf);
/* if we generated output, we can discard the buffer, else we
* keep it in the queue */
if (priv->queued) {
GST_DEBUG_OBJECT (dec, "decoded buffer to %p", priv->queued->data);
priv->decode = g_list_delete_link (priv->decode, walk);
gst_buffer_unref (buf);
} else {
GST_DEBUG_OBJECT (dec, "buffer did not decode, keeping");
}
walk = next;
}
/* drain any aggregation (or otherwise) leftover */
gst_audio_decoder_drain (dec);
/* now send queued data downstream */
timestamp = GST_CLOCK_TIME_NONE;
while (priv->queued) {
GstBuffer *buf = GST_BUFFER_CAST (priv->queued->data);
GstClockTime duration;
duration = GST_BUFFER_DURATION (buf);
/* duration should always be valid for raw audio */
g_assert (GST_CLOCK_TIME_IS_VALID (duration));
/* interpolate (backward) if needed */
if (G_LIKELY (timestamp != -1)) {
if (timestamp > duration)
timestamp -= duration;
else
timestamp = 0;
}
if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
GST_LOG_OBJECT (dec, "applying reverse interpolated ts %"
GST_TIME_FORMAT, GST_TIME_ARGS (timestamp));
GST_BUFFER_TIMESTAMP (buf) = timestamp;
} else {
/* track otherwise */
timestamp = GST_BUFFER_TIMESTAMP (buf);
GST_LOG_OBJECT (dec, "tracking ts %" GST_TIME_FORMAT,
GST_TIME_ARGS (timestamp));
}
if (G_LIKELY (res == GST_FLOW_OK)) {
GST_DEBUG_OBJECT (dec, "pushing buffer %p of size %" G_GSIZE_FORMAT ", "
"time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT, buf,
gst_buffer_get_size (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
/* should be already, but let's be sure */
buf = gst_buffer_make_writable (buf);
/* avoid stray DISCONT from forward processing,
* which have no meaning in reverse pushing */
GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
res = gst_audio_decoder_push_forward (dec, buf);
} else {
gst_buffer_unref (buf);
}
priv->queued = g_list_delete_link (priv->queued, priv->queued);
}
return res;
}
static GstFlowReturn
gst_audio_decoder_chain_reverse (GstAudioDecoder * dec, GstBuffer * buf)
{
GstAudioDecoderPrivate *priv = dec->priv;
GstFlowReturn result = GST_FLOW_OK;
/* if we have a discont, move buffers to the decode list */
if (!buf || GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)) {
GST_DEBUG_OBJECT (dec, "received discont");
while (priv->gather) {
GstBuffer *gbuf;
gbuf = GST_BUFFER_CAST (priv->gather->data);
/* remove from the gather list */
priv->gather = g_list_delete_link (priv->gather, priv->gather);
/* copy to decode queue */
priv->decode = g_list_prepend (priv->decode, gbuf);
}
/* decode stuff in the decode queue */
gst_audio_decoder_flush_decode (dec);
}
if (G_LIKELY (buf)) {
GST_DEBUG_OBJECT (dec, "gathering buffer %p of size %" G_GSIZE_FORMAT ", "
"time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT, buf,
gst_buffer_get_size (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
/* add buffer to gather queue */
priv->gather = g_list_prepend (priv->gather, buf);
}
return result;
}
static GstFlowReturn
gst_audio_decoder_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
GstAudioDecoder *dec;
GstFlowReturn ret;
dec = GST_AUDIO_DECODER (parent);
if (G_UNLIKELY (!gst_pad_has_current_caps (pad) && dec->priv->needs_format))
goto not_negotiated;
GST_LOG_OBJECT (dec,
"received buffer of size %" G_GSIZE_FORMAT " with ts %" GST_TIME_FORMAT
", duration %" GST_TIME_FORMAT, gst_buffer_get_size (buffer),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)));
GST_AUDIO_DECODER_STREAM_LOCK (dec);
dec->priv->ctx.had_input_data = TRUE;
if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) {
gint64 samples, ts;
/* track present position */
ts = dec->priv->base_ts;
samples = dec->priv->samples;
GST_DEBUG_OBJECT (dec, "handling discont");
gst_audio_decoder_flush (dec, FALSE);
dec->priv->discont = TRUE;
/* buffer may claim DISCONT loudly, if it can't tell us where we are now,
* we'll stick to where we were ...
* Particularly useful/needed for upstream BYTE based */
if (dec->input_segment.rate > 0.0
&& !GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
GST_DEBUG_OBJECT (dec, "... but restoring previous ts tracking");
dec->priv->base_ts = ts;
dec->priv->samples = samples;
}
}
if (dec->input_segment.rate > 0.0)
ret = gst_audio_decoder_chain_forward (dec, buffer);
else
ret = gst_audio_decoder_chain_reverse (dec, buffer);
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
return ret;
/* ERRORS */
not_negotiated:
{
GST_ELEMENT_ERROR (dec, CORE, NEGOTIATION, (NULL),
("decoder not initialized"));
gst_buffer_unref (buffer);
return GST_FLOW_NOT_NEGOTIATED;
}
}
/* perform upstream byte <-> time conversion (duration, seeking)
* if subclass allows and if enough data for moderately decent conversion */
static inline gboolean
gst_audio_decoder_do_byte (GstAudioDecoder * dec)
{
return dec->priv->ctx.do_estimate_rate && dec->priv->ctx.info.bpf &&
dec->priv->ctx.info.rate <= dec->priv->samples_out;
}
/* Must be called holding the GST_AUDIO_DECODER_STREAM_LOCK */
static gboolean
gst_audio_decoder_negotiate_default_caps (GstAudioDecoder * dec)
{
GstCaps *caps;
gint i;
gint channels = 0;
gint rate;
guint64 channel_mask = 0;
gint caps_size;
GstStructure *structure;
caps = gst_pad_get_current_caps (dec->srcpad);
if (caps && !gst_audio_info_from_caps (&dec->priv->ctx.info, caps))
goto caps_error;
if (caps)
gst_caps_unref (caps);
caps = gst_pad_get_allowed_caps (dec->srcpad);
if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps))
goto caps_error;
/* before fixating, try to use whatever upstream provided */
caps = gst_caps_make_writable (caps);
caps_size = gst_caps_get_size (caps);
if (dec->priv->ctx.input_caps) {
GstCaps *sinkcaps = dec->priv->ctx.input_caps;
GstStructure *structure = gst_caps_get_structure (sinkcaps, 0);
if (gst_structure_get_int (structure, "rate", &rate)) {
for (i = 0; i < caps_size; i++) {
gst_structure_set (gst_caps_get_structure (caps, i), "rate",
G_TYPE_INT, rate, NULL);
}
}
if (gst_structure_get_int (structure, "channels", &channels)) {
for (i = 0; i < caps_size; i++) {
gst_structure_set (gst_caps_get_structure (caps, i), "channels",
G_TYPE_INT, channels, NULL);
}
}
if (gst_structure_get (structure, "channel-mask", GST_TYPE_BITMASK,
&channel_mask, NULL)) {
for (i = 0; i < caps_size; i++) {
gst_structure_set (gst_caps_get_structure (caps, i), "channel-mask",
GST_TYPE_BITMASK, channel_mask, NULL);
}
}
}
for (i = 0; i < caps_size; i++) {
structure = gst_caps_get_structure (caps, i);
gst_structure_fixate_field_nearest_int (structure,
"channels", GST_AUDIO_DEF_CHANNELS);
gst_structure_fixate_field_nearest_int (structure,
"rate", GST_AUDIO_DEF_RATE);
}
caps = gst_caps_fixate (caps);
structure = gst_caps_get_structure (caps, 0);
/* Need to add a channel-mask if channels > 2 */
gst_structure_get_int (structure, "channels", &channels);
if (channels > 2 && !gst_structure_has_field (structure, "channel-mask")) {
if (channels <= 8) {
channel_mask = 0;
for (i = 0; i < channels; i++)
channel_mask |=
G_GUINT64_CONSTANT (1) << default_positions[channels - 1][i];
gst_structure_set (structure, "channel-mask",
GST_TYPE_BITMASK, channel_mask, NULL);
} else {
GST_WARNING_OBJECT (dec, "No default channel-mask for %d channels",
channels);
}
}
if (!caps || !gst_audio_info_from_caps (&dec->priv->ctx.info, caps))
goto caps_error;
GST_INFO_OBJECT (dec,
"Chose default caps %" GST_PTR_FORMAT " for initial gap", caps);
gst_caps_unref (caps);
if (!gst_audio_decoder_negotiate_unlocked (dec)) {
GST_INFO_OBJECT (dec, "Failed to negotiate default caps for initial gap");
gst_pad_mark_reconfigure (dec->srcpad);
return FALSE;
}
return TRUE;
caps_error:
{
if (caps)
gst_caps_unref (caps);
return FALSE;
}
}
static gboolean
gst_audio_decoder_handle_gap (GstAudioDecoder * dec, GstEvent * event)
{
gboolean ret;
GstClockTime timestamp, duration;
/* Ensure we have caps first */
GST_AUDIO_DECODER_STREAM_LOCK (dec);
if (!GST_AUDIO_INFO_IS_VALID (&dec->priv->ctx.info)) {
if (!gst_audio_decoder_negotiate_default_caps (dec)) {
GST_ELEMENT_ERROR (dec, STREAM, FORMAT, (NULL),
("Decoder output not negotiated before GAP event."));
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
return FALSE;
}
}
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
gst_event_parse_gap (event, &timestamp, &duration);
/* time progressed without data, see if we can fill the gap with
* some concealment data */
GST_DEBUG_OBJECT (dec,
"gap event: plc %d, do_plc %d, position %" GST_TIME_FORMAT
" duration %" GST_TIME_FORMAT,
dec->priv->plc, dec->priv->ctx.do_plc,
GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration));
if (dec->priv->plc && dec->priv->ctx.do_plc && dec->input_segment.rate > 0.0) {
GstAudioDecoderClass *klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GstBuffer *buf;
/* hand subclass empty frame with duration that needs covering */
buf = gst_buffer_new ();
GST_BUFFER_TIMESTAMP (buf) = timestamp;
GST_BUFFER_DURATION (buf) = duration;
/* best effort, not much error handling */
gst_audio_decoder_handle_frame (dec, klass, buf);
ret = TRUE;
gst_event_unref (event);
} else {
GstFlowReturn flowret;
/* sub-class doesn't know how to handle empty buffers,
* so just try sending GAP downstream */
flowret = check_pending_reconfigure (dec);
if (flowret == GST_FLOW_OK) {
send_pending_events (dec);
ret = gst_audio_decoder_push_event (dec, event);
} else {
ret = FALSE;
}
}
return ret;
}
static GList *
_flush_events (GstPad * pad, GList * events)
{
GList *tmp;
for (tmp = events; tmp; tmp = tmp->next) {
if (GST_EVENT_TYPE (tmp->data) != GST_EVENT_EOS &&
GST_EVENT_TYPE (tmp->data) != GST_EVENT_SEGMENT &&
GST_EVENT_IS_STICKY (tmp->data)) {
gst_pad_store_sticky_event (pad, GST_EVENT_CAST (tmp->data));
}
gst_event_unref (tmp->data);
}
g_list_free (events);
return NULL;
}
static gboolean
gst_audio_decoder_sink_eventfunc (GstAudioDecoder * dec, GstEvent * event)
{
gboolean ret;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_STREAM_START:
GST_AUDIO_DECODER_STREAM_LOCK (dec);
/* finish any data in current segment and clear the decoder
* to be ready for new stream data */
gst_audio_decoder_drain (dec);
gst_audio_decoder_flush (dec, FALSE);
GST_DEBUG_OBJECT (dec, "received STREAM_START. Clearing taglist");
/* Flush our merged taglist after a STREAM_START */
if (dec->priv->taglist) {
gst_tag_list_unref (dec->priv->taglist);
dec->priv->taglist = NULL;
}
dec->priv->taglist_changed = FALSE;
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
ret = gst_audio_decoder_push_event (dec, event);
break;
case GST_EVENT_SEGMENT:
{
GstSegment seg;
GstFormat format;
GST_AUDIO_DECODER_STREAM_LOCK (dec);
gst_event_copy_segment (event, &seg);
format = seg.format;
if (format == GST_FORMAT_TIME) {
GST_DEBUG_OBJECT (dec, "received TIME SEGMENT %" GST_SEGMENT_FORMAT,
&seg);
} else {
gint64 nstart;
GST_DEBUG_OBJECT (dec, "received SEGMENT %" GST_SEGMENT_FORMAT, &seg);
/* handle newsegment resulting from legacy simple seeking */
/* note that we need to convert this whether or not enough data
* to handle initial newsegment */
if (dec->priv->ctx.do_estimate_rate &&
gst_pad_query_convert (dec->sinkpad, GST_FORMAT_BYTES, seg.start,
GST_FORMAT_TIME, &nstart)) {
/* best attempt convert */
/* as these are only estimates, stop is kept open-ended to avoid
* premature cutting */
GST_DEBUG_OBJECT (dec, "converted to TIME start %" GST_TIME_FORMAT,
GST_TIME_ARGS (nstart));
seg.format = GST_FORMAT_TIME;
seg.start = nstart;
seg.time = nstart;
seg.stop = GST_CLOCK_TIME_NONE;
/* replace event */
gst_event_unref (event);
event = gst_event_new_segment (&seg);
} else {
GST_DEBUG_OBJECT (dec, "unsupported format; ignoring");
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
gst_event_unref (event);
ret = FALSE;
break;
}
}
/* prepare for next segment */
/* Use the segment start as a base timestamp
* in case upstream does not come up with anything better
* (e.g. upstream BYTE) */
if (format != GST_FORMAT_TIME) {
dec->priv->base_ts = seg.start;
dec->priv->samples = 0;
}
/* and follow along with segment */
dec->input_segment = seg;
dec->priv->pending_events =
g_list_append (dec->priv->pending_events, event);
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
ret = TRUE;
break;
}
case GST_EVENT_GAP:
ret = gst_audio_decoder_handle_gap (dec, event);
break;
case GST_EVENT_FLUSH_STOP:
GST_AUDIO_DECODER_STREAM_LOCK (dec);
/* prepare for fresh start */
gst_audio_decoder_flush (dec, TRUE);
dec->priv->pending_events = _flush_events (dec->srcpad,
dec->priv->pending_events);
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
/* Forward FLUSH_STOP, it is expected to be forwarded immediately
* and no buffers are queued anyway. */
ret = gst_audio_decoder_push_event (dec, event);
break;
case GST_EVENT_EOS:
GST_AUDIO_DECODER_STREAM_LOCK (dec);
gst_audio_decoder_drain (dec);
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
if (dec->priv->ctx.had_input_data && !dec->priv->ctx.had_output_data) {
GST_ELEMENT_ERROR (dec, STREAM, DECODE,
("No valid frames decoded before end of stream"),
("no valid frames found"));
}
/* Forward EOS because no buffer or serialized event will come after
* EOS and nothing could trigger another _finish_frame() call. */
if (dec->priv->pending_events)
send_pending_events (dec);
ret = gst_audio_decoder_push_event (dec, event);
break;
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
ret = gst_audio_decoder_sink_setcaps (dec, caps);
gst_event_unref (event);
break;
}
case GST_EVENT_TAG:
{
GstTagList *tags;
gst_event_parse_tag (event, &tags);
if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_STREAM) {
gst_audio_decoder_merge_tags (dec, tags, GST_TAG_MERGE_REPLACE);
gst_event_unref (event);
event = NULL;
ret = TRUE;
break;
}
/* fall through */
}
default:
if (!GST_EVENT_IS_SERIALIZED (event)) {
ret =
gst_pad_event_default (dec->sinkpad, GST_OBJECT_CAST (dec), event);
} else {
GST_DEBUG_OBJECT (dec, "Enqueuing event %d, %s", GST_EVENT_TYPE (event),
GST_EVENT_TYPE_NAME (event));
GST_AUDIO_DECODER_STREAM_LOCK (dec);
dec->priv->pending_events =
g_list_append (dec->priv->pending_events, event);
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
ret = TRUE;
}
break;
}
return ret;
}
static gboolean
gst_audio_decoder_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
GstAudioDecoder *dec;
GstAudioDecoderClass *klass;
gboolean ret;
dec = GST_AUDIO_DECODER (parent);
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GST_DEBUG_OBJECT (dec, "received event %d, %s", GST_EVENT_TYPE (event),
GST_EVENT_TYPE_NAME (event));
if (klass->sink_event)
ret = klass->sink_event (dec, event);
else {
gst_event_unref (event);
ret = FALSE;
}
return ret;
}
static gboolean
gst_audio_decoder_do_seek (GstAudioDecoder * dec, GstEvent * event)
{
GstSeekFlags flags;
GstSeekType start_type, end_type;
GstFormat format;
gdouble rate;
gint64 start, start_time, end_time;
GstSegment seek_segment;
guint32 seqnum;
gst_event_parse_seek (event, &rate, &format, &flags, &start_type,
&start_time, &end_type, &end_time);
/* we'll handle plain open-ended flushing seeks with the simple approach */
if (rate != 1.0) {
GST_DEBUG_OBJECT (dec, "unsupported seek: rate");
return FALSE;
}
if (start_type != GST_SEEK_TYPE_SET) {
GST_DEBUG_OBJECT (dec, "unsupported seek: start time");
return FALSE;
}
if ((end_type != GST_SEEK_TYPE_SET && end_type != GST_SEEK_TYPE_NONE) ||
(end_type == GST_SEEK_TYPE_SET && end_time != GST_CLOCK_TIME_NONE)) {
GST_DEBUG_OBJECT (dec, "unsupported seek: end time");
return FALSE;
}
if (!(flags & GST_SEEK_FLAG_FLUSH)) {
GST_DEBUG_OBJECT (dec, "unsupported seek: not flushing");
return FALSE;
}
memcpy (&seek_segment, &dec->output_segment, sizeof (seek_segment));
gst_segment_do_seek (&seek_segment, rate, format, flags, start_type,
start_time, end_type, end_time, NULL);
start_time = seek_segment.position;
if (!gst_pad_query_convert (dec->sinkpad, GST_FORMAT_TIME, start_time,
GST_FORMAT_BYTES, &start)) {
GST_DEBUG_OBJECT (dec, "conversion failed");
return FALSE;
}
seqnum = gst_event_get_seqnum (event);
event = gst_event_new_seek (1.0, GST_FORMAT_BYTES, flags,
GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE, -1);
gst_event_set_seqnum (event, seqnum);
GST_DEBUG_OBJECT (dec, "seeking to %" GST_TIME_FORMAT " at byte offset %"
G_GINT64_FORMAT, GST_TIME_ARGS (start_time), start);
return gst_pad_push_event (dec->sinkpad, event);
}
static gboolean
gst_audio_decoder_src_eventfunc (GstAudioDecoder * dec, GstEvent * event)
{
gboolean res;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
{
GstFormat format;
gdouble rate;
GstSeekFlags flags;
GstSeekType start_type, stop_type;
gint64 start, stop;
gint64 tstart, tstop;
guint32 seqnum;
gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
&stop_type, &stop);
seqnum = gst_event_get_seqnum (event);
/* upstream gets a chance first */
if ((res = gst_pad_push_event (dec->sinkpad, event)))
break;
/* if upstream fails for a time seek, maybe we can help if allowed */
if (format == GST_FORMAT_TIME) {
if (gst_audio_decoder_do_byte (dec))
res = gst_audio_decoder_do_seek (dec, event);
break;
}
/* ... though a non-time seek can be aided as well */
/* First bring the requested format to time */
if (!(res =
gst_pad_query_convert (dec->srcpad, format, start,
GST_FORMAT_TIME, &tstart)))
goto convert_error;
if (!(res =
gst_pad_query_convert (dec->srcpad, format, stop, GST_FORMAT_TIME,
&tstop)))
goto convert_error;
/* then seek with time on the peer */
event = gst_event_new_seek (rate, GST_FORMAT_TIME,
flags, start_type, tstart, stop_type, tstop);
gst_event_set_seqnum (event, seqnum);
res = gst_pad_push_event (dec->sinkpad, event);
break;
}
default:
res = gst_pad_event_default (dec->srcpad, GST_OBJECT_CAST (dec), event);
break;
}
done:
return res;
/* ERRORS */
convert_error:
{
GST_DEBUG_OBJECT (dec, "cannot convert start/stop for seek");
goto done;
}
}
static gboolean
gst_audio_decoder_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstAudioDecoder *dec;
GstAudioDecoderClass *klass;
gboolean ret;
dec = GST_AUDIO_DECODER (parent);
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GST_DEBUG_OBJECT (dec, "received event %d, %s", GST_EVENT_TYPE (event),
GST_EVENT_TYPE_NAME (event));
if (klass->src_event)
ret = klass->src_event (dec, event);
else {
gst_event_unref (event);
ret = FALSE;
}
return ret;
}
static gboolean
gst_audio_decoder_decide_allocation_default (GstAudioDecoder * dec,
GstQuery * query)
{
GstAllocator *allocator = NULL;
GstAllocationParams params;
gboolean update_allocator;
/* we got configuration from our peer or the decide_allocation method,
* parse them */
if (gst_query_get_n_allocation_params (query) > 0) {
/* try the allocator */
gst_query_parse_nth_allocation_param (query, 0, &allocator, &params);
update_allocator = TRUE;
} else {
allocator = NULL;
gst_allocation_params_init (&params);
update_allocator = FALSE;
}
if (update_allocator)
gst_query_set_nth_allocation_param (query, 0, allocator, &params);
else
gst_query_add_allocation_param (query, allocator, &params);
if (allocator)
gst_object_unref (allocator);
return TRUE;
}
static gboolean
gst_audio_decoder_propose_allocation_default (GstAudioDecoder * dec,
GstQuery * query)
{
return TRUE;
}
/*
* gst_audio_encoded_audio_convert:
* @fmt: audio format of the encoded audio
* @bytes: number of encoded bytes
* @samples: number of encoded samples
* @src_format: source format
* @src_value: source value
* @dest_format: destination format
* @dest_value: destination format
*
* Helper function to convert @src_value in @src_format to @dest_value in
* @dest_format for encoded audio data. Conversion is possible between
* BYTE and TIME format by using estimated bitrate based on
* @samples and @bytes (and @fmt).
*/
/* FIXME: make gst_audio_encoded_audio_convert() public? */
static gboolean
gst_audio_encoded_audio_convert (GstAudioInfo * fmt,
gint64 bytes, gint64 samples, GstFormat src_format,
gint64 src_value, GstFormat * dest_format, gint64 * dest_value)
{
gboolean res = FALSE;
g_return_val_if_fail (dest_format != NULL, FALSE);
g_return_val_if_fail (dest_value != NULL, FALSE);
if (G_UNLIKELY (src_format == *dest_format || src_value == 0 ||
src_value == -1)) {
if (dest_value)
*dest_value = src_value;
return TRUE;
}
if (samples == 0 || bytes == 0 || fmt->rate == 0) {
GST_DEBUG ("not enough metadata yet to convert");
goto exit;
}
bytes *= fmt->rate;
switch (src_format) {
case GST_FORMAT_BYTES:
switch (*dest_format) {
case GST_FORMAT_TIME:
*dest_value = gst_util_uint64_scale (src_value,
GST_SECOND * samples, bytes);
res = TRUE;
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_TIME:
switch (*dest_format) {
case GST_FORMAT_BYTES:
*dest_value = gst_util_uint64_scale (src_value, bytes,
samples * GST_SECOND);
res = TRUE;
break;
default:
res = FALSE;
}
break;
default:
res = FALSE;
}
exit:
return res;
}
/**
* gst_audio_decoder_proxy_getcaps:
* @decoder: a #GstAudioDecoder
* @caps: (allow-none): initial caps
* @filter: (allow-none): filter caps
*
* Returns caps that express @caps (or sink template caps if @caps == NULL)
* restricted to rate/channels/... combinations supported by downstream
* elements.
*
* Returns: (transfer full): a #GstCaps owned by caller
*
* Since: 1.6
*/
GstCaps *
gst_audio_decoder_proxy_getcaps (GstAudioDecoder * decoder, GstCaps * caps,
GstCaps * filter)
{
return __gst_audio_element_proxy_getcaps (GST_ELEMENT_CAST (decoder),
GST_AUDIO_DECODER_SINK_PAD (decoder),
GST_AUDIO_DECODER_SRC_PAD (decoder), caps, filter);
}
static GstCaps *
gst_audio_decoder_sink_getcaps (GstAudioDecoder * decoder, GstCaps * filter)
{
GstAudioDecoderClass *klass;
GstCaps *caps;
klass = GST_AUDIO_DECODER_GET_CLASS (decoder);
if (klass->getcaps)
caps = klass->getcaps (decoder, filter);
else
caps = gst_audio_decoder_proxy_getcaps (decoder, NULL, filter);
GST_LOG_OBJECT (decoder, "Returning caps %" GST_PTR_FORMAT, caps);
return caps;
}
static gboolean
gst_audio_decoder_sink_query (GstPad * pad, GstObject * parent,
GstQuery * query)
{
gboolean res = FALSE;
GstAudioDecoder *dec;
dec = GST_AUDIO_DECODER (parent);
GST_LOG_OBJECT (dec, "handling query: %" GST_PTR_FORMAT, query);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_FORMATS:
{
gst_query_set_formats (query, 2, GST_FORMAT_TIME, GST_FORMAT_BYTES);
res = TRUE;
break;
}
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res = gst_audio_encoded_audio_convert (&dec->priv->ctx.info,
dec->priv->bytes_in, dec->priv->samples_out,
src_fmt, src_val, &dest_fmt, &dest_val)))
goto error;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
case GST_QUERY_ALLOCATION:
{
GstAudioDecoderClass *klass = GST_AUDIO_DECODER_GET_CLASS (dec);
if (klass->propose_allocation)
res = klass->propose_allocation (dec, query);
break;
}
case GST_QUERY_CAPS:{
GstCaps *filter, *caps;
gst_query_parse_caps (query, &filter);
caps = gst_audio_decoder_sink_getcaps (dec, filter);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
res = TRUE;
break;
}
case GST_QUERY_ACCEPT_CAPS:{
GstCaps *caps;
GstCaps *allowed_caps;
GstCaps *template_caps;
gboolean accept;
gst_query_parse_accept_caps (query, &caps);
template_caps = gst_pad_get_pad_template_caps (pad);
accept = gst_caps_is_subset (caps, template_caps);
gst_caps_unref (template_caps);
if (accept) {
allowed_caps = gst_pad_query_caps (GST_AUDIO_DECODER_SINK_PAD (dec),
caps);
accept = gst_caps_can_intersect (caps, allowed_caps);
gst_caps_unref (allowed_caps);
}
gst_query_set_accept_caps_result (query, accept);
res = TRUE;
break;
}
case GST_QUERY_SEEKING:
{
GstFormat format;
/* non-TIME segments are discarded, so we won't seek that way either */
gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
if (format != GST_FORMAT_TIME) {
GST_DEBUG_OBJECT (dec, "discarding non-TIME SEEKING query");
res = FALSE;
break;
}
/* fall-through */
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
error:
return res;
}
/* FIXME ? are any of these queries (other than latency) a decoder's business ??
* also, the conversion stuff might seem to make sense, but seems to not mind
* segment stuff etc at all
* Supposedly that's backward compatibility ... */
static gboolean
gst_audio_decoder_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstAudioDecoder *dec;
gboolean res = FALSE;
dec = GST_AUDIO_DECODER (parent);
GST_LOG_OBJECT (dec, "handling query: %" GST_PTR_FORMAT, query);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_DURATION:
{
GstFormat format;
/* upstream in any case */
if ((res = gst_pad_query_default (pad, parent, query)))
break;
gst_query_parse_duration (query, &format, NULL);
/* try answering TIME by converting from BYTE if subclass allows */
if (format == GST_FORMAT_TIME && gst_audio_decoder_do_byte (dec)) {
gint64 value;
if (gst_pad_peer_query_duration (dec->sinkpad, GST_FORMAT_BYTES,
&value)) {
GST_LOG_OBJECT (dec, "upstream size %" G_GINT64_FORMAT, value);
if (gst_pad_query_convert (dec->sinkpad, GST_FORMAT_BYTES, value,
GST_FORMAT_TIME, &value)) {
gst_query_set_duration (query, GST_FORMAT_TIME, value);
res = TRUE;
}
}
}
break;
}
case GST_QUERY_POSITION:
{
GstFormat format;
gint64 time, value;
if ((res = gst_pad_peer_query (dec->sinkpad, query))) {
GST_LOG_OBJECT (dec, "returning peer response");
break;
}
/* we start from the last seen time */
time = dec->output_segment.position;
/* correct for the segment values */
time =
gst_segment_to_stream_time (&dec->output_segment, GST_FORMAT_TIME,
time);
GST_LOG_OBJECT (dec,
"query %p: our time: %" GST_TIME_FORMAT, query, GST_TIME_ARGS (time));
/* and convert to the final format */
gst_query_parse_position (query, &format, NULL);
if (!(res = gst_pad_query_convert (pad, GST_FORMAT_TIME, time,
format, &value)))
break;
gst_query_set_position (query, format, value);
GST_LOG_OBJECT (dec,
"query %p: we return %" G_GINT64_FORMAT " (format %u)", query, value,
format);
break;
}
case GST_QUERY_FORMATS:
{
gst_query_set_formats (query, 3,
GST_FORMAT_TIME, GST_FORMAT_BYTES, GST_FORMAT_DEFAULT);
res = TRUE;
break;
}
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res = gst_audio_info_convert (&dec->priv->ctx.info,
src_fmt, src_val, dest_fmt, &dest_val)))
break;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
case GST_QUERY_LATENCY:
{
if ((res = gst_pad_peer_query (dec->sinkpad, query))) {
gboolean live;
GstClockTime min_latency, max_latency;
gst_query_parse_latency (query, &live, &min_latency, &max_latency);
GST_DEBUG_OBJECT (dec, "Peer latency: live %d, min %"
GST_TIME_FORMAT " max %" GST_TIME_FORMAT, live,
GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
GST_OBJECT_LOCK (dec);
/* add our latency */
min_latency += dec->priv->ctx.min_latency;
if (max_latency == -1 || dec->priv->ctx.max_latency == -1)
max_latency = -1;
else
max_latency += dec->priv->ctx.max_latency;
GST_OBJECT_UNLOCK (dec);
gst_query_set_latency (query, live, min_latency, max_latency);
}
break;
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
return res;
}
static gboolean
gst_audio_decoder_stop (GstAudioDecoder * dec)
{
GstAudioDecoderClass *klass;
gboolean ret = TRUE;
GST_DEBUG_OBJECT (dec, "gst_audio_decoder_stop");
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
if (klass->stop) {
ret = klass->stop (dec);
}
/* clean up */
gst_audio_decoder_reset (dec, TRUE);
if (ret)
dec->priv->active = FALSE;
return ret;
}
static gboolean
gst_audio_decoder_start (GstAudioDecoder * dec)
{
GstAudioDecoderClass *klass;
gboolean ret = TRUE;
GST_DEBUG_OBJECT (dec, "gst_audio_decoder_start");
klass = GST_AUDIO_DECODER_GET_CLASS (dec);
/* arrange clean state */
gst_audio_decoder_reset (dec, TRUE);
if (klass->start) {
ret = klass->start (dec);
}
if (ret)
dec->priv->active = TRUE;
return ret;
}
static void
gst_audio_decoder_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstAudioDecoder *dec;
dec = GST_AUDIO_DECODER (object);
switch (prop_id) {
case PROP_LATENCY:
g_value_set_int64 (value, dec->priv->latency);
break;
case PROP_TOLERANCE:
g_value_set_int64 (value, dec->priv->tolerance);
break;
case PROP_PLC:
g_value_set_boolean (value, dec->priv->plc);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_audio_decoder_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstAudioDecoder *dec;
dec = GST_AUDIO_DECODER (object);
switch (prop_id) {
case PROP_LATENCY:
dec->priv->latency = g_value_get_int64 (value);
break;
case PROP_TOLERANCE:
dec->priv->tolerance = g_value_get_int64 (value);
break;
case PROP_PLC:
dec->priv->plc = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstStateChangeReturn
gst_audio_decoder_change_state (GstElement * element, GstStateChange transition)
{
GstAudioDecoder *codec;
GstAudioDecoderClass *klass;
GstStateChangeReturn ret;
codec = GST_AUDIO_DECODER (element);
klass = GST_AUDIO_DECODER_GET_CLASS (codec);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (klass->open) {
if (!klass->open (codec))
goto open_failed;
}
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
if (!gst_audio_decoder_start (codec)) {
goto start_failed;
}
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
if (!gst_audio_decoder_stop (codec)) {
goto stop_failed;
}
break;
case GST_STATE_CHANGE_READY_TO_NULL:
if (klass->close) {
if (!klass->close (codec))
goto close_failed;
}
break;
default:
break;
}
return ret;
start_failed:
{
GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to start codec"));
return GST_STATE_CHANGE_FAILURE;
}
stop_failed:
{
GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to stop codec"));
return GST_STATE_CHANGE_FAILURE;
}
open_failed:
{
GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to open codec"));
return GST_STATE_CHANGE_FAILURE;
}
close_failed:
{
GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to close codec"));
return GST_STATE_CHANGE_FAILURE;
}
}
GstFlowReturn
_gst_audio_decoder_error (GstAudioDecoder * dec, gint weight,
GQuark domain, gint code, gchar * txt, gchar * dbg, const gchar * file,
const gchar * function, gint line)
{
if (txt)
GST_WARNING_OBJECT (dec, "error: %s", txt);
if (dbg)
GST_WARNING_OBJECT (dec, "error: %s", dbg);
dec->priv->error_count += weight;
dec->priv->discont = TRUE;
if (dec->priv->ctx.max_errors >= 0
&& dec->priv->ctx.max_errors < dec->priv->error_count) {
gst_element_message_full (GST_ELEMENT (dec), GST_MESSAGE_ERROR, domain,
code, txt, dbg, file, function, line);
return GST_FLOW_ERROR;
} else {
g_free (txt);
g_free (dbg);
return GST_FLOW_OK;
}
}
/**
* gst_audio_decoder_get_audio_info:
* @dec: a #GstAudioDecoder
*
* Returns: a #GstAudioInfo describing the input audio format
*/
GstAudioInfo *
gst_audio_decoder_get_audio_info (GstAudioDecoder * dec)
{
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), NULL);
return &dec->priv->ctx.info;
}
/**
* gst_audio_decoder_set_plc_aware:
* @dec: a #GstAudioDecoder
* @plc: new plc state
*
* Indicates whether or not subclass handles packet loss concealment (plc).
*/
void
gst_audio_decoder_set_plc_aware (GstAudioDecoder * dec, gboolean plc)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
dec->priv->ctx.do_plc = plc;
}
/**
* gst_audio_decoder_get_plc_aware:
* @dec: a #GstAudioDecoder
*
* Returns: currently configured plc handling
*/
gint
gst_audio_decoder_get_plc_aware (GstAudioDecoder * dec)
{
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), 0);
return dec->priv->ctx.do_plc;
}
/**
* gst_audio_decoder_set_estimate_rate:
* @dec: a #GstAudioDecoder
* @enabled: whether to enable byte to time conversion
*
* Allows baseclass to perform byte to time estimated conversion.
*/
void
gst_audio_decoder_set_estimate_rate (GstAudioDecoder * dec, gboolean enabled)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
dec->priv->ctx.do_estimate_rate = enabled;
}
/**
* gst_audio_decoder_get_estimate_rate:
* @dec: a #GstAudioDecoder
*
* Returns: currently configured byte to time conversion setting
*/
gint
gst_audio_decoder_get_estimate_rate (GstAudioDecoder * dec)
{
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), 0);
return dec->priv->ctx.do_estimate_rate;
}
/**
* gst_audio_decoder_get_delay:
* @dec: a #GstAudioDecoder
*
* Returns: currently configured decoder delay
*/
gint
gst_audio_decoder_get_delay (GstAudioDecoder * dec)
{
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), 0);
return dec->priv->ctx.delay;
}
/**
* gst_audio_decoder_set_max_errors:
* @dec: a #GstAudioDecoder
* @num: max tolerated errors
*
* Sets numbers of tolerated decoder errors, where a tolerated one is then only
* warned about, but more than tolerated will lead to fatal error. You can set
* -1 for never returning fatal errors. Default is set to
* GST_AUDIO_DECODER_MAX_ERRORS.
*/
void
gst_audio_decoder_set_max_errors (GstAudioDecoder * dec, gint num)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
dec->priv->ctx.max_errors = num;
}
/**
* gst_audio_decoder_get_max_errors:
* @dec: a #GstAudioDecoder
*
* Returns: currently configured decoder tolerated error count.
*/
gint
gst_audio_decoder_get_max_errors (GstAudioDecoder * dec)
{
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), 0);
return dec->priv->ctx.max_errors;
}
/**
* gst_audio_decoder_set_latency:
* @dec: a #GstAudioDecoder
* @min: minimum latency
* @max: maximum latency
*
* Sets decoder latency.
*/
void
gst_audio_decoder_set_latency (GstAudioDecoder * dec,
GstClockTime min, GstClockTime max)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
g_return_if_fail (GST_CLOCK_TIME_IS_VALID (min));
g_return_if_fail (min <= max);
GST_OBJECT_LOCK (dec);
dec->priv->ctx.min_latency = min;
dec->priv->ctx.max_latency = max;
GST_OBJECT_UNLOCK (dec);
/* post latency message on the bus */
gst_element_post_message (GST_ELEMENT (dec),
gst_message_new_latency (GST_OBJECT (dec)));
}
/**
* gst_audio_decoder_get_latency:
* @dec: a #GstAudioDecoder
* @min: (out) (allow-none): a pointer to storage to hold minimum latency
* @max: (out) (allow-none): a pointer to storage to hold maximum latency
*
* Sets the variables pointed to by @min and @max to the currently configured
* latency.
*/
void
gst_audio_decoder_get_latency (GstAudioDecoder * dec,
GstClockTime * min, GstClockTime * max)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
GST_OBJECT_LOCK (dec);
if (min)
*min = dec->priv->ctx.min_latency;
if (max)
*max = dec->priv->ctx.max_latency;
GST_OBJECT_UNLOCK (dec);
}
/**
* gst_audio_decoder_get_parse_state:
* @dec: a #GstAudioDecoder
* @sync: a pointer to a variable to hold the current sync state
* @eos: a pointer to a variable to hold the current eos state
*
* Return current parsing (sync and eos) state.
*/
void
gst_audio_decoder_get_parse_state (GstAudioDecoder * dec,
gboolean * sync, gboolean * eos)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
if (sync)
*sync = dec->priv->ctx.sync;
if (eos)
*eos = dec->priv->ctx.eos;
}
/**
* gst_audio_decoder_set_plc:
* @dec: a #GstAudioDecoder
* @enabled: new state
*
* Enable or disable decoder packet loss concealment, provided subclass
* and codec are capable and allow handling plc.
*
* MT safe.
*/
void
gst_audio_decoder_set_plc (GstAudioDecoder * dec, gboolean enabled)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
GST_LOG_OBJECT (dec, "enabled: %d", enabled);
GST_OBJECT_LOCK (dec);
dec->priv->plc = enabled;
GST_OBJECT_UNLOCK (dec);
}
/**
* gst_audio_decoder_get_plc:
* @dec: a #GstAudioDecoder
*
* Queries decoder packet loss concealment handling.
*
* Returns: TRUE if packet loss concealment is enabled.
*
* MT safe.
*/
gboolean
gst_audio_decoder_get_plc (GstAudioDecoder * dec)
{
gboolean result;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), FALSE);
GST_OBJECT_LOCK (dec);
result = dec->priv->plc;
GST_OBJECT_UNLOCK (dec);
return result;
}
/**
* gst_audio_decoder_set_min_latency:
* @dec: a #GstAudioDecoder
* @num: new minimum latency
*
* Sets decoder minimum aggregation latency.
*
* MT safe.
*/
void
gst_audio_decoder_set_min_latency (GstAudioDecoder * dec, GstClockTime num)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
GST_OBJECT_LOCK (dec);
dec->priv->latency = num;
GST_OBJECT_UNLOCK (dec);
}
/**
* gst_audio_decoder_get_min_latency:
* @dec: a #GstAudioDecoder
*
* Queries decoder's latency aggregation.
*
* Returns: aggregation latency.
*
* MT safe.
*/
GstClockTime
gst_audio_decoder_get_min_latency (GstAudioDecoder * dec)
{
GstClockTime result;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), FALSE);
GST_OBJECT_LOCK (dec);
result = dec->priv->latency;
GST_OBJECT_UNLOCK (dec);
return result;
}
/**
* gst_audio_decoder_set_tolerance:
* @dec: a #GstAudioDecoder
* @tolerance: new tolerance
*
* Configures decoder audio jitter tolerance threshold.
*
* MT safe.
*/
void
gst_audio_decoder_set_tolerance (GstAudioDecoder * dec, GstClockTime tolerance)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
GST_OBJECT_LOCK (dec);
dec->priv->tolerance = tolerance;
GST_OBJECT_UNLOCK (dec);
}
/**
* gst_audio_decoder_get_tolerance:
* @dec: a #GstAudioDecoder
*
* Queries current audio jitter tolerance threshold.
*
* Returns: decoder audio jitter tolerance threshold.
*
* MT safe.
*/
GstClockTime
gst_audio_decoder_get_tolerance (GstAudioDecoder * dec)
{
GstClockTime result;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), 0);
GST_OBJECT_LOCK (dec);
result = dec->priv->tolerance;
GST_OBJECT_UNLOCK (dec);
return result;
}
/**
* gst_audio_decoder_set_drainable:
* @dec: a #GstAudioDecoder
* @enabled: new state
*
* Configures decoder drain handling. If drainable, subclass might
* be handed a NULL buffer to have it return any leftover decoded data.
* Otherwise, it is not considered so capable and will only ever be passed
* real data.
*
* MT safe.
*/
void
gst_audio_decoder_set_drainable (GstAudioDecoder * dec, gboolean enabled)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
GST_OBJECT_LOCK (dec);
dec->priv->drainable = enabled;
GST_OBJECT_UNLOCK (dec);
}
/**
* gst_audio_decoder_get_drainable:
* @dec: a #GstAudioDecoder
*
* Queries decoder drain handling.
*
* Returns: TRUE if drainable handling is enabled.
*
* MT safe.
*/
gboolean
gst_audio_decoder_get_drainable (GstAudioDecoder * dec)
{
gboolean result;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), 0);
GST_OBJECT_LOCK (dec);
result = dec->priv->drainable;
GST_OBJECT_UNLOCK (dec);
return result;
}
/**
* gst_audio_decoder_set_needs_format:
* @dec: a #GstAudioDecoder
* @enabled: new state
*
* Configures decoder format needs. If enabled, subclass needs to be
* negotiated with format caps before it can process any data. It will then
* never be handed any data before it has been configured.
* Otherwise, it might be handed data without having been configured and
* is then expected being able to do so either by default
* or based on the input data.
*
* MT safe.
*/
void
gst_audio_decoder_set_needs_format (GstAudioDecoder * dec, gboolean enabled)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
GST_OBJECT_LOCK (dec);
dec->priv->needs_format = enabled;
GST_OBJECT_UNLOCK (dec);
}
/**
* gst_audio_decoder_get_needs_format:
* @dec: a #GstAudioDecoder
*
* Queries decoder required format handling.
*
* Returns: TRUE if required format handling is enabled.
*
* MT safe.
*/
gboolean
gst_audio_decoder_get_needs_format (GstAudioDecoder * dec)
{
gboolean result;
g_return_val_if_fail (GST_IS_AUDIO_DECODER (dec), FALSE);
GST_OBJECT_LOCK (dec);
result = dec->priv->needs_format;
GST_OBJECT_UNLOCK (dec);
return result;
}
/**
* gst_audio_decoder_merge_tags:
* @dec: a #GstAudioDecoder
* @tags: a #GstTagList to merge
* @mode: the #GstTagMergeMode to use
*
* Adds tags to so-called pending tags, which will be processed
* before pushing out data downstream.
*
* Note that this is provided for convenience, and the subclass is
* not required to use this and can still do tag handling on its own,
* although it should be aware that baseclass already takes care
* of the usual CODEC/AUDIO_CODEC tags.
*
* MT safe.
*/
void
gst_audio_decoder_merge_tags (GstAudioDecoder * dec,
const GstTagList * tags, GstTagMergeMode mode)
{
GstTagList *otags;
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
g_return_if_fail (tags == NULL || GST_IS_TAG_LIST (tags));
GST_AUDIO_DECODER_STREAM_LOCK (dec);
if (tags)
GST_DEBUG_OBJECT (dec, "merging tags %" GST_PTR_FORMAT, tags);
otags = dec->priv->taglist;
dec->priv->taglist = gst_tag_list_merge (dec->priv->taglist, tags, mode);
if (otags)
gst_tag_list_unref (otags);
dec->priv->taglist_changed = TRUE;
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
}
/**
* gst_audio_decoder_allocate_output_buffer:
* @dec: a #GstAudioDecoder
* @size: size of the buffer
*
* Helper function that allocates a buffer to hold an audio frame
* for @dec's current output format.
*
* Returns: (transfer full): allocated buffer
*/
GstBuffer *
gst_audio_decoder_allocate_output_buffer (GstAudioDecoder * dec, gsize size)
{
GstBuffer *buffer = NULL;
gboolean needs_reconfigure = FALSE;
g_return_val_if_fail (size > 0, NULL);
GST_DEBUG ("alloc src buffer");
GST_AUDIO_DECODER_STREAM_LOCK (dec);
needs_reconfigure = gst_pad_check_reconfigure (dec->srcpad);
if (G_UNLIKELY (dec->priv->ctx.output_format_changed ||
(GST_AUDIO_INFO_IS_VALID (&dec->priv->ctx.info)
&& needs_reconfigure))) {
if (!gst_audio_decoder_negotiate_unlocked (dec)) {
GST_INFO_OBJECT (dec, "Failed to negotiate, fallback allocation");
gst_pad_mark_reconfigure (dec->srcpad);
goto fallback;
}
}
buffer =
gst_buffer_new_allocate (dec->priv->ctx.allocator, size,
&dec->priv->ctx.params);
if (!buffer) {
GST_INFO_OBJECT (dec, "couldn't allocate output buffer");
goto fallback;
}
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
return buffer;
fallback:
buffer = gst_buffer_new_allocate (NULL, size, NULL);
GST_AUDIO_DECODER_STREAM_UNLOCK (dec);
return buffer;
}
/**
* gst_audio_decoder_get_allocator:
* @dec: a #GstAudioDecoder
* @allocator: (out) (allow-none) (transfer full): the #GstAllocator
* used
* @params: (out) (allow-none) (transfer full): the
* #GstAllocatorParams of @allocator
*
* Lets #GstAudioDecoder sub-classes to know the memory @allocator
* used by the base class and its @params.
*
* Unref the @allocator after use it.
*/
void
gst_audio_decoder_get_allocator (GstAudioDecoder * dec,
GstAllocator ** allocator, GstAllocationParams * params)
{
g_return_if_fail (GST_IS_AUDIO_DECODER (dec));
if (allocator)
*allocator = dec->priv->ctx.allocator ?
gst_object_ref (dec->priv->ctx.allocator) : NULL;
if (params)
*params = dec->priv->ctx.params;
}