From 90d99f23c617513fdcac7d5bdfe84f8c97672fbc Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Tue, 1 Mar 2011 14:08:18 +0100 Subject: [PATCH] baseaudiocodec: ... and also rename to baseaudiodecoder --- gst-libs/gst/audio/gstbaseaudiodecoder.c | 939 +++++++++++++++++++++++++++++++ gst-libs/gst/audio/gstbaseaudiodecoder.h | 220 ++++++++ 2 files changed, 1159 insertions(+) create mode 100644 gst-libs/gst/audio/gstbaseaudiodecoder.c create mode 100644 gst-libs/gst/audio/gstbaseaudiodecoder.h diff --git a/gst-libs/gst/audio/gstbaseaudiodecoder.c b/gst-libs/gst/audio/gstbaseaudiodecoder.c new file mode 100644 index 0000000..7bf6697 --- /dev/null +++ b/gst-libs/gst/audio/gstbaseaudiodecoder.c @@ -0,0 +1,939 @@ +/* GStreamer + * Copyright (C) 2009 Igalia S.L. + * Author: Iago Toral Quiroga + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gstbaseaudiodecoder + * @short_description: Base class for codec elements + * @see_also: #GstBaseTransform, #GstBaseSource, #GstBaseSink + * + * #GstBaseAudioDecoder is the base class for codec elements ion GStreamer. It is + * a layer on top of #GstElement that provides simplified interface to plugin + * writers, hangling many details for you. Its way of operation is explained + * below. + * + * Subclasses are responsible for specifying the codec's source pad caps. For + * that purpose they should provide an implementation of ::negotiate_src_caps. + * If the subclass provides an implementation of this method, it will be + * invoked by #GstBaseAudioDecoder on its sink_setcaps function. Otherwise, if + * the subclass does not provide an implementation of this method, the subclass + * will be responsible for calling gst_base_audio_decoder_set_src_caps() to + * complete the caps negotiation before any buffers are pushed out. + * + * Each buffer received on the codec's sink pad is pushed to its input + * adapter. When there is enough data present in the input adapter + * (configured in the #GstBaseAudioDecoder:input-buffer-size + * property), the method ::process_data is called on the subclass. Subclasses + * must provide an implementation of this method, which would read from the + * input adapter, encode or decode the data, and push it to the output adapter. + * If #GstBaseAudioDecoder:input-buffer-size is set to 0 ::process_data will be + * invoked as soon as there is any data on the input adapter. + * + * Similarly, when there is enough data present on the output adapter, + * (configured in the #GstBaseAudioDecoder:output-buffer-size property), + * buffers will be pushed out through the codec's source pad. If + * #GstBaseAudioDecoder:output-buffer-size is set to 0 a buffer will be pushed + * out as soon as there is any data present on the output adapter. Notice + * that if no implementation of ::negotiate_src_caps has been provided by the + * subclass, it must call gst_base_audio_decoder_set_src_caps() to complete + * the caps negotiation process or otherwise attempting to push buffers + * through the codec's source pad will fail. + * + * It is possible for subclasses to take control on how and when buffers + * are pushed out by overriding the ::push_data method. If subclasses + * provide an implementation of this method #GstBaseAudioDecoder will + * not push buffers out by itself, instead, whenever there* is data present + * in the output adapter, it will invoke ::push_data on subclass, which + * will implement there any logic necessary for pushing buffers out when + * appropriate. In this mode of operation, the property + * ::output_buffer_size is ignored in #GstBaseAudioDecoder. In any case, + * buffers should be pushed using gst_base_audio_decoder_push_buffer(). + * + * #GstBaseAudioDecoder checks for discontinuities and handles them + * appropriately when pushing buffers out (setting the discontinuous + * flag on the output buffers when necessary). Subclasses can check if + * the data present on the adapters represents a discontinuity by checking + * the discont field of #GstBaseAudioDecoder. Also, subclasses can provide + * an implementation for the ::handle_discont method, which will be invoked + * whenever a discontinuity is detected on the source stream. + * + * Because data is not processed immediately and is stored in adapters, + * depending on how the actual codec operates it may be possible to + * receive an end-of-stream event before all the data in the adapters + * has been processed and pushed out. If this can happen, the subclass + * must provide implementation of the ::flush_input method, which should + * then read the data present int the input adapter, process it and + * store the result in the output adapter. The subclass may also want + * provide an implementation for the ::flush_output method, which would + * take care of reading the data from the output adapter and push it + * out through the codec's source pad. If no implementation is provided + * for the ::flush_out method, #GstBaseAudioDecoder will create a single + * buffer with all the data present in the output adapter and push it + * out. If a subclass needs to force a flush on the adapters for some + * reason, it should call gst_base_audio_decoder_flush(), which will then + * invoke ::flush_input and/or ::flush_output appropriately. + * + * Subclasses may provide an implementation for the ::start, ::stop + * and ::reset methods when needed. This methods will be called + * from #GstBaseAudioDecoder when needed (on state changes, + * discontinuities, etc), so they must never invoke the + * implementation on the parent class. When a subclass needs to + * start, stop or reset the codec itself, it should use the public + * functions gst_base_audio_decoder_{start,stop,reset}(), which call + * the corresponding methods on the parent class, which will then + * call the functions provided by the subclass (if any). + * + * #GstBaseAudioDecoder also provides an sink event handler. + * Subclasses that want to be notified on these events, can provide + * an implementation of the ::event function, which will be called after + * #GstBaseAudioDecoder has processed the event itself. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstbaseaudiodecoder.h" +#include +#include + +/* + * FIXME: maybe we need more work with the segments (see ac3 decoder) + */ + +GST_DEBUG_CATEGORY (baseaudiodecoder_debug); +#define GST_CAT_DEFAULT baseaudiodecoder_debug + +/* ----- Signals and properties ----- */ +enum +{ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_INPUT_BUFFER_SIZE, + PROP_OUTPUT_BUFFER_SIZE +}; + +/* ----- Function prototypes ----- */ + +static void gst_base_audio_decoder_finalize (GObject * object); +static void gst_base_audio_decoder_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_base_audio_decoder_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstStateChangeReturn gst_base_audio_decoder_change_state (GstElement * + element, GstStateChange transition); +static gboolean gst_base_audio_decoder_sink_event (GstPad * pad, + GstEvent * event); +static gboolean gst_base_audio_decoder_sink_setcaps (GstPad * pad, + GstCaps * caps); +static GstFlowReturn gst_base_audio_decoder_chain (GstPad * pad, + GstBuffer * buf); +static void gst_base_audio_decoder_handle_discont (GstBaseAudioDecoder * codec, + GstBuffer * buf); + +/* ----- GObject setup ----- */ + +GST_BOILERPLATE (GstBaseAudioDecoder, gst_base_audio_decoder, GstElement, + GST_TYPE_ELEMENT); + +static void +gst_base_audio_decoder_base_init (gpointer g_class) +{ + GST_DEBUG_CATEGORY_INIT (baseaudiodecoder_debug, "baseaudiodecoder", 0, + "Base Audio Codec Classes"); +} + +static void +gst_base_audio_decoder_class_init (GstBaseAudioDecoderClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + + gobject_class = G_OBJECT_CLASS (klass); + element_class = GST_ELEMENT_CLASS (klass); + + gobject_class->set_property = gst_base_audio_decoder_set_property; + gobject_class->get_property = gst_base_audio_decoder_get_property; + gobject_class->finalize = gst_base_audio_decoder_finalize; + + element_class->change_state = gst_base_audio_decoder_change_state; + + klass->start = NULL; + klass->stop = NULL; + klass->reset = NULL; + klass->event = NULL; + klass->handle_discont = NULL; + klass->flush_input = NULL; + klass->flush_output = NULL; + klass->process_data = NULL; + klass->push_data = NULL; + klass->negotiate_src_caps = NULL; + + /* Properties */ + g_object_class_install_property (gobject_class, PROP_INPUT_BUFFER_SIZE, + g_param_spec_uint ("input-buffer-size", "Input buffer size", + "Size of the input buffers in bytes (0 for not setting a " + "particular size)", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_OUTPUT_BUFFER_SIZE, + g_param_spec_uint ("output-buffer-size", "Output buffer size", + "Size of the output buffers in bytes (0 for not setting a " + "particular size)", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); +} + +static void +gst_base_audio_decoder_init (GstBaseAudioDecoder * codec, + GstBaseAudioDecoderClass * klass) +{ + GstPadTemplate *pad_template; + + GST_DEBUG ("gst_base_audio_decoder_init"); + + /* Setup sink pad */ + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "sink"); + g_return_if_fail (pad_template != NULL); + + codec->sinkpad = gst_pad_new_from_template (pad_template, "sink"); + gst_pad_set_event_function (codec->sinkpad, + gst_base_audio_decoder_sink_event); + gst_pad_set_setcaps_function (codec->sinkpad, + gst_base_audio_decoder_sink_setcaps); + gst_pad_set_chain_function (codec->sinkpad, gst_base_audio_decoder_chain); + gst_element_add_pad (GST_ELEMENT (codec), codec->sinkpad); + + /* Setup source pad */ + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "src"); + g_return_if_fail (pad_template != NULL); + + codec->srcpad = gst_pad_new_from_template (pad_template, "src"); + gst_pad_use_fixed_caps (codec->srcpad); + gst_element_add_pad (GST_ELEMENT (codec), codec->srcpad); + + /* Setup adapters */ + codec->input_adapter = gst_adapter_new (); + codec->output_adapter = gst_adapter_new (); + codec->input_buffer_size = 0; + codec->output_buffer_size = 0; + + /* Setup state */ + memset (&codec->state, 0, sizeof (GstAudioState)); + gst_segment_init (&codec->state.segment, GST_FORMAT_TIME); + + codec->started = FALSE; + codec->bytes_in = 0; + codec->bytes_out = 0; + codec->discont = TRUE; + codec->caps_set = FALSE; + codec->first_ts = -1; + codec->last_ts = -1; +} + +static void +gst_base_audio_decoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstBaseAudioDecoder *codec; + + codec = GST_BASE_AUDIO_DECODER (object); + + switch (prop_id) { + case PROP_INPUT_BUFFER_SIZE: + g_value_set_uint (value, codec->input_buffer_size); + break; + case PROP_OUTPUT_BUFFER_SIZE: + g_value_set_uint (value, codec->output_buffer_size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_base_audio_decoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstBaseAudioDecoder *codec; + + codec = GST_BASE_AUDIO_DECODER (object); + + switch (prop_id) { + case PROP_INPUT_BUFFER_SIZE: + codec->input_buffer_size = g_value_get_uint (value); + break; + case PROP_OUTPUT_BUFFER_SIZE: + codec->output_buffer_size = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_base_audio_decoder_finalize (GObject * object) +{ + GstBaseAudioDecoder *codec; + + g_return_if_fail (GST_IS_BASE_AUDIO_DECODER (object)); + codec = GST_BASE_AUDIO_DECODER (object); + + if (codec->input_adapter) { + g_object_unref (codec->input_adapter); + } + if (codec->output_adapter) { + g_object_unref (codec->output_adapter); + } + if (codec->codec_data) { + gst_buffer_unref (codec->codec_data); + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* ----- Private element implementation ----- */ + +static void +gst_base_audio_decoder_read_state_from_caps (GstBaseAudioDecoder * codec, + GstCaps * caps) +{ + GstStructure *structure; + const GValue *codec_data; + + structure = gst_caps_get_structure (caps, 0); + + if (codec->codec_data) { + gst_buffer_unref (codec->codec_data); + codec->codec_data = NULL; + } + + codec_data = gst_structure_get_value (structure, "codec_data"); + if (codec_data && G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) { + codec->codec_data = gst_value_get_buffer (codec_data); + } + + gst_structure_get_int (structure, "channels", &codec->state.channels); + gst_structure_get_int (structure, "rate", &codec->state.rate); + gst_structure_get_int (structure, "depth", &codec->state.sample_depth); + gst_structure_get_int (structure, "width", &codec->state.bytes_per_sample); + codec->state.bytes_per_sample /= 8; + codec->state.frame_size = + codec->state.bytes_per_sample * codec->state.channels; +} + +static gboolean +gst_base_audio_decoder_sink_event (GstPad * pad, GstEvent * event) +{ + GstBaseAudioDecoder *codec; + GstBaseAudioDecoderClass *codec_class; + gboolean ret = FALSE; + + codec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + /* Flush any data still present in the adapters */ + gst_base_audio_decoder_flush (codec); + ret = gst_pad_push_event (codec->srcpad, event); + break; + case GST_EVENT_FLUSH_STOP: + gst_base_audio_decoder_reset (codec); + ret = gst_pad_push_event (codec->srcpad, event); + break; + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + if (format != GST_FORMAT_TIME) + goto newseg_wrong_format; + + if (rate <= 0.0) + goto newseg_wrong_rate; + + GST_DEBUG ("news egment %lld %lld", start, time); + gst_segment_set_newsegment_full (&codec->state.segment, + update, rate, arate, format, start, stop, time); + ret = gst_pad_push_event (codec->srcpad, event); + break; + } + default: + ret = gst_pad_push_event (codec->srcpad, event); + break; + } + + /* Let the subclass see the event too */ + if (codec_class->event) { + if (!codec_class->event (codec, event)) { + ret = FALSE; + goto subclass_event_error; + } + } + +done: + gst_object_unref (codec); + return ret; + +newseg_wrong_format: + GST_DEBUG ("received non TIME newsegment"); + gst_event_unref (event); + goto done; + +newseg_wrong_rate: + GST_DEBUG ("negative rates not supported"); + gst_event_unref (event); + goto done; + +subclass_event_error: + GST_DEBUG ("codec implementation failed to proces event"); + gst_event_unref (event); + goto done; +} + +static gboolean +gst_base_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstBaseAudioDecoder *codec; + GstBaseAudioDecoderClass *codec_class; + + codec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + GST_DEBUG ("gst_base_audio_decoder_sink_setcaps %" GST_PTR_FORMAT, caps); + + /* Let the subclass provide the source caps and we will set them + on the codec's source pad */ + if (codec_class->negotiate_src_caps) { + GstCaps *src_caps; + src_caps = codec_class->negotiate_src_caps (codec, caps); + if (!gst_base_audio_decoder_set_src_caps (codec, src_caps)) { + GST_DEBUG ("Caps negotiation failed!"); + g_object_unref (codec); + gst_caps_unref (src_caps); + return FALSE; + } + gst_caps_unref (src_caps); + } else { + /* If the subclass does not provide a negotiate_src_caps method, then + it will be responsible for calling gst_base_audio_decoder_set_src_caps + with appropriate caps before we try to push buffers out */ + GST_DEBUG ("Subclass does not provide negotiate_src_caps, is that ok?"); + } + + gst_base_audio_decoder_start (codec); + + g_object_unref (codec); + + return TRUE; +} + +static GstStateChangeReturn +gst_base_audio_decoder_change_state (GstElement * element, + GstStateChange transition) +{ + GstBaseAudioDecoder *codec; + GstBaseAudioDecoderClass *codec_class; + GstStateChangeReturn ret; + + codec = GST_BASE_AUDIO_DECODER (element); + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_base_audio_decoder_start (codec)) { + goto start_failed; + } + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (!gst_base_audio_decoder_reset (codec)) { + goto reset_failed; + } + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + ret = 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_base_audio_decoder_stop (codec)) { + goto stop_failed; + } + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; + +start_failed: + { + GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to start codec")); + return GST_STATE_CHANGE_FAILURE; + } +reset_failed: + { + GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to reset codec")); + return GST_STATE_CHANGE_FAILURE; + } +stop_failed: + { + GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), ("Failed to stop codec")); + return GST_STATE_CHANGE_FAILURE; + } +} + +static void +gst_base_audio_decoder_handle_discont (GstBaseAudioDecoder * codec, + GstBuffer * buffer) +{ + GstBaseAudioDecoderClass *codec_class; + + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + /* Reset codec on discont */ + if (codec->started) { + gst_base_audio_decoder_reset (codec); + } + + codec->discont = TRUE; + + /* Let the subclass do its stuff too if that is needed */ + if (codec_class->handle_discont) { + codec_class->handle_discont (codec, buffer); + } +} + +static GstFlowReturn +gst_base_audio_decoder_chain (GstPad * pad, GstBuffer * buf) +{ + GstBaseAudioDecoder *codec; + GstBaseAudioDecoderClass *codec_class; + GstBuffer *outbuf; + GstFlowReturn ret; + guint bytes_ready; + guint64 timestamp; + + codec = GST_BASE_AUDIO_DECODER (gst_pad_get_parent (pad)); + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + GST_DEBUG ("gst_base_audio_decoder_chain"); + + /* Make sure we have started our codec */ + if (G_UNLIKELY (!codec->started)) { + if (G_UNLIKELY (!gst_base_audio_decoder_start (codec))) { + GST_ELEMENT_ERROR (codec, LIBRARY, INIT, (NULL), + ("Failed to start codec")); + gst_object_unref (codec); + return GST_FLOW_ERROR; + } + } + + /* Handle timestamps */ + timestamp = GST_BUFFER_TIMESTAMP (buf); + if (GST_CLOCK_TIME_IS_VALID (timestamp)) { + GST_DEBUG ("buffer timestamp %" GST_TIME_FORMAT " duration:%" + GST_TIME_FORMAT, GST_TIME_ARGS (timestamp), + GST_TIME_ARGS (GST_BUFFER_DURATION (buf))); + if (gst_adapter_available (codec->input_adapter) == 0) { + codec->first_ts = timestamp; + } + codec->last_ts = timestamp; + } + + /* Check for discontinuity */ + if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT))) { + GST_DEBUG ("received DISCONT buffer"); + gst_base_audio_decoder_handle_discont (codec, buf); + } + + /* Push buffer to the input adapter so the codec can + take data from it as needed */ + codec->bytes_in += GST_BUFFER_SIZE (buf); + gst_adapter_push (codec->input_adapter, buf); + + GST_DEBUG ("Input buffer size: %ld bytes", GST_BUFFER_SIZE (buf)); + + /* Check if we have enough data to be processed. While we have + enough data on the input adapter, instruct the element to + process it */ + ret = GST_FLOW_OK; + bytes_ready = gst_adapter_available (codec->input_adapter); + while (ret == GST_FLOW_OK && bytes_ready > 0 && + bytes_ready >= codec->input_buffer_size) { + GST_DEBUG ("Processing data"); + ret = codec_class->process_data (codec); + bytes_ready = gst_adapter_available (codec->input_adapter); + GST_DEBUG ("%ld bytes remaining on the input", bytes_ready); + } + + /* FIXME: is it possible that we have enough data in the output + adapter but we have to wait for more data before we can + push buffers out? In that case we need a custom GST_FLOW. + Not sure if we could handle pushing buffers here in that + case though, since we always push in output_buffer_size + blocks. */ + + /* If no error was raised, check if we can push buffers out */ + if (G_LIKELY (ret == GST_FLOW_OK)) { + bytes_ready = gst_adapter_available (codec->output_adapter); + GST_DEBUG ("Processed input correctly"); + GST_DEBUG ("%ld bytes on the output", bytes_ready); + + /* If the subclass wants to control how buffers are pushed out + let it do it */ + if (bytes_ready > 0 && codec_class->push_data) { + GST_DEBUG ("Calling push_data on the subclass"); + codec_class->push_data (codec); + } else if (bytes_ready > 0 && bytes_ready >= codec->output_buffer_size) { + /* We have enough data in the output adapter, so take a buffer, apply + clipping, push it out and repeat while we have enough data */ + guint bytes_to_push; + + bytes_to_push = + codec->output_buffer_size ? codec->output_buffer_size : bytes_ready; + + do { + GST_DEBUG ("Pushing a buffer out (%ld bytes)", bytes_to_push); + + outbuf = gst_adapter_take_buffer (codec->output_adapter, bytes_to_push); + + /* Set buffer timestamp/duration if needed (and possible) */ + if (!GST_BUFFER_TIMESTAMP_IS_VALID (outbuf) && codec->first_ts != -1) { + GST_DEBUG ("Computing output buffer timestamp"); + GST_BUFFER_TIMESTAMP (outbuf) = codec->first_ts; + } + + if (!GST_BUFFER_DURATION_IS_VALID (outbuf) && codec->state.frame_size) { + guint nsamples; + GST_DEBUG ("Computing output buffer duration"); + nsamples = GST_BUFFER_SIZE (outbuf) / codec->state.frame_size; + GST_BUFFER_DURATION (outbuf) = + gst_util_uint64_scale_int (GST_SECOND, nsamples, + codec->state.rate); + } + + if (codec->first_ts != -1) { + codec->first_ts += GST_BUFFER_DURATION (outbuf); + if (codec->first_ts > codec->last_ts) { + codec->last_ts = codec->first_ts; + } + } + + GST_DEBUG ("out buffer timestamp %" GST_TIME_FORMAT " duration:%" + GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), + GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf))); + + /* Clip buffer */ + if (codec->state.segment.format == GST_FORMAT_TIME || + codec->state.segment.format == GST_FORMAT_DEFAULT) { + GST_DEBUG ("Clipping buffer"); + outbuf = gst_audio_buffer_clip (outbuf, &codec->state.segment, + codec->state.rate, codec->state.frame_size); + } + + /* Set DISCONT flag on the output buffer if needed */ + if (G_LIKELY (outbuf)) { + if (G_UNLIKELY (codec->discont)) { + GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); + codec->discont = FALSE; + GST_DEBUG ("Buffer is discont"); + } + + ret = gst_base_audio_decoder_push_buffer (codec, outbuf); + } + + /* See if we can push another buffer */ + bytes_ready = gst_adapter_available (codec->output_adapter); + GST_DEBUG ("%ld bytes left on the output", bytes_ready); + } while (ret == GST_FLOW_OK && bytes_ready >= bytes_to_push); + } else { + /* We need more data before we can push a buffer out */ + GST_DEBUG ("Not pushing out, need more data"); + ret = GST_FLOW_OK; + } + } else { + /* We got an error */ + GST_DEBUG ("Got error while processing data"); + } + + GST_DEBUG ("chain-done"); + + return ret; +} + +/* ----- Element public API ----- */ + +/** + * gst_base_audio_decoder_reset: + * @codec: The #GstBaseAudioDecoder instance. + * + * Resets the codec. + * + * This method will also invoke the subclass's reset virtual method + * if available. Niotice that reseting the codec will clear the + * input and output adapters. + * + * Returns: TRUE if the start operation was successful. + */ +gboolean +gst_base_audio_decoder_reset (GstBaseAudioDecoder * codec) +{ + GstBaseAudioDecoderClass *codec_class; + + GST_DEBUG ("gst_base_audio_decoder_reset"); + + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + gst_adapter_clear (codec->input_adapter); + gst_adapter_clear (codec->output_adapter); + + /* FIXME: is this needed? */ + gst_segment_init (&codec->state.segment, GST_FORMAT_TIME); + + codec->first_ts = -1; + codec->last_ts = -1; + + if (codec_class->reset) { + codec_class->reset (codec); + } + + return TRUE; +} + +/** + * gst_base_audio_decoder_stop: + * @codec: The #GstBaseAudioDecoder instance. + * + * Stop the codec. Normally this will be used for closing resource. + * + * This method will also invoke the subclass's stop virtual method + * if available. + * + * Returns: TRUE if the start operation was successful. + */ +gboolean +gst_base_audio_decoder_stop (GstBaseAudioDecoder * codec) +{ + GstBaseAudioDecoderClass *codec_class; + + GST_DEBUG ("gst_base_audio_decoder_stop"); + + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + gst_base_audio_decoder_reset (codec); + + codec->bytes_in = 0; + codec->bytes_out = 0; + + if (codec_class->stop) { + codec_class->stop (codec); + } + + codec->started = FALSE; + + return TRUE; +} + +/** + * gst_base_audio_decoder_start: + * @codec: The #GstBaseAudioDecoder instance. + * + * Setup the codec so it can start processing data. Normally + * this will be used for opening resources needed for operation. + * + * This method will also invoke the subclass's start virtual method + * if available. + * + * Returns: TRUE if the start operation was successful. + */ +gboolean +gst_base_audio_decoder_start (GstBaseAudioDecoder * codec) +{ + GstBaseAudioDecoderClass *codec_class; + + GST_DEBUG ("gst_base_audio_decoder_start"); + + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + gst_base_audio_decoder_reset (codec); + + codec->bytes_in = 0; + codec->bytes_out = 0; + + if (codec_class->start) { + codec_class->start (codec); + } + + codec->started = TRUE; + + return TRUE; +} + +/** + * gst_base_audio_decoder_flush: + * @codec: The #GstBaseAudioDecoder instance. + * + * Flushes the input and output adapters. Subclasses should provide + * a flush_input implementation to allow flushing the input adapter. + * For the output adapter subclasses should provide a flush_output + * implementation. If no flush_output implementation is provided + * the output adapter will be flushed by pushing a single buffer + * containing all the data present in the output adapter. + * + * It is guaranteed that any data present in the adapters will be cleared + * after calling this method even if the operation flush + * operation was not successfull. + * + * Returns: TRUE if the flush operation was successful (any data present in + * the adapters was properly processed). + */ +gboolean +gst_base_audio_decoder_flush (GstBaseAudioDecoder * codec) +{ + GstFlowReturn ret_i = GST_FLOW_OK; + GstFlowReturn ret_o = GST_FLOW_OK; + guint bytes; + GstBaseAudioDecoderClass *codec_class; + + GST_DEBUG ("gst_base_audio_decoder_flush"); + + codec_class = GST_BASE_AUDIO_DECODER_GET_CLASS (codec); + + /* Flush input adapter */ + bytes = gst_adapter_available (codec->input_adapter); + if (bytes > 0) { + GST_DEBUG ("Flushing input adapter"); + /* If the subclass provides a flush_input implementation, use that. + Otherwise we will clear the adapter and lose the data */ + if (codec_class->flush_input) { + ret_i = codec_class->flush_input (codec); + if (ret_i != GST_FLOW_OK) { + GST_DEBUG ("failed to flush input"); + } + } else { + GST_DEBUG ("Received EOS but cannot flush input, data will be lost"); + ret_i = GST_FLOW_ERROR; + } + gst_adapter_clear (codec->input_adapter); + } + + /* Flush output adapter */ + bytes = gst_adapter_available (codec->output_adapter); + if (bytes > 0) { + /* If the subclass provides a flush_output implementation, use that. + Otherwise just push a single buffer with the adapter contents */ + GST_DEBUG ("Flushing output adapter"); + if (codec_class->flush_output) { + ret_o = codec_class->flush_output (codec); + if (ret_o != GST_FLOW_OK) { + GST_DEBUG ("failed to flush output (flush_output)"); + } + } else { + GstBuffer *outbuf = + gst_adapter_take_buffer (codec->output_adapter, bytes); + ret_o = gst_base_audio_decoder_push_buffer (codec, outbuf); + gst_buffer_unref (outbuf); + if (ret_o != GST_FLOW_OK) { + GST_DEBUG ("Forced output flush failed"); + } + } + gst_adapter_clear (codec->output_adapter); + } + + return (ret_i == GST_FLOW_OK && ret_o == GST_FLOW_OK); +} + +/** + * gst_base_audio_decoder_set_src_caps: + * @codec: #GstBaseAudioDecoder instance + * @caps: The caps to set on the source pad of @codec. + * + * Attempts to set @caps as the source caps of @codec. If the new caps + * are accepted on the source pad, this will issue a flush on the adapters + * to ensure that any data received with the old caps is processed first + * and a reset of the codec. + * + * Returns: TRUE if caps were set successfully. + */ +gboolean +gst_base_audio_decoder_set_src_caps (GstBaseAudioDecoder * codec, + GstCaps * caps) +{ + gboolean ret; + + GST_DEBUG ("gst_base_audio_decoder_set_src_caps %" GST_PTR_FORMAT, caps); + + /* First, check if the pad accepts the new caps */ + if (!gst_pad_accept_caps (codec->srcpad, caps)) { + GST_DEBUG ("pad does not accept new caps"); + return FALSE; + } + + /* If we have data in our adapters we should probably flush first */ + gst_base_audio_decoder_flush (codec); + + /* Set the caps on the pad */ + ret = gst_pad_set_caps (codec->srcpad, caps); + + /* And update the state of the codec from the caps */ + if (ret) { + gst_base_audio_decoder_read_state_from_caps (codec, caps); + codec->caps_set = TRUE; + } + + return ret; +} + +/** + * gst_base_audio_decoder_push_buffer: + * @codec: #GstBaseAudioDecoder instance + * @buffer: a #GstBuffer. + * + * Pushes a buffer through the source pad. + * + * Returns: a #GstFlowReturn indicating the result of the push operation. + */ +GstFlowReturn +gst_base_audio_decoder_push_buffer (GstBaseAudioDecoder * codec, + GstBuffer * buffer) +{ + codec->bytes_out += GST_BUFFER_SIZE (buffer); + return gst_pad_push (codec->srcpad, buffer); +} diff --git a/gst-libs/gst/audio/gstbaseaudiodecoder.h b/gst-libs/gst/audio/gstbaseaudiodecoder.h new file mode 100644 index 0000000..429954e --- /dev/null +++ b/gst-libs/gst/audio/gstbaseaudiodecoder.h @@ -0,0 +1,220 @@ +/* GStreamer + * Copyright (C) 2009 Igalia S.L. + * Author: Iago Toral Quiroga + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GST_BASE_AUDIO_DECODER_H_ +#define _GST_BASE_AUDIO_DECODER_H_ + +#ifndef GST_USE_UNSTABLE_API +#warning "GstBaseAudioDecoder is unstable API and may change in future." +#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_BASE_AUDIO_DECODER \ + (gst_base_audio_decoder_get_type()) +#define GST_BASE_AUDIO_DECODER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASE_AUDIO_DECODER,GstBaseAudioDecoder)) +#define GST_BASE_AUDIO_DECODER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASE_AUDIO_DECODER,GstBaseAudioDecoderClass)) +#define GST_BASE_AUDIO_DECODER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_BASE_AUDIO_DECODER,GstBaseAudioDecoderClass)) +#define GST_IS_BASE_AUDIO_DECODER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASE_AUDIO_DECODER)) +#define GST_IS_BASE_AUDIO_DECODER_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_AUDIO_DECODER)) + +/** + * GST_BASE_AUDIO_DECODER_SINK_NAME: + * + * The name of the templates for the sink pad. + */ +#define GST_BASE_AUDIO_DECODER_SINK_NAME "sink" +/** + * GST_BASE_AUDIO_DECODER_SRC_NAME: + * + * The name of the templates for the source pad. + */ +#define GST_BASE_AUDIO_DECODER_SRC_NAME "src" + +/** + * GST_BASE_AUDIO_DECODER_SRC_PAD: + * @obj: base audio codec instance + * + * Gives the pointer to the source #GstPad object of the element. + */ +#define GST_BASE_AUDIO_DECODER_SRC_PAD(obj) (((GstBaseAudioDecoder *) (obj))->srcpad) + +/** + * GST_BASE_AUDIO_DECODER_SINK_PAD: + * @obj: base audio codec instance + * + * Gives the pointer to the sink #GstPad object of the element. + */ +#define GST_BASE_AUDIO_DECODER_SINK_PAD(obj) (((GstBaseAudioDecoder *) (obj))->sinkpad) + +/** + * GST_BASE_AUDIO_DECODER_INPUT_ADAPTER: + * @obj: base audio codec instance + * + * Gives the pointer to the input #GstAdapter object of the element. + */ +#define GST_BASE_AUDIO_DECODER_INPUT_ADAPTER(obj) (((GstBaseAudioDecoder *) (obj))->input_adapter) + +/** + * GST_BASE_AUDIO_DECODER_OUTPUT_ADAPTER: + * @obj: base audio codec instance + * + * Gives the pointer to the output #GstAdapter object of the element. + */ +#define GST_BASE_AUDIO_DECODER_OUTPUT_ADAPTER(obj) (((GstBaseAudioDecoder *) (obj))->output_adapter) + +typedef struct _GstBaseAudioDecoder GstBaseAudioDecoder; +typedef struct _GstBaseAudioDecoderClass GstBaseAudioDecoderClass; +typedef struct _GstAudioState GstAudioState; + +struct _GstAudioState +{ + gint channels; + gint rate; + gint bytes_per_sample; + gint sample_depth; + gint frame_size; + GstSegment segment; +}; + +/** + * GstBaseAudioDecoder: + * @element: the parent element. + * @caps_set: whether caps have been set on the codec's source pad. + * @sinkpad: the sink pad. + * @srcpad: the source pad. + * @input_adapter: the input adapter that will be filled with the input buffers. + * @output_adapter: the output adapter. Subclasses will read from the input + * adapter, process the data and fill the output adapter with the result. + * @input_buffer_size: The minimum amount of data that should be present on the + * input adapter for the codec to process it. + * @output_buffer_size: The minimum amount of data that should be present on the + * output adapter for the codec to push buffers out. + * @bytes_in: total bytes that have been received. + * @bytes_out: total bytes that have been pushed out. + * @discont: whether the next buffer to push represents a discontinuity in the + * stream. + * @state: Audio stream information. See #GstAudioState for details. + * @codec_data: The codec data. + * @started: Whether the codec has been started and is ready to process data + * or not. + * @first_ts: timestamp of the first buffer in the input adapter. + * @last_ts: timestamp of the last buffer in the input adapter. + * + * The opaque #GstBaseAudioDecoder data structure. + */ +struct _GstBaseAudioDecoder +{ + GstElement element; + + /*< private >*/ + gboolean caps_set; + + /*< protected >*/ + GstPad *sinkpad; + GstPad *srcpad; + GstAdapter *input_adapter; + GstAdapter *output_adapter; + guint input_buffer_size; + guint output_buffer_size; + guint64 bytes_in; + guint64 bytes_out; + gboolean discont; + GstAudioState state; + GstBuffer *codec_data; + gboolean started; + + guint64 first_ts; + guint64 last_ts; +}; + +/** + * GstBaseAudioDecoderClass: + * @parent_class: Element parent class + * @start: Start processing. Ideal for opening resources in the subclass + * @stop: Stop processing. Subclasses should use this to close resources. + * @reset: Resets the codec. Called on discontinuities, etc. + * @event: Override this to handle events arriving on the sink pad. + * @handle_discont: Override to be notified on discontinuities. + * @flush_input: Subclasses may implement this to flush the input adapter, + * processing any data present in it and filling the output adapter with the + * result. This could be necessary if it is possible for the codec to + * receive an end-of-stream event before all the data in the input + * adapter has been processed. + * @flush_output: Subclasses may implement this to flush the output adapter, + * pushing buffers out through the codec's source pad when the end-of-stream + * event is received and there is data waiting to be processed in the + * adapters. + * @process_data: Subclasses must implement this. They should read from the + * input adapter, encode/decode the data present in it and fill the + * output adapter with the result. + * @push_data: Normally, #GstBaseAudioDecoder will handle pushing buffers out. + * However, it is possible for developers to take control of when and how + * buffers are pushed out by overriding this method. If subclasses provide + * an implementation, #GstBaseAudioDecoder will not push any buffers, + * instead, whenever there is data on the output adapter, it will call this + * method on the subclass, which would be the sole responsible for + * pushing the buffers out when appropriate. + * @negotiate_src_caps: Subclasses can implement this method to provide + * appropriate caps to be set on the codec's source pad. If they don't + * provide this, they will be responsible for calling + * gst_base_audio_decoder_set_src_caps when appropriate. + */ +struct _GstBaseAudioDecoderClass +{ + GstElementClass parent_class; + + gboolean (*start) (GstBaseAudioDecoder *codec); + gboolean (*stop) (GstBaseAudioDecoder *codec); + gboolean (*reset) (GstBaseAudioDecoder *codec); + + GstFlowReturn (*event) (GstBaseAudioDecoder *codec, GstEvent *event); + void (*handle_discont) (GstBaseAudioDecoder *codec, GstBuffer *buffer); + gboolean (*flush_input) (GstBaseAudioDecoder *codec); + gboolean (*flush_output) (GstBaseAudioDecoder *codec); + GstFlowReturn (*process_data) (GstBaseAudioDecoder *codec); + GstFlowReturn (*push_data) (GstBaseAudioDecoder *codec); + GstCaps * (*negotiate_src_caps) (GstBaseAudioDecoder *codec, + GstCaps *sink_caps); +}; + +GType gst_base_audio_decoder_get_type (void); +gboolean gst_base_audio_decoder_reset (GstBaseAudioDecoder *codec); +gboolean gst_base_audio_decoder_stop (GstBaseAudioDecoder *codec); +gboolean gst_base_audio_decoder_start (GstBaseAudioDecoder *codec); +gboolean gst_base_audio_decoder_flush (GstBaseAudioDecoder *codec); +gboolean gst_base_audio_decoder_set_src_caps (GstBaseAudioDecoder *codec, + GstCaps *caps); +GstFlowReturn gst_base_audio_decoder_push_buffer (GstBaseAudioDecoder *codec, + GstBuffer *buffer); + +G_END_DECLS + +#endif + -- 2.7.4