mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-03 16:09:39 +00:00
62f09513e5
Don't call wait_event() at all for gap events, as basesink will end up waiting for the time that the gap event would be rendered out at the audio device. There's no need to render it at all, just treat it as a handy point to resync the audio if needed, let the ringbuffer render silence, and place the next buffer into the ringbuffer where it belongs. The only thing we really need to do is make sure the ringbuffer and clock are running, and wait for preroll. Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2749 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5178>
2535 lines
81 KiB
C
2535 lines
81 KiB
C
/* GStreamer
|
|
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
|
|
* 2005 Wim Taymans <wim@fluendo.com>
|
|
*
|
|
* gstaudiobasesink.c:
|
|
*
|
|
* 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:gstaudiobasesink
|
|
* @title: GstAudioBaseSink
|
|
* @short_description: Base class for audio sinks
|
|
* @see_also: #GstAudioSink, #GstAudioRingBuffer.
|
|
*
|
|
* This is the base class for audio sinks. Subclasses need to implement the
|
|
* ::create_ringbuffer vmethod. This base class will then take care of
|
|
* writing samples to the ringbuffer, synchronisation, clipping and flushing.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include <gst/audio/audio.h>
|
|
#include "gstaudiobasesink.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_audio_base_sink_debug);
|
|
#define GST_CAT_DEFAULT gst_audio_base_sink_debug
|
|
|
|
struct _GstAudioBaseSinkPrivate
|
|
{
|
|
/* upstream latency */
|
|
GstClockTime us_latency;
|
|
/* the clock slaving algorithm in use */
|
|
GstAudioBaseSinkSlaveMethod slave_method;
|
|
/* running average of clock skew */
|
|
GstClockTimeDiff avg_skew;
|
|
/* the number of samples we aligned last time */
|
|
gint64 last_align;
|
|
|
|
gboolean sync_latency;
|
|
|
|
GstClockTime eos_time;
|
|
|
|
/* number of microseconds we allow clock slaving to drift
|
|
* before resyncing */
|
|
guint64 drift_tolerance;
|
|
|
|
/* number of nanoseconds we allow timestamps to drift
|
|
* before resyncing */
|
|
GstClockTime alignment_threshold;
|
|
|
|
/* time of the previous detected discont candidate */
|
|
GstClockTime discont_time;
|
|
|
|
/* number of nanoseconds to wait until creating a discontinuity */
|
|
GstClockTime discont_wait;
|
|
|
|
/* custom slaving algorithm callback */
|
|
GstAudioBaseSinkCustomSlavingCallback custom_slaving_callback;
|
|
gpointer custom_slaving_cb_data;
|
|
GDestroyNotify custom_slaving_cb_notify;
|
|
};
|
|
|
|
/* BaseAudioSink signals and args */
|
|
enum
|
|
{
|
|
/* FILL ME */
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
/* FIXME: 2.0, store the buffer_time and latency_time in nanoseconds */
|
|
#define DEFAULT_BUFFER_TIME ((200 * GST_MSECOND) / GST_USECOND)
|
|
#define DEFAULT_LATENCY_TIME ((10 * GST_MSECOND) / GST_USECOND)
|
|
#define DEFAULT_PROVIDE_CLOCK TRUE
|
|
#define DEFAULT_SLAVE_METHOD GST_AUDIO_BASE_SINK_SLAVE_SKEW
|
|
|
|
/* FIXME, enable pull mode when clock slaving and trick modes are figured out */
|
|
#define DEFAULT_CAN_ACTIVATE_PULL FALSE
|
|
|
|
/* when timestamps drift for more than 40ms we resync. This should
|
|
* be enough to compensate for timestamp rounding errors. */
|
|
#define DEFAULT_ALIGNMENT_THRESHOLD (40 * GST_MSECOND)
|
|
|
|
/* when clock slaving drift for more than 40ms we resync. This is
|
|
* a reasonable default */
|
|
#define DEFAULT_DRIFT_TOLERANCE ((40 * GST_MSECOND) / GST_USECOND)
|
|
|
|
/* allow for one second before resyncing to see if the timestamps drift will
|
|
* fix itself, or is a permanent offset */
|
|
#define DEFAULT_DISCONT_WAIT (1 * GST_SECOND)
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_BUFFER_TIME,
|
|
PROP_LATENCY_TIME,
|
|
PROP_PROVIDE_CLOCK,
|
|
PROP_SLAVE_METHOD,
|
|
PROP_CAN_ACTIVATE_PULL,
|
|
PROP_ALIGNMENT_THRESHOLD,
|
|
PROP_DRIFT_TOLERANCE,
|
|
PROP_DISCONT_WAIT,
|
|
|
|
PROP_LAST
|
|
};
|
|
|
|
#define _do_init \
|
|
GST_DEBUG_CATEGORY_INIT (gst_audio_base_sink_debug, "audiobasesink", 0, "audiobasesink element");
|
|
#define gst_audio_base_sink_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstAudioBaseSink, gst_audio_base_sink,
|
|
GST_TYPE_BASE_SINK, G_ADD_PRIVATE (GstAudioBaseSink) _do_init);
|
|
|
|
static void gst_audio_base_sink_dispose (GObject * object);
|
|
|
|
static void gst_audio_base_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_audio_base_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static GstStateChangeReturn gst_audio_base_sink_change_state (GstElement *
|
|
element, GstStateChange transition);
|
|
static gboolean gst_audio_base_sink_activate_pull (GstBaseSink * basesink,
|
|
gboolean active);
|
|
static gboolean gst_audio_base_sink_query (GstElement * element, GstQuery *
|
|
query);
|
|
|
|
static GstClock *gst_audio_base_sink_provide_clock (GstElement * elem);
|
|
static inline void gst_audio_base_sink_reset_sync (GstAudioBaseSink * sink);
|
|
static GstClockTime gst_audio_base_sink_get_time (GstClock * clock,
|
|
GstAudioBaseSink * sink);
|
|
static void gst_audio_base_sink_callback (GstAudioRingBuffer * rbuf,
|
|
guint8 * data, guint len, gpointer user_data);
|
|
|
|
static GstFlowReturn gst_audio_base_sink_preroll (GstBaseSink * bsink,
|
|
GstBuffer * buffer);
|
|
static GstFlowReturn gst_audio_base_sink_render (GstBaseSink * bsink,
|
|
GstBuffer * buffer);
|
|
static gboolean gst_audio_base_sink_event (GstBaseSink * bsink,
|
|
GstEvent * event);
|
|
static GstFlowReturn gst_audio_base_sink_wait_event (GstBaseSink * bsink,
|
|
GstEvent * event);
|
|
static void gst_audio_base_sink_get_times (GstBaseSink * bsink,
|
|
GstBuffer * buffer, GstClockTime * start, GstClockTime * end);
|
|
static gboolean gst_audio_base_sink_setcaps (GstBaseSink * bsink,
|
|
GstCaps * caps);
|
|
static GstCaps *gst_audio_base_sink_fixate (GstBaseSink * bsink,
|
|
GstCaps * caps);
|
|
|
|
static gboolean gst_audio_base_sink_query_pad (GstBaseSink * bsink,
|
|
GstQuery * query);
|
|
|
|
|
|
/* static guint gst_audio_base_sink_signals[LAST_SIGNAL] = { 0 }; */
|
|
|
|
static void
|
|
gst_audio_base_sink_class_init (GstAudioBaseSinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstBaseSinkClass *gstbasesink_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstbasesink_class = (GstBaseSinkClass *) klass;
|
|
|
|
gobject_class->set_property = gst_audio_base_sink_set_property;
|
|
gobject_class->get_property = gst_audio_base_sink_get_property;
|
|
gobject_class->dispose = gst_audio_base_sink_dispose;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BUFFER_TIME,
|
|
g_param_spec_int64 ("buffer-time", "Buffer Time",
|
|
"Size of audio buffer in microseconds, this is the minimum "
|
|
"latency that the sink reports", 1, G_MAXINT64, DEFAULT_BUFFER_TIME,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_LATENCY_TIME,
|
|
g_param_spec_int64 ("latency-time", "Latency Time",
|
|
"The minimum amount of data to write in each iteration "
|
|
"in microseconds", 1, G_MAXINT64, DEFAULT_LATENCY_TIME,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PROVIDE_CLOCK,
|
|
g_param_spec_boolean ("provide-clock", "Provide Clock",
|
|
"Provide a clock to be used as the global pipeline clock",
|
|
DEFAULT_PROVIDE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SLAVE_METHOD,
|
|
g_param_spec_enum ("slave-method", "Slave Method",
|
|
"Algorithm used to match the rate of the masterclock",
|
|
GST_TYPE_AUDIO_BASE_SINK_SLAVE_METHOD, DEFAULT_SLAVE_METHOD,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_CAN_ACTIVATE_PULL,
|
|
g_param_spec_boolean ("can-activate-pull", "Allow Pull Scheduling",
|
|
"Allow pull-based scheduling", DEFAULT_CAN_ACTIVATE_PULL,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
/**
|
|
* GstAudioBaseSink:drift-tolerance:
|
|
*
|
|
* Controls the amount of time in microseconds that clocks are allowed
|
|
* to drift before resynchronisation happens.
|
|
*/
|
|
g_object_class_install_property (gobject_class, PROP_DRIFT_TOLERANCE,
|
|
g_param_spec_int64 ("drift-tolerance", "Drift Tolerance",
|
|
"Tolerance for clock drift in microseconds", 1,
|
|
G_MAXINT64, DEFAULT_DRIFT_TOLERANCE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
/**
|
|
* GstAudioBaseSink:alignment_threshold:
|
|
*
|
|
* Controls the amount of time in nanoseconds that timestamps are allowed
|
|
* to drift from their ideal time before choosing not to align them.
|
|
*/
|
|
g_object_class_install_property (gobject_class, PROP_ALIGNMENT_THRESHOLD,
|
|
g_param_spec_uint64 ("alignment-threshold", "Alignment Threshold",
|
|
"Timestamp alignment threshold in nanoseconds", 1,
|
|
G_MAXUINT64 - 1, DEFAULT_ALIGNMENT_THRESHOLD,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GstAudioBaseSink:discont-wait:
|
|
*
|
|
* A window of time in nanoseconds to wait before creating a discontinuity as
|
|
* a result of breaching the drift-tolerance.
|
|
*/
|
|
g_object_class_install_property (gobject_class, PROP_DISCONT_WAIT,
|
|
g_param_spec_uint64 ("discont-wait", "Discont Wait",
|
|
"Window of time in nanoseconds to wait before "
|
|
"creating a discontinuity", 0,
|
|
G_MAXUINT64 - 1, DEFAULT_DISCONT_WAIT,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gstelement_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_audio_base_sink_change_state);
|
|
gstelement_class->provide_clock =
|
|
GST_DEBUG_FUNCPTR (gst_audio_base_sink_provide_clock);
|
|
gstelement_class->query = GST_DEBUG_FUNCPTR (gst_audio_base_sink_query);
|
|
|
|
gstbasesink_class->fixate = GST_DEBUG_FUNCPTR (gst_audio_base_sink_fixate);
|
|
gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_audio_base_sink_setcaps);
|
|
gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_audio_base_sink_event);
|
|
gstbasesink_class->wait_event =
|
|
GST_DEBUG_FUNCPTR (gst_audio_base_sink_wait_event);
|
|
gstbasesink_class->get_times =
|
|
GST_DEBUG_FUNCPTR (gst_audio_base_sink_get_times);
|
|
gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_audio_base_sink_preroll);
|
|
gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_audio_base_sink_render);
|
|
gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_audio_base_sink_query_pad);
|
|
gstbasesink_class->activate_pull =
|
|
GST_DEBUG_FUNCPTR (gst_audio_base_sink_activate_pull);
|
|
|
|
/* ref class from a thread-safe context to work around missing bit of
|
|
* thread-safety in GObject */
|
|
g_type_class_ref (GST_TYPE_AUDIO_CLOCK);
|
|
g_type_class_ref (GST_TYPE_AUDIO_RING_BUFFER);
|
|
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_init (GstAudioBaseSink * audiobasesink)
|
|
{
|
|
GstBaseSink *basesink = GST_BASE_SINK_CAST (audiobasesink);
|
|
|
|
audiobasesink->priv =
|
|
gst_audio_base_sink_get_instance_private (audiobasesink);
|
|
|
|
audiobasesink->buffer_time = DEFAULT_BUFFER_TIME;
|
|
audiobasesink->latency_time = DEFAULT_LATENCY_TIME;
|
|
audiobasesink->priv->slave_method = DEFAULT_SLAVE_METHOD;
|
|
audiobasesink->priv->drift_tolerance = DEFAULT_DRIFT_TOLERANCE;
|
|
audiobasesink->priv->alignment_threshold = DEFAULT_ALIGNMENT_THRESHOLD;
|
|
audiobasesink->priv->discont_wait = DEFAULT_DISCONT_WAIT;
|
|
audiobasesink->priv->custom_slaving_callback = NULL;
|
|
audiobasesink->priv->custom_slaving_cb_data = NULL;
|
|
audiobasesink->priv->custom_slaving_cb_notify = NULL;
|
|
|
|
audiobasesink->provided_clock = gst_audio_clock_new ("GstAudioSinkClock",
|
|
(GstAudioClockGetTimeFunc) gst_audio_base_sink_get_time, audiobasesink,
|
|
NULL);
|
|
|
|
basesink->can_activate_push = TRUE;
|
|
basesink->can_activate_pull = DEFAULT_CAN_ACTIVATE_PULL;
|
|
|
|
gst_base_sink_set_last_sample_enabled (basesink, FALSE);
|
|
if (DEFAULT_PROVIDE_CLOCK)
|
|
GST_OBJECT_FLAG_SET (basesink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
|
|
else
|
|
GST_OBJECT_FLAG_UNSET (basesink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_dispose (GObject * object)
|
|
{
|
|
GstAudioBaseSink *sink;
|
|
|
|
sink = GST_AUDIO_BASE_SINK (object);
|
|
|
|
if (sink->priv->custom_slaving_cb_notify)
|
|
sink->priv->custom_slaving_cb_notify (sink->priv->custom_slaving_cb_data);
|
|
|
|
if (sink->provided_clock) {
|
|
gst_audio_clock_invalidate (GST_AUDIO_CLOCK (sink->provided_clock));
|
|
gst_object_unref (sink->provided_clock);
|
|
sink->provided_clock = NULL;
|
|
}
|
|
|
|
if (sink->ringbuffer) {
|
|
gst_object_unparent (GST_OBJECT_CAST (sink->ringbuffer));
|
|
sink->ringbuffer = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
|
|
static GstClock *
|
|
gst_audio_base_sink_provide_clock (GstElement * elem)
|
|
{
|
|
GstAudioBaseSink *sink;
|
|
GstClock *clock;
|
|
|
|
sink = GST_AUDIO_BASE_SINK (elem);
|
|
|
|
/* we have no ringbuffer (must be NULL state) */
|
|
if (sink->ringbuffer == NULL)
|
|
goto wrong_state;
|
|
|
|
if (!gst_audio_ring_buffer_is_acquired (sink->ringbuffer))
|
|
goto wrong_state;
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
if (!GST_OBJECT_FLAG_IS_SET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK))
|
|
goto clock_disabled;
|
|
|
|
clock = GST_CLOCK_CAST (gst_object_ref (sink->provided_clock));
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
return clock;
|
|
|
|
/* ERRORS */
|
|
wrong_state:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "ringbuffer not acquired");
|
|
return NULL;
|
|
}
|
|
clock_disabled:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "clock provide disabled");
|
|
GST_OBJECT_UNLOCK (sink);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_base_sink_is_self_provided_clock (GstAudioBaseSink * sink)
|
|
{
|
|
return (sink->provided_clock && GST_IS_AUDIO_CLOCK (sink->provided_clock) &&
|
|
GST_AUDIO_CLOCK_CAST (sink->provided_clock)->func ==
|
|
(GstAudioClockGetTimeFunc) gst_audio_base_sink_get_time);
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_base_sink_query_pad (GstBaseSink * bsink, GstQuery * query)
|
|
{
|
|
gboolean res = FALSE;
|
|
GstAudioBaseSink *basesink;
|
|
|
|
basesink = GST_AUDIO_BASE_SINK (bsink);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CONVERT:
|
|
{
|
|
GstFormat src_fmt, dest_fmt;
|
|
gint64 src_val, dest_val;
|
|
|
|
GST_LOG_OBJECT (basesink, "query convert");
|
|
|
|
if (basesink->ringbuffer) {
|
|
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, NULL);
|
|
res =
|
|
gst_audio_ring_buffer_convert (basesink->ringbuffer, src_fmt,
|
|
src_val, dest_fmt, &dest_val);
|
|
if (res) {
|
|
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_base_sink_query (GstElement * element, GstQuery * query)
|
|
{
|
|
gboolean res = FALSE;
|
|
GstAudioBaseSink *basesink;
|
|
|
|
basesink = GST_AUDIO_BASE_SINK (element);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_LATENCY:
|
|
{
|
|
gboolean live, us_live;
|
|
GstClockTime min_l, max_l;
|
|
|
|
GST_DEBUG_OBJECT (basesink, "latency query");
|
|
|
|
/* ask parent first, it will do an upstream query for us. */
|
|
if ((res =
|
|
gst_base_sink_query_latency (GST_BASE_SINK_CAST (basesink), &live,
|
|
&us_live, &min_l, &max_l))) {
|
|
GstClockTime base_latency, min_latency, max_latency;
|
|
|
|
/* we and upstream are both live, adjust the min_latency */
|
|
if (live && us_live) {
|
|
GstAudioRingBufferSpec *spec;
|
|
|
|
GST_OBJECT_LOCK (basesink);
|
|
if (!basesink->ringbuffer || !basesink->ringbuffer->spec.info.rate) {
|
|
GST_OBJECT_UNLOCK (basesink);
|
|
|
|
GST_DEBUG_OBJECT (basesink,
|
|
"we are not negotiated, can't report latency yet");
|
|
res = FALSE;
|
|
goto done;
|
|
}
|
|
spec = &basesink->ringbuffer->spec;
|
|
|
|
basesink->priv->us_latency = min_l;
|
|
|
|
base_latency =
|
|
gst_util_uint64_scale_int (spec->seglatency * spec->segsize,
|
|
GST_SECOND, spec->info.rate * spec->info.bpf);
|
|
GST_OBJECT_UNLOCK (basesink);
|
|
|
|
/* we cannot go lower than the buffer size and the min peer latency */
|
|
min_latency = base_latency + min_l;
|
|
/* the max latency is the max of the peer, we can delay an infinite
|
|
* amount of time. */
|
|
max_latency = (max_l == -1) ? -1 : (base_latency + max_l);
|
|
|
|
GST_DEBUG_OBJECT (basesink,
|
|
"peer min %" GST_TIME_FORMAT ", our min latency: %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (min_l),
|
|
GST_TIME_ARGS (min_latency));
|
|
GST_DEBUG_OBJECT (basesink,
|
|
"peer max %" GST_TIME_FORMAT ", our max latency: %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (max_l),
|
|
GST_TIME_ARGS (max_latency));
|
|
} else {
|
|
GST_DEBUG_OBJECT (basesink,
|
|
"peer or we are not live, don't care about latency");
|
|
min_latency = min_l;
|
|
max_latency = max_l;
|
|
}
|
|
gst_query_set_latency (query, live, min_latency, max_latency);
|
|
}
|
|
break;
|
|
}
|
|
case GST_QUERY_CONVERT:
|
|
{
|
|
GstFormat src_fmt, dest_fmt;
|
|
gint64 src_val, dest_val;
|
|
|
|
GST_LOG_OBJECT (basesink, "query convert");
|
|
|
|
if (basesink->ringbuffer) {
|
|
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, NULL);
|
|
res =
|
|
gst_audio_ring_buffer_convert (basesink->ringbuffer, src_fmt,
|
|
src_val, dest_fmt, &dest_val);
|
|
if (res) {
|
|
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
res = GST_ELEMENT_CLASS (parent_class)->query (element, query);
|
|
break;
|
|
}
|
|
|
|
done:
|
|
return res;
|
|
}
|
|
|
|
|
|
/* we call this function without holding the lock on sink for performance
|
|
* reasons. Try hard to not deal with and invalid ringbuffer and rate. */
|
|
static GstClockTime
|
|
gst_audio_base_sink_get_time (GstClock * clock, GstAudioBaseSink * sink)
|
|
{
|
|
guint64 raw, samples;
|
|
guint delay;
|
|
GstClockTime result;
|
|
GstAudioRingBuffer *ringbuffer;
|
|
gint rate;
|
|
|
|
if ((ringbuffer = sink->ringbuffer) == NULL)
|
|
return GST_CLOCK_TIME_NONE;
|
|
|
|
if ((rate = ringbuffer->spec.info.rate) == 0)
|
|
return GST_CLOCK_TIME_NONE;
|
|
|
|
/* our processed samples are always increasing */
|
|
raw = samples = gst_audio_ring_buffer_samples_done (ringbuffer);
|
|
|
|
/* the number of samples not yet processed, this is still queued in the
|
|
* device (not played for playback). */
|
|
delay = gst_audio_ring_buffer_delay (ringbuffer);
|
|
|
|
if (G_LIKELY (samples >= delay))
|
|
samples -= delay;
|
|
else
|
|
samples = 0;
|
|
|
|
result = gst_util_uint64_scale_int (samples, GST_SECOND, rate);
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"processed samples: raw %" G_GUINT64_FORMAT ", delay %u, real %"
|
|
G_GUINT64_FORMAT ", time %" GST_TIME_FORMAT,
|
|
raw, delay, samples, GST_TIME_ARGS (result));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_set_provide_clock:
|
|
* @sink: a #GstAudioBaseSink
|
|
* @provide: new state
|
|
*
|
|
* Controls whether @sink will provide a clock or not. If @provide is %TRUE,
|
|
* gst_element_provide_clock() will return a clock that reflects the datarate
|
|
* of @sink. If @provide is %FALSE, gst_element_provide_clock() will return
|
|
* NULL.
|
|
*/
|
|
void
|
|
gst_audio_base_sink_set_provide_clock (GstAudioBaseSink * sink,
|
|
gboolean provide)
|
|
{
|
|
g_return_if_fail (GST_IS_AUDIO_BASE_SINK (sink));
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
if (provide)
|
|
GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
|
|
else
|
|
GST_OBJECT_FLAG_UNSET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_get_provide_clock:
|
|
* @sink: a #GstAudioBaseSink
|
|
*
|
|
* Queries whether @sink will provide a clock or not. See also
|
|
* gst_audio_base_sink_set_provide_clock.
|
|
*
|
|
* Returns: %TRUE if @sink will provide a clock.
|
|
*/
|
|
gboolean
|
|
gst_audio_base_sink_get_provide_clock (GstAudioBaseSink * sink)
|
|
{
|
|
gboolean result;
|
|
|
|
g_return_val_if_fail (GST_IS_AUDIO_BASE_SINK (sink), FALSE);
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
result = GST_OBJECT_FLAG_IS_SET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_set_slave_method:
|
|
* @sink: a #GstAudioBaseSink
|
|
* @method: the new slave method
|
|
*
|
|
* Controls how clock slaving will be performed in @sink.
|
|
*/
|
|
void
|
|
gst_audio_base_sink_set_slave_method (GstAudioBaseSink * sink,
|
|
GstAudioBaseSinkSlaveMethod method)
|
|
{
|
|
g_return_if_fail (GST_IS_AUDIO_BASE_SINK (sink));
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
sink->priv->slave_method = method;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_get_slave_method:
|
|
* @sink: a #GstAudioBaseSink
|
|
*
|
|
* Get the current slave method used by @sink.
|
|
*
|
|
* Returns: The current slave method used by @sink.
|
|
*/
|
|
GstAudioBaseSinkSlaveMethod
|
|
gst_audio_base_sink_get_slave_method (GstAudioBaseSink * sink)
|
|
{
|
|
GstAudioBaseSinkSlaveMethod result;
|
|
|
|
g_return_val_if_fail (GST_IS_AUDIO_BASE_SINK (sink), -1);
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
result = sink->priv->slave_method;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* gst_audio_base_sink_set_drift_tolerance:
|
|
* @sink: a #GstAudioBaseSink
|
|
* @drift_tolerance: the new drift tolerance in microseconds
|
|
*
|
|
* Controls the sink's drift tolerance.
|
|
*/
|
|
void
|
|
gst_audio_base_sink_set_drift_tolerance (GstAudioBaseSink * sink,
|
|
gint64 drift_tolerance)
|
|
{
|
|
g_return_if_fail (GST_IS_AUDIO_BASE_SINK (sink));
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
sink->priv->drift_tolerance = drift_tolerance;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_get_drift_tolerance:
|
|
* @sink: a #GstAudioBaseSink
|
|
*
|
|
* Get the current drift tolerance, in microseconds, used by @sink.
|
|
*
|
|
* Returns: The current drift tolerance used by @sink.
|
|
*/
|
|
gint64
|
|
gst_audio_base_sink_get_drift_tolerance (GstAudioBaseSink * sink)
|
|
{
|
|
gint64 result;
|
|
|
|
g_return_val_if_fail (GST_IS_AUDIO_BASE_SINK (sink), -1);
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
result = sink->priv->drift_tolerance;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_set_alignment_threshold:
|
|
* @sink: a #GstAudioBaseSink
|
|
* @alignment_threshold: the new alignment threshold in nanoseconds
|
|
*
|
|
* Controls the sink's alignment threshold.
|
|
*/
|
|
void
|
|
gst_audio_base_sink_set_alignment_threshold (GstAudioBaseSink * sink,
|
|
GstClockTime alignment_threshold)
|
|
{
|
|
g_return_if_fail (GST_IS_AUDIO_BASE_SINK (sink));
|
|
g_return_if_fail (GST_CLOCK_TIME_IS_VALID (alignment_threshold));
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
sink->priv->alignment_threshold = alignment_threshold;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_get_alignment_threshold:
|
|
* @sink: a #GstAudioBaseSink
|
|
*
|
|
* Get the current alignment threshold, in nanoseconds, used by @sink.
|
|
*
|
|
* Returns: The current alignment threshold used by @sink.
|
|
*/
|
|
GstClockTime
|
|
gst_audio_base_sink_get_alignment_threshold (GstAudioBaseSink * sink)
|
|
{
|
|
GstClockTime result;
|
|
|
|
g_return_val_if_fail (GST_IS_AUDIO_BASE_SINK (sink), GST_CLOCK_TIME_NONE);
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
result = sink->priv->alignment_threshold;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_set_discont_wait:
|
|
* @sink: a #GstAudioBaseSink
|
|
* @discont_wait: the new discont wait in nanoseconds
|
|
*
|
|
* Controls how long the sink will wait before creating a discontinuity.
|
|
*/
|
|
void
|
|
gst_audio_base_sink_set_discont_wait (GstAudioBaseSink * sink,
|
|
GstClockTime discont_wait)
|
|
{
|
|
g_return_if_fail (GST_IS_AUDIO_BASE_SINK (sink));
|
|
g_return_if_fail (GST_CLOCK_TIME_IS_VALID (discont_wait));
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
sink->priv->discont_wait = discont_wait;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_set_custom_slaving_callback:
|
|
* @sink: a #GstAudioBaseSink
|
|
* @callback: a #GstAudioBaseSinkCustomSlavingCallback
|
|
* @user_data: user data passed to the callback
|
|
* @notify : called when user_data becomes unused
|
|
*
|
|
* Sets the custom slaving callback. This callback will
|
|
* be invoked if the slave-method property is set to
|
|
* GST_AUDIO_BASE_SINK_SLAVE_CUSTOM and the audio sink
|
|
* receives and plays samples.
|
|
*
|
|
* Setting the callback to NULL causes the sink to
|
|
* behave as if the GST_AUDIO_BASE_SINK_SLAVE_NONE
|
|
* method were used.
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
void
|
|
gst_audio_base_sink_set_custom_slaving_callback (GstAudioBaseSink * sink,
|
|
GstAudioBaseSinkCustomSlavingCallback callback,
|
|
gpointer user_data, GDestroyNotify notify)
|
|
{
|
|
g_return_if_fail (GST_IS_AUDIO_BASE_SINK (sink));
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
sink->priv->custom_slaving_callback = callback;
|
|
sink->priv->custom_slaving_cb_data = user_data;
|
|
sink->priv->custom_slaving_cb_notify = notify;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_custom_cb_report_discont (GstAudioBaseSink * sink,
|
|
GstAudioBaseSinkDiscontReason discont_reason)
|
|
{
|
|
if ((sink->priv->custom_slaving_callback != NULL) &&
|
|
(sink->priv->slave_method == GST_AUDIO_BASE_SINK_SLAVE_CUSTOM)) {
|
|
sink->priv->custom_slaving_callback (sink, GST_CLOCK_TIME_NONE,
|
|
GST_CLOCK_TIME_NONE, NULL, discont_reason,
|
|
sink->priv->custom_slaving_cb_data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_report_device_failure:
|
|
* @sink: a #GstAudioBaseSink
|
|
*
|
|
* Informs this base class that the audio output device has failed for
|
|
* some reason, causing a discontinuity (for example, because the device
|
|
* recovered from the error, but lost all contents of its ring buffer).
|
|
* This function is typically called by derived classes, and is useful
|
|
* for the custom slave method.
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
void
|
|
gst_audio_base_sink_report_device_failure (GstAudioBaseSink * sink)
|
|
{
|
|
g_return_if_fail (GST_IS_AUDIO_BASE_SINK (sink));
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
gst_audio_base_sink_custom_cb_report_discont (sink,
|
|
GST_AUDIO_BASE_SINK_DISCONT_REASON_DEVICE_FAILURE);
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_get_discont_wait:
|
|
* @sink: a #GstAudioBaseSink
|
|
*
|
|
* Get the current discont wait, in nanoseconds, used by @sink.
|
|
*
|
|
* Returns: The current discont wait used by @sink.
|
|
*/
|
|
GstClockTime
|
|
gst_audio_base_sink_get_discont_wait (GstAudioBaseSink * sink)
|
|
{
|
|
GstClockTime result;
|
|
|
|
g_return_val_if_fail (GST_IS_AUDIO_BASE_SINK (sink), -1);
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
result = sink->priv->discont_wait;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAudioBaseSink *sink;
|
|
|
|
sink = GST_AUDIO_BASE_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_BUFFER_TIME:
|
|
sink->buffer_time = g_value_get_int64 (value);
|
|
break;
|
|
case PROP_LATENCY_TIME:
|
|
sink->latency_time = g_value_get_int64 (value);
|
|
break;
|
|
case PROP_PROVIDE_CLOCK:
|
|
gst_audio_base_sink_set_provide_clock (sink, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_SLAVE_METHOD:
|
|
gst_audio_base_sink_set_slave_method (sink, g_value_get_enum (value));
|
|
break;
|
|
case PROP_CAN_ACTIVATE_PULL:
|
|
GST_BASE_SINK (sink)->can_activate_pull = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_DRIFT_TOLERANCE:
|
|
gst_audio_base_sink_set_drift_tolerance (sink, g_value_get_int64 (value));
|
|
break;
|
|
case PROP_ALIGNMENT_THRESHOLD:
|
|
gst_audio_base_sink_set_alignment_threshold (sink,
|
|
g_value_get_uint64 (value));
|
|
break;
|
|
case PROP_DISCONT_WAIT:
|
|
gst_audio_base_sink_set_discont_wait (sink, g_value_get_uint64 (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAudioBaseSink *sink;
|
|
|
|
sink = GST_AUDIO_BASE_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_BUFFER_TIME:
|
|
g_value_set_int64 (value, sink->buffer_time);
|
|
break;
|
|
case PROP_LATENCY_TIME:
|
|
g_value_set_int64 (value, sink->latency_time);
|
|
break;
|
|
case PROP_PROVIDE_CLOCK:
|
|
g_value_set_boolean (value, gst_audio_base_sink_get_provide_clock (sink));
|
|
break;
|
|
case PROP_SLAVE_METHOD:
|
|
g_value_set_enum (value, gst_audio_base_sink_get_slave_method (sink));
|
|
break;
|
|
case PROP_CAN_ACTIVATE_PULL:
|
|
g_value_set_boolean (value, GST_BASE_SINK (sink)->can_activate_pull);
|
|
break;
|
|
case PROP_DRIFT_TOLERANCE:
|
|
g_value_set_int64 (value, gst_audio_base_sink_get_drift_tolerance (sink));
|
|
break;
|
|
case PROP_ALIGNMENT_THRESHOLD:
|
|
g_value_set_uint64 (value,
|
|
gst_audio_base_sink_get_alignment_threshold (sink));
|
|
break;
|
|
case PROP_DISCONT_WAIT:
|
|
g_value_set_uint64 (value, gst_audio_base_sink_get_discont_wait (sink));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_base_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
|
|
{
|
|
GstAudioBaseSink *sink = GST_AUDIO_BASE_SINK (bsink);
|
|
GstAudioRingBufferSpec *spec;
|
|
GstClockTime now, internal_time;
|
|
GstClockTime crate_num, crate_denom;
|
|
|
|
if (!sink->ringbuffer)
|
|
return FALSE;
|
|
|
|
spec = &sink->ringbuffer->spec;
|
|
|
|
if (G_UNLIKELY (spec->caps && gst_caps_is_equal (spec->caps, caps))) {
|
|
GST_DEBUG_OBJECT (sink,
|
|
"Ringbuffer caps haven't changed, skipping reconfiguration");
|
|
return TRUE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (sink, "release old ringbuffer");
|
|
|
|
/* get current time, updates the last_time. When the subclass has a clock that
|
|
* restarts from 0 when a new format is negotiated, it will call
|
|
* gst_audio_clock_reset() which will use this last_time to create an offset
|
|
* so that time from the clock keeps on increasing monotonically. */
|
|
now = gst_clock_get_time (sink->provided_clock);
|
|
internal_time = gst_clock_get_internal_time (sink->provided_clock);
|
|
|
|
GST_DEBUG_OBJECT (sink, "time was %" GST_TIME_FORMAT, GST_TIME_ARGS (now));
|
|
|
|
/* release old ringbuffer */
|
|
gst_audio_ring_buffer_pause (sink->ringbuffer);
|
|
gst_audio_ring_buffer_activate (sink->ringbuffer, FALSE);
|
|
gst_audio_ring_buffer_release (sink->ringbuffer);
|
|
|
|
GST_DEBUG_OBJECT (sink, "parse caps");
|
|
|
|
spec->buffer_time = sink->buffer_time;
|
|
spec->latency_time = sink->latency_time;
|
|
|
|
/* parse new caps */
|
|
if (!gst_audio_ring_buffer_parse_caps (spec, caps))
|
|
goto parse_error;
|
|
|
|
gst_audio_ring_buffer_debug_spec_buff (spec);
|
|
|
|
GST_DEBUG_OBJECT (sink, "acquire ringbuffer");
|
|
if (!gst_audio_ring_buffer_acquire (sink->ringbuffer, spec))
|
|
goto acquire_error;
|
|
|
|
/* If we use our own clock, we need to adjust the offset since it will now
|
|
* restart from zero */
|
|
if (gst_audio_base_sink_is_self_provided_clock (sink))
|
|
gst_audio_clock_reset (GST_AUDIO_CLOCK (sink->provided_clock), 0);
|
|
|
|
/* We need to resync since the ringbuffer restarted */
|
|
gst_audio_base_sink_reset_sync (sink);
|
|
|
|
gst_audio_base_sink_custom_cb_report_discont (sink,
|
|
GST_AUDIO_BASE_SINK_DISCONT_REASON_NEW_CAPS);
|
|
|
|
if (bsink->pad_mode == GST_PAD_MODE_PUSH) {
|
|
GST_DEBUG_OBJECT (sink, "activate ringbuffer");
|
|
gst_audio_ring_buffer_activate (sink->ringbuffer, TRUE);
|
|
}
|
|
|
|
/* due to possible changes in the spec file we should recalibrate the clock */
|
|
gst_clock_get_calibration (sink->provided_clock, NULL, NULL,
|
|
&crate_num, &crate_denom);
|
|
gst_clock_set_calibration (sink->provided_clock,
|
|
internal_time, now, crate_num, crate_denom);
|
|
|
|
/* calculate actual latency and buffer times.
|
|
* FIXME: In 2.0, store the latency_time internally in ns */
|
|
spec->latency_time = gst_util_uint64_scale (spec->segsize,
|
|
(GST_SECOND / GST_USECOND), spec->info.rate * spec->info.bpf);
|
|
|
|
spec->buffer_time = spec->segtotal * spec->latency_time;
|
|
|
|
gst_audio_ring_buffer_debug_spec_buff (spec);
|
|
|
|
gst_element_post_message (GST_ELEMENT_CAST (bsink),
|
|
gst_message_new_latency (GST_OBJECT (bsink)));
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
parse_error:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "could not parse caps");
|
|
GST_ELEMENT_ERROR (sink, STREAM, FORMAT,
|
|
(NULL), ("cannot parse audio format."));
|
|
return FALSE;
|
|
}
|
|
acquire_error:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "could not acquire ringbuffer");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_audio_base_sink_fixate (GstBaseSink * bsink, GstCaps * caps)
|
|
{
|
|
GstStructure *s;
|
|
gint width, depth;
|
|
|
|
caps = gst_caps_make_writable (caps);
|
|
|
|
s = gst_caps_get_structure (caps, 0);
|
|
|
|
/* fields for all formats */
|
|
gst_structure_fixate_field_nearest_int (s, "rate", 44100);
|
|
gst_structure_fixate_field_nearest_int (s, "channels", 2);
|
|
gst_structure_fixate_field_nearest_int (s, "width", 16);
|
|
|
|
/* fields for int */
|
|
if (gst_structure_has_field (s, "depth")) {
|
|
gst_structure_get_int (s, "width", &width);
|
|
/* round width to nearest multiple of 8 for the depth */
|
|
depth = GST_ROUND_UP_8 (width);
|
|
gst_structure_fixate_field_nearest_int (s, "depth", depth);
|
|
}
|
|
if (gst_structure_has_field (s, "signed"))
|
|
gst_structure_fixate_field_boolean (s, "signed", TRUE);
|
|
if (gst_structure_has_field (s, "endianness"))
|
|
gst_structure_fixate_field_nearest_int (s, "endianness", G_BYTE_ORDER);
|
|
|
|
caps = GST_BASE_SINK_CLASS (parent_class)->fixate (bsink, caps);
|
|
|
|
return caps;
|
|
}
|
|
|
|
static inline void
|
|
gst_audio_base_sink_reset_sync (GstAudioBaseSink * sink)
|
|
{
|
|
sink->next_sample = -1;
|
|
sink->priv->eos_time = -1;
|
|
sink->priv->discont_time = -1;
|
|
sink->priv->avg_skew = -1;
|
|
sink->priv->last_align = 0;
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
|
|
GstClockTime * start, GstClockTime * end)
|
|
{
|
|
/* our clock sync is a bit too much for the base class to handle so
|
|
* we implement it ourselves. */
|
|
*start = GST_CLOCK_TIME_NONE;
|
|
*end = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_force_start (GstAudioBaseSink * sink)
|
|
{
|
|
/* Set the eos_rendering flag so sub-classes definitely start the clock.
|
|
* FIXME 2.0: Pass this as a flag to gst_audio_ring_buffer_start() */
|
|
g_atomic_int_set (&sink->eos_rendering, 1);
|
|
gst_audio_ring_buffer_start (sink->ringbuffer);
|
|
g_atomic_int_set (&sink->eos_rendering, 0);
|
|
}
|
|
|
|
/* This waits for the drain to happen and can be canceled */
|
|
static GstFlowReturn
|
|
gst_audio_base_sink_drain (GstAudioBaseSink * sink)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
if (!sink->ringbuffer)
|
|
return ret;
|
|
if (!sink->ringbuffer->spec.info.rate)
|
|
return ret;
|
|
|
|
/* if PLAYING is interrupted,
|
|
* arrange to have clock running when going to PLAYING again */
|
|
g_atomic_int_set (&sink->eos_rendering, 1);
|
|
|
|
/* need to start playback before we can drain, but only when
|
|
* we have successfully negotiated a format and thus acquired the
|
|
* ringbuffer. */
|
|
if (gst_audio_ring_buffer_is_acquired (sink->ringbuffer))
|
|
gst_audio_ring_buffer_start (sink->ringbuffer);
|
|
|
|
if (sink->priv->eos_time != -1) {
|
|
GST_DEBUG_OBJECT (sink,
|
|
"last sample time %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (sink->priv->eos_time));
|
|
|
|
/* wait for the EOS time to be reached, this is the time when the last
|
|
* sample is played. */
|
|
ret = gst_base_sink_wait (GST_BASE_SINK (sink), sink->priv->eos_time, NULL);
|
|
|
|
GST_DEBUG_OBJECT (sink, "drained audio");
|
|
}
|
|
g_atomic_int_set (&sink->eos_rendering, 0);
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_audio_base_sink_wait_event (GstBaseSink * bsink, GstEvent * event)
|
|
{
|
|
GstAudioBaseSink *sink = GST_AUDIO_BASE_SINK (bsink);
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
gboolean clear_force_start_flag = FALSE;
|
|
|
|
/* For both gap and EOS events, make sure the ringbuffer is running
|
|
* before trying to wait on the event! */
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_EOS:
|
|
case GST_EVENT_GAP:
|
|
/* We must have a negotiated format before starting the ringbuffer */
|
|
if (G_UNLIKELY (!gst_audio_ring_buffer_is_acquired (sink->ringbuffer))) {
|
|
GST_ELEMENT_ERROR (sink, STREAM, FORMAT, (NULL),
|
|
("Sink not negotiated before %s event.",
|
|
GST_EVENT_TYPE_NAME (event)));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
gst_audio_base_sink_force_start (sink);
|
|
/* Make sure the ringbuffer will start again if interrupted during event_wait() */
|
|
g_atomic_int_set (&sink->eos_rendering, 1);
|
|
clear_force_start_flag = TRUE;
|
|
|
|
/* For gap events, don't actually wait for the clock to
|
|
* reach that time, or it will drain the ringbuffer, just
|
|
* ensure we're prerolled and let the next actual buffer
|
|
* get rendered where it belongs */
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_GAP) {
|
|
ret = gst_base_sink_do_preroll (bsink, GST_MINI_OBJECT_CAST (event));
|
|
goto done;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_BASE_SINK_CLASS (parent_class)->wait_event (bsink, event);
|
|
if (ret != GST_FLOW_OK)
|
|
goto done;
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_EOS:
|
|
/* now wait till we played everything */
|
|
ret = gst_audio_base_sink_drain (sink);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
done:
|
|
if (clear_force_start_flag)
|
|
g_atomic_int_set (&sink->eos_rendering, 0);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_base_sink_event (GstBaseSink * bsink, GstEvent * event)
|
|
{
|
|
GstAudioBaseSink *sink = GST_AUDIO_BASE_SINK (bsink);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_FLUSH_START:
|
|
if (sink->ringbuffer)
|
|
gst_audio_ring_buffer_set_flushing (sink->ringbuffer, TRUE);
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
/* always resync on sample after a flush */
|
|
gst_audio_base_sink_reset_sync (sink);
|
|
|
|
gst_audio_base_sink_custom_cb_report_discont (sink,
|
|
GST_AUDIO_BASE_SINK_DISCONT_REASON_FLUSH);
|
|
|
|
if (sink->ringbuffer)
|
|
gst_audio_ring_buffer_set_flushing (sink->ringbuffer, FALSE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_audio_base_sink_preroll (GstBaseSink * bsink, GstBuffer * buffer)
|
|
{
|
|
GstAudioBaseSink *sink = GST_AUDIO_BASE_SINK (bsink);
|
|
|
|
if (!gst_audio_ring_buffer_is_acquired (sink->ringbuffer))
|
|
goto wrong_state;
|
|
|
|
/* we don't really do anything when prerolling. We could make a
|
|
* property to play this buffer to have some sort of scrubbing
|
|
* support. */
|
|
return GST_FLOW_OK;
|
|
|
|
wrong_state:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "ringbuffer in wrong state");
|
|
GST_ELEMENT_ERROR (sink, STREAM, FORMAT, (NULL), ("sink not negotiated."));
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
}
|
|
|
|
static guint64
|
|
gst_audio_base_sink_get_offset (GstAudioBaseSink * sink)
|
|
{
|
|
guint64 sample, sps;
|
|
gint writeseg, segdone;
|
|
gint diff;
|
|
|
|
/* assume we can append to the previous sample */
|
|
sample = sink->next_sample;
|
|
/* no previous sample, try to insert at position 0 */
|
|
if (sample == -1)
|
|
sample = 0;
|
|
|
|
sps = sink->ringbuffer->samples_per_seg;
|
|
|
|
/* figure out the segment and the offset inside the segment where
|
|
* the sample should be written. */
|
|
writeseg = sample / sps;
|
|
|
|
/* get the currently processed segment */
|
|
segdone = g_atomic_int_get (&sink->ringbuffer->segdone)
|
|
- sink->ringbuffer->segbase;
|
|
|
|
/* see how far away it is from the write segment */
|
|
diff = writeseg - segdone;
|
|
if (diff < 0) {
|
|
/* sample would be dropped, position to next playable position */
|
|
sample = (segdone + 1) * sps;
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
static GstClockTime
|
|
clock_convert_external (GstClockTime external, GstClockTime cinternal,
|
|
GstClockTime cexternal, GstClockTime crate_num, GstClockTime crate_denom)
|
|
{
|
|
/* adjust for rate and speed */
|
|
if (external >= cexternal) {
|
|
external =
|
|
gst_util_uint64_scale (external - cexternal, crate_denom, crate_num);
|
|
external += cinternal;
|
|
} else {
|
|
external =
|
|
gst_util_uint64_scale (cexternal - external, crate_denom, crate_num);
|
|
if (cinternal > external)
|
|
external = cinternal - external;
|
|
else
|
|
external = 0;
|
|
}
|
|
return external;
|
|
}
|
|
|
|
|
|
/* apply the clock offset and invoke a custom callback
|
|
* which might also request changes to the playout pointer
|
|
*
|
|
* this reuses code from the skewing algorithm, but leaves
|
|
* decision on whether or not to skew (and how much to skew)
|
|
* up to the callback */
|
|
static void
|
|
gst_audio_base_sink_custom_slaving (GstAudioBaseSink * sink,
|
|
GstClockTime render_start, GstClockTime render_stop,
|
|
GstClockTime * srender_start, GstClockTime * srender_stop)
|
|
{
|
|
GstClockTime cinternal, cexternal, crate_num, crate_denom;
|
|
GstClockTime etime, itime;
|
|
GstClockTimeDiff requested_skew;
|
|
gint driftsamples;
|
|
gint64 last_align;
|
|
|
|
/* get calibration parameters to compensate for offsets */
|
|
gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal,
|
|
&crate_num, &crate_denom);
|
|
|
|
/* sample clocks and figure out clock skew */
|
|
etime = gst_clock_get_time (GST_ELEMENT_CLOCK (sink));
|
|
itime = gst_audio_clock_get_time (GST_AUDIO_CLOCK (sink->provided_clock));
|
|
itime =
|
|
gst_audio_clock_adjust (GST_AUDIO_CLOCK (sink->provided_clock), itime);
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"internal %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT
|
|
" cinternal %" GST_TIME_FORMAT " cexternal %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (itime), GST_TIME_ARGS (etime),
|
|
GST_TIME_ARGS (cinternal), GST_TIME_ARGS (cexternal));
|
|
|
|
/* make sure we never go below 0 */
|
|
etime = etime > cexternal ? etime - cexternal : 0;
|
|
itime = itime > cinternal ? itime - cinternal : 0;
|
|
|
|
/* don't do any skewing unless the callback explicitly requests one */
|
|
requested_skew = 0;
|
|
|
|
if (sink->priv->custom_slaving_callback != NULL) {
|
|
sink->priv->custom_slaving_callback (sink, etime, itime, &requested_skew,
|
|
FALSE, sink->priv->custom_slaving_cb_data);
|
|
GST_DEBUG_OBJECT (sink, "custom slaving requested skew %" GST_STIME_FORMAT,
|
|
GST_STIME_ARGS (requested_skew));
|
|
} else {
|
|
GST_DEBUG_OBJECT (sink,
|
|
"no custom slaving callback set - clock drift will not be compensated");
|
|
}
|
|
|
|
if (requested_skew > 0) {
|
|
cexternal = (cexternal > requested_skew) ? (cexternal - requested_skew) : 0;
|
|
|
|
driftsamples =
|
|
(sink->ringbuffer->spec.info.rate * requested_skew) / GST_SECOND;
|
|
last_align = sink->priv->last_align;
|
|
|
|
/* if we were aligning in the wrong direction or we aligned more than what we
|
|
* will correct, resync */
|
|
if ((last_align < 0) || (last_align > driftsamples))
|
|
sink->next_sample = -1;
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"last_align %" G_GINT64_FORMAT " driftsamples %u, next %"
|
|
G_GUINT64_FORMAT, last_align, driftsamples, sink->next_sample);
|
|
|
|
gst_clock_set_calibration (sink->provided_clock, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
} else if (requested_skew < 0) {
|
|
cexternal += ABS (requested_skew);
|
|
|
|
driftsamples =
|
|
(sink->ringbuffer->spec.info.rate * ABS (requested_skew)) / GST_SECOND;
|
|
last_align = sink->priv->last_align;
|
|
|
|
/* if we were aligning in the wrong direction or we aligned more than what we
|
|
* will correct, resync */
|
|
if ((last_align > 0) || (-last_align > driftsamples))
|
|
sink->next_sample = -1;
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"last_align %" G_GINT64_FORMAT " driftsamples %u, next %"
|
|
G_GUINT64_FORMAT, last_align, driftsamples, sink->next_sample);
|
|
|
|
gst_clock_set_calibration (sink->provided_clock, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
}
|
|
|
|
/* convert, ignoring speed */
|
|
render_start = clock_convert_external (render_start, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
render_stop = clock_convert_external (render_stop, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
|
|
*srender_start = render_start;
|
|
*srender_stop = render_stop;
|
|
}
|
|
|
|
/* algorithm to calculate sample positions that will result in resampling to
|
|
* match the clock rate of the master */
|
|
static void
|
|
gst_audio_base_sink_resample_slaving (GstAudioBaseSink * sink,
|
|
GstClockTime render_start, GstClockTime render_stop,
|
|
GstClockTime * srender_start, GstClockTime * srender_stop)
|
|
{
|
|
GstClockTime cinternal, cexternal;
|
|
GstClockTime crate_num, crate_denom;
|
|
|
|
/* FIXME, we can sample and add observations here or use the timeouts on the
|
|
* clock. No idea which one is better or more stable. The timeout seems more
|
|
* arbitrary but this one seems more demanding and does not work when there is
|
|
* no data coming in to the sink. */
|
|
#if 0
|
|
GstClockTime etime, itime;
|
|
gdouble r_squared;
|
|
|
|
/* sample clocks and figure out clock skew */
|
|
etime = gst_clock_get_time (GST_ELEMENT_CLOCK (sink));
|
|
itime = gst_audio_clock_get_time (sink->provided_clock);
|
|
|
|
/* add new observation */
|
|
gst_clock_add_observation (sink->provided_clock, itime, etime, &r_squared);
|
|
#endif
|
|
|
|
/* get calibration parameters to compensate for speed and offset differences
|
|
* when we are slaved */
|
|
gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal,
|
|
&crate_num, &crate_denom);
|
|
|
|
GST_DEBUG_OBJECT (sink, "internal %" GST_TIME_FORMAT " external %"
|
|
GST_TIME_FORMAT " %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT " = %f",
|
|
GST_TIME_ARGS (cinternal), GST_TIME_ARGS (cexternal), crate_num,
|
|
crate_denom, gst_guint64_to_gdouble (crate_num) /
|
|
gst_guint64_to_gdouble (crate_denom));
|
|
|
|
if (crate_num == 0)
|
|
crate_denom = crate_num = 1;
|
|
|
|
/* bring external time to internal time */
|
|
render_start = clock_convert_external (render_start, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
render_stop = clock_convert_external (render_stop, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"after slaving: start %" GST_TIME_FORMAT " - stop %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (render_start), GST_TIME_ARGS (render_stop));
|
|
|
|
*srender_start = render_start;
|
|
*srender_stop = render_stop;
|
|
}
|
|
|
|
/* algorithm to calculate sample positions that will result in changing the
|
|
* playout pointer to match the clock rate of the master */
|
|
static void
|
|
gst_audio_base_sink_skew_slaving (GstAudioBaseSink * sink,
|
|
GstClockTime render_start, GstClockTime render_stop,
|
|
GstClockTime * srender_start, GstClockTime * srender_stop)
|
|
{
|
|
GstClockTime cinternal, cexternal, crate_num, crate_denom;
|
|
GstClockTime etime, itime;
|
|
GstClockTimeDiff skew, drift, mdrift2;
|
|
gint driftsamples;
|
|
gint64 last_align;
|
|
|
|
/* get calibration parameters to compensate for offsets */
|
|
gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal,
|
|
&crate_num, &crate_denom);
|
|
|
|
/* sample clocks and figure out clock skew */
|
|
etime = gst_clock_get_time (GST_ELEMENT_CLOCK (sink));
|
|
itime = gst_audio_clock_get_time (GST_AUDIO_CLOCK (sink->provided_clock));
|
|
itime =
|
|
gst_audio_clock_adjust (GST_AUDIO_CLOCK (sink->provided_clock), itime);
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"internal %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT
|
|
" cinternal %" GST_TIME_FORMAT " cexternal %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (itime), GST_TIME_ARGS (etime),
|
|
GST_TIME_ARGS (cinternal), GST_TIME_ARGS (cexternal));
|
|
|
|
/* make sure we never go below 0 */
|
|
etime = etime > cexternal ? etime - cexternal : 0;
|
|
itime = itime > cinternal ? itime - cinternal : 0;
|
|
|
|
/* do itime - etime.
|
|
* positive value means external clock goes slower
|
|
* negative value means external clock goes faster */
|
|
skew = GST_CLOCK_DIFF (etime, itime);
|
|
if (sink->priv->avg_skew == -1) {
|
|
/* first observation */
|
|
sink->priv->avg_skew = skew;
|
|
} else {
|
|
/* next observations use a moving average */
|
|
sink->priv->avg_skew = (31 * sink->priv->avg_skew + skew) / 32;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (sink, "internal %" GST_TIME_FORMAT " external %"
|
|
GST_TIME_FORMAT " skew %" GST_STIME_FORMAT " avg %" GST_STIME_FORMAT,
|
|
GST_TIME_ARGS (itime), GST_TIME_ARGS (etime), GST_STIME_ARGS (skew),
|
|
GST_STIME_ARGS (sink->priv->avg_skew));
|
|
|
|
/* the max drift we allow */
|
|
mdrift2 = (sink->priv->drift_tolerance * 1000) / 2;
|
|
|
|
/* adjust playout pointer based on skew */
|
|
if (sink->priv->avg_skew > mdrift2) {
|
|
/* master is running slower, move external time backwards */
|
|
GST_WARNING_OBJECT (sink,
|
|
"correct clock skew %" GST_STIME_FORMAT " > %" GST_STIME_FORMAT,
|
|
GST_STIME_ARGS (sink->priv->avg_skew), GST_STIME_ARGS (mdrift2));
|
|
|
|
/* Move the external time backward by the average skew, but don't ever
|
|
* go negative. Moving the average skew by the same distance defines
|
|
* the new clock skew window center point. This allows the clock to
|
|
* drift equally into either direction after the correction. */
|
|
if (G_LIKELY (cexternal > sink->priv->avg_skew))
|
|
drift = sink->priv->avg_skew;
|
|
else
|
|
drift = cexternal;
|
|
cexternal -= drift;
|
|
sink->priv->avg_skew -= drift;
|
|
|
|
driftsamples = (sink->ringbuffer->spec.info.rate * drift) / GST_SECOND;
|
|
last_align = sink->priv->last_align;
|
|
|
|
/* if we were aligning in the wrong direction or we aligned more than what
|
|
* we will correct, resync */
|
|
if (last_align < 0 || last_align > driftsamples)
|
|
sink->next_sample = -1;
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"last_align %" G_GINT64_FORMAT " driftsamples %u, next %"
|
|
G_GUINT64_FORMAT, last_align, driftsamples, sink->next_sample);
|
|
|
|
gst_clock_set_calibration (sink->provided_clock, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
} else if (sink->priv->avg_skew < -mdrift2) {
|
|
/* master is running faster, move external time forwards */
|
|
GST_WARNING_OBJECT (sink,
|
|
"correct clock skew %" GST_STIME_FORMAT " < -%" GST_STIME_FORMAT,
|
|
GST_STIME_ARGS (sink->priv->avg_skew), GST_STIME_ARGS (mdrift2));
|
|
|
|
/* Move the external time forward by the average skew, and move the
|
|
* average skew by the same distance (which equals a reset to 0). This
|
|
* defines the new clock skew window center point. This allows the
|
|
* clock to drift equally into either direction after the correction. */
|
|
drift = -sink->priv->avg_skew;
|
|
cexternal += drift;
|
|
sink->priv->avg_skew = 0;
|
|
|
|
driftsamples = (sink->ringbuffer->spec.info.rate * drift) / GST_SECOND;
|
|
last_align = sink->priv->last_align;
|
|
|
|
/* if we were aligning in the wrong direction or we aligned more than what
|
|
* we will correct, resync */
|
|
if (last_align > 0 || -last_align > driftsamples)
|
|
sink->next_sample = -1;
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"last_align %" G_GINT64_FORMAT " driftsamples %u, next %"
|
|
G_GUINT64_FORMAT, last_align, driftsamples, sink->next_sample);
|
|
|
|
gst_clock_set_calibration (sink->provided_clock, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
}
|
|
|
|
/* convert, ignoring speed */
|
|
render_start = clock_convert_external (render_start, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
render_stop = clock_convert_external (render_stop, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
|
|
*srender_start = render_start;
|
|
*srender_stop = render_stop;
|
|
}
|
|
|
|
/* apply the clock offset but do no slaving otherwise */
|
|
static void
|
|
gst_audio_base_sink_none_slaving (GstAudioBaseSink * sink,
|
|
GstClockTime render_start, GstClockTime render_stop,
|
|
GstClockTime * srender_start, GstClockTime * srender_stop)
|
|
{
|
|
GstClockTime cinternal, cexternal, crate_num, crate_denom;
|
|
|
|
/* get calibration parameters to compensate for offsets */
|
|
gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal,
|
|
&crate_num, &crate_denom);
|
|
|
|
/* convert, ignoring speed */
|
|
render_start = clock_convert_external (render_start, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
render_stop = clock_convert_external (render_stop, cinternal, cexternal,
|
|
crate_num, crate_denom);
|
|
|
|
*srender_start = render_start;
|
|
*srender_stop = render_stop;
|
|
}
|
|
|
|
/* converts render_start and render_stop to their slaved values */
|
|
static void
|
|
gst_audio_base_sink_handle_slaving (GstAudioBaseSink * sink,
|
|
GstClockTime render_start, GstClockTime render_stop,
|
|
GstClockTime * srender_start, GstClockTime * srender_stop)
|
|
{
|
|
switch (sink->priv->slave_method) {
|
|
case GST_AUDIO_BASE_SINK_SLAVE_RESAMPLE:
|
|
gst_audio_base_sink_resample_slaving (sink, render_start, render_stop,
|
|
srender_start, srender_stop);
|
|
break;
|
|
case GST_AUDIO_BASE_SINK_SLAVE_SKEW:
|
|
gst_audio_base_sink_skew_slaving (sink, render_start, render_stop,
|
|
srender_start, srender_stop);
|
|
break;
|
|
case GST_AUDIO_BASE_SINK_SLAVE_NONE:
|
|
gst_audio_base_sink_none_slaving (sink, render_start, render_stop,
|
|
srender_start, srender_stop);
|
|
break;
|
|
case GST_AUDIO_BASE_SINK_SLAVE_CUSTOM:
|
|
gst_audio_base_sink_custom_slaving (sink, render_start, render_stop,
|
|
srender_start, srender_stop);
|
|
break;
|
|
default:
|
|
g_warning ("unknown slaving method %d", sink->priv->slave_method);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* must be called with LOCK */
|
|
static GstFlowReturn
|
|
gst_audio_base_sink_sync_latency (GstBaseSink * bsink, GstMiniObject * obj)
|
|
{
|
|
GstClock *clock;
|
|
GstClockReturn status;
|
|
GstClockTime time, render_delay;
|
|
GstFlowReturn ret;
|
|
GstAudioBaseSink *sink;
|
|
GstClockTime itime, etime;
|
|
GstClockTime rate_num, rate_denom;
|
|
GstClockTimeDiff jitter;
|
|
|
|
sink = GST_AUDIO_BASE_SINK (bsink);
|
|
|
|
clock = GST_ELEMENT_CLOCK (sink);
|
|
if (G_UNLIKELY (clock == NULL))
|
|
goto no_clock;
|
|
|
|
/* we provided the global clock, don't need to do anything special */
|
|
if (clock == sink->provided_clock)
|
|
goto no_slaving;
|
|
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
do {
|
|
GST_DEBUG_OBJECT (sink, "checking preroll");
|
|
|
|
ret = gst_base_sink_do_preroll (bsink, obj);
|
|
if (ret != GST_FLOW_OK)
|
|
goto flushing;
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
time = sink->priv->us_latency;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
/* Renderdelay is added onto our own latency, and needs
|
|
* to be subtracted as well */
|
|
render_delay = gst_base_sink_get_render_delay (bsink);
|
|
|
|
if (G_LIKELY (time > render_delay))
|
|
time -= render_delay;
|
|
else
|
|
time = 0;
|
|
|
|
/* preroll done, we can sync since we are in PLAYING now. */
|
|
GST_DEBUG_OBJECT (sink, "possibly waiting for clock to reach %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (time));
|
|
|
|
/* wait for the clock, this can be interrupted because we got shut down or
|
|
* we PAUSED. */
|
|
status = gst_base_sink_wait_clock (bsink, time, &jitter);
|
|
|
|
GST_DEBUG_OBJECT (sink, "clock returned %d %" GST_TIME_FORMAT, status,
|
|
GST_TIME_ARGS (jitter));
|
|
|
|
/* invalid time, no clock or sync disabled, just continue then */
|
|
if (status == GST_CLOCK_BADTIME)
|
|
break;
|
|
|
|
/* waiting could have been interrupted and we can be flushing now */
|
|
if (G_UNLIKELY (bsink->flushing))
|
|
goto flushing;
|
|
|
|
/* retry if we got unscheduled, which means we did not reach the timeout
|
|
* yet. if some other error occurs, we continue. */
|
|
} while (status == GST_CLOCK_UNSCHEDULED);
|
|
|
|
GST_DEBUG_OBJECT (sink, "latency synced");
|
|
|
|
/* We might need to take the object lock within gst_audio_clock_get_time(),
|
|
* so call that before we take it again */
|
|
itime = gst_audio_clock_get_time (GST_AUDIO_CLOCK (sink->provided_clock));
|
|
itime =
|
|
gst_audio_clock_adjust (GST_AUDIO_CLOCK (sink->provided_clock), itime);
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
|
|
/* when we prerolled in time, we can accurately set the calibration,
|
|
* our internal clock should exactly have been the latency (== the running
|
|
* time of the external clock) */
|
|
etime = GST_ELEMENT_CAST (sink)->base_time + time;
|
|
|
|
if (status == GST_CLOCK_EARLY) {
|
|
/* when we prerolled late, we have to take into account the lateness */
|
|
GST_DEBUG_OBJECT (sink, "late preroll, adding jitter");
|
|
etime += jitter;
|
|
}
|
|
|
|
/* start ringbuffer so we can start slaving right away when we need to */
|
|
gst_audio_base_sink_force_start (sink);
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"internal time: %" GST_TIME_FORMAT " external time: %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (itime), GST_TIME_ARGS (etime));
|
|
|
|
/* copy the original calibrated rate but update the internal and external
|
|
* times. */
|
|
gst_clock_get_calibration (sink->provided_clock, NULL, NULL, &rate_num,
|
|
&rate_denom);
|
|
gst_clock_set_calibration (sink->provided_clock, itime, etime,
|
|
rate_num, rate_denom);
|
|
|
|
switch (sink->priv->slave_method) {
|
|
case GST_AUDIO_BASE_SINK_SLAVE_RESAMPLE:
|
|
/* only set as master when we are resampling */
|
|
GST_DEBUG_OBJECT (sink, "Setting clock as master");
|
|
gst_clock_set_master (sink->provided_clock, clock);
|
|
break;
|
|
case GST_AUDIO_BASE_SINK_SLAVE_SKEW:
|
|
case GST_AUDIO_BASE_SINK_SLAVE_NONE:
|
|
case GST_AUDIO_BASE_SINK_SLAVE_CUSTOM:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
gst_audio_base_sink_reset_sync (sink);
|
|
|
|
gst_audio_base_sink_custom_cb_report_discont (sink,
|
|
GST_AUDIO_BASE_SINK_DISCONT_REASON_SYNC_LATENCY);
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
/* ERRORS */
|
|
no_clock:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "we have no clock");
|
|
return GST_FLOW_OK;
|
|
}
|
|
no_slaving:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "we are not slaved");
|
|
return GST_FLOW_OK;
|
|
}
|
|
flushing:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "we are flushing");
|
|
GST_OBJECT_LOCK (sink);
|
|
return GST_FLOW_FLUSHING;
|
|
}
|
|
}
|
|
|
|
static gint64
|
|
gst_audio_base_sink_get_alignment (GstAudioBaseSink * sink,
|
|
GstClockTime sample_offset)
|
|
{
|
|
GstAudioRingBuffer *ringbuf = sink->ringbuffer;
|
|
gint64 align;
|
|
gint64 sample_diff;
|
|
gint64 max_sample_diff;
|
|
gint segdone = g_atomic_int_get (&ringbuf->segdone) - ringbuf->segbase;
|
|
gint64 samples_done = segdone * (gint64) ringbuf->samples_per_seg;
|
|
gint64 headroom = sample_offset - samples_done;
|
|
gboolean allow_align = TRUE;
|
|
gboolean discont = FALSE;
|
|
gint rate;
|
|
|
|
/* now try to align the sample to the previous one. */
|
|
|
|
/* calc align with previous sample and determine how big the
|
|
* difference is. */
|
|
align = sink->next_sample - sample_offset;
|
|
sample_diff = ABS (align);
|
|
|
|
/* calculate the max allowed drift in units of samples. */
|
|
rate = GST_AUDIO_INFO_RATE (&ringbuf->spec.info);
|
|
max_sample_diff = gst_util_uint64_scale_int (sink->priv->alignment_threshold,
|
|
rate, GST_SECOND);
|
|
|
|
/* don't align if it means writing behind the read-segment */
|
|
if (sample_diff > headroom && align < 0)
|
|
allow_align = FALSE;
|
|
|
|
if (G_UNLIKELY (sample_diff >= max_sample_diff)) {
|
|
/* wait before deciding to make a discontinuity */
|
|
if (sink->priv->discont_wait > 0) {
|
|
GstClockTime time = gst_util_uint64_scale_int (sample_offset,
|
|
GST_SECOND, rate);
|
|
if (sink->priv->discont_time == -1) {
|
|
/* discont candidate */
|
|
sink->priv->discont_time = time;
|
|
} else if (time - sink->priv->discont_time >= sink->priv->discont_wait) {
|
|
/* discont_wait expired, discontinuity detected */
|
|
discont = TRUE;
|
|
sink->priv->discont_time = -1;
|
|
}
|
|
} else {
|
|
discont = TRUE;
|
|
}
|
|
} else if (G_UNLIKELY (sink->priv->discont_time != -1)) {
|
|
/* we have had a discont, but are now back on track! */
|
|
sink->priv->discont_time = -1;
|
|
}
|
|
|
|
if (G_LIKELY (!discont && allow_align)) {
|
|
GST_DEBUG_OBJECT (sink,
|
|
"align with prev sample, ABS (%" G_GINT64_FORMAT ") < %"
|
|
G_GINT64_FORMAT, align, max_sample_diff);
|
|
} else {
|
|
gint64 diff_s G_GNUC_UNUSED;
|
|
|
|
/* calculate sample diff in seconds for error message */
|
|
diff_s = gst_util_uint64_scale_int (sample_diff, GST_SECOND, rate);
|
|
|
|
/* timestamps drifted apart from previous samples too much, we need to
|
|
* resync. We log this as an element warning. */
|
|
GST_WARNING_OBJECT (sink,
|
|
"Unexpected discontinuity in audio timestamps of "
|
|
"%s%" GST_TIME_FORMAT ", resyncing",
|
|
sample_offset > sink->next_sample ? "+" : "-", GST_TIME_ARGS (diff_s));
|
|
align = 0;
|
|
|
|
gst_audio_base_sink_custom_cb_report_discont (sink,
|
|
GST_AUDIO_BASE_SINK_DISCONT_REASON_ALIGNMENT);
|
|
}
|
|
|
|
return align;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_audio_base_sink_render (GstBaseSink * bsink, GstBuffer * buf)
|
|
{
|
|
GstClockTime time, stop, render_start, render_stop, sample_offset;
|
|
GstClockTimeDiff sync_offset, ts_offset;
|
|
GstAudioBaseSinkClass *bclass;
|
|
GstAudioBaseSink *sink;
|
|
GstAudioRingBuffer *ringbuf;
|
|
gint64 diff, align;
|
|
guint64 ctime, cstop;
|
|
gsize offset;
|
|
GstMapInfo info;
|
|
gsize size;
|
|
guint samples, written;
|
|
gint bpf, rate;
|
|
gint accum;
|
|
gint out_samples;
|
|
GstClockTime base_time, render_delay, latency;
|
|
GstClock *clock;
|
|
gboolean sync, slaved, align_next;
|
|
GstFlowReturn ret;
|
|
GstSegment clip_seg;
|
|
gint64 time_offset;
|
|
GstBuffer *out = NULL;
|
|
|
|
sink = GST_AUDIO_BASE_SINK (bsink);
|
|
bclass = GST_AUDIO_BASE_SINK_GET_CLASS (sink);
|
|
|
|
ringbuf = sink->ringbuffer;
|
|
|
|
/* can't do anything when we don't have the device */
|
|
if (G_UNLIKELY (!gst_audio_ring_buffer_is_acquired (ringbuf)))
|
|
goto wrong_state;
|
|
|
|
/* Wait for upstream latency before starting the ringbuffer, we do this so
|
|
* that we can align the first sample of the ringbuffer to the base_time +
|
|
* latency. */
|
|
GST_OBJECT_LOCK (sink);
|
|
base_time = GST_ELEMENT_CAST (sink)->base_time;
|
|
if (G_UNLIKELY (sink->priv->sync_latency)) {
|
|
ret = gst_audio_base_sink_sync_latency (bsink, GST_MINI_OBJECT_CAST (buf));
|
|
GST_OBJECT_UNLOCK (sink);
|
|
if (G_UNLIKELY (ret != GST_FLOW_OK))
|
|
goto sync_latency_failed;
|
|
/* only do this once until we are set back to PLAYING */
|
|
sink->priv->sync_latency = FALSE;
|
|
} else {
|
|
GST_OBJECT_UNLOCK (sink);
|
|
}
|
|
|
|
/* Before we go on, let's see if we need to payload the data. If yes, we also
|
|
* need to unref the output buffer before leaving. */
|
|
if (bclass->payload) {
|
|
out = bclass->payload (sink, buf);
|
|
|
|
if (!out)
|
|
goto payload_failed;
|
|
|
|
buf = out;
|
|
}
|
|
|
|
bpf = GST_AUDIO_INFO_BPF (&ringbuf->spec.info);
|
|
rate = GST_AUDIO_INFO_RATE (&ringbuf->spec.info);
|
|
|
|
size = gst_buffer_get_size (buf);
|
|
if (G_UNLIKELY (size % bpf) != 0)
|
|
goto wrong_size;
|
|
|
|
samples = size / bpf;
|
|
|
|
time = GST_BUFFER_PTS (buf);
|
|
|
|
/* Last ditch attempt to ensure that we only play silence if
|
|
* we are in trickmode no-audio mode (or if a buffer is marked as a GAP)
|
|
* by dropping the buffer contents and rendering as a gap event instead */
|
|
if (G_UNLIKELY ((bsink->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO)
|
|
|| (buf && GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP)))) {
|
|
GstClockTime duration;
|
|
GstEvent *event;
|
|
GstBaseSinkClass *bclass;
|
|
GST_DEBUG_OBJECT (bsink,
|
|
"Received GAP or ignoring audio for trickplay. Dropping contents");
|
|
|
|
duration = gst_util_uint64_scale_int (samples, GST_SECOND, rate);
|
|
event = gst_event_new_gap (time, duration);
|
|
|
|
bclass = GST_BASE_SINK_GET_CLASS (bsink);
|
|
ret = bclass->wait_event (bsink, event);
|
|
gst_event_unref (event);
|
|
|
|
/* Ensure we'll resync on the next buffer as if discont */
|
|
sink->next_sample = -1;
|
|
goto done;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"time %" GST_TIME_FORMAT ", start %"
|
|
GST_TIME_FORMAT ", samples %u", GST_TIME_ARGS (time),
|
|
GST_TIME_ARGS (bsink->segment.start), samples);
|
|
|
|
offset = 0;
|
|
|
|
/* if not valid timestamp or we can't clip or sync, try to play
|
|
* sample ASAP */
|
|
if (!GST_CLOCK_TIME_IS_VALID (time)) {
|
|
render_start = gst_audio_base_sink_get_offset (sink);
|
|
render_stop = render_start + samples;
|
|
GST_DEBUG_OBJECT (sink, "Buffer of size %" G_GSIZE_FORMAT " has no time."
|
|
" Using render_start=%" G_GUINT64_FORMAT, size, render_start);
|
|
/* we don't have a start so we don't know stop either */
|
|
stop = -1;
|
|
goto no_align;
|
|
}
|
|
|
|
/* let's calc stop based on the number of samples in the buffer instead
|
|
* of trusting the DURATION */
|
|
stop = time + gst_util_uint64_scale_int (samples, GST_SECOND, rate);
|
|
|
|
/* prepare the clipping segment. Since we will be subtracting ts-offset and
|
|
* device-delay later we scale the start and stop with those values so that we
|
|
* can correctly clip them */
|
|
clip_seg.format = GST_FORMAT_TIME;
|
|
clip_seg.start = bsink->segment.start;
|
|
clip_seg.stop = bsink->segment.stop;
|
|
clip_seg.duration = -1;
|
|
|
|
/* the sync offset is the combination of ts-offset and device-delay */
|
|
latency = gst_base_sink_get_latency (bsink);
|
|
ts_offset = gst_base_sink_get_ts_offset (bsink);
|
|
render_delay = gst_base_sink_get_render_delay (bsink);
|
|
sync_offset = ts_offset - render_delay + latency;
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"sync-offset %" GST_STIME_FORMAT ", render-delay %" GST_TIME_FORMAT
|
|
", ts-offset %" GST_STIME_FORMAT, GST_STIME_ARGS (sync_offset),
|
|
GST_TIME_ARGS (render_delay), GST_STIME_ARGS (ts_offset));
|
|
|
|
/* compensate for ts-offset and device-delay when negative we need to
|
|
* clip. */
|
|
if (G_UNLIKELY (sync_offset < 0)) {
|
|
clip_seg.start += -sync_offset;
|
|
if (clip_seg.stop != -1)
|
|
clip_seg.stop += -sync_offset;
|
|
}
|
|
|
|
/* samples should be rendered based on their timestamp. All samples
|
|
* arriving before the segment.start or after segment.stop are to be
|
|
* thrown away. All samples should also be clipped to the segment
|
|
* boundaries */
|
|
if (G_UNLIKELY (!gst_segment_clip (&clip_seg, GST_FORMAT_TIME, time, stop,
|
|
&ctime, &cstop)))
|
|
goto out_of_segment;
|
|
|
|
/* see if some clipping happened */
|
|
diff = ctime - time;
|
|
if (G_UNLIKELY (diff > 0)) {
|
|
/* bring clipped time to samples */
|
|
diff = gst_util_uint64_scale_int (diff, rate, GST_SECOND);
|
|
GST_DEBUG_OBJECT (sink, "clipping start to %" GST_TIME_FORMAT " %"
|
|
G_GUINT64_FORMAT " samples", GST_TIME_ARGS (ctime), diff);
|
|
samples -= diff;
|
|
offset += diff * bpf;
|
|
time = ctime;
|
|
}
|
|
diff = stop - cstop;
|
|
if (G_UNLIKELY (diff > 0)) {
|
|
/* bring clipped time to samples */
|
|
diff = gst_util_uint64_scale_int (diff, rate, GST_SECOND);
|
|
GST_DEBUG_OBJECT (sink, "clipping stop to %" GST_TIME_FORMAT " %"
|
|
G_GUINT64_FORMAT " samples", GST_TIME_ARGS (cstop), diff);
|
|
samples -= diff;
|
|
stop = cstop;
|
|
}
|
|
|
|
/* figure out how to sync */
|
|
if (G_LIKELY ((clock = GST_ELEMENT_CLOCK (bsink))))
|
|
sync = bsink->sync;
|
|
else
|
|
sync = FALSE;
|
|
|
|
if (G_UNLIKELY (!sync)) {
|
|
/* no sync needed, play sample ASAP */
|
|
render_start = gst_audio_base_sink_get_offset (sink);
|
|
render_stop = render_start + samples;
|
|
GST_DEBUG_OBJECT (sink,
|
|
"no sync needed. Using render_start=%" G_GUINT64_FORMAT, render_start);
|
|
goto no_align;
|
|
}
|
|
|
|
/* bring buffer start and stop times to running time */
|
|
render_start =
|
|
gst_segment_to_running_time (&bsink->segment, GST_FORMAT_TIME, time);
|
|
render_stop =
|
|
gst_segment_to_running_time (&bsink->segment, GST_FORMAT_TIME, stop);
|
|
|
|
if (render_start == GST_CLOCK_TIME_NONE || render_stop == GST_CLOCK_TIME_NONE)
|
|
goto too_late;
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"running: start %" GST_TIME_FORMAT " - stop %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (render_start), GST_TIME_ARGS (render_stop));
|
|
|
|
/* store the time of the last sample, we'll use this to perform sync on the
|
|
* last sample when draining the buffer */
|
|
if (G_LIKELY (bsink->segment.rate >= 0.0)) {
|
|
sink->priv->eos_time = render_stop;
|
|
} else {
|
|
sink->priv->eos_time = render_start;
|
|
}
|
|
|
|
if (G_UNLIKELY (sync_offset != 0)) {
|
|
/* compensate for ts-offset and delay. We know this will not underflow
|
|
* because we clipped above. */
|
|
GST_DEBUG_OBJECT (sink,
|
|
"compensating for sync-offset %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (sync_offset));
|
|
render_start += sync_offset;
|
|
render_stop += sync_offset;
|
|
}
|
|
|
|
if (base_time != 0) {
|
|
GST_DEBUG_OBJECT (sink, "adding base_time %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (base_time));
|
|
|
|
/* add base time to sync against the clock */
|
|
render_start += base_time;
|
|
render_stop += base_time;
|
|
}
|
|
|
|
if (G_UNLIKELY ((slaved = (clock != sink->provided_clock)))) {
|
|
/* handle clock slaving */
|
|
gst_audio_base_sink_handle_slaving (sink, render_start, render_stop,
|
|
&render_start, &render_stop);
|
|
} else {
|
|
/* no slaving needed but we need to adapt to the clock calibration
|
|
* parameters */
|
|
gst_audio_base_sink_none_slaving (sink, render_start, render_stop,
|
|
&render_start, &render_stop);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"final timestamps: start %" GST_TIME_FORMAT " - stop %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (render_start), GST_TIME_ARGS (render_stop));
|
|
|
|
/* bring to position in the ringbuffer */
|
|
time_offset = GST_AUDIO_CLOCK_CAST (sink->provided_clock)->time_offset;
|
|
|
|
if (G_UNLIKELY (time_offset != 0)) {
|
|
GST_DEBUG_OBJECT (sink,
|
|
"apply time offset %" GST_STIME_FORMAT, GST_STIME_ARGS (time_offset));
|
|
|
|
if (render_start > time_offset)
|
|
render_start -= time_offset;
|
|
else
|
|
render_start = 0;
|
|
if (render_stop > time_offset)
|
|
render_stop -= time_offset;
|
|
else
|
|
render_stop = 0;
|
|
}
|
|
|
|
/* in some clock slaving cases, all late samples end up at 0 first,
|
|
* and subsequent ones align with that until threshold exceeded,
|
|
* and then sync back to 0 and so on, so avoid that altogether */
|
|
if (G_UNLIKELY (render_start == 0 && render_stop == 0))
|
|
goto too_late;
|
|
|
|
/* and bring the time to the rate corrected offset in the buffer */
|
|
render_start = gst_util_uint64_scale_int (render_start, rate, GST_SECOND);
|
|
render_stop = gst_util_uint64_scale_int (render_stop, rate, GST_SECOND);
|
|
|
|
/* If the slaving got us an interval spanning 0, render_start will
|
|
have been set to 0. So if render_start is 0, we check whether
|
|
render_stop is set to contain all samples. If not, we need to
|
|
drop samples to match. */
|
|
if (render_start == 0) {
|
|
guint nsamples = render_stop - render_start;
|
|
if (nsamples < samples) {
|
|
guint diff;
|
|
|
|
diff = samples - nsamples;
|
|
GST_DEBUG_OBJECT (bsink, "Clipped start: %u/%u samples", nsamples,
|
|
samples);
|
|
samples -= diff;
|
|
offset += diff * bpf;
|
|
}
|
|
}
|
|
|
|
/* positive playback rate, first sample is render_start, negative rate, first
|
|
* sample is render_stop. When no rate conversion is active, render exactly
|
|
* the amount of input samples to avoid aligning to rounding errors. */
|
|
if (G_LIKELY (bsink->segment.rate >= 0.0)) {
|
|
sample_offset = render_start;
|
|
if (G_LIKELY (bsink->segment.rate == 1.0))
|
|
render_stop = sample_offset + samples;
|
|
} else {
|
|
sample_offset = render_stop;
|
|
if (bsink->segment.rate == -1.0)
|
|
render_start = sample_offset + samples;
|
|
}
|
|
|
|
/* always resync after a discont */
|
|
if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT) ||
|
|
GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_RESYNC))) {
|
|
GST_DEBUG_OBJECT (sink, "resync after discont/resync");
|
|
goto no_align;
|
|
}
|
|
|
|
/* resync when we don't know what to align the sample with */
|
|
if (G_UNLIKELY (sink->next_sample == -1)) {
|
|
GST_DEBUG_OBJECT (sink,
|
|
"no align possible: no previous sample position known");
|
|
goto no_align;
|
|
}
|
|
|
|
align = gst_audio_base_sink_get_alignment (sink, sample_offset);
|
|
sink->priv->last_align = align;
|
|
|
|
/* apply alignment */
|
|
render_start += align;
|
|
|
|
/* only align stop if we are not slaved to resample */
|
|
if (G_UNLIKELY (slaved
|
|
&& sink->priv->slave_method == GST_AUDIO_BASE_SINK_SLAVE_RESAMPLE)) {
|
|
GST_DEBUG_OBJECT (sink, "no stop time align needed: we are slaved");
|
|
goto no_align;
|
|
}
|
|
render_stop += align;
|
|
|
|
no_align:
|
|
/* number of target samples is difference between start and stop */
|
|
out_samples = render_stop - render_start;
|
|
|
|
/* we render the first or last sample first, depending on the rate */
|
|
if (G_LIKELY (bsink->segment.rate >= 0.0))
|
|
sample_offset = render_start;
|
|
else
|
|
sample_offset = render_stop;
|
|
|
|
GST_DEBUG_OBJECT (sink, "rendering at %" G_GUINT64_FORMAT " %d/%d",
|
|
sample_offset, samples, out_samples);
|
|
|
|
/* we need to accumulate over different runs for when we get interrupted */
|
|
accum = 0;
|
|
align_next = TRUE;
|
|
gst_buffer_map (buf, &info, GST_MAP_READ);
|
|
do {
|
|
written =
|
|
gst_audio_ring_buffer_commit (ringbuf, &sample_offset,
|
|
info.data + offset, samples, out_samples, &accum);
|
|
|
|
GST_DEBUG_OBJECT (sink, "wrote %u of %u", written, samples);
|
|
/* if we wrote all, we're done */
|
|
if (G_LIKELY (written == samples))
|
|
break;
|
|
|
|
/* else something interrupted us and we wait for preroll. */
|
|
if ((ret = gst_base_sink_wait_preroll (bsink)) != GST_FLOW_OK)
|
|
goto stopping;
|
|
|
|
/* if we got interrupted, we cannot assume that the next sample should
|
|
* be aligned to this one */
|
|
align_next = FALSE;
|
|
|
|
/* update the output samples. FIXME, this will just skip them when pausing
|
|
* during trick mode */
|
|
if (out_samples > written) {
|
|
out_samples -= written;
|
|
accum = 0;
|
|
} else
|
|
break;
|
|
|
|
samples -= written;
|
|
offset += written * bpf;
|
|
} while (TRUE);
|
|
gst_buffer_unmap (buf, &info);
|
|
|
|
if (G_LIKELY (align_next))
|
|
sink->next_sample = sample_offset;
|
|
else
|
|
sink->next_sample = -1;
|
|
|
|
GST_DEBUG_OBJECT (sink, "next sample expected at %" G_GUINT64_FORMAT,
|
|
sink->next_sample);
|
|
|
|
if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (stop)
|
|
&& stop >= bsink->segment.stop)) {
|
|
GST_DEBUG_OBJECT (sink,
|
|
"start playback because we are at the end of segment");
|
|
gst_audio_base_sink_force_start (sink);
|
|
}
|
|
|
|
ret = GST_FLOW_OK;
|
|
|
|
done:
|
|
if (out)
|
|
gst_buffer_unref (out);
|
|
|
|
return ret;
|
|
|
|
/* SPECIAL cases */
|
|
out_of_segment:
|
|
{
|
|
GST_DEBUG_OBJECT (sink,
|
|
"dropping sample out of segment time %" GST_TIME_FORMAT ", start %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (time),
|
|
GST_TIME_ARGS (bsink->segment.start));
|
|
ret = GST_FLOW_OK;
|
|
goto done;
|
|
}
|
|
too_late:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "dropping late sample");
|
|
ret = GST_FLOW_OK;
|
|
goto done;
|
|
}
|
|
/* ERRORS */
|
|
payload_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (sink, STREAM, FORMAT, (NULL), ("failed to payload."));
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
wrong_state:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "ringbuffer not negotiated");
|
|
GST_ELEMENT_ERROR (sink, STREAM, FORMAT, (NULL), ("sink not negotiated."));
|
|
ret = GST_FLOW_NOT_NEGOTIATED;
|
|
goto done;
|
|
}
|
|
wrong_size:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "wrong size");
|
|
GST_ELEMENT_ERROR (sink, STREAM, WRONG_TYPE,
|
|
(NULL), ("sink received buffer of wrong size."));
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
stopping:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "preroll got interrupted: %d (%s)", ret,
|
|
gst_flow_get_name (ret));
|
|
gst_buffer_unmap (buf, &info);
|
|
goto done;
|
|
}
|
|
sync_latency_failed:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "failed waiting for latency");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_audio_base_sink_create_ringbuffer:
|
|
* @sink: a #GstAudioBaseSink.
|
|
*
|
|
* Create and return the #GstAudioRingBuffer for @sink. This function will
|
|
* call the ::create_ringbuffer vmethod and will set @sink as the parent of
|
|
* the returned buffer (see gst_object_set_parent()).
|
|
*
|
|
* Returns: (transfer none) (nullable): The new ringbuffer of @sink.
|
|
*/
|
|
GstAudioRingBuffer *
|
|
gst_audio_base_sink_create_ringbuffer (GstAudioBaseSink * sink)
|
|
{
|
|
GstAudioBaseSinkClass *bclass;
|
|
GstAudioRingBuffer *buffer = NULL;
|
|
|
|
bclass = GST_AUDIO_BASE_SINK_GET_CLASS (sink);
|
|
if (bclass->create_ringbuffer)
|
|
buffer = bclass->create_ringbuffer (sink);
|
|
|
|
if (buffer)
|
|
gst_object_set_parent (GST_OBJECT (buffer), GST_OBJECT (sink));
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static void
|
|
gst_audio_base_sink_callback (GstAudioRingBuffer * rbuf, guint8 * data,
|
|
guint len, gpointer user_data)
|
|
{
|
|
GstBaseSink *basesink;
|
|
GstAudioBaseSink *sink;
|
|
GstBuffer *buf = NULL;
|
|
GstFlowReturn ret;
|
|
gsize size;
|
|
|
|
basesink = GST_BASE_SINK (user_data);
|
|
sink = GST_AUDIO_BASE_SINK (user_data);
|
|
|
|
GST_PAD_STREAM_LOCK (basesink->sinkpad);
|
|
|
|
/* would be nice to arrange for pad_alloc_buffer to return data -- as it is we
|
|
* will copy twice, once into data, once into DMA */
|
|
GST_LOG_OBJECT (basesink, "pulling %u bytes offset %" G_GUINT64_FORMAT
|
|
" to fill audio buffer", len, basesink->offset);
|
|
ret =
|
|
gst_pad_pull_range (basesink->sinkpad, basesink->segment.position, len,
|
|
&buf);
|
|
|
|
if (ret != GST_FLOW_OK) {
|
|
if (ret == GST_FLOW_EOS)
|
|
goto eos;
|
|
else
|
|
goto error;
|
|
}
|
|
|
|
GST_BASE_SINK_PREROLL_LOCK (basesink);
|
|
if (basesink->flushing)
|
|
goto flushing;
|
|
|
|
/* complete preroll and wait for PLAYING */
|
|
ret = gst_base_sink_do_preroll (basesink, GST_MINI_OBJECT_CAST (buf));
|
|
if (ret != GST_FLOW_OK)
|
|
goto preroll_error;
|
|
|
|
size = gst_buffer_get_size (buf);
|
|
|
|
if (len != size) {
|
|
GST_INFO_OBJECT (basesink,
|
|
"got different size than requested from sink pad: %u"
|
|
" != %" G_GSIZE_FORMAT, len, size);
|
|
len = MIN (size, len);
|
|
}
|
|
|
|
basesink->segment.position += len;
|
|
|
|
gst_buffer_extract (buf, 0, data, len);
|
|
GST_BASE_SINK_PREROLL_UNLOCK (basesink);
|
|
|
|
GST_PAD_STREAM_UNLOCK (basesink->sinkpad);
|
|
|
|
return;
|
|
|
|
error:
|
|
{
|
|
GST_WARNING_OBJECT (basesink, "Got flow '%s' but can't return it: %d",
|
|
gst_flow_get_name (ret), ret);
|
|
gst_audio_ring_buffer_pause (rbuf);
|
|
GST_PAD_STREAM_UNLOCK (basesink->sinkpad);
|
|
return;
|
|
}
|
|
eos:
|
|
{
|
|
/* FIXME: this is not quite correct; we'll be called endlessly until
|
|
* the sink gets shut down; maybe we should set a flag somewhere, or
|
|
* set segment.stop and segment.duration to the last sample or so */
|
|
GST_DEBUG_OBJECT (sink, "EOS");
|
|
gst_audio_base_sink_drain (sink);
|
|
gst_audio_ring_buffer_pause (rbuf);
|
|
gst_element_post_message (GST_ELEMENT_CAST (sink),
|
|
gst_message_new_eos (GST_OBJECT_CAST (sink)));
|
|
GST_PAD_STREAM_UNLOCK (basesink->sinkpad);
|
|
return;
|
|
}
|
|
flushing:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "we are flushing");
|
|
gst_audio_ring_buffer_pause (rbuf);
|
|
GST_BASE_SINK_PREROLL_UNLOCK (basesink);
|
|
GST_PAD_STREAM_UNLOCK (basesink->sinkpad);
|
|
return;
|
|
}
|
|
preroll_error:
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "error %s", gst_flow_get_name (ret));
|
|
gst_audio_ring_buffer_pause (rbuf);
|
|
GST_BASE_SINK_PREROLL_UNLOCK (basesink);
|
|
GST_PAD_STREAM_UNLOCK (basesink->sinkpad);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_base_sink_activate_pull (GstBaseSink * basesink, gboolean active)
|
|
{
|
|
gboolean ret;
|
|
GstAudioBaseSink *sink = GST_AUDIO_BASE_SINK (basesink);
|
|
|
|
if (active) {
|
|
GST_DEBUG_OBJECT (basesink, "activating pull");
|
|
|
|
gst_audio_ring_buffer_set_callback (sink->ringbuffer,
|
|
gst_audio_base_sink_callback, sink);
|
|
|
|
ret = gst_audio_ring_buffer_activate (sink->ringbuffer, TRUE);
|
|
} else {
|
|
GST_DEBUG_OBJECT (basesink, "deactivating pull");
|
|
gst_audio_ring_buffer_set_callback (sink->ringbuffer, NULL, NULL);
|
|
ret = gst_audio_ring_buffer_activate (sink->ringbuffer, FALSE);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_audio_base_sink_change_state (GstElement * element,
|
|
GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
|
GstAudioBaseSink *sink = GST_AUDIO_BASE_SINK (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:{
|
|
GstAudioRingBuffer *rb;
|
|
|
|
gst_audio_clock_reset (GST_AUDIO_CLOCK (sink->provided_clock), 0);
|
|
rb = gst_audio_base_sink_create_ringbuffer (sink);
|
|
if (rb == NULL)
|
|
goto create_failed;
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
sink->ringbuffer = rb;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
if (!gst_audio_ring_buffer_open_device (sink->ringbuffer)) {
|
|
GST_OBJECT_LOCK (sink);
|
|
gst_object_unparent (GST_OBJECT_CAST (sink->ringbuffer));
|
|
sink->ringbuffer = NULL;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
goto open_failed;
|
|
}
|
|
break;
|
|
}
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
gst_audio_base_sink_reset_sync (sink);
|
|
gst_audio_ring_buffer_set_flushing (sink->ringbuffer, FALSE);
|
|
gst_audio_ring_buffer_may_start (sink->ringbuffer, FALSE);
|
|
|
|
/* Only post clock-provide messages if this is the clock that
|
|
* we've created. If the subclass has overridden it the subclass
|
|
* should post this messages whenever necessary */
|
|
if (gst_audio_base_sink_is_self_provided_clock (sink))
|
|
gst_element_post_message (element,
|
|
gst_message_new_clock_provide (GST_OBJECT_CAST (element),
|
|
sink->provided_clock, TRUE));
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
{
|
|
gboolean eos;
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
GST_DEBUG_OBJECT (sink, "ringbuffer may start now");
|
|
sink->priv->sync_latency = TRUE;
|
|
eos = GST_BASE_SINK (sink)->eos;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
|
|
gst_audio_ring_buffer_may_start (sink->ringbuffer, TRUE);
|
|
if (GST_BASE_SINK_CAST (sink)->pad_mode == GST_PAD_MODE_PULL ||
|
|
g_atomic_int_get (&sink->eos_rendering) || eos) {
|
|
/* we always start the ringbuffer in pull mode immediately */
|
|
/* sync rendering on eos needs running clock,
|
|
* and others need running clock when finished rendering eos */
|
|
gst_audio_ring_buffer_start (sink->ringbuffer);
|
|
}
|
|
break;
|
|
}
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
/* ringbuffer cannot start anymore */
|
|
gst_audio_ring_buffer_may_start (sink->ringbuffer, FALSE);
|
|
gst_audio_ring_buffer_pause (sink->ringbuffer);
|
|
|
|
GST_OBJECT_LOCK (sink);
|
|
sink->priv->sync_latency = FALSE;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
/* Only post clock-lost messages if this is the clock that
|
|
* we've created. If the subclass has overridden it the subclass
|
|
* should post this messages whenever necessary */
|
|
if (gst_audio_base_sink_is_self_provided_clock (sink))
|
|
gst_element_post_message (element,
|
|
gst_message_new_clock_lost (GST_OBJECT_CAST (element),
|
|
sink->provided_clock));
|
|
|
|
/* make sure we unblock before calling the parent state change
|
|
* so it can grab the STREAM_LOCK */
|
|
gst_audio_ring_buffer_set_flushing (sink->ringbuffer, TRUE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
/* stop slaving ourselves to the master, if any */
|
|
gst_clock_set_master (sink->provided_clock, NULL);
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
gst_audio_ring_buffer_activate (sink->ringbuffer, FALSE);
|
|
gst_audio_ring_buffer_release (sink->ringbuffer);
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
/* we release again here because the acquire happens when setting the
|
|
* caps, which happens before we commit the state to PAUSED and thus the
|
|
* PAUSED->READY state change (see above, where we release the ringbuffer)
|
|
* might not be called when we get here. */
|
|
gst_audio_ring_buffer_activate (sink->ringbuffer, FALSE);
|
|
gst_audio_ring_buffer_release (sink->ringbuffer);
|
|
gst_audio_ring_buffer_close_device (sink->ringbuffer);
|
|
GST_OBJECT_LOCK (sink);
|
|
gst_object_unparent (GST_OBJECT_CAST (sink->ringbuffer));
|
|
sink->ringbuffer = NULL;
|
|
GST_OBJECT_UNLOCK (sink);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
create_failed:
|
|
{
|
|
/* subclass must post a meaningful error message */
|
|
GST_DEBUG_OBJECT (sink, "create failed");
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
}
|
|
open_failed:
|
|
{
|
|
/* subclass must post a meaningful error message */
|
|
GST_DEBUG_OBJECT (sink, "open failed");
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
}
|
|
}
|