diff options
author | Carlos Rafael Giani <dv@pseudoterminal.org> | 2016-07-27 01:41:20 +0200 |
---|---|---|
committer | Jan Schmidt <jan@centricular.com> | 2017-05-22 16:34:05 +0200 |
commit | 2e4c6d6a49a2c26e19cf8c1159231b316b9082a3 (patch) | |
tree | 37aede0a26d3b1a58d77452ba57a91a805297ea4 /gst-libs/gst/audio | |
parent | f3de920b126a8a39d4dde422e803d27ec1e5baad (diff) | |
download | gstreamer-plugins-bad-2e4c6d6a49a2c26e19cf8c1159231b316b9082a3.tar.gz |
audio: Add nonstreamaudiodecoder base class
https://bugzilla.gnome.org/show_bug.cgi?id=768576
Diffstat (limited to 'gst-libs/gst/audio')
-rw-r--r-- | gst-libs/gst/audio/Makefile.am | 5 | ||||
-rw-r--r-- | gst-libs/gst/audio/gstnonstreamaudiodecoder.c | 2511 | ||||
-rw-r--r-- | gst-libs/gst/audio/gstnonstreamaudiodecoder.h | 405 | ||||
-rw-r--r-- | gst-libs/gst/audio/meson.build | 4 |
4 files changed, 2921 insertions, 4 deletions
diff --git a/gst-libs/gst/audio/Makefile.am b/gst-libs/gst/audio/Makefile.am index d81014894..02154fa1d 100644 --- a/gst-libs/gst/audio/Makefile.am +++ b/gst-libs/gst/audio/Makefile.am @@ -4,7 +4,8 @@ lib_LTLIBRARIES = libgstbadaudio-@GST_API_VERSION@.la CLEANFILES = libgstbadaudio_@GST_API_VERSION@_la_SOURCES = \ - gstaudioaggregator.c + gstaudioaggregator.c \ + gstnonstreamaudiodecoder.c nodist_libgstbadaudio_@GST_API_VERSION@_la_SOURCES = $(BUILT_SOURCES) @@ -24,4 +25,4 @@ libgstbadaudio_@GST_API_VERSION@_la_LIBADD = \ libgstbadaudio_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) libgstaudio_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/audio -libgstaudio_@GST_API_VERSION@include_HEADERS = gstaudioaggregator.h +libgstaudio_@GST_API_VERSION@include_HEADERS = gstaudioaggregator.h gstnonstreamaudiodecoder.h diff --git a/gst-libs/gst/audio/gstnonstreamaudiodecoder.c b/gst-libs/gst/audio/gstnonstreamaudiodecoder.c new file mode 100644 index 000000000..853930316 --- /dev/null +++ b/gst-libs/gst/audio/gstnonstreamaudiodecoder.c @@ -0,0 +1,2511 @@ +/* GStreamer + * Copyright (C) <2017> Carlos Rafael Giani <dv at pseudoterminal dot org> + * + * 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:gstnonstreamaudiodecoder + * @short_description: Base class for decoding of non-streaming audio + * @see_also: #GstAudioDecoder + * + * This base class is for decoders which do not operate on a streaming model. + * That is: they load the encoded media at once, as part of an initialization, + * and afterwards can decode samples (sometimes referred to as "rendering the + * samples"). + * + * This sets it apart from GstAudioDecoder, which is a base class for + * streaming audio decoders. + * + * The base class is conceptually a mix between decoder and parser. This is + * unavoidable, since virtually no format that isn't streaming based has a + * clear distinction between parsing and decoding. As a result, this class + * also handles seeking. + * + * Non-streaming audio formats tend to have some characteristics unknown to + * more "regular" bitstreams. These include subsongs and looping. + * + * Subsongs are a set of songs-within-a-song. An analogy would be a multitrack + * recording, where each track is its own song. The first subsong is typically + * the "main" one. Subsongs were popular for video games to enable context- + * aware music; for example, subsong #0 would be the "main" song, #1 would be + * an alternate song playing when a fight started, #2 would be heard during + * conversations etc. The base class is designed to always have at least one + * subsong. If the subclass doesn't provide any, the base class creates a + * "pseudo" subsong, which is actually the whole song. + * Downstream is informed about the subsong using a table of contents (TOC), + * but only if there are at least 2 subsongs. + * + * Looping refers to jumps within the song, typically backwards to the loop + * start (although bi-directional looping is possible). The loop is defined + * by a chronological start and end; once the playback position reaches the + * loop end, it jumps back to the loop start. + * Depending on the subclass, looping may not be possible at all, or it + * may only be possible to enable/disable it (that is, either no looping, or + * an infinite amount of loops), or it may allow for defining a finite number + * of times the loop is repeated. + * Looping can affect output in two ways. Either, the playback position is + * reset to the start of the loop, similar to what happens after a seek event. + * Or, it is not reset, so the pipeline sees playback steadily moving forwards, + * the playback position monotonically increasing. However, seeking must + * always happen within the confines of the defined subsong duration; for + * example, if a subsong is 2 minutes long, steady playback is at 5 minutes + * (because infinite looping is enabled), then seeking will still place the + * position within the 2 minute period. + * Loop count 0 means no looping. Loop count -1 means infinite looping. + * Nonzero positive values indicate how often a loop shall occur. + * + * If the initial subsong and loop count are set to values the subclass does + * not support, the subclass has a chance to correct these values. + * @get_property then reports the corrected versions. + * + * The base class operates as follows: + * <orderedlist> + * <listitem> + * <itemizedlist><title>Unloaded mode</title> + * <listitem><para> + * Initial values are set. If a current subsong has already been + * defined (for example over the command line with gst-launch), then + * the subsong index is copied over to current_subsong . + * Same goes for the num-loops and output-mode properties. + * Media is NOT loaded yet. + * </para></listitem> + * <listitem><para> + * Once the sinkpad is activated, the process continues. The sinkpad is + * activated in push mode, and the class accumulates the incoming media + * data in an adapter inside the sinkpad's chain function until either an + * EOS event is received from upstream, or the number of bytes reported + * by upstream is reached. Then it loads the media, and starts the decoder + * output task. + * <listitem><para> + * If upstream cannot respond to the size query (in bytes) of @load_from_buffer + * fails, an error is reported, and the pipeline stops. + * </para></listitem> + * <listitem><para> + * If there are no errors, @load_from_buffer is called to load the media. The + * subclass must at least call gst_nonstream_audio_decoder_set_output_audioinfo() + * there, and is free to make use of the initial subsong, output mode, and + * position. If the actual output mode or position differs from the initial + * value,it must set the initial value to the actual one (for example, if + * the actual starting position is always 0, set *initial_position to 0). + * If loading is unsuccessful, an error is reported, and the pipeline + * stops. Otherwise, the base class calls @get_current_subsong to retrieve + * the actual current subsong, @get_subsong_duration to report the current + * subsong's duration in a duration event and message, and @get_subsong_tags + * to send tags downstream in an event (these functions are optional; if + * set to NULL, the associated operation is skipped). Afterwards, the base + * class switches to loaded mode, and starts the decoder output task. + * </para></listitem> + * </itemizedlist> + * <itemizedlist><title>Loaded mode</title> + * <listitem><para> + * Inside the decoder output task, the base class repeatedly calls @decode, + * which returns a buffer with decoded, ready-to-play samples. If the + * subclass reached the end of playback, @decode returns FALSE, otherwise + * TRUE. + * </para></listitem> + * <listitem><para> + * Upon reaching a loop end, subclass either ignores that, or loops back + * to the beginning of the loop. In the latter case, if the output mode is set + * to LOOPING, the subclass must call gst_nonstream_audio_decoder_handle_loop() + * *after* the playback position moved to the start of the loop. In + * STEADY mode, the subclass must *not* call this function. + * Since many decoders only provide a callback for when the looping occurs, + * and that looping occurs inside the decoding operation itself, the following + * mechanism for subclass is suggested: set a flag inside such a callback. + * Then, in the next @decode call, before doing the decoding, check this flag. + * If it is set, gst_nonstream_audio_decoder_handle_loop() is called, and the + * flag is cleared. + * (This function call is necessary in LOOPING mode because it updates the + * current segment and makes sure the next buffer that is sent downstream + * has its DISCONT flag set.) + * </para></listitem> + * <listitem><para> + * When the current subsong is switched, @set_current_subsong is called. + * If it fails, a warning is reported, and nothing else is done. Otherwise, + * it calls @get_subsong_duration to get the new current subsongs's + * duration, @get_subsong_tags to get its tags, reports a new duration + * (i.e. it sends a duration event downstream and generates a duration + * message), updates the current segment, and sends the subsong's tags in + * an event downstream. (If @set_current_subsong has been set to NULL by + * the subclass, attempts to set a current subsong are ignored; likewise, + * if @get_subsong_duration is NULL, no duration is reported, and if + * @get_subsong_tags is NULL, no tags are sent downstream.) + * </para></listitem> + * <listitem><para> + * When an attempt is made to switch the output mode, it is checked against + * the bitmask returned by @get_supported_output_modes. If the proposed + * new output mode is supported, the current segment is updated + * (it is open-ended in STEADY mode, and covers the (sub)song length in + * LOOPING mode), and the subclass' @set_output_mode function is called + * unless it is set to NULL. Subclasses should reset internal loop counters + * in this function. + * </para></listitem> + * </itemizedlist> + * </listitem> + * </orderedlist> + * + * The relationship between (sub)song duration, output mode, and number of loops + * is defined this way (this is all done by the base class automatically): + * <itemizedlist> + * <listitem><para> + * Segments have their duration and stop values set to GST_CLOCK_TIME_NONE in + * STEADY mode, and to the duration of the (sub)song in LOOPING mode. + * </para></listitem> + * <listitem><para> + * The duration that is returned to a DURATION query is always the duration + * of the (sub)song, regardless of number of loops or output mode. The same + * goes for DURATION messages and tags. + * </para></listitem> + * <listitem><para> + * If the number of loops is >0 or -1, durations of TOC entries are set to + * the duration of the respective subsong in LOOPING mode and to G_MAXINT64 in + * STEADY mode. If the number of loops is 0, entry durations are set to the + * subsong duration regardless of the output mode. + * </para></listitem> + * </itemizedlist> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <gst/gst.h> +#include <gst/audio/audio.h> + +#include "gstnonstreamaudiodecoder.h" + + +GST_DEBUG_CATEGORY (nonstream_audiodecoder_debug); +#define GST_CAT_DEFAULT nonstream_audiodecoder_debug + + +enum +{ + PROP_0, + PROP_CURRENT_SUBSONG, + PROP_SUBSONG_MODE, + PROP_NUM_LOOPS, + PROP_OUTPUT_MODE +}; + +#define DEFAULT_CURRENT_SUBSONG 0 +#define DEFAULT_SUBSONG_MODE GST_NONSTREAM_AUDIO_SUBSONG_MODE_DECODER_DEFAULT +#define DEFAULT_NUM_SUBSONGS 0 +#define DEFAULT_NUM_LOOPS 0 +#define DEFAULT_OUTPUT_MODE GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY + + + + +static GstElementClass *gst_nonstream_audio_decoder_parent_class = NULL; + +static void +gst_nonstream_audio_decoder_class_init (GstNonstreamAudioDecoderClass * klass); +static void gst_nonstream_audio_decoder_init (GstNonstreamAudioDecoder * dec, + GstNonstreamAudioDecoderClass * klass); + +static void gst_nonstream_audio_decoder_finalize (GObject * object); +static void gst_nonstream_audio_decoder_set_property (GObject * object, + guint prop_id, GValue const *value, GParamSpec * pspec); +static void gst_nonstream_audio_decoder_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstStateChangeReturn gst_nonstream_audio_decoder_change_state (GstElement + * element, GstStateChange transition); + +static gboolean gst_nonstream_audio_decoder_sink_event (GstPad * pad, + GstObject * parent, GstEvent * event); +static gboolean gst_nonstream_audio_decoder_sink_query (GstPad * pad, + GstObject * parent, GstQuery * query); +static GstFlowReturn gst_nonstream_audio_decoder_chain (GstPad * pad, + GstObject * parent, GstBuffer * buffer); + +static gboolean gst_nonstream_audio_decoder_src_event (GstPad * pad, + GstObject * parent, GstEvent * event); +static gboolean gst_nonstream_audio_decoder_src_query (GstPad * pad, + GstObject * parent, GstQuery * query); + +static void +gst_nonstream_audio_decoder_set_initial_state (GstNonstreamAudioDecoder * dec); +static void gst_nonstream_audio_decoder_cleanup_state (GstNonstreamAudioDecoder + * dec); + +static gboolean gst_nonstream_audio_decoder_negotiate (GstNonstreamAudioDecoder + * dec); + +static gboolean +gst_nonstream_audio_decoder_negotiate_default (GstNonstreamAudioDecoder * dec); +static gboolean +gst_nonstream_audio_decoder_decide_allocation_default (GstNonstreamAudioDecoder + * dec, GstQuery * query); +static gboolean +gst_nonstream_audio_decoder_propose_allocation_default (GstNonstreamAudioDecoder + * dec, GstQuery * query); + +static gboolean +gst_nonstream_audio_decoder_get_upstream_size (GstNonstreamAudioDecoder * dec, + gint64 * length); +static gboolean +gst_nonstream_audio_decoder_load_from_buffer (GstNonstreamAudioDecoder * dec, + GstBuffer * buffer); +static gboolean +gst_nonstream_audio_decoder_load_from_custom (GstNonstreamAudioDecoder * dec); +static gboolean +gst_nonstream_audio_decoder_finish_load (GstNonstreamAudioDecoder * dec, + gboolean load_ok, GstClockTime initial_position, + gboolean send_stream_start); + +static gboolean gst_nonstream_audio_decoder_start_task (GstNonstreamAudioDecoder + * dec); +static gboolean gst_nonstream_audio_decoder_stop_task (GstNonstreamAudioDecoder + * dec); + +static gboolean +gst_nonstream_audio_decoder_switch_to_subsong (GstNonstreamAudioDecoder * dec, + guint new_subsong, guint32 const *seqnum); + +static void gst_nonstream_audio_decoder_update_toc (GstNonstreamAudioDecoder * + dec, GstNonstreamAudioDecoderClass * klass); +static void +gst_nonstream_audio_decoder_update_subsong_duration (GstNonstreamAudioDecoder * + dec, GstClockTime duration); +static void +gst_nonstream_audio_decoder_output_new_segment (GstNonstreamAudioDecoder * dec, + GstClockTime start_position); +static gboolean gst_nonstream_audio_decoder_do_seek (GstNonstreamAudioDecoder * + dec, GstEvent * event); + +static GstTagList + * gst_nonstream_audio_decoder_add_main_tags (GstNonstreamAudioDecoder * dec, + GstTagList * tags); + +static void gst_nonstream_audio_decoder_output_task (GstNonstreamAudioDecoder * + dec); + +static char const *get_seek_type_name (GstSeekType seek_type); + + + + +static GType gst_nonstream_audio_decoder_output_mode_get_type (void); +#define GST_TYPE_NONSTREAM_AUDIO_DECODER_OUTPUT_MODE (gst_nonstream_audio_decoder_output_mode_get_type()) + +static GType gst_nonstream_audio_decoder_subsong_mode_get_type (void); +#define GST_TYPE_NONSTREAM_AUDIO_DECODER_SUBSONG_MODE (gst_nonstream_audio_decoder_subsong_mode_get_type()) + + +static GType +gst_nonstream_audio_decoder_output_mode_get_type (void) +{ + static GType gst_nonstream_audio_decoder_output_mode_type = 0; + + if (!gst_nonstream_audio_decoder_output_mode_type) { + static GEnumValue output_mode_values[] = { + {GST_NONSTREAM_AUDIO_OUTPUT_MODE_LOOPING, "Looping output", "looping"}, + {GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY, "Steady output", "steady"}, + {0, NULL, NULL}, + }; + + gst_nonstream_audio_decoder_output_mode_type = + g_enum_register_static ("NonstreamAudioOutputMode", output_mode_values); + } + + return gst_nonstream_audio_decoder_output_mode_type; +} + + +static GType +gst_nonstream_audio_decoder_subsong_mode_get_type (void) +{ + static GType gst_nonstream_audio_decoder_subsong_mode_type = 0; + + if (!gst_nonstream_audio_decoder_subsong_mode_type) { + static GEnumValue subsong_mode_values[] = { + {GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE, "Play single subsong", + "single"}, + {GST_NONSTREAM_AUDIO_SUBSONG_MODE_ALL, "Play all subsongs", "all"}, + {GST_NONSTREAM_AUDIO_SUBSONG_MODE_DECODER_DEFAULT, + "Decoder specific default behavior", "default"}, + {0, NULL, NULL}, + }; + + gst_nonstream_audio_decoder_subsong_mode_type = + g_enum_register_static ("NonstreamAudioSubsongMode", + subsong_mode_values); + } + + return gst_nonstream_audio_decoder_subsong_mode_type; +} + + + +/* Manually defining the GType instead of using G_DEFINE_TYPE_WITH_CODE() + * because the _init() function needs to be able to access the derived + * class' sink- and srcpads */ + + +GType +gst_nonstream_audio_decoder_get_type (void) +{ + static volatile gsize nonstream_audio_decoder_type = 0; + + if (g_once_init_enter (&nonstream_audio_decoder_type)) { + GType type_; + static const GTypeInfo nonstream_audio_decoder_info = { + sizeof (GstNonstreamAudioDecoderClass), + NULL, + NULL, + (GClassInitFunc) gst_nonstream_audio_decoder_class_init, + NULL, + NULL, + sizeof (GstNonstreamAudioDecoder), + 0, + (GInstanceInitFunc) gst_nonstream_audio_decoder_init, + NULL + }; + + type_ = g_type_register_static (GST_TYPE_ELEMENT, + "GstNonstreamAudioDecoder", + &nonstream_audio_decoder_info, G_TYPE_FLAG_ABSTRACT); + g_once_init_leave (&nonstream_audio_decoder_type, type_); + } + + return nonstream_audio_decoder_type; +} + + + + +static void +gst_nonstream_audio_decoder_class_init (GstNonstreamAudioDecoderClass * klass) +{ + GObjectClass *object_class; + GstElementClass *element_class; + + object_class = G_OBJECT_CLASS (klass); + element_class = GST_ELEMENT_CLASS (klass); + + gst_nonstream_audio_decoder_parent_class = g_type_class_peek_parent (klass); + + GST_DEBUG_CATEGORY_INIT (nonstream_audiodecoder_debug, + "nonstreamaudiodecoder", 0, "nonstream audio decoder base class"); + + object_class->finalize = + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_finalize); + object_class->set_property = + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_set_property); + object_class->get_property = + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_get_property); + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_change_state); + + klass->seek = NULL; + klass->tell = NULL; + + klass->load_from_buffer = NULL; + klass->load_from_custom = NULL; + + klass->get_main_tags = NULL; + + klass->get_current_subsong = NULL; + klass->set_current_subsong = NULL; + + klass->get_num_subsongs = NULL; + klass->get_subsong_duration = NULL; + klass->get_subsong_tags = NULL; + klass->set_subsong_mode = NULL; + + klass->set_num_loops = NULL; + klass->get_num_loops = NULL; + + klass->decode = NULL; + + klass->negotiate = + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_negotiate_default); + + klass->decide_allocation = + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_decide_allocation_default); + klass->propose_allocation = + GST_DEBUG_FUNCPTR + (gst_nonstream_audio_decoder_propose_allocation_default); + + klass->loads_from_sinkpad = TRUE; + + g_object_class_install_property (object_class, + PROP_CURRENT_SUBSONG, + g_param_spec_uint ("current-subsong", + "Currently active subsong", + "Subsong that is currently selected for playback", + 0, G_MAXUINT, + DEFAULT_CURRENT_SUBSONG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) + ); + + g_object_class_install_property (object_class, + PROP_SUBSONG_MODE, + g_param_spec_enum ("subsong-mode", + "Subsong mode", + "Mode which defines how to treat subsongs", + GST_TYPE_NONSTREAM_AUDIO_DECODER_SUBSONG_MODE, + DEFAULT_SUBSONG_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) + ); + + g_object_class_install_property (object_class, + PROP_NUM_LOOPS, + g_param_spec_int ("num-loops", + "Number of playback loops", + "Number of times a playback loop shall be executed (special values: 0 = no looping; -1 = infinite loop)", + -1, G_MAXINT, + DEFAULT_NUM_LOOPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) + ); + + g_object_class_install_property (object_class, + PROP_OUTPUT_MODE, + g_param_spec_enum ("output-mode", + "Output mode", + "Which mode playback shall use when a loop is encountered; looping = reset position to start of loop, steady = do not reset position", + GST_TYPE_NONSTREAM_AUDIO_DECODER_OUTPUT_MODE, + DEFAULT_OUTPUT_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) + ); +} + + +static void +gst_nonstream_audio_decoder_init (GstNonstreamAudioDecoder * dec, + GstNonstreamAudioDecoderClass * klass) +{ + GstPadTemplate *pad_template; + + /* These are set here, not in gst_nonstream_audio_decoder_set_initial_state(), + * because these are values for the properties; they are not supposed to be + * reset in the READY->NULL state change */ + dec->current_subsong = DEFAULT_CURRENT_SUBSONG; + dec->subsong_mode = DEFAULT_SUBSONG_MODE; + dec->output_mode = DEFAULT_OUTPUT_MODE; + dec->num_loops = DEFAULT_NUM_LOOPS; + + /* Calling this here, not in the NULL->READY state change, + * to make sure get_property calls return valid values */ + gst_nonstream_audio_decoder_set_initial_state (dec); + + dec->input_data_adapter = gst_adapter_new (); + g_mutex_init (&(dec->mutex)); + + { + /* set up src pad */ + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "src"); + g_return_if_fail (pad_template != NULL); /* derived class is supposed to define a src pad template */ + + dec->srcpad = gst_pad_new_from_template (pad_template, "src"); + gst_pad_set_event_function (dec->srcpad, + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_src_event)); + gst_pad_set_query_function (dec->srcpad, + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_src_query)); + gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad); + } + + if (klass->loads_from_sinkpad) { + /* set up sink pad if this class loads from a sinkpad */ + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "sink"); + g_return_if_fail (pad_template != NULL); /* derived class is supposed to define a sink pad template */ + + dec->sinkpad = gst_pad_new_from_template (pad_template, "sink"); + gst_pad_set_event_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_sink_event)); + gst_pad_set_query_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_sink_query)); + gst_pad_set_chain_function (dec->sinkpad, + GST_DEBUG_FUNCPTR (gst_nonstream_audio_decoder_chain)); + gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad); + } +} + + + + +static void +gst_nonstream_audio_decoder_finalize (GObject * object) +{ + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (object); + + g_mutex_clear (&(dec->mutex)); + g_object_unref (G_OBJECT (dec->input_data_adapter)); + + G_OBJECT_CLASS (gst_nonstream_audio_decoder_parent_class)->finalize (object); +} + + +static void +gst_nonstream_audio_decoder_set_property (GObject * object, guint prop_id, + GValue const *value, GParamSpec * pspec) +{ + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (object); + GstNonstreamAudioDecoderClass *klass = + GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + switch (prop_id) { + case PROP_OUTPUT_MODE: + { + GstNonstreamAudioOutputMode new_output_mode; + new_output_mode = g_value_get_enum (value); + + g_assert (klass->get_supported_output_modes); + + if ((klass->get_supported_output_modes (dec) & (1u << new_output_mode)) == + 0) { + GST_WARNING_OBJECT (dec, + "could not set output mode to %s (not supported by subclass)", + (new_output_mode == + GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY) ? "steady" : "looping"); + break; + } + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + if (new_output_mode != dec->output_mode) { + gboolean proceed = TRUE; + + if (dec->loaded_mode) { + GstClockTime cur_position; + + if (klass->set_output_mode != NULL) { + if (klass->set_output_mode (dec, new_output_mode, &cur_position)) + proceed = TRUE; + else { + proceed = FALSE; + GST_WARNING_OBJECT (dec, "switching to new output mode failed"); + } + } else + GST_DEBUG_OBJECT (dec, + "cannot call set_output_mode, since it is NULL"); + + if (proceed) { + gst_nonstream_audio_decoder_output_new_segment (dec, cur_position); + dec->output_mode = new_output_mode; + } + } + + if (proceed) { + /* store output mode in case the property is set before the media got loaded */ + dec->output_mode = new_output_mode; + } + } + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + break; + } + + case PROP_CURRENT_SUBSONG: + { + guint new_subsong = g_value_get_uint (value); + gst_nonstream_audio_decoder_switch_to_subsong (dec, new_subsong, NULL); + + break; + } + + case PROP_SUBSONG_MODE: + { + GstNonstreamAudioSubsongMode new_subsong_mode = g_value_get_enum (value); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + if (new_subsong_mode != dec->subsong_mode) { + gboolean proceed = TRUE; + + if (dec->loaded_mode) { + GstClockTime cur_position; + + if (klass->set_subsong_mode != NULL) { + if (klass->set_subsong_mode (dec, new_subsong_mode, &cur_position)) + proceed = TRUE; + else { + proceed = FALSE; + GST_WARNING_OBJECT (dec, "switching to new subsong mode failed"); + } + } else + GST_DEBUG_OBJECT (dec, + "cannot call set_subsong_mode, since it is NULL"); + + if (proceed) { + if (GST_CLOCK_TIME_IS_VALID (cur_position)) + gst_nonstream_audio_decoder_output_new_segment (dec, + cur_position); + dec->subsong_mode = new_subsong_mode; + } + } + + if (proceed) { + /* store subsong mode in case the property is set before the media got loaded */ + dec->subsong_mode = new_subsong_mode; + } + } + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + break; + } + + case PROP_NUM_LOOPS: + { + gint new_num_loops = g_value_get_int (value); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + if (new_num_loops != dec->num_loops) { + if (dec->loaded_mode) { + if (klass->set_num_loops != NULL) { + if (!(klass->set_num_loops (dec, new_num_loops))) + GST_WARNING_OBJECT (dec, "setting number of loops to %u failed", + new_num_loops); + } else + GST_DEBUG_OBJECT (dec, + "cannot call set_num_loops, since it is NULL"); + } + + /* store number of loops in case the property is set before the media got loaded */ + dec->num_loops = new_num_loops; + } + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + break; + } + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +gst_nonstream_audio_decoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (object); + + switch (prop_id) { + case PROP_OUTPUT_MODE: + { + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + g_value_set_enum (value, dec->output_mode); + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + break; + } + + case PROP_CURRENT_SUBSONG: + { + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + g_value_set_uint (value, dec->current_subsong); + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + break; + } + + case PROP_SUBSONG_MODE: + { + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + g_value_set_enum (value, dec->subsong_mode); + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + break; + } + + case PROP_NUM_LOOPS: + { + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + g_value_set_int (value, dec->num_loops); + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + break; + } + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + + +static GstStateChangeReturn +gst_nonstream_audio_decoder_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret; + + ret = + GST_ELEMENT_CLASS (gst_nonstream_audio_decoder_parent_class)->change_state + (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + { + + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (element); + GstNonstreamAudioDecoderClass *klass = + GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + /* For decoders that load with some custom method, + * this is now the time to load + * + * It is done *after* calling the parent class' change_state vfunc, + * since the pad states need to be set up in order for the loading + * to succeed, since it will try to push a new_caps event + * downstream etc. (upwards state changes typically are handled + * *before* calling the parent class' change_state vfunc ; this is + * a special case) */ + if (!(klass->loads_from_sinkpad) && !(dec->loaded_mode)) { + gboolean ret; + + /* load_from_custom is required if loads_from_sinkpad is FALSE */ + g_assert (klass->load_from_custom != NULL); + + ret = gst_nonstream_audio_decoder_load_from_custom (dec); + + if (!ret) { + GST_ERROR_OBJECT (dec, "loading from custom source failed"); + return GST_STATE_CHANGE_FAILURE; + } + + if (!gst_nonstream_audio_decoder_start_task (dec)) + return GST_STATE_CHANGE_FAILURE; + + } + + break; + } + + case GST_STATE_CHANGE_PAUSED_TO_READY: + { + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (element); + if (!gst_nonstream_audio_decoder_stop_task (dec)) + return GST_STATE_CHANGE_FAILURE; + break; + } + + case GST_STATE_CHANGE_READY_TO_NULL: + { + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (element); + + /* In the READY->NULL state change, reset the decoder to an + * initial state ensure it can be used for a fresh new session */ + gst_nonstream_audio_decoder_cleanup_state (dec); + break; + } + + default: + break; + } + + return ret; +} + + + +static gboolean +gst_nonstream_audio_decoder_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + gboolean res = FALSE; + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (parent); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + { + /* Upstream sends in a byte segment, which is uninteresting here, + * since a custom segment event is generated anyway */ + gst_event_unref (event); + res = TRUE; + break; + } + + case GST_EVENT_EOS: + { + gsize avail_size; + GstBuffer *adapter_buffer; + + if (dec->loaded_mode) { + /* If media has already been loaded, then the decoder + * task has been started; the EOS event can be ignored */ + + GST_DEBUG_OBJECT (dec, + "EOS received after media was loaded -> ignoring"); + res = TRUE; + } else { + /* take all data in the input data adapter, + * and try to load the media from it */ + + avail_size = gst_adapter_available (dec->input_data_adapter); + if (avail_size == 0) { + GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), + ("EOS event raised, but no data was received - cannot load anything")); + return FALSE; + } + + adapter_buffer = + gst_adapter_take_buffer (dec->input_data_adapter, avail_size); + + if (!gst_nonstream_audio_decoder_load_from_buffer (dec, adapter_buffer)) { + return FALSE; + } + + res = gst_nonstream_audio_decoder_start_task (dec); + } + + break; + } + + default: + res = gst_pad_event_default (pad, parent, event); + } + + return res; +} + + +static gboolean +gst_nonstream_audio_decoder_sink_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + gboolean res = FALSE; + GstNonstreamAudioDecoder *dec; + GstNonstreamAudioDecoderClass *klass; + + dec = GST_NONSTREAM_AUDIO_DECODER (parent); + klass = GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_ALLOCATION: + { + if (klass->propose_allocation != NULL) + res = klass->propose_allocation (dec, query); + + break; + } + + default: + res = gst_pad_query_default (pad, parent, query); + } + + return res; +} + + +static GstFlowReturn +gst_nonstream_audio_decoder_chain (G_GNUC_UNUSED GstPad * pad, + GstObject * parent, GstBuffer * buffer) +{ + GstFlowReturn flow_ret = GST_FLOW_OK; + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (parent); + + /* query upstream size in bytes to know how many bytes to expect + * this is a safety measure to prevent the case when upstream never + * reaches EOS (or only after a long time) and we keep loading and + * loading and eventually run out of memory */ + if (dec->upstream_size < 0) { + if (!gst_nonstream_audio_decoder_get_upstream_size (dec, + &(dec->upstream_size))) { + GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), + ("Cannot load - upstream size (in bytes) could not be determined")); + return GST_FLOW_ERROR; + } + } + + if (dec->loaded_mode) { + /* media is already loaded - discard any incoming + * buffers, since they are not needed */ + + GST_DEBUG_OBJECT (dec, "received data after media was loaded - ignoring"); + + gst_buffer_unref (buffer); + } else { + /* accumulate data until end-of-stream or the upstream + * size is reached, then load media and commence playback */ + + gint64 avail_size; + + gst_adapter_push (dec->input_data_adapter, buffer); + avail_size = gst_adapter_available (dec->input_data_adapter); + if (avail_size >= dec->upstream_size) { + GstBuffer *adapter_buffer = + gst_adapter_take_buffer (dec->input_data_adapter, avail_size); + + if (gst_nonstream_audio_decoder_load_from_buffer (dec, adapter_buffer)) + flow_ret = + gst_nonstream_audio_decoder_start_task (dec) ? GST_FLOW_OK : + GST_FLOW_ERROR; + else + flow_ret = GST_FLOW_ERROR; + } + } + + return flow_ret; +} + + + +static gboolean +gst_nonstream_audio_decoder_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + gboolean res = FALSE; + GstNonstreamAudioDecoder *dec = GST_NONSTREAM_AUDIO_DECODER (parent); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + res = gst_nonstream_audio_decoder_do_seek (dec, event); + break; + } + + case GST_EVENT_TOC_SELECT: + { + /* NOTE: This event may be received multiple times if it + * was originally sent to a bin containing multiple sink + * elements (for example, playbin). This is OK and does + * not break anything. */ + + gchar *uid = NULL; + guint subsong_idx = 0; + guint32 seqnum; + + gst_event_parse_toc_select (event, &uid); + + if ((uid != NULL) + && (sscanf (uid, "nonstream-subsong-%05u", &subsong_idx) == 1)) { + seqnum = gst_event_get_seqnum (event); + + GST_DEBUG_OBJECT (dec, + "received TOC select event (sequence number %" G_GUINT32_FORMAT + "), switching to subsong %u", seqnum, subsong_idx); + + gst_nonstream_audio_decoder_switch_to_subsong (dec, subsong_idx, + &seqnum); + } + + g_free (uid); + + res = TRUE; + + break; + } + + default: + res = gst_pad_event_default (pad, parent, event); + } + + return res; +} + + +static gboolean +gst_nonstream_audio_decoder_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + gboolean res = FALSE; + GstNonstreamAudioDecoder *dec; + GstNonstreamAudioDecoderClass *klass; + GstFormat format; + + dec = GST_NONSTREAM_AUDIO_DECODER (parent); + klass = GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_DURATION: + { + GST_TRACE_OBJECT (parent, "duration query"); + + if (!(dec->loaded_mode)) { + GST_DEBUG_OBJECT (parent, + "cannot respond to duration query: nothing is loaded yet"); + break; + } + + GST_TRACE_OBJECT (parent, "parsing duration query"); + gst_query_parse_duration (query, &format, NULL); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + if ((format == GST_FORMAT_TIME) + && (dec->subsong_duration != GST_CLOCK_TIME_NONE)) { + GST_DEBUG_OBJECT (parent, + "responding to query with duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (dec->subsong_duration)); + gst_query_set_duration (query, format, dec->subsong_duration); + res = TRUE; + } else if (format != GST_FORMAT_TIME) + GST_DEBUG_OBJECT (parent, + "cannot respond to duration query: format is %s, expected time format", + gst_format_get_name (format)); + else if (dec->subsong_duration == GST_CLOCK_TIME_NONE) + GST_DEBUG_OBJECT (parent, + "cannot respond to duration query: no valid subsong duration available"); + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + break; + } + + case GST_QUERY_POSITION: + { + if (!(dec->loaded_mode)) { + GST_DEBUG_OBJECT (parent, + "cannot respond to position query: nothing is loaded yet"); + break; + } + + if (klass->tell == NULL) { + GST_DEBUG_OBJECT (parent, + "cannot respond to position query: subclass does not have tell() function defined"); + break; + } + + gst_query_parse_position (query, &format, NULL); + if (format == GST_FORMAT_TIME) { + GstClockTime pos; + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + pos = klass->tell (dec); + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + GST_DEBUG_OBJECT (parent, + "position query received with format TIME -> reporting position %" + GST_TIME_FORMAT, GST_TIME_ARGS (pos)); + gst_query_set_position (query, format, pos); + res = TRUE; + } else { + GST_DEBUG_OBJECT (parent, + "position query received with unsupported format %s -> not reporting anything", + gst_format_get_name (format)); + } + + break; + } + + case GST_QUERY_SEEKING: + { + gboolean b; + GstFormat fmt; + GstClockTime duration; + + b = dec->loaded_mode; + + if (!b) { + GST_DEBUG_OBJECT (parent, + "cannot respond to seeking query: nothing is loaded yet"); + break; + } + + if (klass->seek == NULL) { + GST_DEBUG_OBJECT (parent, + "cannot respond to seeking query: subclass does not have seek() function defined"); + break; + } + + gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + duration = dec->subsong_duration; + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + if (fmt == GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (parent, + "seeking query received with format TIME -> can seek: yes"); + gst_query_set_seeking (query, fmt, TRUE, 0, duration); + res = TRUE; + } else { + GST_DEBUG_OBJECT (parent, + "seeking query received with unsupported format %s -> can seek: no", + gst_format_get_name (format)); + gst_query_set_seeking (query, fmt, FALSE, 0, -1); + res = TRUE; + } + + break; + } + + default: + res = gst_pad_query_default (pad, parent, query); + } + + return res; +} + + + +static void +gst_nonstream_audio_decoder_set_initial_state (GstNonstreamAudioDecoder * dec) +{ + dec->upstream_size = -1; + dec->loaded_mode = FALSE; + + dec->subsong_duration = GST_CLOCK_TIME_NONE; + + dec->output_format_changed = FALSE; + gst_audio_info_init (&(dec->output_audio_info)); + dec->num_decoded_samples = 0; + dec->cur_pos_in_samples = 0; + gst_segment_init (&(dec->cur_segment), GST_FORMAT_TIME); + dec->discont = FALSE; + + dec->toc = NULL; + + dec->allocator = NULL; +} + + +static void +gst_nonstream_audio_decoder_cleanup_state (GstNonstreamAudioDecoder * dec) +{ + gst_adapter_clear (dec->input_data_adapter); + + if (dec->allocator != NULL) { + gst_object_unref (dec->allocator); + dec->allocator = NULL; + } + + if (dec->toc != NULL) { + gst_toc_unref (dec->toc); + dec->toc = NULL; + } + + gst_nonstream_audio_decoder_set_initial_state (dec); +} + + +static gboolean +gst_nonstream_audio_decoder_negotiate (GstNonstreamAudioDecoder * dec) +{ + /* must be called with lock */ + + GstNonstreamAudioDecoderClass *klass; + gboolean res = TRUE; + + klass = GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + /* protected by a mutex, since the allocator might currently be in use */ + if (klass->negotiate != NULL) + res = klass->negotiate (dec); + + return res; +} + + +static gboolean +gst_nonstream_audio_decoder_negotiate_default (GstNonstreamAudioDecoder * dec) +{ + /* mutex is locked when this is called */ + + GstCaps *caps; + GstNonstreamAudioDecoderClass *klass; + gboolean res = TRUE; + GstQuery *query = NULL; + GstAllocator *allocator; + GstAllocationParams allocation_params; + + g_return_val_if_fail (GST_IS_NONSTREAM_AUDIO_DECODER (dec), FALSE); + g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (&(dec->output_audio_info)), + FALSE); + + klass = GST_NONSTREAM_AUDIO_DECODER_CLASS (G_OBJECT_GET_CLASS (dec)); + + caps = gst_audio_info_to_caps (&(dec->output_audio_info)); + + GST_DEBUG_OBJECT (dec, "setting src caps %" GST_PTR_FORMAT, (gpointer) caps); + + res = gst_pad_push_event (dec->srcpad, gst_event_new_caps (caps)); + /* clear any pending reconfigure flag */ + gst_pad_check_reconfigure (dec->srcpad); + + if (!res) { + GST_WARNING_OBJECT (dec, "could not push new caps event downstream"); + goto done; + } + + GST_TRACE_OBJECT (dec, "src caps set"); + + dec->output_format_changed = FALSE; + + query = gst_query_new_allocation (caps, TRUE); + if (!gst_pad_peer_query (dec->srcpad, query)) { + GST_DEBUG_OBJECT (dec, "didn't get downstream ALLOCATION hints"); + } + + g_assert (klass->decide_allocation != NULL); + res = klass->decide_allocation (dec, query); + + GST_DEBUG_OBJECT (dec, "ALLOCATION (%d) params: %" GST_PTR_FORMAT, res, + (gpointer) 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, + &allocation_params); + } else { + allocator = NULL; + gst_allocation_params_init (&allocation_params); + } + + if (dec->allocator != NULL) + gst_object_unref (dec->allocator); + dec->allocator = allocator; + dec->allocation_params = allocation_params; + +done: + if (query != NULL) + gst_query_unref (query); + gst_caps_unref (caps); + + return res; + +no_decide_allocation: + { + GST_WARNING_OBJECT (dec, "subclass failed to decide allocation"); + goto done; + } +} + + +static gboolean +gst_nonstream_audio_decoder_decide_allocation_default (G_GNUC_UNUSED + GstNonstreamAudioDecoder * 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_nonstream_audio_decoder_propose_allocation_default (G_GNUC_UNUSED + GstNonstreamAudioDecoder * dec, G_GNUC_UNUSED GstQuery * query) +{ + return TRUE; +} + + +static gboolean +gst_nonstream_audio_decoder_get_upstream_size (GstNonstreamAudioDecoder * dec, + gint64 * length) +{ + return gst_pad_peer_query_duration (dec->sinkpad, GST_FORMAT_BYTES, length) + && (*length >= 0); +} + + +static gboolean +gst_nonstream_audio_decoder_load_from_buffer (GstNonstreamAudioDecoder * dec, + GstBuffer * buffer) +{ + gboolean load_ok; + GstClockTime initial_position; + GstNonstreamAudioDecoderClass *klass; + gboolean ret; + + klass = GST_NONSTREAM_AUDIO_DECODER_CLASS (G_OBJECT_GET_CLASS (dec)); + g_assert (klass->load_from_buffer != NULL); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + + GST_LOG_OBJECT (dec, "read %" G_GSIZE_FORMAT " bytes from upstream", + gst_buffer_get_size (buffer)); + + initial_position = 0; + load_ok = + klass->load_from_buffer (dec, buffer, dec->current_subsong, + dec->subsong_mode, &initial_position, &(dec->output_mode), + &(dec->num_loops)); + gst_buffer_unref (buffer); + + ret = + gst_nonstream_audio_decoder_finish_load (dec, load_ok, initial_position, + FALSE); + + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + return ret; +} + + +static gboolean +gst_nonstream_audio_decoder_load_from_custom (GstNonstreamAudioDecoder * dec) +{ + gboolean load_ok; + GstClockTime initial_position; + GstNonstreamAudioDecoderClass *klass; + gboolean ret; + + klass = GST_NONSTREAM_AUDIO_DECODER_CLASS (G_OBJECT_GET_CLASS (dec)); + g_assert (klass->load_from_custom != NULL); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + + GST_LOG_OBJECT (dec, + "reading song from custom source defined by derived class"); + + initial_position = 0; + load_ok = + klass->load_from_custom (dec, dec->current_subsong, dec->subsong_mode, + &initial_position, &(dec->output_mode), &(dec->num_loops)); + + ret = + gst_nonstream_audio_decoder_finish_load (dec, load_ok, initial_position, + TRUE); + + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + return ret; +} + + +static gboolean +gst_nonstream_audio_decoder_finish_load (GstNonstreamAudioDecoder * dec, + gboolean load_ok, GstClockTime initial_position, gboolean send_stream_start) +{ + /* must be called with lock */ + + GstNonstreamAudioDecoderClass *klass = + GST_NONSTREAM_AUDIO_DECODER_CLASS (G_OBJECT_GET_CLASS (dec)); + + GST_TRACE_OBJECT (dec, "enter finish_load"); + + + /* Prerequisites */ + + if (!load_ok) { + GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), ("Loading failed")); + return FALSE; + } + + if (!GST_AUDIO_INFO_IS_VALID (&(dec->output_audio_info))) { + GST_ELEMENT_ERROR (dec, STREAM, DECODE, (NULL), + ("Audio info is invalid after loading")); + return FALSE; + } + + + /* Log the number of available subsongs */ + if (klass->get_num_subsongs != NULL) + GST_DEBUG_OBJECT (dec, "%u subsong(s) available", + klass->get_num_subsongs (dec)); + + + /* Set the current subsong (or use the default value) */ + if (klass->get_current_subsong != NULL) { + GST_TRACE_OBJECT (dec, "requesting current subsong"); + dec->current_subsong = klass->get_current_subsong (dec); + } + + + /* Handle the subsong duration */ + if (klass->get_subsong_duration != NULL) { + GstClockTime duration; + GST_TRACE_OBJECT (dec, "requesting subsong duration"); + duration = klass->get_subsong_duration (dec, dec->current_subsong); + gst_nonstream_audio_decoder_update_subsong_duration (dec, duration); + } + + + /* Send tags downstream (if some exist) */ + if (klass->get_subsong_tags != NULL) { + /* Subsong tags available */ + + GstTagList *tags; + GST_TRACE_OBJECT (dec, "requesting subsong tags"); + tags = klass->get_subsong_tags (dec, dec->current_subsong); + if (tags != NULL) + tags = gst_nonstream_audio_decoder_add_main_tags (dec, tags); + if (tags != NULL) + gst_pad_push_event (dec->srcpad, gst_event_new_tag (tags)); + } else { + /* No subsong tags - just send main tags out */ + + GstTagList *tags = gst_tag_list_new_empty (); + tags = gst_nonstream_audio_decoder_add_main_tags (dec, tags); + gst_pad_push_event (dec->srcpad, gst_event_new_tag (tags)); + } + + + /* Send stream start downstream if requested */ + if (send_stream_start) { + gchar *stream_id; + GstEvent *event; + + stream_id = + gst_pad_create_stream_id (dec->srcpad, GST_ELEMENT_CAST (dec), NULL); + GST_DEBUG_OBJECT (dec, "pushing STREAM_START with stream id \"%s\"", + stream_id); + + event = gst_event_new_stream_start (stream_id); + gst_event_set_group_id (event, gst_util_group_id_next ()); + gst_pad_push_event (dec->srcpad, event); + g_free (stream_id); + } + + + /* Update the table of contents */ + gst_nonstream_audio_decoder_update_toc (dec, klass); + + + /* Negotiate output caps and an allocator */ + GST_TRACE_OBJECT (dec, "negotiating caps and allocator"); + if (!gst_nonstream_audio_decoder_negotiate (dec)) { + GST_ERROR_OBJECT (dec, "negotiation failed - aborting load"); + return FALSE; + } + + + /* Send new segment downstream */ + gst_nonstream_audio_decoder_output_new_segment (dec, initial_position); + + dec->loaded_mode = TRUE; + + GST_TRACE_OBJECT (dec, "exit finish_load"); + + return TRUE; +} + + +static gboolean +gst_nonstream_audio_decoder_start_task (GstNonstreamAudioDecoder * dec) +{ + if (!gst_pad_start_task (dec->srcpad, + (GstTaskFunction) gst_nonstream_audio_decoder_output_task, dec, + NULL)) { + GST_ERROR_OBJECT (dec, "could not start decoder output task"); + return FALSE; + } else + return TRUE; +} + + +static gboolean +gst_nonstream_audio_decoder_stop_task (GstNonstreamAudioDecoder * dec) +{ + if (!gst_pad_stop_task (dec->srcpad)) { + GST_ERROR_OBJECT (dec, "could not stop decoder output task"); + return FALSE; + } else + return TRUE; +} + + +static gboolean +gst_nonstream_audio_decoder_switch_to_subsong (GstNonstreamAudioDecoder * dec, + guint new_subsong, guint32 const *seqnum) +{ + gboolean ret = TRUE; + GstNonstreamAudioDecoderClass *klass = + GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + + if (klass->set_current_subsong == NULL) { + /* If set_current_subsong wasn't set by the subclass, then + * subsongs are not supported. It is not an error if this + * function is called in that case, since it might happen + * because the current-subsong property was set (and since + * this is a base class property, it is always available). */ + GST_DEBUG_OBJECT (dec, "cannot call set_current_subsong, since it is NULL"); + goto finish; + } + + if (dec->loaded_mode) { + GstEvent *fevent; + GstClockTime new_position; + GstClockTime new_subsong_duration = GST_CLOCK_TIME_NONE; + + + /* Check if (a) new_subsong is already the current subsong + * and (b) if new_subsong exceeds the number of available + * subsongs. Do this here, when the song is loaded, + * because prior to loading, the number of subsong is usually + * not known (and the loading process might choose a specific + * subsong to be the current one at the start of playback). */ + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + + if (new_subsong == dec->current_subsong) { + GST_DEBUG_OBJECT (dec, + "subsong %u is already the current subsong - ignoring call", + new_subsong); + goto finish_unlock; + } + + if (klass->get_num_subsongs) { + guint num_subsongs = klass->get_num_subsongs (dec); + + if (new_subsong >= num_subsongs) { + GST_WARNING_OBJECT (dec, + "subsong %u is out of bounds (there are %u subsongs) - not switching", + new_subsong, num_subsongs); + goto finish_unlock; + } + } + + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + + /* Switching subsongs during playback is very similar to a + * flushing seek. Therefore, the stream lock must be taken, + * flush-start/flush-stop events have to be sent, and + * the pad task has to be restarted. */ + + + fevent = gst_event_new_flush_start (); + if (seqnum != NULL) { + gst_event_set_seqnum (fevent, *seqnum); + GST_DEBUG_OBJECT (dec, + "sending flush start event with sequence number %" G_GUINT32_FORMAT, + *seqnum); + } else + GST_DEBUG_OBJECT (dec, "sending flush start event (no sequence number)"); + + gst_pad_push_event (dec->srcpad, gst_event_ref (fevent)); + /* unlock upstream pull_range */ + if (klass->loads_from_sinkpad) + gst_pad_push_event (dec->sinkpad, fevent); + else + gst_event_unref (fevent); + + + GST_PAD_STREAM_LOCK (dec->srcpad); + + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + + + if (!(klass->set_current_subsong (dec, new_subsong, &new_position))) { + /* Switch failed. Do _not_ exit early from here - playback must + * continue from the current subsong, and it cannot do that if + * we exit here. Try getting the current position and proceed as + * if the switch succeeded (but set the return value to FALSE.) */ + + ret = FALSE; + if (klass->tell) + new_position = klass->tell (dec); + else + new_position = 0; + GST_WARNING_OBJECT (dec, "switching to new subsong %u failed", + new_subsong); + } + + /* Flushing seek resets the base time, which means num_decoded_samples + * needs to be set to 0, since it defines the segment.base value */ + dec->num_decoded_samples = 0; + + + fevent = gst_event_new_flush_stop (TRUE); + if (seqnum != NULL) { + gst_event_set_seqnum (fevent, *seqnum); + GST_DEBUG_OBJECT (dec, + "sending flush stop event with sequence number %" G_GUINT32_FORMAT, + *seqnum); + } else + GST_DEBUG_OBJECT (dec, "sending flush stop event (no sequence number)"); + + gst_pad_push_event (dec->srcpad, gst_event_ref (fevent)); + /* unlock upstream pull_range */ + if (klass->loads_from_sinkpad) + gst_pad_push_event (dec->sinkpad, fevent); + else + gst_event_unref (fevent); + + + /* use the new subsong's duration (if one exists) */ + if (klass->get_subsong_duration != NULL) + new_subsong_duration = klass->get_subsong_duration (dec, new_subsong); + gst_nonstream_audio_decoder_update_subsong_duration (dec, + new_subsong_duration); + + /* create a new segment for the new subsong */ + gst_nonstream_audio_decoder_output_new_segment (dec, new_position); + + /* use the new subsong's tags (if any exist) */ + if (klass->get_subsong_tags != NULL) { + GstTagList *subsong_tags = klass->get_subsong_tags (dec, new_subsong); + if (subsong_tags != NULL) + subsong_tags = + gst_nonstream_audio_decoder_add_main_tags (dec, subsong_tags); + if (subsong_tags != NULL) + gst_pad_push_event (dec->srcpad, gst_event_new_tag (subsong_tags)); + } + + GST_DEBUG_OBJECT (dec, "successfully switched to new subsong %u", + new_subsong); + dec->current_subsong = new_subsong; + + + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + + /* Subsong has been switched, and all necessary events have been + * pushed downstream. Restart srcpad task. */ + gst_nonstream_audio_decoder_start_task (dec); + + /* Unlock stream, we are done */ + GST_PAD_STREAM_UNLOCK (dec->srcpad); + } else { + /* If song hasn't been loaded yet, then playback cannot currently + * been happening. In this case, a "switch" is simple - just store + * the current subsong index. When the song is loaded, it will + * start playing this subsong. */ + + GST_DEBUG_OBJECT (dec, + "playback hasn't started yet - storing subsong index %u as the current subsong", + new_subsong); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + dec->current_subsong = new_subsong; + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + } + + +finish: + return ret; + + +finish_unlock: + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + goto finish; +} + + +static void +gst_nonstream_audio_decoder_update_toc (GstNonstreamAudioDecoder * dec, + GstNonstreamAudioDecoderClass * klass) +{ + /* must be called with lock */ + + guint num_subsongs, i; + + if (dec->toc != NULL) { + gst_toc_unref (dec->toc); + dec->toc = NULL; + } + + if (klass->get_num_subsongs == NULL) + return; + + num_subsongs = klass->get_num_subsongs (dec); + if (num_subsongs <= 1) { + GST_DEBUG_OBJECT (dec, "no need for a TOC since there is only one subsong"); + return; + } + + dec->toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); + + if (klass->get_main_tags) { + GstTagList *main_tags = klass->get_main_tags (dec); + if (main_tags) + gst_toc_set_tags (dec->toc, main_tags); + } + + for (i = 0; i < num_subsongs; ++i) { + gchar *uid; + GstTocEntry *entry; + GstClockTime duration; + GstTagList *tags; + + duration = + (klass->get_subsong_duration != + NULL) ? klass->get_subsong_duration (dec, i) : GST_CLOCK_TIME_NONE; + tags = + (klass->get_subsong_tags != NULL) ? klass->get_subsong_tags (dec, + i) : NULL; + if (!tags) + tags = gst_tag_list_new_empty (); + + uid = g_strdup_printf ("nonstream-subsong-%05u", i); + entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_TRACK, uid); + /* Set the UID as title tag for TOC entry if no title already present */ + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, GST_TAG_TITLE, uid, NULL); + /* Set the subsong duration as duration tag for TOC entry if no duration already present */ + if (duration != GST_CLOCK_TIME_NONE) + gst_tag_list_add (tags, GST_TAG_MERGE_KEEP, GST_TAG_DURATION, duration, + NULL); + + /* FIXME: TOC does not allow GST_CLOCK_TIME_NONE as a stop value */ + if (duration == GST_CLOCK_TIME_NONE) + duration = G_MAXINT64; + + /* Subsongs always start at 00:00 */ + gst_toc_entry_set_start_stop_times (entry, 0, duration); + gst_toc_entry_set_tags (entry, tags); + + /* NOTE: *not* adding loop count via gst_toc_entry_set_loop(), since + * in GstNonstreamAudioDecoder, looping is a playback property, not + * a property of the subsongs themselves */ + + GST_DEBUG_OBJECT (dec, + "new toc entry: uid: \"%s\" duration: %" GST_TIME_FORMAT " tags: %" + GST_PTR_FORMAT, uid, GST_TIME_ARGS (duration), (gpointer) tags); + + gst_toc_append_entry (dec->toc, entry); + + g_free (uid); + } + + gst_pad_push_event (dec->srcpad, gst_event_new_toc (dec->toc, FALSE)); +} + + +static void +gst_nonstream_audio_decoder_update_subsong_duration (GstNonstreamAudioDecoder * + dec, GstClockTime duration) +{ + /* must be called with lock */ + + dec->subsong_duration = duration; + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + gst_element_post_message (GST_ELEMENT (dec), + gst_message_new_duration_changed (GST_OBJECT (dec))); + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); +} + + +static void +gst_nonstream_audio_decoder_output_new_segment (GstNonstreamAudioDecoder * dec, + GstClockTime start_position) +{ + /* must be called with lock */ + + GstSegment segment; + + gst_segment_init (&segment, GST_FORMAT_TIME); + + segment.base = + gst_util_uint64_scale_int (dec->num_decoded_samples, GST_SECOND, + dec->output_audio_info.rate); + segment.start = 0; + segment.time = start_position; + segment.offset = 0; + segment.position = 0; + + /* note that num_decoded_samples isn't being reset; it is the + * analogue to the segment base value, and thus is supposed to + * monotonically increase, except for when a flushing seek happens + * (since a flushing seek is supposed to be a fresh restart for + * the whole pipeline) */ + dec->cur_pos_in_samples = 0; + + /* stop/duration members are not set, on purpose - in case of loops, + * new segments will be generated, which automatically put an implicit + * end on the current segment (the segment implicitely "ends" when the + * new one starts), and having a stop value might cause very slight + * gaps occasionally due to slight jitter in the calculation of + * base times etc. */ + + GST_DEBUG_OBJECT (dec, + "output new segment with base %" GST_TIME_FORMAT " time %" + GST_TIME_FORMAT, GST_TIME_ARGS (segment.base), + GST_TIME_ARGS (segment.time)); + + dec->cur_segment = segment; + dec->discont = TRUE; + + gst_pad_push_event (dec->srcpad, gst_event_new_segment (&segment)); +} + + +static gboolean +gst_nonstream_audio_decoder_do_seek (GstNonstreamAudioDecoder * dec, + GstEvent * event) +{ + gboolean res; + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + GstClockTime new_position; + gint64 start, stop; + GstSegment segment; + guint32 seqnum; + gboolean flush; + GstNonstreamAudioDecoderClass *klass = + GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + if (klass->seek == NULL) { + GST_DEBUG_OBJECT (dec, + "cannot seek: subclass does not have seek() function defined"); + return FALSE; + } + + if (!dec->loaded_mode) { + GST_DEBUG_OBJECT (dec, "nothing loaded yet - cannot seek"); + return FALSE; + } + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + if (!GST_AUDIO_INFO_IS_VALID (&(dec->output_audio_info))) { + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + GST_DEBUG_OBJECT (dec, "no valid output audioinfo present - cannot seek"); + return FALSE; + } + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + + GST_DEBUG_OBJECT (dec, "starting seek"); + + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, + &stop_type, &stop); + seqnum = gst_event_get_seqnum (event); + + GST_DEBUG_OBJECT (dec, + "seek event data: " + "rate %f format %s " + "start type %s start %" GST_TIME_FORMAT " " + "stop type %s stop %" GST_TIME_FORMAT, + rate, gst_format_get_name (format), + get_seek_type_name (start_type), GST_TIME_ARGS (start), + get_seek_type_name (stop_type), GST_TIME_ARGS (stop) + ); + + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (dec, "seeking is only supported in TIME format"); + return FALSE; + } + + if (rate < 0) { + GST_DEBUG_OBJECT (dec, "only positive seek rates are supported"); + return FALSE; + } + + flush = ((flags & GST_SEEK_FLAG_FLUSH) == GST_SEEK_FLAG_FLUSH); + + if (flush) { + GstEvent *fevent = gst_event_new_flush_start (); + gst_event_set_seqnum (fevent, seqnum); + + GST_DEBUG_OBJECT (dec, + "sending flush start event with sequence number %" G_GUINT32_FORMAT, + seqnum); + + gst_pad_push_event (dec->srcpad, gst_event_ref (fevent)); + /* unlock upstream pull_range */ + if (klass->loads_from_sinkpad) + gst_pad_push_event (dec->sinkpad, fevent); + else + gst_event_unref (fevent); + } else + gst_pad_pause_task (dec->srcpad); + + GST_PAD_STREAM_LOCK (dec->srcpad); + + segment = dec->cur_segment; + + if (!gst_segment_do_seek (&segment, + rate, format, flags, start_type, start, stop_type, stop, NULL)) { + GST_DEBUG_OBJECT (dec, "could not seek in segment"); + GST_PAD_STREAM_UNLOCK (dec->srcpad); + return FALSE; + } + + GST_DEBUG_OBJECT (dec, + "segment data: " + "seek event data: " + "rate %f applied rate %f " + "format %s " + "base %" GST_TIME_FORMAT " " + "offset %" GST_TIME_FORMAT " " + "start %" GST_TIME_FORMAT " " + "stop %" GST_TIME_FORMAT " " + "time %" GST_TIME_FORMAT " " + "position %" GST_TIME_FORMAT " " + "duration %" GST_TIME_FORMAT, + segment.rate, segment.applied_rate, + gst_format_get_name (segment.format), + GST_TIME_ARGS (segment.base), + GST_TIME_ARGS (segment.offset), + GST_TIME_ARGS (segment.start), + GST_TIME_ARGS (segment.stop), + GST_TIME_ARGS (segment.time), + GST_TIME_ARGS (segment.position), GST_TIME_ARGS (segment.duration) + ); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + + new_position = segment.position; + res = klass->seek (dec, &new_position); + segment.position = new_position; + + dec->cur_segment = segment; + dec->cur_pos_in_samples = + gst_util_uint64_scale_int (dec->cur_segment.position, + dec->output_audio_info.rate, GST_SECOND); + dec->num_decoded_samples = 0; + + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + if (flush) { + GstEvent *fevent = gst_event_new_flush_stop (TRUE); + gst_event_set_seqnum (fevent, seqnum); + + GST_DEBUG_OBJECT (dec, + "sending flush stop event with sequence number %" G_GUINT32_FORMAT, + seqnum); + + gst_pad_push_event (dec->srcpad, gst_event_ref (fevent)); + if (klass->loads_from_sinkpad) + gst_pad_push_event (dec->sinkpad, fevent); + else + gst_event_unref (fevent); + } + + if (res) { + if (flags & GST_SEEK_FLAG_SEGMENT) { + GST_DEBUG_OBJECT (dec, "posting SEGMENT_START message"); + + gst_element_post_message (GST_ELEMENT (dec), + gst_message_new_segment_start (GST_OBJECT (dec), + GST_FORMAT_TIME, segment.start) + ); + } + + gst_pad_push_event (dec->srcpad, gst_event_new_segment (&segment)); + + GST_INFO_OBJECT (dec, "seek succeeded"); + + gst_nonstream_audio_decoder_start_task (dec); + } else { + GST_WARNING_OBJECT (dec, "seek failed"); + } + + GST_PAD_STREAM_UNLOCK (dec->srcpad); + + gst_event_unref (event); + + return res; +} + + +static GstTagList * +gst_nonstream_audio_decoder_add_main_tags (GstNonstreamAudioDecoder * dec, + GstTagList * tags) +{ + GstNonstreamAudioDecoderClass *klass = + GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec); + + if (!klass->get_main_tags) + return tags; + + tags = gst_tag_list_make_writable (tags); + if (tags) { + GstClockTime duration; + GstTagList *main_tags; + + /* Get main tags. If some exist, merge them with the given tags, + * and return the merged result. Otherwise, just return the given tags. */ + main_tags = klass->get_main_tags (dec); + if (main_tags) { + tags = gst_tag_list_merge (main_tags, tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_unref (main_tags); + } + + /* Add subsong duration if available */ + duration = dec->subsong_duration; + if (GST_CLOCK_TIME_IS_VALID (duration)) + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_DURATION, duration, + NULL); + + return tags; + } else { + GST_ERROR_OBJECT (dec, "could not make subsong tags writable"); + return NULL; + } +} + + +static void +gst_nonstream_audio_decoder_output_task (GstNonstreamAudioDecoder * dec) +{ + GstFlowReturn flow; + GstBuffer *outbuf; + guint num_samples; + + GstNonstreamAudioDecoderClass *klass; + klass = GST_NONSTREAM_AUDIO_DECODER_CLASS (G_OBJECT_GET_CLASS (dec)); + g_assert (klass->decode != NULL); + + GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (dec); + + /* perform the actual decoding */ + if (!(klass->decode (dec, &outbuf, &num_samples))) { + /* EOS case */ + GST_INFO_OBJECT (dec, "decode() reports end -> sending EOS event"); + gst_pad_push_event (dec->srcpad, gst_event_new_eos ()); + goto pause_unlock; + } + + if (outbuf == NULL) { + GST_ERROR_OBJECT (outbuf, "decode() produced NULL buffer"); + goto pause_unlock; + } + + /* set the buffer's metadata */ + GST_BUFFER_DURATION (outbuf) = + gst_util_uint64_scale_int (num_samples, GST_SECOND, + dec->output_audio_info.rate); + GST_BUFFER_OFFSET (outbuf) = dec->cur_pos_in_samples; + GST_BUFFER_OFFSET_END (outbuf) = dec->cur_pos_in_samples + num_samples; + GST_BUFFER_PTS (outbuf) = + gst_util_uint64_scale_int (dec->cur_pos_in_samples, GST_SECOND, + dec->output_audio_info.rate); + GST_BUFFER_DTS (outbuf) = GST_BUFFER_PTS (outbuf); + + if (G_UNLIKELY (dec->discont)) { + GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); + dec->discont = FALSE; + } + + GST_LOG_OBJECT (dec, + "output buffer stats: num_samples = %u duration = %" GST_TIME_FORMAT + " cur_pos_in_samples = %" G_GUINT64_FORMAT " timestamp = %" + GST_TIME_FORMAT, num_samples, + GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), dec->cur_pos_in_samples, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)) + ); + + /* increment sample counters */ + dec->cur_pos_in_samples += num_samples; + dec->num_decoded_samples += num_samples; + + /* the decode() call might have set a new output format -> renegotiate + * before sending the new buffer downstream */ + if (G_UNLIKELY (dec->output_format_changed || + (GST_AUDIO_INFO_IS_VALID (&(dec->output_audio_info)) + && gst_pad_check_reconfigure (dec->srcpad)) + )) { + if (!gst_nonstream_audio_decoder_negotiate (dec)) { + gst_buffer_unref (outbuf); + GST_LOG_OBJECT (dec, "could not push output buffer: negotiation failed"); + goto pause_unlock; + } + } + + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + + /* push new samples downstream + * no need to unref buffer - gst_pad_push() does it in + * all cases (success and failure) */ + flow = gst_pad_push (dec->srcpad, outbuf); + switch (flow) { + case GST_FLOW_OK: + break; + + case GST_FLOW_FLUSHING: + GST_LOG_OBJECT (dec, "pipeline is being flushed - pausing task"); + goto pause; + + case GST_FLOW_NOT_NEGOTIATED: + if (gst_pad_needs_reconfigure (dec->srcpad)) { + GST_DEBUG_OBJECT (dec, "trying to renegotiate"); + break; + } + /* fallthrough to default */ + + default: + GST_ELEMENT_ERROR (dec, STREAM, FAILED, ("Internal data flow error."), + ("streaming task paused, reason %s (%d)", gst_flow_get_name (flow), + flow)); + } + + return; + +pause: + GST_INFO_OBJECT (dec, "pausing task"); + /* NOT using stop_task here, since that would cause a deadlock. + * See the gst_pad_stop_task() documentation for details. */ + gst_pad_pause_task (dec->srcpad); + return; +pause_unlock: + GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (dec); + goto pause; +} + + +static char const * +get_seek_type_name (GstSeekType seek_type) +{ + switch (seek_type) { + case GST_SEEK_TYPE_NONE: + return "none"; + case GST_SEEK_TYPE_SET: + return "set"; + case GST_SEEK_TYPE_END: + return "end"; + default: + return "<unknown>"; + } +} + + + + +/** + * gst_nonstream_audio_decoder_handle_loop: + * @dec: a #GstNonstreamAudioDecoder + * @new_position New position the next loop starts with + * + * Reports that a loop has been completed and creates a new appropriate + * segment for the next loop. + * + * @new_position exists because a loop may not start at the beginning. + * + * This function is only useful for subclasses which can be in the + * GST_NONSTREAM_AUDIO_OUTPUT_MODE_LOOPING output mode, since in the + * GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY output mode, this function + * does nothing. See #GstNonstreamAudioOutputMode for more details. + * + * The subclass calls this during playback when it loops. It produces + * a new segment with updated base time and internal time values, to allow + * for seamless looping. It does *not* check the number of elapsed loops; + * this is up the subclass. + * + * Note that if this function is called, then it must be done after the + * last samples of the loop have been decoded and pushed downstream. + * + * This function must be called with the decoder mutex lock held, since it + * is typically called from within @decode (which in turn are called with + * the lock already held). + */ +void +gst_nonstream_audio_decoder_handle_loop (GstNonstreamAudioDecoder * dec, + GstClockTime new_position) +{ + if (dec->output_mode == GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY) { + /* handle_loop makes no sense with open-ended decoders */ + GST_WARNING_OBJECT (dec, + "ignoring handle_loop() call, since the decoder output mode is \"steady\""); + return; + } + + GST_DEBUG_OBJECT (dec, + "handle_loop() invoked with new_position = %" GST_TIME_FORMAT, + GST_TIME_ARGS (new_position)); + + dec->discont = TRUE; + + gst_nonstream_audio_decoder_output_new_segment (dec, new_position); +} + + +/** + * gst_nonstream_audio_decoder_set_output_format: + * @dec: a #GstNonstreamAudioDecoder + * @audio_info: Valid audio info structure containing the output format + * + * Sets the output caps by means of a GstAudioInfo structure. + * + * This must be called latest in the first @decode call, to ensure src caps are + * set before decoded samples are sent downstream. Typically, this is called + * from inside @load_from_buffer or @load_from_custom. + * + * This function must be called with the decoder mutex lock held, since it + * is typically called from within the aforementioned vfuncs (which in turn + * are called with the lock already held). + * + * Returns: TRUE if setting the output format succeeded, FALSE otherwise + */ +gboolean +gst_nonstream_audio_decoder_set_output_format (GstNonstreamAudioDecoder * dec, + GstAudioInfo const *audio_info) +{ + GstCaps *caps; + GstCaps *templ_caps; + gboolean caps_ok; + gboolean res = TRUE; + + g_return_val_if_fail (GST_IS_NONSTREAM_AUDIO_DECODER (dec), FALSE); + + caps = gst_audio_info_to_caps (audio_info); + if (caps == NULL) { + GST_WARNING_OBJECT (dec, "Could not create caps out of audio info"); + return FALSE; + } + + templ_caps = gst_pad_get_pad_template_caps (dec->srcpad); + caps_ok = gst_caps_is_subset (caps, templ_caps); + + if (caps_ok) { + dec->output_audio_info = *audio_info; + dec->output_format_changed = TRUE; + + GST_INFO_OBJECT (dec, "setting output format to %" GST_PTR_FORMAT, + (gpointer) caps); + } else { + GST_WARNING_OBJECT (dec, + "requested output format %" GST_PTR_FORMAT " does not match template %" + GST_PTR_FORMAT, (gpointer) caps, (gpointer) templ_caps); + + res = FALSE; + } + + gst_caps_unref (caps); + gst_caps_unref (templ_caps); + + return res; +} + + +/** + * gst_nonstream_audio_decoder_set_output_format_simple: + * @dec: a #GstNonstreamAudioDecoder + * @sample_rate: Output sample rate to use, in Hz + * @sample_format: Output sample format to use + * @num_channels: Number of output channels to use + * + * Convenience function; sets the output caps by means of common parameters. + * + * Internally, this fills a GstAudioInfo structure and calls + * gst_nonstream_audio_decoder_set_output_format(). + * + * Returns: TRUE if setting the output format succeeded, FALSE otherwise + */ +gboolean +gst_nonstream_audio_decoder_set_output_format_simple (GstNonstreamAudioDecoder * + dec, guint sample_rate, GstAudioFormat sample_format, guint num_channels) +{ + GstAudioInfo output_audio_info; + + gst_audio_info_init (&output_audio_info); + + gst_audio_info_set_format (&output_audio_info, + sample_format, sample_rate, num_channels, NULL); + + return gst_nonstream_audio_decoder_set_output_format (dec, + &output_audio_info); +} + + +/** + * gst_nonstream_audio_decoder_get_downstream_info: + * @dec: a #GstNonstreamAudioDecoder + * @format: #GstAudioFormat value to fill with a sample format + * @sample_rate: Integer to fill with a sample rate + * @num_channels: Integer to fill with a channel count + * + * Gets sample format, sample rate, channel count from the allowed srcpad caps. + * + * This is useful for when the subclass wishes to adjust one or more output + * parameters to whatever downstream is supporting. For example, the output + * sample rate is often a freely adjustable value in module players. + * + * This function tries to find a value inside the srcpad peer's caps for + * @format, @sample_rate, @num_chnanels . Any of these can be NULL; they + * (and the corresponding downstream caps) are then skipped while retrieving + * information. Non-fixated caps are fixated first; the value closest to + * their present value is then chosen. For example, if the variables pointed + * to by the arguments are GST_AUDIO_FORMAT_16, 48000 Hz, and 2 channels, + * and the downstream caps are: + * + * "audio/x-raw, format={S16LE,S32LE}, rate=[1,32000], channels=[1,MAX]" + * + * Then @format and @channels stay the same, while @sample_rate is set to 32000 Hz. + * This way, the initial values the the variables pointed to by the arguments + * are set to can be used as default output values. Note that if no downstream + * caps can be retrieved, then this function does nothing, therefore it is + * necessary to ensure that @format, @sample_rate, and @channels have valid + * initial values. + * + * Decoder lock is not held by this function, so it can be called from within + * any of the class vfuncs. + */ +void +gst_nonstream_audio_decoder_get_downstream_info (GstNonstreamAudioDecoder * dec, + GstAudioFormat * format, gint * sample_rate, gint * num_channels) +{ + GstCaps *allowed_srccaps; + guint structure_nr, num_structures; + gboolean ds_format_found = FALSE, ds_rate_found = FALSE, ds_channels_found = + FALSE; + + g_return_if_fail (GST_IS_NONSTREAM_AUDIO_DECODER (dec)); + + allowed_srccaps = gst_pad_get_allowed_caps (dec->srcpad); + if (allowed_srccaps == NULL) { + GST_INFO_OBJECT (dec, + "no downstream caps available - not modifying arguments"); + return; + } + + num_structures = gst_caps_get_size (allowed_srccaps); + GST_DEBUG_OBJECT (dec, "%u structure(s) in downstream caps", num_structures); + for (structure_nr = 0; structure_nr < num_structures; ++structure_nr) { + GstStructure *structure; + + ds_format_found = FALSE; + ds_rate_found = FALSE; + ds_channels_found = FALSE; + + structure = gst_caps_get_structure (allowed_srccaps, structure_nr); + + /* If all formats which need to be queried are present in the structure, + * check its contents */ + if (((format == NULL) || gst_structure_has_field (structure, "format")) && + ((sample_rate == NULL) || gst_structure_has_field (structure, "rate")) + && ((num_channels == NULL) + || gst_structure_has_field (structure, "channels"))) { + gint fixated_sample_rate; + gint fixated_num_channels; + GstAudioFormat fixated_format = 0; + GstStructure *fixated_str; + gboolean passed = TRUE; + + /* Make a copy of the structure, since we need to modify + * (fixate) values inside */ + fixated_str = gst_structure_copy (structure); + + /* Try to fixate and retrieve the sample format */ + if (passed && (format != NULL)) { + passed = FALSE; + + if ((gst_structure_get_field_type (fixated_str, + "format") == G_TYPE_STRING) + || gst_structure_fixate_field_string (fixated_str, "format", + gst_audio_format_to_string (*format))) { + gchar const *fmt_str = + gst_structure_get_string (fixated_str, "format"); + if (fmt_str + && ((fixated_format = + gst_audio_format_from_string (fmt_str)) != + GST_AUDIO_FORMAT_UNKNOWN)) { + GST_DEBUG_OBJECT (dec, "found fixated format: %s", fmt_str); + ds_format_found = TRUE; + passed = TRUE; + } + } + } + + /* Try to fixate and retrieve the sample rate */ + if (passed && (sample_rate != NULL)) { + passed = FALSE; + + if ((gst_structure_get_field_type (fixated_str, "rate") == G_TYPE_INT) + || gst_structure_fixate_field_nearest_int (fixated_str, "rate", + *sample_rate)) { + if (gst_structure_get_int (fixated_str, "rate", &fixated_sample_rate)) { + GST_DEBUG_OBJECT (dec, "found fixated sample rate: %d", + fixated_sample_rate); + ds_rate_found = TRUE; + passed = TRUE; + } + } + } + + /* Try to fixate and retrieve the channel count */ + if (passed && (num_channels != NULL)) { + passed = FALSE; + + if ((gst_structure_get_field_type (fixated_str, + "channels") == G_TYPE_INT) + || gst_structure_fixate_field_nearest_int (fixated_str, "channels", + *num_channels)) { + if (gst_structure_get_int (fixated_str, "channels", + &fixated_num_channels)) { + GST_DEBUG_OBJECT (dec, "found fixated channel count: %d", + fixated_num_channels); + ds_channels_found = TRUE; + passed = TRUE; + } + } + } + + gst_structure_free (fixated_str); + + if (ds_format_found && ds_rate_found && ds_channels_found) { + *format = fixated_format; + *sample_rate = fixated_sample_rate; + *num_channels = fixated_num_channels; + break; + } + } + } + + gst_caps_unref (allowed_srccaps); + + if ((format != NULL) && !ds_format_found) + GST_INFO_OBJECT (dec, + "downstream did not specify format - using default (%s)", + gst_audio_format_to_string (*format)); + if ((sample_rate != NULL) && !ds_rate_found) + GST_INFO_OBJECT (dec, + "downstream did not specify sample rate - using default (%d Hz)", + *sample_rate); + if ((num_channels != NULL) && !ds_channels_found) + GST_INFO_OBJECT (dec, + "downstream did not specify number of channels - using default (%d channels)", + *num_channels); +} + + +/** + * gst_nonstream_audio_decoder_allocate_output_buffer: + * @dec: Decoder instance + * @size: Size of the output buffer, in bytes + * + * Allocates an output buffer with the internally configured buffer pool. + * + * This function may only be called from within @load_from_buffer, + * @load_from_custom, and @decode. + * + * Returns: Newly allocated output buffer, or NULL if allocation failed + */ +GstBuffer * +gst_nonstream_audio_decoder_allocate_output_buffer (GstNonstreamAudioDecoder * + dec, gsize size) +{ + if (G_UNLIKELY (dec->output_format_changed || + (GST_AUDIO_INFO_IS_VALID (&(dec->output_audio_info)) + && gst_pad_check_reconfigure (dec->srcpad)) + )) { + /* renegotiate if necessary, before allocating, + * to make sure the right allocator and the right allocation + * params are used */ + if (!gst_nonstream_audio_decoder_negotiate (dec)) { + GST_ERROR_OBJECT (dec, + "could not allocate output buffer because negotation failed"); + return NULL; + } + } + + return gst_buffer_new_allocate (dec->allocator, size, + &(dec->allocation_params)); +} diff --git a/gst-libs/gst/audio/gstnonstreamaudiodecoder.h b/gst-libs/gst/audio/gstnonstreamaudiodecoder.h new file mode 100644 index 000000000..4f390099c --- /dev/null +++ b/gst-libs/gst/audio/gstnonstreamaudiodecoder.h @@ -0,0 +1,405 @@ +/* GStreamer + * Copyright (C) <2017> Carlos Rafael Giani <dv at pseudoterminal dot org> + * + * 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. + */ + + +#ifndef __GST_NONSTREAM_AUDIO_DECODER_H__ +#define __GST_NONSTREAM_AUDIO_DECODER_H__ + +#include <gst/gst.h> +#include <gst/base/gstadapter.h> +#include <gst/audio/audio.h> + + +G_BEGIN_DECLS + + +typedef struct _GstNonstreamAudioDecoder GstNonstreamAudioDecoder; +typedef struct _GstNonstreamAudioDecoderClass GstNonstreamAudioDecoderClass; + + +/** + * GstNonstreamAudioOutputMode: + * @GST_NONSTREAM_AUDIO_OUTPUT_MODE_LOOPING: Playback position is moved back to the beginning of the loop + * @GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY: Playback position increases steadily, even when looping + * + * The output mode defines how the output behaves with regards to looping. Either the playback position is + * moved back to the beginning of the loop, acting like a backwards seek, or it increases steadily, as if + * loop were "unrolled". + */ +typedef enum +{ + GST_NONSTREAM_AUDIO_OUTPUT_MODE_LOOPING, + GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY +} GstNonstreamAudioOutputMode; + + +/** + * GstNonstreamAudioSubsongMode: + * @GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE: Only the current subsong is played + * @GST_NONSTREAM_AUDIO_SUBSONG_MODE_ALL: All subsongs are played (current subsong index is ignored) + * @GST_NONSTREAM_AUDIO_SUBSONG_MODE_DECODER_DEFAULT: Use decoder specific default behavior + * + * The subsong mode defines how the decoder shall handle subsongs. + */ +typedef enum +{ + GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE, + GST_NONSTREAM_AUDIO_SUBSONG_MODE_ALL, + GST_NONSTREAM_AUDIO_SUBSONG_MODE_DECODER_DEFAULT +} GstNonstreamAudioSubsongMode; + + +#define GST_TYPE_NONSTREAM_AUDIO_DECODER (gst_nonstream_audio_decoder_get_type()) +#define GST_NONSTREAM_AUDIO_DECODER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_NONSTREAM_AUDIO_DECODER, GstNonstreamAudioDecoder)) +#define GST_NONSTREAM_AUDIO_DECODER_CAST(obj) ((GstNonstreamAudioDecoder *)(obj)) +#define GST_NONSTREAM_AUDIO_DECODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_NONSTREAM_AUDIO_DECODER, GstNonstreamAudioDecoderClass)) +#define GST_NONSTREAM_AUDIO_DECODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_NONSTREAM_AUDIO_DECODER, GstNonstreamAudioDecoderClass)) +#define GST_IS_NONSTREAM_AUDIO_DECODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_NONSTREAM_AUDIO_DECODER)) +#define GST_IS_NONSTREAM_AUDIO_DECODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_NONSTREAM_AUDIO_DECODER)) + +/** + * GST_NONSTREAM_AUDIO_DECODER_SINK_NAME: + * + * The name of the template for the sink pad. + */ +#define GST_NONSTREAM_AUDIO_DECODER_SINK_NAME "sink" +/** + * GST_NONSTREAM_AUDIO_DECODER_SRC_NAME: + * + * The name of the template for the source pad. + */ +#define GST_NONSTREAM_AUDIO_DECODER_SRC_NAME "src" + +/** + * GST_NONSTREAM_AUDIO_DECODER_SINK_PAD: + * @obj: base nonstream audio codec instance + * + * Gives the pointer to the sink #GstPad object of the element. + */ +#define GST_NONSTREAM_AUDIO_DECODER_SINK_PAD(obj) (((GstNonstreamAudioDecoder *) (obj))->sinkpad) +/** + * GST_NONSTREAM_AUDIO_DECODER_SRC_PAD: + * @obj: base nonstream audio codec instance + * + * Gives the pointer to the source #GstPad object of the element. + */ +#define GST_NONSTREAM_AUDIO_DECODER_SRC_PAD(obj) (((GstNonstreamAudioDecoder *) (obj))->srcpad) + + +/** + * GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX: + * @obj: base nonstream audio codec instance + * + * Locks the decoder mutex. + * + * Internally, the mutex is locked before one of the class vfuncs are + * called, when position and duration queries are handled, and when + * properties are set/retrieved. + * + * Derived classes should call lock during decoder related modifications + * (for example, setting/clearing filter banks), when at the same time + * audio might get decoded. An example are configuration changes that + * happen when properties are set. Properties might be set from another + * thread, so while the derived decoder is reconfigured, the mutex + * should be locked. + */ +#define GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX(obj) g_mutex_lock(&(((GstNonstreamAudioDecoder *)(obj))->mutex)) +#define GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX(obj) g_mutex_unlock(&(((GstNonstreamAudioDecoder *)(obj))->mutex)) + + +/** + * GstNonstreamAudioDecoder: + * + * The opaque #GstNonstreamAudioDecoder data structure. + */ +struct _GstNonstreamAudioDecoder +{ + GstElement element; + + /*< protected > */ + + /* source and sink pads */ + GstPad *sinkpad, *srcpad; + + /* loading information */ + gint64 upstream_size; + gboolean loaded_mode; + GstAdapter *input_data_adapter; + + /* subsong states */ + guint current_subsong; + GstNonstreamAudioSubsongMode subsong_mode; + GstClockTime subsong_duration; + + /* output states */ + GstNonstreamAudioOutputMode output_mode; + gint num_loops; + gboolean output_format_changed; + GstAudioInfo output_audio_info; + /* The difference between these two values is: cur_pos_in_samples is + * used for the GstBuffer offsets, while num_decoded_samples is used + * for the segment base time values. + * cur_pos_in_samples is reset after seeking, looping (when output mode + * is LOOPING) and switching subsongs, while num_decoded is only reset + * to 0 after a flushing seek (because flushing seeks alter the + * pipeline's base_time). */ + guint64 cur_pos_in_samples, num_decoded_samples; + GstSegment cur_segment; + gboolean discont; + + /* metadata */ + GstToc *toc; + + /* allocation */ + GstAllocator *allocator; + GstAllocationParams allocation_params; + + /* thread safety */ + GMutex mutex; +}; + + +/** + * GstNonstreamAudioDecoderClass: + * @element_class: The parent class structure + * @seek: Optional. + * Called when a seek event is received by the parent class. + * new_position is a pointer to a GstClockTime integer which + * contains a position relative to the current subsong. + * Minimum is 0, maximum is the subsong length. + * After this function finishes, new_position is set to the + * actual new position (which may differ from the request + * position, depending on the decoder). + * @tell: Optional. + * Called when a position query is received by the parent class. + * The position that this function returns must be relative to + * the current subsong. Thus, the minimum is 0, and the maximum + * is the subsong length. + * @load_from_buffer: Required if loads_from_sinkpad is set to TRUE (the default value). + * Loads the media from the given buffer. The entire media is supplied at once, + * so after this call, loading should be finished. This function + * can also make use of a suggested initial subsong & subsong mode and initial + * playback position (but isn't required to). In case it chooses a different starting + * position, the function must pass this position to *initial_position. + * The subclass does not have to unref the input buffer; the base class does that + * already. + * @load_from_custom: Required if loads_from_sinkpad is set to FALSE. + * Loads the media in a way defined by the custom sink. Data is not supplied; + * the derived class has to handle this on its own. Otherwise, this function is + * identical to @load_from_buffer. + * @get_main_tags: Optional. + * Returns a tag list containing the main song tags, or NULL if there are + * no such tags. Returned tags will be unref'd. Use this vfunc instead of + * manually pushing a tag event downstream to avoid edge cases where not yet + * pushed sticky tag events get overwritten before they are pushed (can for + * example happen with decodebin if tags are pushed downstream before the + * decodebin pads are linked). + * @set_current_subsong: Optional. + * Sets the current subsong. This function is allowed to switch to a different + * subsong than the required one, and can optionally make use of the suggested initial + * position. In case it chooses a different starting position, the function must pass + * this position to *initial_position. + * This function switches the subsong mode to GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE + * automatically. + * If this function is implemented by the subclass, @get_current_subsong and + * @get_num_subsongs should be implemented as well. + * @get_current_subsong: Optional. + * Returns the current subsong. + * If the current subsong mode is not GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE, this + * function's return value is undefined. + * If this function is implemented by the subclass, + * @get_num_subsongs should be implemented as well. + * @get_num_subsongs: Optional. + * Returns the number of subsongs available. + * The return values 0 and 1 have a similar, but distinct, meaning. + * If this function returns 0, then this decoder does not support subsongs at all. + * @get_current_subsong must then also always return 0. In other words, this function + * either never returns 0, or never returns anything else than 0. + * A return value of 1 means that the media contains either only one or no subsongs + * (the entire song is then considered to be one single subsong). 1 also means that only + * this very media has no or just one subsong, and the decoder itself can + * support multiple subsongs. + * @get_subsong_duration: Optional. + * Returns the duration of a subsong. Returns GST_CLOCK_TIME_NONE if duration is unknown. + * @get_subsong_tags: Optional. + * Returns tags for a subsong, or NULL if there are no tags. + * Returned tags will be unref'd. + * @set_subsong_mode: Optional. + * Sets the current subsong mode. Since this might influence the current playback position, + * this function must set the initial_position integer argument to a defined value. + * If the playback position is not affected at all, it must be set to GST_CLOCK_TIME_NONE. + * If the subsong is restarted after the mode switch, it is recommended to set the value + * to the position in the playback right after the switch (or 0 if the subsongs are always + * reset back to the beginning). + * @set_num_loops: Optional. + * Sets the number of loops for playback. If this is called during playback, + * the subclass must set any internal loop counters to zero. A loop value of -1 + * means infinite looping; 0 means no looping; and when the num_loops is greater than 0, + * playback should loop exactly num_loops times. If this function is implemented, + * @get_num_loops should be implemented as well. The function can ignore the given values + * and choose another; however, @get_num_loops should return this other value afterwards. + * It is up to the subclass to define where the loop starts and ends. It can mean that only + * a subset at the end or in the middle of a song is repeated, for example. + * If the current subsong mode is GST_NONSTREAM_AUDIO_SUBSONG_MODE_SINGLE, then the subsong + * is repeated this many times. If it is GST_NONSTREAM_AUDIO_SUBSONG_MODE_ALL, then all + * subsongs are repeated this many times. With GST_NONSTREAM_AUDIO_SUBSONG_MODE_DECODER_DEFAULT, + * the behavior is decoder specific. + * @get_num_loops: Optional. + * Returns the number of loops for playback. + * @get_supported_output_modes: Always required. + * Returns a bitmask containing the output modes the subclass supports. + * The mask is formed by a bitwise OR combination of integers, which can be calculated + * this way: 1 << GST_NONSTREAM_AUDIO_OUTPUT_MODE_<mode> , where mode is either STEADY or LOOPING + * @set_output_mode: Optional. + * Sets the output mode the subclass has to use. Unlike with most other functions, the subclass + * cannot choose a different mode; it must use the requested one. + * If the output mode is set to LOOPING, @gst_nonstream_audio_decoder_handle_loop + * must be called after playback moved back to the start of a loop. + * @decode: Always required. + * Allocates an output buffer, fills it with decoded audio samples, and must be passed on to + * *buffer . The number of decoded samples must be passed on to *num_samples. + * If decoding finishes or the decoding is no longer possible (for example, due to an + * unrecoverable error), this function returns FALSE, otherwise TRUE. + * @decide_allocation: Optional. + * Sets up the allocation parameters for allocating output + * buffers. The passed in query contains the result of the + * downstream allocation query. + * Subclasses should chain up to the parent implementation to + * invoke the default handler. + * @propose_allocation: Optional. + * Proposes buffer allocation parameters for upstream elements. + * Subclasses should chain up to the parent implementation to + * invoke the default handler. + * + * Subclasses can override any of the available optional virtual methods or not, as + * needed. At minimum, @load_from_buffer (or @load_from_custom), @get_supported_output_modes, + * and @decode need to be overridden. + * + * All functions are called with a locked decoder mutex. + * + * <note> If GST_ELEMENT_ERROR, GST_ELEMENT_WARNING, or GST_ELEMENT_INFO are called from + * inside one of these functions, it is strongly recommended to unlock the decoder mutex + * before and re-lock it after these macros to prevent potential deadlocks in case the + * application does something with the element when it receives an ERROR/WARNING/INFO + * message. Same goes for gst_element_post_message() calls and non-serialized events. </note> + * + * By default, this class works by reading media data from the sinkpad, and then commencing + * playback. Some decoders cannot be given data from a memory block, so the usual way of + * reading all upstream data and passing it to @load_from_buffer doesn't work then. In this case, + * set the value of loads_from_sinkpad to FALSE. This changes the way this class operates; + * it does not require a sinkpad to exist anymore, and will call @load_from_custom instead. + * One example of a decoder where this makes sense is UADE (Unix Amiga Delitracker Emulator). + * For some formats (such as TFMX), it needs to do the file loading by itself. + * Since most decoders can read input data from a memory block, the default value of + * loads_from_sinkpad is TRUE. + */ +struct _GstNonstreamAudioDecoderClass +{ + GstElementClass element_class; + + gboolean loads_from_sinkpad; + + /*< public > */ + /* virtual methods for subclasses */ + + gboolean (*seek) (GstNonstreamAudioDecoder * dec, + GstClockTime * new_position); + GstClockTime (*tell) (GstNonstreamAudioDecoder * dec); + + gboolean (*load_from_buffer) (GstNonstreamAudioDecoder * dec, + GstBuffer * source_data, + guint initial_subsong, + GstNonstreamAudioSubsongMode initial_subsong_mode, + GstClockTime * initial_position, + GstNonstreamAudioOutputMode * initial_output_mode, + gint * initial_num_loops); + gboolean (*load_from_custom) (GstNonstreamAudioDecoder * dec, + guint initial_subsong, + GstNonstreamAudioSubsongMode initial_subsong_mode, + GstClockTime * initial_position, + GstNonstreamAudioOutputMode * initial_output_mode, + gint * initial_num_loops); + + GstTagList * (*get_main_tags) (GstNonstreamAudioDecoder * dec); + + gboolean (*set_current_subsong) (GstNonstreamAudioDecoder * dec, + guint subsong, + GstClockTime * initial_position); + guint (*get_current_subsong) (GstNonstreamAudioDecoder * dec); + + guint (*get_num_subsongs) (GstNonstreamAudioDecoder * dec); + GstClockTime (*get_subsong_duration) (GstNonstreamAudioDecoder * dec, + guint subsong); + GstTagList * (*get_subsong_tags) (GstNonstreamAudioDecoder * dec, + guint subsong); + gboolean (*set_subsong_mode) (GstNonstreamAudioDecoder * dec, + GstNonstreamAudioSubsongMode mode, + GstClockTime * initial_position); + + gboolean (*set_num_loops) (GstNonstreamAudioDecoder * dec, + gint num_loops); + gint (*get_num_loops) (GstNonstreamAudioDecoder * dec); + + guint (*get_supported_output_modes) (GstNonstreamAudioDecoder * dec); + gboolean (*set_output_mode) (GstNonstreamAudioDecoder * dec, + GstNonstreamAudioOutputMode mode, + GstClockTime * current_position); + + gboolean (*decode) (GstNonstreamAudioDecoder * dec, + GstBuffer ** buffer, + guint * num_samples); + + gboolean (*negotiate) (GstNonstreamAudioDecoder * dec); + + gboolean (*decide_allocation) (GstNonstreamAudioDecoder * dec, + GstQuery * query); + gboolean (*propose_allocation) (GstNonstreamAudioDecoder * dec, + GstQuery * query); + + /*< private > */ + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + + +GType gst_nonstream_audio_decoder_get_type (void); + + +void gst_nonstream_audio_decoder_handle_loop (GstNonstreamAudioDecoder * dec, + GstClockTime new_position); + +gboolean gst_nonstream_audio_decoder_set_output_format (GstNonstreamAudioDecoder * dec, + GstAudioInfo const *audio_info); +gboolean gst_nonstream_audio_decoder_set_output_format_simple (GstNonstreamAudioDecoder * dec, + guint sample_rate, + GstAudioFormat sample_format, + guint num_channels); + +void gst_nonstream_audio_decoder_get_downstream_info (GstNonstreamAudioDecoder * dec, + GstAudioFormat * format, + gint * sample_rate, + gint * num_channels); + +GstBuffer *gst_nonstream_audio_decoder_allocate_output_buffer (GstNonstreamAudioDecoder * dec, + gsize size); + + +G_END_DECLS + + +#endif /* __GST_NONSTREAM_AUDIO_DECODER_H__ */ diff --git a/gst-libs/gst/audio/meson.build b/gst-libs/gst/audio/meson.build index e04c2a10f..2120a9299 100644 --- a/gst-libs/gst/audio/meson.build +++ b/gst-libs/gst/audio/meson.build @@ -1,5 +1,5 @@ -badaudio_sources = ['gstaudioaggregator.c'] -badaudio_headers = ['gstaudioaggregator.h'] +badaudio_sources = ['gstaudioaggregator.c', 'gstnonstreamaudiodecoder.c'] +badaudio_headers = ['gstaudioaggregator.h', 'gstnonstreamaudiodecoder.h'] install_headers(badaudio_headers, subdir : 'gstreamer-1.0/gst/audio') |