gstreamer/gst-libs/gst/audio/gstnonstreamaudiodecoder.c
Matthew Waters 640a65bf96 gst: don't use volatile to mean atomic
volatile is not sufficient to provide atomic guarantees and real atomics
should be used instead.  GCC 11 has started warning about using volatile
with atomic operations.

https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719

Discovered in https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/868

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2098>
2021-03-22 14:34:36 +11:00

2489 lines
79 KiB
C

/* 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:
* * Unloaded mode
* - 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.
* - 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.
* - If upstream cannot respond to the size query (in bytes) of @load_from_buffer
* fails, an error is reported, and the pipeline stops.
* - 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_format()
* 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.
*
* * Loaded mode</title>
* - 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.
* - 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.)
* - 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.)
* - 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.
*
* 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):
*
* * 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.
*
* * 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.
*
* * 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.
*/
#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 ("GstNonstreamAudioOutputMode",
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 ("GstNonstreamAudioSubsongMode",
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 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");
proceed = FALSE;
}
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");
proceed = FALSE;
}
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;
dec = GST_NONSTREAM_AUDIO_DECODER (parent);
klass = GST_NONSTREAM_AUDIO_DECODER_GET_CLASS (dec);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_DURATION:
{
GstFormat format;
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:
{
GstFormat format;
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:
{
GstFormat fmt;
GstClockTime duration;
if (!dec->loaded_mode) {
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 (fmt));
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 implicitly "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 negotiation failed");
return NULL;
}
}
return gst_buffer_new_allocate (dec->allocator, size,
&(dec->allocation_params));
}