diff --git a/gst-libs/gst/audio/gstbaseaudiodecoder.c b/gst-libs/gst/audio/gstbaseaudiodecoder.c index 7bf6697416..27dbe5d1e2 100644 --- a/gst-libs/gst/audio/gstbaseaudiodecoder.c +++ b/gst-libs/gst/audio/gstbaseaudiodecoder.c @@ -1,6 +1,9 @@ /* GStreamer * Copyright (C) 2009 Igalia S.L. * Author: Iago Toral Quiroga + * Copyright (C) 2011 Mark Nauwelaerts . + * Copyright (C) 2011 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -20,89 +23,124 @@ /** * SECTION:gstbaseaudiodecoder - * @short_description: Base class for codec elements - * @see_also: #GstBaseTransform, #GstBaseSource, #GstBaseSink + * @short_description: Base class for audio decoders + * @see_also: #GstBaseTransform * - * #GstBaseAudioDecoder is the base class for codec elements ion GStreamer. It is - * a layer on top of #GstElement that provides simplified interface to plugin - * writers, hangling many details for you. Its way of operation is explained - * below. + * This base class is for audio decoders turning encoded data into + * raw audio samples. * - * Subclasses are responsible for specifying the codec's source pad caps. For - * that purpose they should provide an implementation of ::negotiate_src_caps. - * If the subclass provides an implementation of this method, it will be - * invoked by #GstBaseAudioDecoder on its sink_setcaps function. Otherwise, if - * the subclass does not provide an implementation of this method, the subclass - * will be responsible for calling gst_base_audio_decoder_set_src_caps() to - * complete the caps negotiation before any buffers are pushed out. + * GstBaseAudioDecoder and subclass should cooperate as follows. + * + * + * Configuration + * + * Initially, GstBaseAudioEncoder 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). + * + * + * GstBaseAudioEncoder 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. + * + * + * GstBaseAudioEncoder calls @stop at end of all processing. + * + * + * + * As of configuration stage, and throughout processing, GstBaseAudioDecoder + * provides a GstBaseAudioDecoderContext that provides required context, + * e.g. describing the format of output audio data + * (valid when output caps have been caps) or current parsing state. + * Conversely, subclass can and should configure context to inform + * base class of its expectation w.r.t. buffer handling. + * + * + * Data processing + * + * 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. + * + * + * Input frame is provided to subclass' @handle_frame. + * + * + * If codec processing results in decoded data, subclass should call + * @gst_base_audio_decoder_finish_frame to have decoded data pushed + * downstream. + * + * + * 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. + * + * + * During the parsing process GstBaseAudioEncoderClass will handle both + * srcpad and sinkpad events. Sink events will be passed to subclass + * if @event callback has been provided. + * + * + * + * + * Shutdown phase + * + * GstBaseAudioEncoder class calls @stop to inform the subclass that data + * parsing will be stopped. + * + * + * + * * - * Each buffer received on the codec's sink pad is pushed to its input - * adapter. When there is enough data present in the input adapter - * (configured in the #GstBaseAudioDecoder:input-buffer-size - * property), the method ::process_data is called on the subclass. Subclasses - * must provide an implementation of this method, which would read from the - * input adapter, encode or decode the data, and push it to the output adapter. - * If #GstBaseAudioDecoder:input-buffer-size is set to 0 ::process_data will be - * invoked as soon as there is any data on the input adapter. + * 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_base_audio_decoder_finish_frame. * - * Similarly, when there is enough data present on the output adapter, - * (configured in the #GstBaseAudioDecoder:output-buffer-size property), - * buffers will be pushed out through the codec's source pad. If - * #GstBaseAudioDecoder:output-buffer-size is set to 0 a buffer will be pushed - * out as soon as there is any data present on the output adapter. Notice - * that if no implementation of ::negotiate_src_caps has been provided by the - * subclass, it must call gst_base_audio_decoder_set_src_caps() to complete - * the caps negotiation process or otherwise attempting to push buffers - * through the codec's source pad will fail. + * 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. * - * It is possible for subclasses to take control on how and when buffers - * are pushed out by overriding the ::push_data method. If subclasses - * provide an implementation of this method #GstBaseAudioDecoder will - * not push buffers out by itself, instead, whenever there* is data present - * in the output adapter, it will invoke ::push_data on subclass, which - * will implement there any logic necessary for pushing buffers out when - * appropriate. In this mode of operation, the property - * ::output_buffer_size is ignored in #GstBaseAudioDecoder. In any case, - * buffers should be pushed using gst_base_audio_decoder_push_buffer(). + * 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 #GstBaseAudioDecoder:tolerance, then resync to upstream + * occurs (which would happen always if the tolerance mechanism is disabled). * - * #GstBaseAudioDecoder checks for discontinuities and handles them - * appropriately when pushing buffers out (setting the discontinuous - * flag on the output buffers when necessary). Subclasses can check if - * the data present on the adapters represents a discontinuity by checking - * the discont field of #GstBaseAudioDecoder. Also, subclasses can provide - * an implementation for the ::handle_discont method, which will be invoked - * whenever a discontinuity is detected on the source stream. + * 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. * - * Because data is not processed immediately and is stored in adapters, - * depending on how the actual codec operates it may be possible to - * receive an end-of-stream event before all the data in the adapters - * has been processed and pushed out. If this can happen, the subclass - * must provide implementation of the ::flush_input method, which should - * then read the data present int the input adapter, process it and - * store the result in the output adapter. The subclass may also want - * provide an implementation for the ::flush_output method, which would - * take care of reading the data from the output adapter and push it - * out through the codec's source pad. If no implementation is provided - * for the ::flush_out method, #GstBaseAudioDecoder will create a single - * buffer with all the data present in the output adapter and push it - * out. If a subclass needs to force a flush on the adapters for some - * reason, it should call gst_base_audio_decoder_flush(), which will then - * invoke ::flush_input and/or ::flush_output appropriately. + * On the other hand, it should be noted that baseclass does not handle seeking, + * nor are subclasses expected to do so, as this should rather be left to + * upstream demuxer, parser or alike. * - * Subclasses may provide an implementation for the ::start, ::stop - * and ::reset methods when needed. This methods will be called - * from #GstBaseAudioDecoder when needed (on state changes, - * discontinuities, etc), so they must never invoke the - * implementation on the parent class. When a subclass needs to - * start, stop or reset the codec itself, it should use the public - * functions gst_base_audio_decoder_{start,stop,reset}(), which call - * the corresponding methods on the parent class, which will then - * call the functions provided by the subclass (if any). - * - * #GstBaseAudioDecoder also provides an sink event handler. - * Subclasses that want to be notified on these events, can provide - * an implementation of the ::event function, which will be called after - * #GstBaseAudioDecoder has processed the event itself. + * Things that subclass need to take care of: + * + * Provide pad templates + * + * Set source pad caps when appropriate + * + * + * Set user-configurable properties to sane defaults for format and + * implementing codec at hand, and convey some subclass capabilities and + * expectations in context. + * + * + * Accept data in @handle_frame and provide encoded results to + * @gst_base_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. + * + * */ #ifdef HAVE_CONFIG_H @@ -111,16 +149,17 @@ #include "gstbaseaudiodecoder.h" #include -#include +#include -/* - * FIXME: maybe we need more work with the segments (see ac3 decoder) - */ +#include GST_DEBUG_CATEGORY (baseaudiodecoder_debug); #define GST_CAT_DEFAULT baseaudiodecoder_debug -/* ----- Signals and properties ----- */ +#define GST_BASE_AUDIO_DECODER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_AUDIO_DECODER, \ + GstBaseAudioDecoderPrivate)) + enum { LAST_SIGNAL @@ -129,11 +168,66 @@ enum enum { PROP_0, - PROP_INPUT_BUFFER_SIZE, - PROP_OUTPUT_BUFFER_SIZE + PROP_LATENCY, + PROP_TOLERANCE, + PROP_PLC +}; + +#define DEFAULT_LATENCY 0 +#define DEFAULT_TOLERANCE 0 +#define DEFAULT_PLC FALSE + +struct _GstBaseAudioDecoderPrivate +{ + /* 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; + /* 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; + + /* 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 */ + GstBaseAudioDecoderContext ctx; }; -/* ----- Function prototypes ----- */ static void gst_base_audio_decoder_finalize (GObject * object); static void gst_base_audio_decoder_set_property (GObject * object, @@ -141,18 +235,31 @@ static void gst_base_audio_decoder_set_property (GObject * object, static void gst_base_audio_decoder_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_base_audio_decoder_clear_queues (GstBaseAudioDecoder * dec); +static GstFlowReturn gst_base_audio_decoder_chain_reverse (GstBaseAudioDecoder * + dec, GstBuffer * buf); + static GstStateChangeReturn gst_base_audio_decoder_change_state (GstElement * element, GstStateChange transition); static gboolean gst_base_audio_decoder_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_base_audio_decoder_src_event (GstPad * pad, + GstEvent * event); static gboolean gst_base_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps); +static gboolean gst_base_audio_decoder_src_setcaps (GstPad * pad, + GstCaps * caps); static GstFlowReturn gst_base_audio_decoder_chain (GstPad * pad, GstBuffer * buf); -static void gst_base_audio_decoder_handle_discont (GstBaseAudioDecoder * codec, - GstBuffer * buf); +static gboolean gst_base_audio_decoder_src_query (GstPad * pad, + GstQuery * query); +static gboolean gst_base_audio_decoder_sink_query (GstPad * pad, + GstQuery * query); +static const GstQueryType *gst_base_audio_decoder_get_query_types (GstPad * + pad); +static void gst_base_audio_decoder_reset (GstBaseAudioDecoder * dec, + gboolean full); -/* ----- GObject setup ----- */ GST_BOILERPLATE (GstBaseAudioDecoder, gst_base_audio_decoder, GstElement, GST_TYPE_ELEMENT); @@ -160,8 +267,6 @@ GST_BOILERPLATE (GstBaseAudioDecoder, gst_base_audio_decoder, GstElement, static void gst_base_audio_decoder_base_init (gpointer g_class) { - GST_DEBUG_CATEGORY_INIT (baseaudiodecoder_debug, "baseaudiodecoder", 0, - "Base Audio Codec Classes"); } static void @@ -173,98 +278,1318 @@ gst_base_audio_decoder_class_init (GstBaseAudioDecoderClass * klass) gobject_class = G_OBJECT_CLASS (klass); element_class = GST_ELEMENT_CLASS (klass); + parent_class = g_type_class_peek_parent (klass); + + g_type_class_add_private (klass, sizeof (GstBaseAudioDecoderPrivate)); + + GST_DEBUG_CATEGORY_INIT (baseaudiodecoder_debug, "baseaudiodecoder", 0, + "baseaudiodecoder element"); + gobject_class->set_property = gst_base_audio_decoder_set_property; gobject_class->get_property = gst_base_audio_decoder_get_property; gobject_class->finalize = gst_base_audio_decoder_finalize; element_class->change_state = gst_base_audio_decoder_change_state; - klass->start = NULL; - klass->stop = NULL; - klass->reset = NULL; - klass->event = NULL; - klass->handle_discont = NULL; - klass->flush_input = NULL; - klass->flush_output = NULL; - klass->process_data = NULL; - klass->push_data = NULL; - klass->negotiate_src_caps = NULL; - /* Properties */ - g_object_class_install_property (gobject_class, PROP_INPUT_BUFFER_SIZE, - g_param_spec_uint ("input-buffer-size", "Input buffer size", - "Size of the input buffers in bytes (0 for not setting a " - "particular size)", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_LATENCY, + g_param_spec_int64 ("latency", "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_OUTPUT_BUFFER_SIZE, - g_param_spec_uint ("output-buffer-size", "Output buffer size", - "Size of the output buffers in bytes (0 for not setting a " - "particular size)", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); + 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)); } static void -gst_base_audio_decoder_init (GstBaseAudioDecoder * codec, +gst_base_audio_decoder_init (GstBaseAudioDecoder * dec, GstBaseAudioDecoderClass * klass) { GstPadTemplate *pad_template; - GST_DEBUG ("gst_base_audio_decoder_init"); + GST_DEBUG_OBJECT (dec, "gst_base_audio_decoder_init"); + + dec->priv = GST_BASE_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); - codec->sinkpad = gst_pad_new_from_template (pad_template, "sink"); - gst_pad_set_event_function (codec->sinkpad, - gst_base_audio_decoder_sink_event); - gst_pad_set_setcaps_function (codec->sinkpad, - gst_base_audio_decoder_sink_setcaps); - gst_pad_set_chain_function (codec->sinkpad, gst_base_audio_decoder_chain); - gst_element_add_pad (GST_ELEMENT (codec), codec->sinkpad); + dec->sinkpad = gst_pad_new_from_template (pad_template, "sink"); + gst_pad_set_event_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_audio_decoder_sink_event)); + gst_pad_set_setcaps_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_audio_decoder_sink_setcaps)); + gst_pad_set_chain_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_audio_decoder_chain)); + gst_pad_set_query_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_base_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); - codec->srcpad = gst_pad_new_from_template (pad_template, "src"); - gst_pad_use_fixed_caps (codec->srcpad); - gst_element_add_pad (GST_ELEMENT (codec), codec->srcpad); + dec->srcpad = gst_pad_new_from_template (pad_template, "src"); + gst_pad_set_setcaps_function (dec->srcpad, + GST_DEBUG_FUNCPTR (gst_base_audio_decoder_src_setcaps)); + gst_pad_set_event_function (dec->srcpad, + GST_DEBUG_FUNCPTR (gst_base_audio_decoder_src_event)); + gst_pad_set_query_function (dec->srcpad, + GST_DEBUG_FUNCPTR (gst_base_audio_decoder_src_query)); + gst_pad_set_query_type_function (dec->srcpad, + GST_DEBUG_FUNCPTR (gst_base_audio_decoder_get_query_types)); + gst_pad_use_fixed_caps (dec->srcpad); + gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad); + GST_DEBUG_OBJECT (dec, "srcpad created"); - /* Setup adapters */ - codec->input_adapter = gst_adapter_new (); - codec->output_adapter = gst_adapter_new (); - codec->input_buffer_size = 0; - codec->output_buffer_size = 0; + dec->priv->adapter = gst_adapter_new (); + dec->priv->adapter_out = gst_adapter_new (); + g_queue_init (&dec->priv->frames); + dec->ctx = &dec->priv->ctx; - /* Setup state */ - memset (&codec->state, 0, sizeof (GstAudioState)); - gst_segment_init (&codec->state.segment, GST_FORMAT_TIME); + /* property default */ + dec->latency = DEFAULT_LATENCY; + dec->tolerance = DEFAULT_TOLERANCE; - codec->started = FALSE; - codec->bytes_in = 0; - codec->bytes_out = 0; - codec->discont = TRUE; - codec->caps_set = FALSE; - codec->first_ts = -1; - codec->last_ts = -1; + /* init state */ + gst_base_audio_decoder_reset (dec, TRUE); + GST_DEBUG_OBJECT (dec, "init ok"); +} + +static void +gst_base_audio_decoder_reset (GstBaseAudioDecoder * dec, gboolean full) +{ + GST_DEBUG_OBJECT (dec, "gst_base_audio_decoder_reset"); + + GST_OBJECT_LOCK (dec); + + if (full) { + dec->priv->active = FALSE; + dec->priv->bytes_in = 0; + dec->priv->samples_out = 0; + dec->priv->agg = -1; + gst_base_audio_decoder_clear_queues (dec); + + g_free (dec->ctx->state.channel_pos); + memset (dec->ctx, 0, sizeof (dec->ctx)); + + gst_segment_init (&dec->segment, GST_FORMAT_TIME); + } + + 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->drained = TRUE; + dec->priv->base_ts = GST_CLOCK_TIME_NONE; + dec->priv->samples = 0; + dec->priv->discont = TRUE; + dec->priv->sync_flush = FALSE; + + GST_OBJECT_UNLOCK (dec); +} + +static void +gst_base_audio_decoder_finalize (GObject * object) +{ + GstBaseAudioDecoder *dec; + + g_return_if_fail (GST_IS_BASE_AUDIO_DECODER (object)); + dec = GST_BASE_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_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* automagically perform sanity checking of src caps; + * also extracts output data format */ +static gboolean +gst_base_audio_decoder_src_setcaps (GstPad * pad, GstCaps * caps) +{ + GstBaseAudioDecoder *dec; + GstAudioState *state; + gboolean res = TRUE, changed; + + dec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + state = &dec->ctx->state; + + GST_DEBUG_OBJECT (dec, "setting src caps %" GST_PTR_FORMAT, caps); + + /* parse caps here to check subclass; + * also makes us aware of output format */ + if (!gst_caps_is_fixed (caps)) + goto refuse_caps; + + /* adjust ts tracking to new sample rate */ + if (GST_CLOCK_TIME_IS_VALID (dec->priv->base_ts) && state->rate) { + dec->priv->base_ts += + GST_FRAMES_TO_CLOCK_TIME (dec->priv->samples, state->rate); + dec->priv->samples = 0; + } + + if (!gst_base_audio_parse_caps (caps, state, &changed)) + goto refuse_caps; + + gst_object_unref (dec); + return res; + + /* ERRORS */ +refuse_caps: + { + GST_WARNING_OBJECT (dec, "rejected caps %" GST_PTR_FORMAT, caps); + gst_object_unref (dec); + return res; + } +} + +static gboolean +gst_base_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstBaseAudioDecoder *dec; + GstBaseAudioDecoderClass *klass; + gboolean res = TRUE; + + dec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + + GST_DEBUG_OBJECT (dec, "caps: %" GST_PTR_FORMAT, caps); + + if (klass->set_format) + res = klass->set_format (dec, caps); + + g_object_unref (dec); + return res; +} + +static void +gst_base_audio_decoder_setup (GstBaseAudioDecoder * 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; +} + +/* mini aggregator combining output buffers into fewer larger ones, + * if so allowed/configured */ +static GstFlowReturn +gst_base_audio_decoder_output (GstBaseAudioDecoder * dec, GstBuffer * buf) +{ + GstBaseAudioDecoderClass *klass; + GstBaseAudioDecoderPrivate *priv; + GstBaseAudioDecoderContext *ctx; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *inbuf = NULL; + + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + priv = dec->priv; + ctx = dec->ctx; + + if (G_UNLIKELY (priv->agg < 0)) + gst_base_audio_decoder_setup (dec); + + if (G_LIKELY (buf)) { + g_return_val_if_fail (ctx->state.bpf != 0, GST_FLOW_ERROR); + + GST_LOG_OBJECT (dec, "output buffer of size %d with ts %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT, GST_BUFFER_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->segment, ctx->state.rate, + ctx->state.bpf); + if (G_UNLIKELY (!buf)) { + GST_DEBUG_OBJECT (dec, "no data after clipping to segment"); + } else { + GST_LOG_OBJECT (dec, + "buffer after segment clipping has size %d with ts %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT, GST_BUFFER_SIZE (buf), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + } + } else { + GST_DEBUG_OBJECT (dec, "no output buffer"); + } + +again: + inbuf = NULL; + if (priv->agg && dec->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_SIZE (buf); + buf = NULL; + } + if (priv->out_dur > dec->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)) { + + /* decorate */ + gst_buffer_set_caps (buf, GST_PAD_CAPS (dec->srcpad)); + + if (G_UNLIKELY (priv->discont)) { + GST_LOG_OBJECT (dec, "marking discont"); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + priv->discont = FALSE; + } + + 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->segment.last_stop = + 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 %d with ts %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT, GST_BUFFER_SIZE (buf), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + + if (dec->segment.rate > 0.0) { + ret = gst_pad_push (dec->srcpad, 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"); + } + + exit: + if (inbuf) { + buf = inbuf; + goto again; + } + } + + return ret; +} + +GstFlowReturn +gst_base_audio_decoder_finish_frame (GstBaseAudioDecoder * dec, GstBuffer * buf, + gint frames) +{ + GstBaseAudioDecoderPrivate *priv; + GstBaseAudioDecoderContext *ctx; + gint samples = 0; + GstClockTime ts, next_ts; + + /* subclass should know what it is producing by now */ + g_return_val_if_fail (buf == NULL || GST_PAD_CAPS (dec->srcpad) != NULL, + GST_FLOW_ERROR); + /* subclass should not hand us no data */ + g_return_val_if_fail (buf == NULL || GST_BUFFER_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->ctx; + + GST_LOG_OBJECT (dec, "accepting %d bytes == %d samples for %d frames", + buf ? GST_BUFFER_SIZE (buf) : -1, + buf ? GST_BUFFER_SIZE (buf) / ctx->state.bpf : -1, frames); + + /* output shoud be whole number of sample frames */ + if (G_LIKELY (buf && ctx->state.bpf)) { + if (GST_BUFFER_SIZE (buf) % ctx->state.bpf) + goto wrong_buffer; + /* per channel least */ + samples = GST_BUFFER_SIZE (buf) / ctx->state.bpf; + } + + /* frame and ts book-keeping */ + if (G_UNLIKELY (frames < 0)) { + if (G_UNLIKELY (-frames - 1 > priv->frames.length)) + goto overflow; + frames = priv->frames.length + frames + 1; + } else if (G_UNLIKELY (frames > priv->frames.length)) { + if (G_LIKELY (!priv->force)) { + /* no way we can let this pass */ + g_assert_not_reached (); + /* really no way */ + goto overflow; + } + } + + 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->ctx->delay = dec->priv->frames.length; + frames--; + } + + /* 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)); + } + + /* slightly convoluted approach caters for perfect ts if subclass desires */ + if (GST_CLOCK_TIME_IS_VALID (ts)) { + if (dec->tolerance > 0) { + GstClockTimeDiff diff; + + g_assert (GST_CLOCK_TIME_IS_VALID (priv->base_ts)); + next_ts = priv->base_ts + + gst_util_uint64_scale (samples, GST_SECOND, ctx->state.rate); + GST_LOG_OBJECT (dec, "buffer is %" G_GUINT64_FORMAT + " samples past base_ts %" GST_TIME_FORMAT + ", expected ts %" GST_TIME_FORMAT, samples, + GST_TIME_ARGS (priv->base_ts), GST_TIME_ARGS (next_ts)); + diff = GST_CLOCK_DIFF (next_ts, GST_BUFFER_TIMESTAMP (buf)); + 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 < -dec->tolerance || diff > dec->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; + } + } + + if (G_LIKELY (buf)) { + buf = gst_buffer_make_metadata_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->state.rate); + GST_BUFFER_DURATION (buf) = priv->base_ts + + GST_FRAMES_TO_CLOCK_TIME (priv->samples + samples, ctx->state.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->state.rate); + } + priv->samples += samples; + priv->samples_out += samples; + } + + return gst_base_audio_decoder_output (dec, buf); + + /* ERRORS */ +wrong_buffer: + { + GST_ELEMENT_ERROR (dec, STREAM, ENCODE, (NULL), + ("buffer size %d not a multiple of %d", GST_BUFFER_SIZE (buf), + ctx->state.bpf)); + gst_buffer_unref (buf); + return GST_FLOW_ERROR; + } +overflow: + { + GST_ELEMENT_ERROR (dec, STREAM, ENCODE, + ("received more decoded frames %d than provided %d", frames, + priv->frames.length), (NULL)); + if (buf) + gst_buffer_unref (buf); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_base_audio_decoder_handle_frame (GstBaseAudioDecoder * dec, + GstBaseAudioDecoderClass * klass, GstBuffer * buffer) +{ + if (G_LIKELY (buffer)) { + /* keep around for admin */ + GST_LOG_OBJECT (dec, "tracking frame size %d, ts %" GST_TIME_FORMAT, + GST_BUFFER_SIZE (buffer), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + g_queue_push_tail (&dec->priv->frames, buffer); + dec->ctx->delay = dec->priv->frames.length; + dec->priv->bytes_in += GST_BUFFER_SIZE (buffer); + } 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_BASE_AUDIO_DECODER_MAX_SYNC 10 * 8 * 2 * 1024 + +static GstFlowReturn +gst_base_audio_decoder_push_buffers (GstBaseAudioDecoder * dec, gboolean force) +{ + GstBaseAudioDecoderClass *klass; + GstBaseAudioDecoderPrivate *priv; + GstBaseAudioDecoderContext *ctx; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *buffer; + gint av, flush; + + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + priv = dec->priv; + ctx = dec->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; + + /* 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, "setting DISCONT"); + gst_adapter_flush (priv->adapter, offset); + flush = offset; + /* avoid parsing indefinitely */ + priv->sync_flush += offset; + if (priv->sync_flush > GST_BASE_AUDIO_DECODER_MAX_SYNC) + goto parse_failed; + } + + if (ret == GST_FLOW_UNEXPECTED) { + 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 (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_timestamp (priv->adapter, NULL); + if (ts == priv->prev_ts) { + GST_LOG_OBJECT (dec, "ts == prev_ts; discarding"); + ts = GST_CLOCK_TIME_NONE; + } else { + priv->prev_ts = ts; + } + buffer = gst_adapter_take_buffer (priv->adapter, len); + buffer = gst_buffer_make_metadata_writable (buffer); + GST_BUFFER_TIMESTAMP (buffer) = ts; + flush += len; + } else { + if (!force) + break; + buffer = NULL; + } + + ret = gst_base_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_base_audio_decoder_drain (GstBaseAudioDecoder * dec) +{ + GstFlowReturn ret; + + if (dec->priv->drained) + return GST_FLOW_OK; + else { + /* dispatch reverse pending buffers */ + /* chain eventually calls upon drain as well, but by that time + * gather list should be clear, so ok ... */ + if (dec->segment.rate < 0.0 && dec->priv->gather) + gst_base_audio_decoder_chain_reverse (dec, NULL); + /* have subclass give all it can */ + ret = gst_base_audio_decoder_push_buffers (dec, TRUE); + /* ensure all output sent */ + ret = gst_base_audio_decoder_output (dec, NULL); + /* 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_base_audio_decoder_flush (GstBaseAudioDecoder * dec, gboolean hard) +{ + GstBaseAudioDecoderClass *klass; + GstFlowReturn ret = GST_FLOW_OK; + + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + + GST_LOG_OBJECT (dec, "flush hard %d", hard); + + if (!hard) { + ret = gst_base_audio_decoder_drain (dec); + } else { + gst_segment_init (&dec->segment, GST_FORMAT_TIME); + } + /* 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_base_audio_decoder_reset (dec, FALSE); + + return ret; +} + +static GstFlowReturn +gst_base_audio_decoder_chain_forward (GstBaseAudioDecoder * dec, + GstBuffer * buffer) +{ + GstFlowReturn ret; + + /* 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_base_audio_decoder_push_buffers (dec, FALSE); + + GST_LOG_OBJECT (dec, "chain-done"); + return ret; +} + +static void +gst_base_audio_decoder_clear_queues (GstBaseAudioDecoder * dec) +{ + GstBaseAudioDecoderPrivate *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_base_audio_decoder_flush_decode (GstBaseAudioDecoder * dec) +{ + GstBaseAudioDecoderPrivate *priv = dec->priv; + GstFlowReturn res = GST_FLOW_OK; + GList *walk; + + walk = priv->decode; + + GST_DEBUG_OBJECT (dec, "flushing buffers to decoder"); + + /* clear buffer and decoder state */ + gst_base_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_base_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_base_audio_decoder_drain (dec); + + /* now send queued data downstream */ + while (priv->queued) { + GstBuffer *buf = GST_BUFFER_CAST (priv->queued->data); + + if (G_LIKELY (res == GST_FLOW_OK)) { + GST_DEBUG_OBJECT (dec, "pushing buffer %p of size %u, " + "time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT, buf, + GST_BUFFER_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_metadata_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_pad_push (dec->srcpad, buf); + } else { + gst_buffer_unref (buf); + } + + priv->queued = g_list_delete_link (priv->queued, priv->queued); + } + + return res; +} + +static GstFlowReturn +gst_base_audio_decoder_chain_reverse (GstBaseAudioDecoder * dec, + GstBuffer * buf) +{ + GstBaseAudioDecoderPrivate *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_base_audio_decoder_flush_decode (dec); + } + + if (G_LIKELY (buf)) { + GST_DEBUG_OBJECT (dec, "gathering buffer %p of size %u, " + "time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT, buf, + GST_BUFFER_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_base_audio_decoder_chain (GstPad * pad, GstBuffer * buffer) +{ + GstBaseAudioDecoder *dec; + GstFlowReturn ret; + + dec = GST_BASE_AUDIO_DECODER (GST_PAD_PARENT (pad)); + + GST_LOG_OBJECT (dec, + "received buffer of size %d with ts %" GST_TIME_FORMAT + ", duration %" GST_TIME_FORMAT, GST_BUFFER_SIZE (buffer), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); + + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) { + GST_DEBUG_OBJECT (dec, "handling discont"); + gst_base_audio_decoder_flush (dec, FALSE); + dec->priv->discont = TRUE; + } + + if (dec->segment.rate > 0.0) + ret = gst_base_audio_decoder_chain_forward (dec, buffer); + else + ret = gst_base_audio_decoder_chain_reverse (dec, buffer); + + return ret; +} + +static gboolean +gst_base_audio_decoder_sink_eventfunc (GstBaseAudioDecoder * dec, + GstEvent * event) +{ + gboolean handled = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + gboolean update; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + if (format == GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (dec, "received TIME NEW_SEGMENT %" GST_TIME_FORMAT + " -- %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT + ", accum %" GST_TIME_FORMAT, + GST_TIME_ARGS (dec->segment.start), + GST_TIME_ARGS (dec->segment.stop), + GST_TIME_ARGS (dec->segment.time), + GST_TIME_ARGS (dec->segment.accum)); + } else { + GST_DEBUG_OBJECT (dec, "received NEW_SEGMENT %" G_GINT64_FORMAT + " -- %" G_GINT64_FORMAT ", time %" G_GINT64_FORMAT + ", accum %" G_GINT64_FORMAT, + dec->segment.start, dec->segment.stop, + dec->segment.time, dec->segment.accum); + GST_DEBUG_OBJECT (dec, "unsupported format; ignoring"); + break; + } + + /* finish current segment */ + gst_base_audio_decoder_drain (dec); + + if (update) { + /* time progressed without data, see if we can fill the gap with + * some concealment data */ + GST_DEBUG_OBJECT (dec, + "segment update: plc %d, do_plc %d, last_stop %" GST_TIME_FORMAT, + dec->plc, dec->ctx->do_plc, GST_TIME_ARGS (dec->segment.last_stop)); + if (dec->plc && dec->ctx->do_plc && dec->segment.rate > 0.0 && + dec->segment.last_stop < start) { + GstBaseAudioDecoderClass *klass; + GstBuffer *buf; + + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + /* hand subclass empty frame with duration that needs covering */ + buf = gst_buffer_new (); + GST_BUFFER_DURATION (buf) = start - dec->segment.last_stop; + /* best effort, not much error handling */ + gst_base_audio_decoder_handle_frame (dec, klass, buf); + } + } else { + /* prepare for next one */ + gst_base_audio_decoder_flush (dec, FALSE); + } + + /* and follow along with segment */ + gst_segment_set_newsegment_full (&dec->segment, update, rate, arate, + format, start, stop, time); + break; + } + + case GST_EVENT_FLUSH_START: + break; + + case GST_EVENT_FLUSH_STOP: + /* prepare for fresh start */ + gst_base_audio_decoder_flush (dec, FALSE); + break; + + case GST_EVENT_EOS: + gst_base_audio_decoder_drain (dec); + break; + + default: + break; + } + + return handled; +} + +static gboolean +gst_base_audio_decoder_sink_event (GstPad * pad, GstEvent * event) +{ + GstBaseAudioDecoder *dec; + GstBaseAudioDecoderClass *klass; + gboolean handled = FALSE; + gboolean ret = TRUE; + + dec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + + GST_DEBUG_OBJECT (dec, "received event %d, %s", GST_EVENT_TYPE (event), + GST_EVENT_TYPE_NAME (event)); + + if (klass->event) + handled = klass->event (dec, event); + + if (!handled) + handled = gst_base_audio_decoder_sink_eventfunc (dec, event); + + if (!handled) + ret = gst_pad_event_default (pad, event); + + GST_DEBUG_OBJECT (dec, "event handled"); + + gst_object_unref (dec); + return ret; +} + +static gboolean +gst_base_audio_decoder_src_event (GstPad * pad, GstEvent * event) +{ + GstBaseAudioDecoder *dec; + GstBaseAudioDecoderClass *klass; + gboolean res = FALSE; + + dec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + + GST_DEBUG_OBJECT (dec, "received event %d, %s", GST_EVENT_TYPE (event), + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + GstFormat format, tformat; + gdouble rate; + GstSeekFlags flags; + GstSeekType cur_type, stop_type; + gint64 cur, stop; + gint64 tcur, tstop; + guint32 seqnum; + + gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, + &stop_type, &stop); + seqnum = gst_event_get_seqnum (event); + + /* upstream gets a chance first */ + res = gst_pad_push_event (dec->sinkpad, event); + + /* if upstream fails for a non-time seek, maybe we can help */ + if (G_LIKELY (format == GST_FORMAT_TIME || res)) + break; + + /* First bring the requested format to time */ + tformat = GST_FORMAT_TIME; + if (!(res = gst_pad_query_convert (pad, format, cur, &tformat, &tcur))) + goto convert_error; + if (!(res = gst_pad_query_convert (pad, format, stop, &tformat, &tstop))) + goto convert_error; + + /* then seek with time on the peer */ + event = gst_event_new_seek (rate, GST_FORMAT_TIME, + flags, cur_type, tcur, stop_type, tstop); + gst_event_set_seqnum (event, seqnum); + + res = gst_pad_push_event (dec->sinkpad, event); + break; + } + default: + res = gst_pad_push_event (dec->sinkpad, event); + break; + } +done: + gst_object_unref (dec); + + return res; + + /* ERRORS */ +convert_error: + { + GST_DEBUG_OBJECT (dec, "cannot convert start/stop for seek"); + goto done; + } +} + +static gboolean +gst_base_audio_decoder_sink_query (GstPad * pad, GstQuery * query) +{ + gboolean res = TRUE; + GstBaseAudioDecoder *dec; + + dec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + + 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_base_audio_encoded_audio_convert (&dec->ctx->state, + 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; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + +error: + gst_object_unref (dec); + return res; +} + +static const GstQueryType * +gst_base_audio_decoder_get_query_types (GstPad * pad) +{ + static const GstQueryType gst_base_audio_decoder_src_query_types[] = { + GST_QUERY_POSITION, + GST_QUERY_DURATION, + GST_QUERY_CONVERT, + GST_QUERY_LATENCY, + 0 + }; + + return gst_base_audio_decoder_src_query_types; +} + +/* 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_base_audio_decoder_src_query (GstPad * pad, GstQuery * query) +{ + GstBaseAudioDecoder *dec; + GstPad *peerpad; + gboolean res = FALSE; + + dec = GST_BASE_AUDIO_DECODER (GST_PAD_PARENT (pad)); + peerpad = gst_pad_get_peer (GST_PAD (dec->sinkpad)); + + GST_LOG_OBJECT (dec, "handling query: %" GST_PTR_FORMAT, query); + + switch (GST_QUERY_TYPE (query)) { + 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->segment.last_stop; + /* correct for the segment values */ + time = gst_segment_to_stream_time (&dec->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_base_audio_raw_audio_convert (&dec->ctx->state, + 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 */ + if (min_latency != -1) + min_latency += dec->ctx->min_latency; + if (max_latency != -1) + max_latency += dec->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, query); + break; + } + + gst_object_unref (peerpad); + return res; +} + +static gboolean +gst_base_audio_decoder_stop (GstBaseAudioDecoder * dec) +{ + GstBaseAudioDecoderClass *klass; + gboolean ret = TRUE; + + GST_DEBUG_OBJECT (dec, "gst_base_audio_decoder_stop"); + + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + + if (klass->stop) { + ret = klass->stop (dec); + } + + /* clean up */ + gst_base_audio_decoder_reset (dec, TRUE); + + if (ret) + dec->priv->active = FALSE; + + return TRUE; +} + +static gboolean +gst_base_audio_decoder_start (GstBaseAudioDecoder * dec) +{ + GstBaseAudioDecoderClass *klass; + gboolean ret = TRUE; + + GST_DEBUG_OBJECT (dec, "gst_base_audio_decoder_start"); + + klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec); + + /* arrange clean state */ + gst_base_audio_decoder_reset (dec, TRUE); + + if (klass->start) { + ret = klass->start (dec); + } + + if (ret) + dec->priv->active = TRUE; + + return TRUE; } static void gst_base_audio_decoder_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { - GstBaseAudioDecoder *codec; + GstBaseAudioDecoder *dec; - codec = GST_BASE_AUDIO_DECODER (object); + dec = GST_BASE_AUDIO_DECODER (object); switch (prop_id) { - case PROP_INPUT_BUFFER_SIZE: - g_value_set_uint (value, codec->input_buffer_size); + case PROP_LATENCY: + g_value_set_int64 (value, dec->latency); break; - case PROP_OUTPUT_BUFFER_SIZE: - g_value_set_uint (value, codec->output_buffer_size); + case PROP_TOLERANCE: + g_value_set_int64 (value, dec->tolerance); + break; + case PROP_PLC: + g_value_set_boolean (value, dec->plc); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -276,16 +1601,19 @@ static void gst_base_audio_decoder_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { - GstBaseAudioDecoder *codec; + GstBaseAudioDecoder *dec; - codec = GST_BASE_AUDIO_DECODER (object); + dec = GST_BASE_AUDIO_DECODER (object); switch (prop_id) { - case PROP_INPUT_BUFFER_SIZE: - codec->input_buffer_size = g_value_get_uint (value); + case PROP_LATENCY: + dec->latency = g_value_get_int64 (value); break; - case PROP_OUTPUT_BUFFER_SIZE: - codec->output_buffer_size = g_value_get_uint (value); + case PROP_TOLERANCE: + dec->tolerance = g_value_get_int64 (value); + break; + case PROP_PLC: + dec->plc = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -293,169 +1621,6 @@ gst_base_audio_decoder_set_property (GObject * object, guint prop_id, } } -static void -gst_base_audio_decoder_finalize (GObject * object) -{ - GstBaseAudioDecoder *codec; - - g_return_if_fail (GST_IS_BASE_AUDIO_DECODER (object)); - codec = GST_BASE_AUDIO_DECODER (object); - - if (codec->input_adapter) { - g_object_unref (codec->input_adapter); - } - if (codec->output_adapter) { - g_object_unref (codec->output_adapter); - } - if (codec->codec_data) { - gst_buffer_unref (codec->codec_data); - } - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -/* ----- Private element implementation ----- */ - -static void -gst_base_audio_decoder_read_state_from_caps (GstBaseAudioDecoder * codec, - GstCaps * caps) -{ - GstStructure *structure; - const GValue *codec_data; - - structure = gst_caps_get_structure (caps, 0); - - if (codec->codec_data) { - gst_buffer_unref (codec->codec_data); - codec->codec_data = NULL; - } - - codec_data = gst_structure_get_value (structure, "codec_data"); - if (codec_data && G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) { - codec->codec_data = gst_value_get_buffer (codec_data); - } - - gst_structure_get_int (structure, "channels", &codec->state.channels); - gst_structure_get_int (structure, "rate", &codec->state.rate); - gst_structure_get_int (structure, "depth", &codec->state.sample_depth); - gst_structure_get_int (structure, "width", &codec->state.bytes_per_sample); - codec->state.bytes_per_sample /= 8; - codec->state.frame_size = - codec->state.bytes_per_sample * codec->state.channels; -} - -static gboolean -gst_base_audio_decoder_sink_event (GstPad * pad, GstEvent * event) -{ - GstBaseAudioDecoder *codec; - GstBaseAudioDecoderClass *codec_class; - gboolean ret = FALSE; - - codec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_EOS: - /* Flush any data still present in the adapters */ - gst_base_audio_decoder_flush (codec); - ret = gst_pad_push_event (codec->srcpad, event); - break; - case GST_EVENT_FLUSH_STOP: - gst_base_audio_decoder_reset (codec); - ret = gst_pad_push_event (codec->srcpad, event); - break; - case GST_EVENT_NEWSEGMENT: - { - gboolean update; - GstFormat format; - gdouble rate, arate; - gint64 start, stop, time; - - gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, - &start, &stop, &time); - - if (format != GST_FORMAT_TIME) - goto newseg_wrong_format; - - if (rate <= 0.0) - goto newseg_wrong_rate; - - GST_DEBUG ("news egment %lld %lld", start, time); - gst_segment_set_newsegment_full (&codec->state.segment, - update, rate, arate, format, start, stop, time); - ret = gst_pad_push_event (codec->srcpad, event); - break; - } - default: - ret = gst_pad_push_event (codec->srcpad, event); - break; - } - - /* Let the subclass see the event too */ - if (codec_class->event) { - if (!codec_class->event (codec, event)) { - ret = FALSE; - goto subclass_event_error; - } - } - -done: - gst_object_unref (codec); - return ret; - -newseg_wrong_format: - GST_DEBUG ("received non TIME newsegment"); - gst_event_unref (event); - goto done; - -newseg_wrong_rate: - GST_DEBUG ("negative rates not supported"); - gst_event_unref (event); - goto done; - -subclass_event_error: - GST_DEBUG ("codec implementation failed to proces event"); - gst_event_unref (event); - goto done; -} - -static gboolean -gst_base_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps) -{ - GstBaseAudioDecoder *codec; - GstBaseAudioDecoderClass *codec_class; - - codec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - GST_DEBUG ("gst_base_audio_decoder_sink_setcaps %" GST_PTR_FORMAT, caps); - - /* Let the subclass provide the source caps and we will set them - on the codec's source pad */ - if (codec_class->negotiate_src_caps) { - GstCaps *src_caps; - src_caps = codec_class->negotiate_src_caps (codec, caps); - if (!gst_base_audio_decoder_set_src_caps (codec, src_caps)) { - GST_DEBUG ("Caps negotiation failed!"); - g_object_unref (codec); - gst_caps_unref (src_caps); - return FALSE; - } - gst_caps_unref (src_caps); - } else { - /* If the subclass does not provide a negotiate_src_caps method, then - it will be responsible for calling gst_base_audio_decoder_set_src_caps - with appropriate caps before we try to push buffers out */ - GST_DEBUG ("Subclass does not provide negotiate_src_caps, is that ok?"); - } - - gst_base_audio_decoder_start (codec); - - g_object_unref (codec); - - return TRUE; -} - static GstStateChangeReturn gst_base_audio_decoder_change_state (GstElement * element, GstStateChange transition) @@ -469,13 +1634,10 @@ gst_base_audio_decoder_change_state (GstElement * element, switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: - if (!gst_base_audio_decoder_start (codec)) { - goto start_failed; - } break; case GST_STATE_CHANGE_READY_TO_PAUSED: - if (!gst_base_audio_decoder_reset (codec)) { - goto reset_failed; + if (!gst_base_audio_decoder_start (codec)) { + goto start_failed; } break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: @@ -507,433 +1669,9 @@ start_failed: GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to start codec")); return GST_STATE_CHANGE_FAILURE; } -reset_failed: - { - GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to reset codec")); - return GST_STATE_CHANGE_FAILURE; - } stop_failed: { GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to stop codec")); return GST_STATE_CHANGE_FAILURE; } } - -static void -gst_base_audio_decoder_handle_discont (GstBaseAudioDecoder * codec, - GstBuffer * buffer) -{ - GstBaseAudioDecoderClass *codec_class; - - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - /* Reset codec on discont */ - if (codec->started) { - gst_base_audio_decoder_reset (codec); - } - - codec->discont = TRUE; - - /* Let the subclass do its stuff too if that is needed */ - if (codec_class->handle_discont) { - codec_class->handle_discont (codec, buffer); - } -} - -static GstFlowReturn -gst_base_audio_decoder_chain (GstPad * pad, GstBuffer * buf) -{ - GstBaseAudioDecoder *codec; - GstBaseAudioDecoderClass *codec_class; - GstBuffer *outbuf; - GstFlowReturn ret; - guint bytes_ready; - guint64 timestamp; - - codec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - GST_DEBUG ("gst_base_audio_decoder_chain"); - - /* Make sure we have started our codec */ - if (G_UNLIKELY (!codec->started)) { - if (G_UNLIKELY (!gst_base_audio_decoder_start (codec))) { - GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), - ("Failed to start codec")); - gst_object_unref (codec); - return GST_FLOW_ERROR; - } - } - - /* Handle timestamps */ - timestamp = GST_BUFFER_TIMESTAMP (buf); - if (GST_CLOCK_TIME_IS_VALID (timestamp)) { - GST_DEBUG ("buffer timestamp %" GST_TIME_FORMAT " duration:%" - GST_TIME_FORMAT, GST_TIME_ARGS (timestamp), - GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); - if (gst_adapter_available (codec->input_adapter) == 0) { - codec->first_ts = timestamp; - } - codec->last_ts = timestamp; - } - - /* Check for discontinuity */ - if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT))) { - GST_DEBUG ("received DISCONT buffer"); - gst_base_audio_decoder_handle_discont (codec, buf); - } - - /* Push buffer to the input adapter so the codec can - take data from it as needed */ - codec->bytes_in += GST_BUFFER_SIZE (buf); - gst_adapter_push (codec->input_adapter, buf); - - GST_DEBUG ("Input buffer size: %ld bytes", GST_BUFFER_SIZE (buf)); - - /* Check if we have enough data to be processed. While we have - enough data on the input adapter, instruct the element to - process it */ - ret = GST_FLOW_OK; - bytes_ready = gst_adapter_available (codec->input_adapter); - while (ret == GST_FLOW_OK && bytes_ready > 0 && - bytes_ready >= codec->input_buffer_size) { - GST_DEBUG ("Processing data"); - ret = codec_class->process_data (codec); - bytes_ready = gst_adapter_available (codec->input_adapter); - GST_DEBUG ("%ld bytes remaining on the input", bytes_ready); - } - - /* FIXME: is it possible that we have enough data in the output - adapter but we have to wait for more data before we can - push buffers out? In that case we need a custom GST_FLOW. - Not sure if we could handle pushing buffers here in that - case though, since we always push in output_buffer_size - blocks. */ - - /* If no error was raised, check if we can push buffers out */ - if (G_LIKELY (ret == GST_FLOW_OK)) { - bytes_ready = gst_adapter_available (codec->output_adapter); - GST_DEBUG ("Processed input correctly"); - GST_DEBUG ("%ld bytes on the output", bytes_ready); - - /* If the subclass wants to control how buffers are pushed out - let it do it */ - if (bytes_ready > 0 && codec_class->push_data) { - GST_DEBUG ("Calling push_data on the subclass"); - codec_class->push_data (codec); - } else if (bytes_ready > 0 && bytes_ready >= codec->output_buffer_size) { - /* We have enough data in the output adapter, so take a buffer, apply - clipping, push it out and repeat while we have enough data */ - guint bytes_to_push; - - bytes_to_push = - codec->output_buffer_size ? codec->output_buffer_size : bytes_ready; - - do { - GST_DEBUG ("Pushing a buffer out (%ld bytes)", bytes_to_push); - - outbuf = gst_adapter_take_buffer (codec->output_adapter, bytes_to_push); - - /* Set buffer timestamp/duration if needed (and possible) */ - if (!GST_BUFFER_TIMESTAMP_IS_VALID (outbuf) && codec->first_ts != -1) { - GST_DEBUG ("Computing output buffer timestamp"); - GST_BUFFER_TIMESTAMP (outbuf) = codec->first_ts; - } - - if (!GST_BUFFER_DURATION_IS_VALID (outbuf) && codec->state.frame_size) { - guint nsamples; - GST_DEBUG ("Computing output buffer duration"); - nsamples = GST_BUFFER_SIZE (outbuf) / codec->state.frame_size; - GST_BUFFER_DURATION (outbuf) = - gst_util_uint64_scale_int (GST_SECOND, nsamples, - codec->state.rate); - } - - if (codec->first_ts != -1) { - codec->first_ts += GST_BUFFER_DURATION (outbuf); - if (codec->first_ts > codec->last_ts) { - codec->last_ts = codec->first_ts; - } - } - - GST_DEBUG ("out buffer timestamp %" GST_TIME_FORMAT " duration:%" - GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), - GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf))); - - /* Clip buffer */ - if (codec->state.segment.format == GST_FORMAT_TIME || - codec->state.segment.format == GST_FORMAT_DEFAULT) { - GST_DEBUG ("Clipping buffer"); - outbuf = gst_audio_buffer_clip (outbuf, &codec->state.segment, - codec->state.rate, codec->state.frame_size); - } - - /* Set DISCONT flag on the output buffer if needed */ - if (G_LIKELY (outbuf)) { - if (G_UNLIKELY (codec->discont)) { - GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); - codec->discont = FALSE; - GST_DEBUG ("Buffer is discont"); - } - - ret = gst_base_audio_decoder_push_buffer (codec, outbuf); - } - - /* See if we can push another buffer */ - bytes_ready = gst_adapter_available (codec->output_adapter); - GST_DEBUG ("%ld bytes left on the output", bytes_ready); - } while (ret == GST_FLOW_OK && bytes_ready >= bytes_to_push); - } else { - /* We need more data before we can push a buffer out */ - GST_DEBUG ("Not pushing out, need more data"); - ret = GST_FLOW_OK; - } - } else { - /* We got an error */ - GST_DEBUG ("Got error while processing data"); - } - - GST_DEBUG ("chain-done"); - - return ret; -} - -/* ----- Element public API ----- */ - -/** - * gst_base_audio_decoder_reset: - * @codec: The #GstBaseAudioDecoder instance. - * - * Resets the codec. - * - * This method will also invoke the subclass's reset virtual method - * if available. Niotice that reseting the codec will clear the - * input and output adapters. - * - * Returns: TRUE if the start operation was successful. - */ -gboolean -gst_base_audio_decoder_reset (GstBaseAudioDecoder * codec) -{ - GstBaseAudioDecoderClass *codec_class; - - GST_DEBUG ("gst_base_audio_decoder_reset"); - - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - gst_adapter_clear (codec->input_adapter); - gst_adapter_clear (codec->output_adapter); - - /* FIXME: is this needed? */ - gst_segment_init (&codec->state.segment, GST_FORMAT_TIME); - - codec->first_ts = -1; - codec->last_ts = -1; - - if (codec_class->reset) { - codec_class->reset (codec); - } - - return TRUE; -} - -/** - * gst_base_audio_decoder_stop: - * @codec: The #GstBaseAudioDecoder instance. - * - * Stop the codec. Normally this will be used for closing resource. - * - * This method will also invoke the subclass's stop virtual method - * if available. - * - * Returns: TRUE if the start operation was successful. - */ -gboolean -gst_base_audio_decoder_stop (GstBaseAudioDecoder * codec) -{ - GstBaseAudioDecoderClass *codec_class; - - GST_DEBUG ("gst_base_audio_decoder_stop"); - - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - gst_base_audio_decoder_reset (codec); - - codec->bytes_in = 0; - codec->bytes_out = 0; - - if (codec_class->stop) { - codec_class->stop (codec); - } - - codec->started = FALSE; - - return TRUE; -} - -/** - * gst_base_audio_decoder_start: - * @codec: The #GstBaseAudioDecoder instance. - * - * Setup the codec so it can start processing data. Normally - * this will be used for opening resources needed for operation. - * - * This method will also invoke the subclass's start virtual method - * if available. - * - * Returns: TRUE if the start operation was successful. - */ -gboolean -gst_base_audio_decoder_start (GstBaseAudioDecoder * codec) -{ - GstBaseAudioDecoderClass *codec_class; - - GST_DEBUG ("gst_base_audio_decoder_start"); - - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - gst_base_audio_decoder_reset (codec); - - codec->bytes_in = 0; - codec->bytes_out = 0; - - if (codec_class->start) { - codec_class->start (codec); - } - - codec->started = TRUE; - - return TRUE; -} - -/** - * gst_base_audio_decoder_flush: - * @codec: The #GstBaseAudioDecoder instance. - * - * Flushes the input and output adapters. Subclasses should provide - * a flush_input implementation to allow flushing the input adapter. - * For the output adapter subclasses should provide a flush_output - * implementation. If no flush_output implementation is provided - * the output adapter will be flushed by pushing a single buffer - * containing all the data present in the output adapter. - * - * It is guaranteed that any data present in the adapters will be cleared - * after calling this method even if the operation flush - * operation was not successfull. - * - * Returns: TRUE if the flush operation was successful (any data present in - * the adapters was properly processed). - */ -gboolean -gst_base_audio_decoder_flush (GstBaseAudioDecoder * codec) -{ - GstFlowReturn ret_i = GST_FLOW_OK; - GstFlowReturn ret_o = GST_FLOW_OK; - guint bytes; - GstBaseAudioDecoderClass *codec_class; - - GST_DEBUG ("gst_base_audio_decoder_flush"); - - codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); - - /* Flush input adapter */ - bytes = gst_adapter_available (codec->input_adapter); - if (bytes > 0) { - GST_DEBUG ("Flushing input adapter"); - /* If the subclass provides a flush_input implementation, use that. - Otherwise we will clear the adapter and lose the data */ - if (codec_class->flush_input) { - ret_i = codec_class->flush_input (codec); - if (ret_i != GST_FLOW_OK) { - GST_DEBUG ("failed to flush input"); - } - } else { - GST_DEBUG ("Received EOS but cannot flush input, data will be lost"); - ret_i = GST_FLOW_ERROR; - } - gst_adapter_clear (codec->input_adapter); - } - - /* Flush output adapter */ - bytes = gst_adapter_available (codec->output_adapter); - if (bytes > 0) { - /* If the subclass provides a flush_output implementation, use that. - Otherwise just push a single buffer with the adapter contents */ - GST_DEBUG ("Flushing output adapter"); - if (codec_class->flush_output) { - ret_o = codec_class->flush_output (codec); - if (ret_o != GST_FLOW_OK) { - GST_DEBUG ("failed to flush output (flush_output)"); - } - } else { - GstBuffer *outbuf = - gst_adapter_take_buffer (codec->output_adapter, bytes); - ret_o = gst_base_audio_decoder_push_buffer (codec, outbuf); - gst_buffer_unref (outbuf); - if (ret_o != GST_FLOW_OK) { - GST_DEBUG ("Forced output flush failed"); - } - } - gst_adapter_clear (codec->output_adapter); - } - - return (ret_i == GST_FLOW_OK && ret_o == GST_FLOW_OK); -} - -/** - * gst_base_audio_decoder_set_src_caps: - * @codec: #GstBaseAudioDecoder instance - * @caps: The caps to set on the source pad of @codec. - * - * Attempts to set @caps as the source caps of @codec. If the new caps - * are accepted on the source pad, this will issue a flush on the adapters - * to ensure that any data received with the old caps is processed first - * and a reset of the codec. - * - * Returns: TRUE if caps were set successfully. - */ -gboolean -gst_base_audio_decoder_set_src_caps (GstBaseAudioDecoder * codec, - GstCaps * caps) -{ - gboolean ret; - - GST_DEBUG ("gst_base_audio_decoder_set_src_caps %" GST_PTR_FORMAT, caps); - - /* First, check if the pad accepts the new caps */ - if (!gst_pad_accept_caps (codec->srcpad, caps)) { - GST_DEBUG ("pad does not accept new caps"); - return FALSE; - } - - /* If we have data in our adapters we should probably flush first */ - gst_base_audio_decoder_flush (codec); - - /* Set the caps on the pad */ - ret = gst_pad_set_caps (codec->srcpad, caps); - - /* And update the state of the codec from the caps */ - if (ret) { - gst_base_audio_decoder_read_state_from_caps (codec, caps); - codec->caps_set = TRUE; - } - - return ret; -} - -/** - * gst_base_audio_decoder_push_buffer: - * @codec: #GstBaseAudioDecoder instance - * @buffer: a #GstBuffer. - * - * Pushes a buffer through the source pad. - * - * Returns: a #GstFlowReturn indicating the result of the push operation. - */ -GstFlowReturn -gst_base_audio_decoder_push_buffer (GstBaseAudioDecoder * codec, - GstBuffer * buffer) -{ - codec->bytes_out += GST_BUFFER_SIZE (buffer); - return gst_pad_push (codec->srcpad, buffer); -} diff --git a/gst-libs/gst/audio/gstbaseaudiodecoder.h b/gst-libs/gst/audio/gstbaseaudiodecoder.h index 429954e609..16bf6264f4 100644 --- a/gst-libs/gst/audio/gstbaseaudiodecoder.h +++ b/gst-libs/gst/audio/gstbaseaudiodecoder.h @@ -1,6 +1,9 @@ /* GStreamer * Copyright (C) 2009 Igalia S.L. * Author: Iago Toral Quiroga + * Copyright (C) 2011 Mark Nauwelaerts . + * Copyright (C) 2011 Nokia Corporation. All rights reserved. + * Contact: Stefan Kost * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -27,6 +30,7 @@ #endif #include +#include #include G_BEGIN_DECLS @@ -73,59 +77,46 @@ G_BEGIN_DECLS */ #define GST_BASE_AUDIO_DECODER_SINK_PAD(obj) (((GstBaseAudioDecoder *) (obj))->sinkpad) -/** - * GST_BASE_AUDIO_DECODER_INPUT_ADAPTER: - * @obj: base audio codec instance - * - * Gives the pointer to the input #GstAdapter object of the element. - */ -#define GST_BASE_AUDIO_DECODER_INPUT_ADAPTER(obj) (((GstBaseAudioDecoder *) (obj))->input_adapter) - -/** - * GST_BASE_AUDIO_DECODER_OUTPUT_ADAPTER: - * @obj: base audio codec instance - * - * Gives the pointer to the output #GstAdapter object of the element. - */ -#define GST_BASE_AUDIO_DECODER_OUTPUT_ADAPTER(obj) (((GstBaseAudioDecoder *) (obj))->output_adapter) - typedef struct _GstBaseAudioDecoder GstBaseAudioDecoder; typedef struct _GstBaseAudioDecoderClass GstBaseAudioDecoderClass; -typedef struct _GstAudioState GstAudioState; -struct _GstAudioState -{ - gint channels; - gint rate; - gint bytes_per_sample; - gint sample_depth; - gint frame_size; - GstSegment segment; +typedef struct _GstBaseAudioDecoderPrivate GstBaseAudioDecoderPrivate; +typedef struct _GstBaseAudioDecoderContext GstBaseAudioDecoderContext; + +/** + * GstBaseAudioDecoderContext: + * @state: a #GstAudioState describing input audio format + * @eos: no (immediate) subsequent data in stream + * @sync: stream parsing in sync + * @delay: number of frames pending decoding (typically at least 1 for current) + * @do_plc: whether subclass is prepared to handle (packet) loss concealment + * @min_latency: min latency of element + * @max_latency: max latency of element + * @lookahead: decoder lookahead (in units of input rate samples) + * + * Transparent #GstBaseAudioEncoderContext data structure. + */ +struct _GstBaseAudioDecoderContext { + /* input */ + /* (output) audio format */ + GstAudioState state; + + /* parsing state */ + gboolean eos; + gboolean sync; + + /* misc */ + gint delay; + + /* output */ + gboolean do_plc; + /* MT-protected (with LOCK) */ + GstClockTime min_latency; + GstClockTime max_latency; }; /** * GstBaseAudioDecoder: - * @element: the parent element. - * @caps_set: whether caps have been set on the codec's source pad. - * @sinkpad: the sink pad. - * @srcpad: the source pad. - * @input_adapter: the input adapter that will be filled with the input buffers. - * @output_adapter: the output adapter. Subclasses will read from the input - * adapter, process the data and fill the output adapter with the result. - * @input_buffer_size: The minimum amount of data that should be present on the - * input adapter for the codec to process it. - * @output_buffer_size: The minimum amount of data that should be present on the - * output adapter for the codec to push buffers out. - * @bytes_in: total bytes that have been received. - * @bytes_out: total bytes that have been pushed out. - * @discont: whether the next buffer to push represents a discontinuity in the - * stream. - * @state: Audio stream information. See #GstAudioState for details. - * @codec_data: The codec data. - * @started: Whether the codec has been started and is ready to process data - * or not. - * @first_ts: timestamp of the first buffer in the input adapter. - * @last_ts: timestamp of the last buffer in the input adapter. * * The opaque #GstBaseAudioDecoder data structure. */ @@ -133,86 +124,97 @@ struct _GstBaseAudioDecoder { GstElement element; - /*< private >*/ - gboolean caps_set; - /*< protected >*/ - GstPad *sinkpad; - GstPad *srcpad; - GstAdapter *input_adapter; - GstAdapter *output_adapter; - guint input_buffer_size; - guint output_buffer_size; - guint64 bytes_in; - guint64 bytes_out; - gboolean discont; - GstAudioState state; - GstBuffer *codec_data; - gboolean started; + /* source and sink pads */ + GstPad *sinkpad; + GstPad *srcpad; - guint64 first_ts; - guint64 last_ts; + /* MT-protected (with STREAM_LOCK) */ + GstSegment segment; + GstBaseAudioDecoderContext *ctx; + + /* properties */ + GstClockTime latency; + GstClockTime tolerance; + gboolean plc; + + /*< private >*/ + GstBaseAudioDecoderPrivate *priv; + gpointer _gst_reserved[GST_PADDING_LARGE]; }; /** * GstBaseAudioDecoderClass: - * @parent_class: Element parent class - * @start: Start processing. Ideal for opening resources in the subclass - * @stop: Stop processing. Subclasses should use this to close resources. - * @reset: Resets the codec. Called on discontinuities, etc. - * @event: Override this to handle events arriving on the sink pad. - * @handle_discont: Override to be notified on discontinuities. - * @flush_input: Subclasses may implement this to flush the input adapter, - * processing any data present in it and filling the output adapter with the - * result. This could be necessary if it is possible for the codec to - * receive an end-of-stream event before all the data in the input - * adapter has been processed. - * @flush_output: Subclasses may implement this to flush the output adapter, - * pushing buffers out through the codec's source pad when the end-of-stream - * event is received and there is data waiting to be processed in the - * adapters. - * @process_data: Subclasses must implement this. They should read from the - * input adapter, encode/decode the data present in it and fill the - * output adapter with the result. - * @push_data: Normally, #GstBaseAudioDecoder will handle pushing buffers out. - * However, it is possible for developers to take control of when and how - * buffers are pushed out by overriding this method. If subclasses provide - * an implementation, #GstBaseAudioDecoder will not push any buffers, - * instead, whenever there is data on the output adapter, it will call this - * method on the subclass, which would be the sole responsible for - * pushing the buffers out when appropriate. - * @negotiate_src_caps: Subclasses can implement this method to provide - * appropriate caps to be set on the codec's source pad. If they don't - * provide this, they will be responsible for calling - * gst_base_audio_decoder_set_src_caps when appropriate. + * @start: Optional. + * Called when the element starts processing. + * Allows opening external resources. + * @stop: Optional. + * Called when the element stops processing. + * Allows closing external resources. + * @set_format: Notifies subclass of incoming data format (caps). + * @parse: Optional. + * Allows chopping incoming data into manageable units (frames) + * for subsequent decoding. This division is at subclass + * discretion and may or may not correspond to 1 (or more) + * frames as defined by audio format. + * @handle_frame: Provides input data (or NULL to clear any remaining data) + * to subclass. Input data ref management is performed by + * base class, subclass should not care or intervene. + * @flush: Optional. + * Instructs subclass to clear any codec caches and discard + * any pending samples and not yet returned encoded data. + * @hard indicates whether a FLUSH is being processed, + * or otherwise a DISCONT (or conceptually similar). + * @event: Optional. + * Event handler on the sink pad. This function should return + * TRUE if the event was handled and should be discarded + * (i.e. not unref'ed). + * @pre_push: Optional. + * Called just prior to pushing (encoded data) buffer downstream. + * Subclass has full discretionary access to buffer, + * and a not OK flow return will abort downstream pushing. + * + * Subclasses can override any of the available virtual methods or not, as + * needed. At minimum @handle_frame (and likely @set_format) needs to be + * overridden. */ struct _GstBaseAudioDecoderClass { GstElementClass parent_class; - gboolean (*start) (GstBaseAudioDecoder *codec); - gboolean (*stop) (GstBaseAudioDecoder *codec); - gboolean (*reset) (GstBaseAudioDecoder *codec); + /*< public >*/ + /* virtual methods for subclasses */ - GstFlowReturn (*event) (GstBaseAudioDecoder *codec, GstEvent *event); - void (*handle_discont) (GstBaseAudioDecoder *codec, GstBuffer *buffer); - gboolean (*flush_input) (GstBaseAudioDecoder *codec); - gboolean (*flush_output) (GstBaseAudioDecoder *codec); - GstFlowReturn (*process_data) (GstBaseAudioDecoder *codec); - GstFlowReturn (*push_data) (GstBaseAudioDecoder *codec); - GstCaps * (*negotiate_src_caps) (GstBaseAudioDecoder *codec, - GstCaps *sink_caps); + gboolean (*start) (GstBaseAudioDecoder *dec); + + gboolean (*stop) (GstBaseAudioDecoder *dec); + + gboolean (*set_format) (GstBaseAudioDecoder *dec, + GstCaps *caps); + + GstFlowReturn (*parse) (GstBaseAudioDecoder *dec, + GstAdapter *adapter, + gint *offset, gint *length); + + GstFlowReturn (*handle_frame) (GstBaseAudioDecoder *dec, + GstBuffer *buffer); + + void (*flush) (GstBaseAudioDecoder *dec, gboolean hard); + + GstFlowReturn (*pre_push) (GstBaseAudioDecoder *dec, + GstBuffer **buffer); + + gboolean (*event) (GstBaseAudioDecoder *dec, + GstEvent *event); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; }; +GstFlowReturn gst_base_audio_decoder_finish_frame (GstBaseAudioDecoder * dec, + GstBuffer * buf, gint frames); + GType gst_base_audio_decoder_get_type (void); -gboolean gst_base_audio_decoder_reset (GstBaseAudioDecoder *codec); -gboolean gst_base_audio_decoder_stop (GstBaseAudioDecoder *codec); -gboolean gst_base_audio_decoder_start (GstBaseAudioDecoder *codec); -gboolean gst_base_audio_decoder_flush (GstBaseAudioDecoder *codec); -gboolean gst_base_audio_decoder_set_src_caps (GstBaseAudioDecoder *codec, - GstCaps *caps); -GstFlowReturn gst_base_audio_decoder_push_buffer (GstBaseAudioDecoder *codec, - GstBuffer *buffer); G_END_DECLS