| /* 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 |
| * @title: 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. |
| * |
| * ## Configuration |
| * |
| * * 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). |
| * * 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. |
| * * GstAudioDecoder calls @stop at end of all processing. |
| * |
| * 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. |
| * |
| * ## 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_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 GstAudioDecoderClass will handle both |
| * srcpad and sinkpad events. Sink events will be passed to subclass |
| * if @event callback has been provided. |
| * |
| * ## Shutdown phase |
| * |
| * * GstAudioDecoder class calls @stop to inform the subclass that data |
| * parsing will be stopped. |
| * |
| * 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: |
| * |
| * * 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_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 |
| #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; |
| GstCaps *allocation_caps; |
| /* 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_segment are output_segment identical */ |
| gboolean in_out_segment_sync; |
| /* expecting the buffer with DISCONT flag */ |
| gboolean expecting_discont_buf; |
| |
| |
| /* 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; |
| |
| /* upstream stream tags (global tags are passed through as-is) */ |
| GstTagList *upstream_tags; |
| |
| /* subclass tags */ |
| GstTagList *taglist; /* FIXME: rename to decoder_tags */ |
| GstTagMergeMode decoder_tags_merge_mode; |
| |
| gboolean taglist_changed; /* FIXME: rename to tags_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; |
| |
| /* flags */ |
| gboolean use_default_pad_acceptcaps; |
| }; |
| |
| 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 gboolean gst_audio_decoder_sink_query_default (GstAudioDecoder * dec, |
| GstQuery * query); |
| static gboolean gst_audio_decoder_src_query_default (GstAudioDecoder * dec, |
| GstQuery * query); |
| |
| static gboolean gst_audio_decoder_transform_meta_default (GstAudioDecoder * |
| decoder, GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf); |
| |
| 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); |
| audiodecoder_class->sink_query = |
| GST_DEBUG_FUNCPTR (gst_audio_decoder_sink_query_default); |
| audiodecoder_class->src_query = |
| GST_DEBUG_FUNCPTR (gst_audio_decoder_src_query_default); |
| audiodecoder_class->transform_meta = |
| GST_DEBUG_FUNCPTR (gst_audio_decoder_transform_meta_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; |
| GST_OBJECT_LOCK (dec); |
| dec->priv->bytes_in = 0; |
| dec->priv->samples_out = 0; |
| GST_OBJECT_UNLOCK (dec); |
| 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->decoder_tags_merge_mode = GST_TAG_MERGE_KEEP_ALL; |
| if (dec->priv->upstream_tags) { |
| gst_tag_list_unref (dec->priv->upstream_tags); |
| dec->priv->upstream_tags = NULL; |
| } |
| dec->priv->taglist_changed = FALSE; |
| |
| gst_segment_init (&dec->input_segment, GST_FORMAT_TIME); |
| gst_segment_init (&dec->output_segment, GST_FORMAT_TIME); |
| dec->priv->in_out_segment_sync = TRUE; |
| |
| 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_OBJECT_LOCK (dec); |
| gst_caps_replace (&dec->priv->ctx.input_caps, NULL); |
| gst_caps_replace (&dec->priv->ctx.allocation_caps, NULL); |
| |
| memset (&dec->priv->ctx, 0, sizeof (dec->priv->ctx)); |
| |
| gst_audio_info_init (&dec->priv->ctx.info); |
| GST_OBJECT_UNLOCK (dec); |
| 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 GstEvent * |
| gst_audio_decoder_create_merged_tags_event (GstAudioDecoder * dec) |
| { |
| GstTagList *merged_tags; |
| |
| GST_LOG_OBJECT (dec, "upstream : %" GST_PTR_FORMAT, dec->priv->upstream_tags); |
| GST_LOG_OBJECT (dec, "decoder : %" GST_PTR_FORMAT, dec->priv->taglist); |
| GST_LOG_OBJECT (dec, "mode : %d", dec->priv->decoder_tags_merge_mode); |
| |
| merged_tags = |
| gst_tag_list_merge (dec->priv->upstream_tags, |
| dec->priv->taglist, dec->priv->decoder_tags_merge_mode); |
| |
| GST_DEBUG_OBJECT (dec, "merged : %" GST_PTR_FORMAT, merged_tags); |
| |
| if (merged_tags == NULL) |
| return NULL; |
| |
| if (gst_tag_list_is_empty (merged_tags)) { |
| gst_tag_list_unref (merged_tags); |
| return NULL; |
| } |
| |
| return gst_event_new_tag (merged_tags); |
| } |
| |
| 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; |
| dec->priv->in_out_segment_sync = |
| gst_segment_is_equal (&dec->input_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); |
| if (dec->priv->ctx.allocation_caps == NULL) |
| dec->priv->ctx.allocation_caps = gst_caps_ref (caps); |
| |
| 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 (dec->priv->ctx.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, ¶ms); |
| } else { |
| allocator = NULL; |
| gst_allocation_params_init (¶ms); |
| } |
| |
| 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 */ |
| GST_OBJECT_LOCK (dec); |
| dec->priv->ctx.info = *info; |
| GST_OBJECT_UNLOCK (dec); |
| 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"); |
| /* only check and return EOS if upstream still |
| * in the same segment and interested as such */ |
| if (dec->priv->in_out_segment_sync) { |
| 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; |
| dec->priv->in_out_segment_sync = |
| gst_segment_is_equal (&dec->input_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; |
| } |
| |
| static gboolean |
| gst_audio_decoder_transform_meta_default (GstAudioDecoder * |
| decoder, GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf) |
| { |
| const GstMetaInfo *info = meta->info; |
| const gchar *const *tags; |
| |
| tags = gst_meta_api_type_get_tags (info->api); |
| |
| if (!tags || (g_strv_length ((gchar **) tags) == 1 |
| && gst_meta_api_type_has_tag (info->api, |
| g_quark_from_string (GST_META_TAG_AUDIO_STR)))) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| typedef struct |
| { |
| GstAudioDecoder *decoder; |
| GstBuffer *outbuf; |
| } CopyMetaData; |
| |
| static gboolean |
| foreach_metadata (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data) |
| { |
| CopyMetaData *data = user_data; |
| GstAudioDecoder *decoder = data->decoder; |
| GstAudioDecoderClass *klass = GST_AUDIO_DECODER_GET_CLASS (decoder); |
| GstBuffer *outbuf = data->outbuf; |
| const GstMetaInfo *info = (*meta)->info; |
| gboolean do_copy = FALSE; |
| |
| if (gst_meta_api_type_has_tag (info->api, _gst_meta_tag_memory)) { |
| /* never call the transform_meta with memory specific metadata */ |
| GST_DEBUG_OBJECT (decoder, "not copying memory specific metadata %s", |
| g_type_name (info->api)); |
| do_copy = FALSE; |
| } else if (klass->transform_meta) { |
| do_copy = klass->transform_meta (decoder, outbuf, *meta, inbuf); |
| GST_DEBUG_OBJECT (decoder, "transformed metadata %s: copy: %d", |
| g_type_name (info->api), do_copy); |
| } |
| |
| /* we only copy metadata when the subclass implemented a transform_meta |
| * function and when it returns %TRUE */ |
| if (do_copy && info->transform_func) { |
| GstMetaTransformCopy copy_data = { FALSE, 0, -1 }; |
| GST_DEBUG_OBJECT (decoder, "copy metadata %s", g_type_name (info->api)); |
| /* simply copy then */ |
| info->transform_func (outbuf, *meta, inbuf, |
| _gst_meta_transform_copy, ©_data); |
| } |
| return TRUE; |
| } |
| |
| /** |
| * 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; |
| GstAudioDecoderClass *klass = GST_AUDIO_DECODER_GET_CLASS (dec); |
| gint samples = 0; |
| GstClockTime ts, next_ts; |
| gsize size; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GQueue inbufs = G_QUEUE_INIT; |
| |
| /* 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) { |
| gst_buffer_unref (buf); |
| 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) { |
| g_queue_push_tail (&inbufs, 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) { |
| GstEvent *tags_event; |
| |
| tags_event = gst_audio_decoder_create_merged_tags_event (dec); |
| |
| if (tags_event != NULL) |
| gst_audio_decoder_push_event (dec, tags_event); |
| |
| 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); |
| } |
| |
| if (klass->transform_meta) { |
| if (inbufs.length) { |
| GList *l; |
| for (l = inbufs.head; l; l = l->next) { |
| CopyMetaData data; |
| |
| data.decoder = dec; |
| data.outbuf = buf; |
| gst_buffer_foreach_meta (l->data, foreach_metadata, &data); |
| } |
| } else { |
| GST_WARNING_OBJECT (dec, |
| "Can't copy metadata because input buffers disappeared"); |
| } |
| } |
| |
| GST_OBJECT_LOCK (dec); |
| priv->samples += samples; |
| priv->samples_out += samples; |
| GST_OBJECT_UNLOCK (dec); |
| |
| /* 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: |
| g_queue_foreach (&inbufs, (GFunc) gst_buffer_unref, NULL); |
| g_queue_clear (&inbufs); |
| |
| 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; |
| GST_OBJECT_LOCK (dec); |
| dec->priv->bytes_in += size; |
| GST_OBJECT_UNLOCK (dec); |
| } 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); |
| |
| 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); |
| |
| if (G_UNLIKELY (dec->priv->ctx.input_caps == NULL && dec->priv->needs_format)) |
| goto not_negotiated; |
| |
| dec->priv->ctx.had_input_data = TRUE; |
| |
| if (!dec->priv->expecting_discont_buf && |
| 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; |
| } |
| } |
| dec->priv->expecting_discont_buf = FALSE; |
| |
| 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_AUDIO_DECODER_STREAM_UNLOCK (dec); |
| 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) |
| { |
| gboolean ret; |
| |
| GST_OBJECT_LOCK (dec); |
| ret = dec->priv->ctx.do_estimate_rate && dec->priv->ctx.info.bpf && |
| dec->priv->ctx.info.rate <= dec->priv->samples_out; |
| GST_OBJECT_UNLOCK (dec); |
| |
| return ret; |
| } |
| |
| /* Must be called holding the GST_AUDIO_DECODER_STREAM_LOCK */ |
| static gboolean |
| gst_audio_decoder_negotiate_default_caps (GstAudioDecoder * dec) |
| { |
| GstCaps *caps, *templcaps; |
| gint i; |
| gint channels = 0; |
| gint rate; |
| guint64 channel_mask = 0; |
| gint caps_size; |
| GstStructure *structure; |
| GstAudioInfo info; |
| |
| templcaps = gst_pad_get_pad_template_caps (dec->srcpad); |
| caps = gst_pad_peer_query_caps (dec->srcpad, templcaps); |
| if (caps) |
| gst_caps_unref (templcaps); |
| else |
| caps = templcaps; |
| templcaps = NULL; |
| |
| if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) |
| goto caps_error; |
| |
| GST_LOG_OBJECT (dec, "peer caps %" GST_PTR_FORMAT, caps); |
| |
| /* 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); |
| if (gst_structure_has_field (structure, "channels")) |
| gst_structure_fixate_field_nearest_int (structure, |
| "channels", GST_AUDIO_DEF_CHANNELS); |
| else |
| gst_structure_set (structure, "channels", G_TYPE_INT, |
| GST_AUDIO_DEF_CHANNELS, NULL); |
| if (gst_structure_has_field (structure, "rate")) |
| gst_structure_fixate_field_nearest_int (structure, |
| "rate", GST_AUDIO_DEF_RATE); |
| else |
| gst_structure_set (structure, "rate", G_TYPE_INT, GST_AUDIO_DEF_RATE, |
| NULL); |
| } |
| 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")) { |
| channel_mask = gst_audio_channel_get_fallback_mask (channels); |
| if (channel_mask != 0) { |
| 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 (&info, caps)) |
| goto caps_error; |
| |
| GST_OBJECT_LOCK (dec); |
| dec->priv->ctx.info = info; |
| GST_OBJECT_UNLOCK (dec); |
| |
| GST_INFO_OBJECT (dec, |
| "Chose default caps %" GST_PTR_FORMAT " for initial gap", caps); |
| gst_caps_unref (caps); |
| |
| 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; |
| gboolean needs_reconfigure = FALSE; |
| |
| /* 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_AUDIO_DECODER_STREAM_UNLOCK (dec); |
| GST_ELEMENT_ERROR (dec, STREAM, FORMAT, (NULL), |
| ("Decoder output not negotiated before GAP event.")); |
| return FALSE; |
| } |
| needs_reconfigure = TRUE; |
| } |
| needs_reconfigure = gst_pad_check_reconfigure (dec->srcpad) |
| || needs_reconfigure; |
| if (G_UNLIKELY (dec->priv->ctx.output_format_changed || needs_reconfigure)) { |
| if (!gst_audio_decoder_negotiate_unlocked (dec)) { |
| GST_WARNING_OBJECT (dec, "Failed to negotiate with downstream"); |
| gst_pad_mark_reconfigure (dec->srcpad); |
| } |
| } |
| GST_AUDIO_DECODER_STREAM_UNLOCK (dec); |
| |
| gst_event_parse_gap (event, ×tamp, &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; |
| dec->priv->expecting_discont_buf = 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 upstream tags after a STREAM_START */ |
| if (dec->priv->upstream_tags) { |
| gst_tag_list_unref (dec->priv->upstream_tags); |
| dec->priv->upstream_tags = NULL; |
| dec->priv->taglist_changed = TRUE; |
| } |
| 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->priv->in_out_segment_sync = FALSE; |
| 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_SEGMENT_DONE: |
| GST_AUDIO_DECODER_STREAM_LOCK (dec); |
| gst_audio_decoder_drain (dec); |
| GST_AUDIO_DECODER_STREAM_UNLOCK (dec); |
| |
| /* Forward SEGMENT_DONE because no buffer or serialized event might come after |
| * SEGMENT_DONE 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_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_STREAM_LOCK (dec); |
| if (dec->priv->upstream_tags != tags) { |
| if (dec->priv->upstream_tags) |
| gst_tag_list_unref (dec->priv->upstream_tags); |
| dec->priv->upstream_tags = gst_tag_list_ref (tags); |
| GST_INFO_OBJECT (dec, "upstream stream tags: %" GST_PTR_FORMAT, tags); |
| } |
| gst_event_unref (event); |
| event = gst_audio_decoder_create_merged_tags_event (dec); |
| dec->priv->taglist_changed = FALSE; |
| GST_AUDIO_DECODER_STREAM_UNLOCK (dec); |
| |
| /* No tags, go out of here instead of fall through */ |
| if (!event) { |
| 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, ¶ms); |
| update_allocator = TRUE; |
| } else { |
| allocator = NULL; |
| gst_allocation_params_init (¶ms); |
| update_allocator = FALSE; |
| } |
| |
| if (update_allocator) |
| gst_query_set_nth_allocation_param (query, 0, allocator, ¶ms); |
| else |
| gst_query_add_allocation_param (query, allocator, ¶ms); |
| if (allocator) |
| gst_object_unref (allocator); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_audio_decoder_propose_allocation_default (GstAudioDecoder * dec, |
| GstQuery * query) |
| { |
| return TRUE; |
| } |
| |
| /** |
| * 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_default (GstAudioDecoder * dec, GstQuery * query) |
| { |
| GstPad *pad = GST_AUDIO_DECODER_SINK_PAD (dec); |
| gboolean res = FALSE; |
| |
| 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); |
| GST_OBJECT_LOCK (dec); |
| 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); |
| GST_OBJECT_UNLOCK (dec); |
| if (!res) |
| 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:{ |
| if (dec->priv->use_default_pad_acceptcaps) { |
| res = |
| gst_pad_query_default (GST_AUDIO_DECODER_SINK_PAD (dec), |
| GST_OBJECT_CAST (dec), query); |
| } else { |
| 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, GST_OBJECT_CAST (dec), query); |
| break; |
| } |
| |
| error: |
| return res; |
| } |
| |
| static gboolean |
| gst_audio_decoder_sink_query (GstPad * pad, GstObject * parent, |
| GstQuery * query) |
| { |
| GstAudioDecoderClass *dec_class; |
| GstAudioDecoder *dec; |
| gboolean ret = FALSE; |
| |
| dec = GST_AUDIO_DECODER (parent); |
| dec_class = GST_AUDIO_DECODER_GET_CLASS (dec); |
| |
| GST_DEBUG_OBJECT (pad, "received query %" GST_PTR_FORMAT, query); |
| |
| if (dec_class->sink_query) |
| ret = dec_class->sink_query (dec, query); |
| |
| return ret; |
| } |
| |
| /* 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_default (GstAudioDecoder * dec, GstQuery * query) |
| { |
| GstPad *pad = GST_AUDIO_DECODER_SRC_PAD (dec); |
| gboolean res = FALSE; |
| |
| 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, GST_OBJECT_CAST (dec), 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; |
| } |
| |
| /* Refuse BYTES format queries. If it made sense to |
| * answer them, upstream would have already */ |
| gst_query_parse_position (query, &format, NULL); |
| |
| if (format == GST_FORMAT_BYTES) { |
| GST_LOG_OBJECT (dec, "Ignoring BYTES position query"); |
| 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 */ |
| 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); |
| GST_OBJECT_LOCK (dec); |
| res = gst_audio_info_convert (&dec->priv->ctx.info, |
| src_fmt, src_val, dest_fmt, &dest_val); |
| GST_OBJECT_UNLOCK (dec); |
| if (!res) |
| 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, GST_OBJECT_CAST (dec), query); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_audio_decoder_src_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| GstAudioDecoder *dec; |
| GstAudioDecoderClass *dec_class; |
| gboolean ret = FALSE; |
| |
| dec = GST_AUDIO_DECODER (parent); |
| dec_class = GST_AUDIO_DECODER_GET_CLASS (dec); |
| |
| GST_DEBUG_OBJECT (pad, "received query %" GST_PTR_FORMAT, query); |
| |
| if (dec_class->src_query) |
| ret = dec_class->src_query (dec, query); |
| |
| return ret; |
| } |
| |
| 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_allocation_caps: |
| * @dec: a #GstAudioDecoder |
| * @allocation_caps: (allow-none): a #GstCaps or %NULL |
| * |
| * Sets a caps in allocation query which are different from the set |
| * pad's caps. Use this function before calling |
| * gst_audio_decoder_negotiate(). Setting to %NULL the allocation |
| * query will use the caps from the pad. |
| * |
| * Since: 1.10 |
| */ |
| void |
| gst_audio_decoder_set_allocation_caps (GstAudioDecoder * dec, |
| GstCaps * allocation_caps) |
| { |
| g_return_if_fail (GST_IS_AUDIO_DECODER (dec)); |
| |
| gst_caps_replace (&dec->priv->ctx.allocation_caps, allocation_caps); |
| } |
| |
| /** |
| * 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: (allow-none): a #GstTagList to merge, or NULL |
| * @mode: the #GstTagMergeMode to use, usually #GST_TAG_MERGE_REPLACE |
| * |
| * Sets the audio decoder tags and how they should be merged with any |
| * upstream stream tags. This will override any tags previously-set |
| * with gst_audio_decoder_merge_tags(). |
| * |
| * 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. |
| */ |
| void |
| gst_audio_decoder_merge_tags (GstAudioDecoder * dec, |
| const GstTagList * tags, GstTagMergeMode mode) |
| { |
| g_return_if_fail (GST_IS_AUDIO_DECODER (dec)); |
| g_return_if_fail (tags == NULL || GST_IS_TAG_LIST (tags)); |
| g_return_if_fail (mode != GST_TAG_MERGE_UNDEFINED); |
| |
| GST_AUDIO_DECODER_STREAM_LOCK (dec); |
| if (dec->priv->taglist != tags) { |
| if (dec->priv->taglist) { |
| gst_tag_list_unref (dec->priv->taglist); |
| dec->priv->taglist = NULL; |
| dec->priv->decoder_tags_merge_mode = GST_TAG_MERGE_KEEP_ALL; |
| } |
| if (tags) { |
| dec->priv->taglist = gst_tag_list_ref ((GstTagList *) tags); |
| dec->priv->decoder_tags_merge_mode = mode; |
| } |
| |
| GST_DEBUG_OBJECT (dec, "setting decoder tags to %" GST_PTR_FORMAT, tags); |
| 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; |
| } |
| |
| /** |
| * gst_audio_decoder_set_use_default_pad_acceptcaps: |
| * @decoder: a #GstAudioDecoder |
| * @use: if the default pad accept-caps query handling should be used |
| * |
| * Lets #GstAudioDecoder sub-classes decide if they want the sink pad |
| * to use the default pad query handler to reply to accept-caps queries. |
| * |
| * By setting this to true it is possible to further customize the default |
| * handler with %GST_PAD_SET_ACCEPT_INTERSECT and |
| * %GST_PAD_SET_ACCEPT_TEMPLATE |
| * |
| * Since: 1.6 |
| */ |
| void |
| gst_audio_decoder_set_use_default_pad_acceptcaps (GstAudioDecoder * decoder, |
| gboolean use) |
| { |
| decoder->priv->use_default_pad_acceptcaps = use; |
| } |