summaryrefslogtreecommitdiff
path: root/gst-libs/gst/audio
diff options
context:
space:
mode:
authorCarlos Rafael Giani <dv@pseudoterminal.org>2016-07-27 01:41:20 +0200
committerJan Schmidt <jan@centricular.com>2017-05-22 16:34:05 +0200
commit2e4c6d6a49a2c26e19cf8c1159231b316b9082a3 (patch)
tree37aede0a26d3b1a58d77452ba57a91a805297ea4 /gst-libs/gst/audio
parentf3de920b126a8a39d4dde422e803d27ec1e5baad (diff)
downloadgstreamer-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.am5
-rw-r--r--gst-libs/gst/audio/gstnonstreamaudiodecoder.c2511
-rw-r--r--gst-libs/gst/audio/gstnonstreamaudiodecoder.h405
-rw-r--r--gst-libs/gst/audio/meson.build4
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, &params);
+ update_allocator = TRUE;
+ } else {
+ allocator = NULL;
+ gst_allocation_params_init (&params);
+ update_allocator = FALSE;
+ }
+
+ if (update_allocator)
+ gst_query_set_nth_allocation_param (query, 0, allocator, &params);
+ else
+ gst_query_add_allocation_param (query, allocator, &params);
+
+ if (allocator)
+ gst_object_unref (allocator);
+
+ return TRUE;
+}
+
+
+static gboolean
+gst_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')