From 86888d9918c29e2243f2b81b77acc9f646de1488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 8 Nov 2017 19:31:37 +0200 Subject: [PATCH] decklinkaudiosink: Re-implement around GstBaseSink instead of GstAudioBaseSink The Decklink and GstAudioBaseSink APIs don't fit very well together, which causes various problems due to inaccuracies in the clock calculations and the actual ringbuffer and GStreamer's copy getting of sync. Problems are audio drop-outs and A/V sync getting wrong after pausing/seeking. https://bugzilla.gnome.org/show_bug.cgi?id=790114 --- sys/decklink/gstdecklink.cpp | 27 - sys/decklink/gstdecklink.h | 6 - sys/decklink/gstdecklinkaudiosink.cpp | 1165 ++++++++++++------------- sys/decklink/gstdecklinkaudiosink.h | 10 +- sys/decklink/gstdecklinkvideosink.cpp | 22 +- sys/decklink/gstdecklinkvideosink.h | 3 + 6 files changed, 596 insertions(+), 637 deletions(-) diff --git a/sys/decklink/gstdecklink.cpp b/sys/decklink/gstdecklink.cpp index d7d9e503ed..0527913c7d 100644 --- a/sys/decklink/gstdecklink.cpp +++ b/sys/decklink/gstdecklink.cpp @@ -1339,33 +1339,6 @@ gst_decklink_release_nth_output (gint n, GstElement * sink, gboolean is_audio) g_mutex_unlock (&output->lock); } -void -gst_decklink_output_set_audio_clock (GstDecklinkOutput * output, - GstClock * clock) -{ - g_mutex_lock (&output->lock); - if (output->audio_clock) - gst_object_unref (output->audio_clock); - output->audio_clock = clock; - if (clock) - gst_object_ref (clock); - g_mutex_unlock (&output->lock); -} - - -GstClock * -gst_decklink_output_get_audio_clock (GstDecklinkOutput * output) -{ - GstClock *ret = NULL; - - g_mutex_lock (&output->lock); - if (output->audio_clock) - ret = GST_CLOCK_CAST (gst_object_ref (output->audio_clock)); - g_mutex_unlock (&output->lock); - - return ret; -} - GstDecklinkInput * gst_decklink_acquire_nth_input (gint n, GstElement * src, gboolean is_audio) { diff --git a/sys/decklink/gstdecklink.h b/sys/decklink/gstdecklink.h index af98a03246..0b65bb25a4 100644 --- a/sys/decklink/gstdecklink.h +++ b/sys/decklink/gstdecklink.h @@ -226,9 +226,6 @@ struct _GstDecklinkOutput { /* Configured mode or NULL */ const GstDecklinkMode *mode; - /* Set by the audio sink */ - GstClock *audio_clock; - GstElement *audiosink; gboolean audio_enabled; GstElement *videosink; @@ -267,9 +264,6 @@ struct _GstDecklinkInput { GstDecklinkOutput * gst_decklink_acquire_nth_output (gint n, GstElement * sink, gboolean is_audio); void gst_decklink_release_nth_output (gint n, GstElement * sink, gboolean is_audio); -void gst_decklink_output_set_audio_clock (GstDecklinkOutput * output, GstClock * clock); -GstClock * gst_decklink_output_get_audio_clock (GstDecklinkOutput * output); - GstDecklinkInput * gst_decklink_acquire_nth_input (gint n, GstElement * src, gboolean is_audio); void gst_decklink_release_nth_input (gint n, GstElement * src, gboolean is_audio); diff --git a/sys/decklink/gstdecklinkaudiosink.cpp b/sys/decklink/gstdecklinkaudiosink.cpp index 84cd2eaa4b..ce8ab94209 100644 --- a/sys/decklink/gstdecklinkaudiosink.cpp +++ b/sys/decklink/gstdecklinkaudiosink.cpp @@ -23,503 +23,54 @@ #endif #include "gstdecklinkaudiosink.h" +#include "gstdecklinkvideosink.h" +#include GST_DEBUG_CATEGORY_STATIC (gst_decklink_audio_sink_debug); #define GST_CAT_DEFAULT gst_decklink_audio_sink_debug -// Ringbuffer implementation - -#define GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER \ - (gst_decklink_audio_sink_ringbuffer_get_type()) -#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER,GstDecklinkAudioSinkRingBuffer)) -#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST(obj) \ - ((GstDecklinkAudioSinkRingBuffer*) obj) -#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER,GstDecklinkAudioSinkRingBufferClass)) -#define GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER,GstDecklinkAudioSinkRingBufferClass)) -#define GST_IS_DECKLINK_AUDIO_SINK_RING_BUFFER(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER)) -#define GST_IS_DECKLINK_AUDIO_SINK_RING_BUFFER_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER)) - -typedef struct _GstDecklinkAudioSinkRingBuffer GstDecklinkAudioSinkRingBuffer; -typedef struct _GstDecklinkAudioSinkRingBufferClass - GstDecklinkAudioSinkRingBufferClass; - -struct _GstDecklinkAudioSinkRingBuffer -{ - GstAudioRingBuffer object; - - GstDecklinkOutput *output; - GstDecklinkAudioSink *sink; - - GMutex clock_id_lock; - GstClockID clock_id; -}; - -struct _GstDecklinkAudioSinkRingBufferClass -{ - GstAudioRingBufferClass parent_class; -}; - -GType gst_decklink_audio_sink_ringbuffer_get_type (void); - -static void gst_decklink_audio_sink_ringbuffer_finalize (GObject * object); - -static void gst_decklink_audio_sink_ringbuffer_clear_all (GstAudioRingBuffer * - rb); -static guint gst_decklink_audio_sink_ringbuffer_delay (GstAudioRingBuffer * rb); -static gboolean gst_decklink_audio_sink_ringbuffer_start (GstAudioRingBuffer * - rb); -static gboolean gst_decklink_audio_sink_ringbuffer_pause (GstAudioRingBuffer * - rb); -static gboolean gst_decklink_audio_sink_ringbuffer_stop (GstAudioRingBuffer * - rb); -static gboolean gst_decklink_audio_sink_ringbuffer_acquire (GstAudioRingBuffer * - rb, GstAudioRingBufferSpec * spec); -static gboolean gst_decklink_audio_sink_ringbuffer_release (GstAudioRingBuffer * - rb); -static gboolean -gst_decklink_audio_sink_ringbuffer_open_device (GstAudioRingBuffer * rb); -static gboolean -gst_decklink_audio_sink_ringbuffer_close_device (GstAudioRingBuffer * rb); - -#define ringbuffer_parent_class gst_decklink_audio_sink_ringbuffer_parent_class -G_DEFINE_TYPE (GstDecklinkAudioSinkRingBuffer, - gst_decklink_audio_sink_ringbuffer, GST_TYPE_AUDIO_RING_BUFFER); - -static void - gst_decklink_audio_sink_ringbuffer_class_init - (GstDecklinkAudioSinkRingBufferClass * klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GstAudioRingBufferClass *gstringbuffer_class = - GST_AUDIO_RING_BUFFER_CLASS (klass); - - gobject_class->finalize = gst_decklink_audio_sink_ringbuffer_finalize; - - gstringbuffer_class->open_device = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_open_device); - gstringbuffer_class->close_device = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_close_device); - gstringbuffer_class->acquire = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_acquire); - gstringbuffer_class->release = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_release); - gstringbuffer_class->start = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_start); - gstringbuffer_class->pause = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_pause); - gstringbuffer_class->resume = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_start); - gstringbuffer_class->stop = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_stop); - gstringbuffer_class->delay = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_delay); - gstringbuffer_class->clear_all = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_ringbuffer_clear_all); -} - -static void -gst_decklink_audio_sink_ringbuffer_init (GstDecklinkAudioSinkRingBuffer * self) -{ - g_mutex_init (&self->clock_id_lock); -} - -static void -gst_decklink_audio_sink_ringbuffer_finalize (GObject * object) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (object); - - gst_object_unref (self->sink); - self->sink = NULL; - g_mutex_clear (&self->clock_id_lock); - - G_OBJECT_CLASS (ringbuffer_parent_class)->finalize (object); -} - -class GStreamerAudioOutputCallback:public IDeckLinkAudioOutputCallback -{ -public: - GStreamerAudioOutputCallback (GstDecklinkAudioSinkRingBuffer * ringbuffer) - :IDeckLinkAudioOutputCallback (), m_refcount (1) - { - m_ringbuffer = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (gst_object_ref (ringbuffer)); - g_mutex_init (&m_mutex); - } - - virtual HRESULT WINAPI QueryInterface (REFIID, LPVOID *) - { - return E_NOINTERFACE; - } - - virtual ULONG WINAPI AddRef (void) - { - ULONG ret; - - g_mutex_lock (&m_mutex); - m_refcount++; - ret = m_refcount; - g_mutex_unlock (&m_mutex); - - return ret; - } - - virtual ULONG WINAPI Release (void) - { - ULONG ret; - - g_mutex_lock (&m_mutex); - m_refcount--; - ret = m_refcount; - g_mutex_unlock (&m_mutex); - - if (ret == 0) { - delete this; - } - - return ret; - } - - virtual ~ GStreamerAudioOutputCallback () { - gst_object_unref (m_ringbuffer); - g_mutex_clear (&m_mutex); - } - - virtual HRESULT WINAPI RenderAudioSamples (bool preroll) - { - guint8 *ptr; - gint seg; - gint len; - gint bpf; - guint written, written_sum; - HRESULT res; - const GstAudioRingBufferSpec *spec = - &GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer)->spec; - guint delay, max_delay; - - GST_LOG_OBJECT (m_ringbuffer->sink, "Writing audio samples (preroll: %d)", - preroll); - - delay = - gst_audio_ring_buffer_delay (GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer)); - max_delay = MAX ((spec->segtotal * spec->segsize) / 2, spec->segsize); - max_delay /= GST_AUDIO_INFO_BPF (&spec->info); - if (delay > max_delay) { - GstClock *clock = - gst_element_get_clock (GST_ELEMENT_CAST (m_ringbuffer->sink)); - GstClockTime wait_time; - GstClockID clock_id; - GstClockReturn clock_ret; - - GST_DEBUG_OBJECT (m_ringbuffer->sink, "Delay %u > max delay %u", delay, - max_delay); - - wait_time = - gst_util_uint64_scale (delay - max_delay, GST_SECOND, - GST_AUDIO_INFO_RATE (&spec->info)); - GST_DEBUG_OBJECT (m_ringbuffer->sink, "Waiting for %" GST_TIME_FORMAT, - GST_TIME_ARGS (wait_time)); - wait_time += gst_clock_get_time (clock); - - g_mutex_lock (&m_ringbuffer->clock_id_lock); - if (!GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer)->acquired) { - GST_DEBUG_OBJECT (m_ringbuffer->sink, - "Ringbuffer not acquired anymore"); - g_mutex_unlock (&m_ringbuffer->clock_id_lock); - gst_object_unref (clock); - return S_OK; - } - clock_id = gst_clock_new_single_shot_id (clock, wait_time); - m_ringbuffer->clock_id = clock_id; - g_mutex_unlock (&m_ringbuffer->clock_id_lock); - gst_object_unref (clock); - - clock_ret = gst_clock_id_wait (clock_id, NULL); - - g_mutex_lock (&m_ringbuffer->clock_id_lock); - gst_clock_id_unref (clock_id); - m_ringbuffer->clock_id = NULL; - g_mutex_unlock (&m_ringbuffer->clock_id_lock); - - if (clock_ret == GST_CLOCK_UNSCHEDULED) { - GST_DEBUG_OBJECT (m_ringbuffer->sink, "Flushing"); - return S_OK; - } - } - - if (!gst_audio_ring_buffer_prepare_read (GST_AUDIO_RING_BUFFER_CAST - (m_ringbuffer), &seg, &ptr, &len)) { - GST_WARNING_OBJECT (m_ringbuffer->sink, "No segment available"); - return E_FAIL; - } - - bpf = - GST_AUDIO_INFO_BPF (&GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer)-> - spec.info); - len /= bpf; - GST_LOG_OBJECT (m_ringbuffer->sink, - "Write audio samples: %p size %d segment: %d", ptr, len, seg); - - written_sum = 0; - do { - res = - m_ringbuffer->output->output->ScheduleAudioSamples (ptr, len, - 0, 0, &written); - len -= written; - ptr += written * bpf; - written_sum += written; - } while (len > 0 && res == S_OK); - - GST_LOG_OBJECT (m_ringbuffer->sink, "Wrote %u samples: 0x%08lx", - written_sum, (unsigned long) res); - - gst_audio_ring_buffer_clear (GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer), - seg); - gst_audio_ring_buffer_advance (GST_AUDIO_RING_BUFFER_CAST (m_ringbuffer), - 1); - - return res; - } - -private: - GstDecklinkAudioSinkRingBuffer * m_ringbuffer; - GMutex m_mutex; - gint m_refcount; -}; - -static void -gst_decklink_audio_sink_ringbuffer_clear_all (GstAudioRingBuffer * rb) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb); - - GST_DEBUG_OBJECT (self->sink, "Flushing"); - - if (self->output) - self->output->output->FlushBufferedAudioSamples (); -} - -static guint -gst_decklink_audio_sink_ringbuffer_delay (GstAudioRingBuffer * rb) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb); - guint ret = 0; - HRESULT res = S_OK; - - if (self->output) { - if ((res = - self->output->output->GetBufferedAudioSampleFrameCount (&ret)) != - S_OK) - ret = 0; - } - - GST_DEBUG_OBJECT (self->sink, "Delay: %u (0x%08lx)", ret, - (unsigned long) res); - - return ret; -} - -#if 0 -static gboolean -in_same_pipeline (GstElement * a, GstElement * b) -{ - GstObject *root = NULL, *tmp; - gboolean ret = FALSE; - - tmp = gst_object_get_parent (GST_OBJECT_CAST (a)); - while (tmp != NULL) { - if (root) - gst_object_unref (root); - root = tmp; - tmp = gst_object_get_parent (root); - } - - ret = root && gst_object_has_ancestor (GST_OBJECT_CAST (b), root); - - if (root) - gst_object_unref (root); - - return ret; -} -#endif - -static gboolean -gst_decklink_audio_sink_ringbuffer_start (GstAudioRingBuffer * rb) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb); - GstElement *videosink = NULL; - gboolean ret = TRUE; - - // Check if there is a video sink for this output too and if it - // is actually in the same pipeline - g_mutex_lock (&self->output->lock); - if (self->output->videosink) - videosink = GST_ELEMENT_CAST (gst_object_ref (self->output->videosink)); - g_mutex_unlock (&self->output->lock); - - if (!videosink) { - GST_ELEMENT_ERROR (self->sink, STREAM, FAILED, - (NULL), ("Audio sink needs a video sink for its operation")); - ret = FALSE; - } - // FIXME: This causes deadlocks sometimes -#if 0 - else if (!in_same_pipeline (GST_ELEMENT_CAST (self->sink), videosink)) { - GST_ELEMENT_ERROR (self->sink, STREAM, FAILED, - (NULL), ("Audio sink and video sink need to be in the same pipeline")); - ret = FALSE; - } -#endif - - if (videosink) - gst_object_unref (videosink); - return ret; -} - -static gboolean -gst_decklink_audio_sink_ringbuffer_pause (GstAudioRingBuffer * rb) -{ - return TRUE; -} - -static gboolean -gst_decklink_audio_sink_ringbuffer_stop (GstAudioRingBuffer * rb) -{ - return TRUE; -} - -static gboolean -gst_decklink_audio_sink_ringbuffer_acquire (GstAudioRingBuffer * rb, - GstAudioRingBufferSpec * spec) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb); - HRESULT ret; - BMDAudioSampleType sample_depth; - - GST_DEBUG_OBJECT (self->sink, "Acquire"); - - if (spec->info.finfo->format == GST_AUDIO_FORMAT_S16LE) { - sample_depth = bmdAudioSampleType16bitInteger; - } else { - sample_depth = bmdAudioSampleType32bitInteger; - } - - ret = self->output->output->EnableAudioOutput (bmdAudioSampleRate48kHz, - sample_depth, spec->info.channels, bmdAudioOutputStreamContinuous); - if (ret != S_OK) { - GST_WARNING_OBJECT (self->sink, "Failed to enable audio output 0x%08lx", - (unsigned long) ret); - return FALSE; - } - - ret = - self->output-> - output->SetAudioCallback (new GStreamerAudioOutputCallback (self)); - if (ret != S_OK) { - GST_WARNING_OBJECT (self->sink, - "Failed to set audio output callback 0x%08lx", (unsigned long) ret); - return FALSE; - } - - spec->segsize = - (spec->latency_time * GST_AUDIO_INFO_RATE (&spec->info) / - G_USEC_PER_SEC) * GST_AUDIO_INFO_BPF (&spec->info); - spec->segtotal = spec->buffer_time / spec->latency_time; - // set latency to one more segment as we need some headroom - spec->seglatency = spec->segtotal + 1; - - rb->size = spec->segtotal * spec->segsize; - rb->memory = (guint8 *) g_malloc0 (rb->size); - - return TRUE; -} - -static gboolean -gst_decklink_audio_sink_ringbuffer_release (GstAudioRingBuffer * rb) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb); - - GST_DEBUG_OBJECT (self->sink, "Release"); - - if (self->output) { - g_mutex_lock (&self->clock_id_lock); - if (self->clock_id) - gst_clock_id_unschedule (self->clock_id); - g_mutex_unlock (&self->clock_id_lock); - - g_mutex_lock (&self->output->lock); - self->output->audio_enabled = FALSE; - if (self->output->start_scheduled_playback && self->output->videosink) - self->output->start_scheduled_playback (self->output->videosink); - g_mutex_unlock (&self->output->lock); - - self->output->output->DisableAudioOutput (); - } - // free the buffer - g_free (rb->memory); - rb->memory = NULL; - - return TRUE; -} - -static gboolean -gst_decklink_audio_sink_ringbuffer_open_device (GstAudioRingBuffer * rb) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb); - - GST_DEBUG_OBJECT (self->sink, "Open device"); - - self->output = - gst_decklink_acquire_nth_output (self->sink->device_number, - GST_ELEMENT_CAST (self), TRUE); - if (!self->output) { - GST_ERROR_OBJECT (self, "Failed to acquire output"); - return FALSE; - } - - g_object_notify (G_OBJECT (self->sink), "hw-serial-number"); - - gst_decklink_output_set_audio_clock (self->output, - GST_AUDIO_BASE_SINK_CAST (self->sink)->provided_clock); - - return TRUE; -} - -static gboolean -gst_decklink_audio_sink_ringbuffer_close_device (GstAudioRingBuffer * rb) -{ - GstDecklinkAudioSinkRingBuffer *self = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (rb); - - GST_DEBUG_OBJECT (self->sink, "Close device"); - - if (self->output) { - gst_decklink_output_set_audio_clock (self->output, NULL); - gst_decklink_release_nth_output (self->sink->device_number, - GST_ELEMENT_CAST (self), TRUE); - self->output = NULL; - } - - return TRUE; -} +#define DEFAULT_DEVICE_NUMBER (0) +#define DEFAULT_ALIGNMENT_THRESHOLD (40 * GST_MSECOND) +#define DEFAULT_DISCONT_WAIT (1 * GST_SECOND) +// Microseconds for audiobasesink compatibility... +#define DEFAULT_BUFFER_TIME (50 * GST_MSECOND / 1000) enum { PROP_0, PROP_DEVICE_NUMBER, - PROP_HW_SERIAL_NUMBER + PROP_HW_SERIAL_NUMBER, + PROP_ALIGNMENT_THRESHOLD, + PROP_DISCONT_WAIT, + PROP_BUFFER_TIME, }; +static void gst_decklink_audio_sink_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec); +static void gst_decklink_audio_sink_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static void gst_decklink_audio_sink_finalize (GObject * object); + +static GstStateChangeReturn +gst_decklink_audio_sink_change_state (GstElement * element, + GstStateChange transition); +static GstClock *gst_decklink_audio_sink_provide_clock (GstElement * element); + +static GstCaps *gst_decklink_audio_sink_get_caps (GstBaseSink * bsink, + GstCaps * filter); +static gboolean gst_decklink_audio_sink_set_caps (GstBaseSink * bsink, + GstCaps * caps); +static GstFlowReturn gst_decklink_audio_sink_render (GstBaseSink * bsink, + GstBuffer * buffer); +static gboolean gst_decklink_audio_sink_open (GstBaseSink * bsink); +static gboolean gst_decklink_audio_sink_close (GstBaseSink * bsink); +static gboolean gst_decklink_audio_sink_stop (GstDecklinkAudioSink * self); +static gboolean gst_decklink_audio_sink_unlock_stop (GstBaseSink * bsink); +static void gst_decklink_audio_sink_get_times (GstBaseSink * bsink, + GstBuffer * buffer, GstClockTime * start, GstClockTime * end); +static gboolean gst_decklink_audio_sink_query (GstBaseSink * bsink, + GstQuery * query); + static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -528,22 +79,9 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", "layout=interleaved") ); -static void gst_decklink_audio_sink_set_property (GObject * object, - guint property_id, const GValue * value, GParamSpec * pspec); -static void gst_decklink_audio_sink_get_property (GObject * object, - guint property_id, GValue * value, GParamSpec * pspec); -static void gst_decklink_audio_sink_finalize (GObject * object); - -static GstStateChangeReturn gst_decklink_audio_sink_change_state (GstElement * - element, GstStateChange transition); -static GstCaps *gst_decklink_audio_sink_get_caps (GstBaseSink * bsink, - GstCaps * filter); -static GstAudioRingBuffer - * gst_decklink_audio_sink_create_ringbuffer (GstAudioBaseSink * absink); - #define parent_class gst_decklink_audio_sink_parent_class G_DEFINE_TYPE (GstDecklinkAudioSink, gst_decklink_audio_sink, - GST_TYPE_AUDIO_BASE_SINK); + GST_TYPE_BASE_SINK); static void gst_decklink_audio_sink_class_init (GstDecklinkAudioSinkClass * klass) @@ -551,8 +89,6 @@ gst_decklink_audio_sink_class_init (GstDecklinkAudioSinkClass * klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass); - GstAudioBaseSinkClass *audiobasesink_class = - GST_AUDIO_BASE_SINK_CLASS (klass); gobject_class->set_property = gst_decklink_audio_sink_set_property; gobject_class->get_property = gst_decklink_audio_sink_get_property; @@ -560,16 +96,26 @@ gst_decklink_audio_sink_class_init (GstDecklinkAudioSinkClass * klass) element_class->change_state = GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_change_state); + element_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_provide_clock); basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_get_caps); - - audiobasesink_class->create_ringbuffer = - GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_create_ringbuffer); + basesink_class->set_caps = + GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_set_caps); + basesink_class->render = GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_render); + // FIXME: These are misnamed in basesink! + basesink_class->start = GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_open); + basesink_class->stop = GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_close); + basesink_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_unlock_stop); + basesink_class->get_times = + GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_get_times); + basesink_class->query = GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_query); g_object_class_install_property (gobject_class, PROP_DEVICE_NUMBER, g_param_spec_int ("device-number", "Device number", - "Output device instance to use", 0, G_MAXINT, 0, + "Output device instance to use", 0, G_MAXINT, DEFAULT_DEVICE_NUMBER, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT))); @@ -578,6 +124,28 @@ gst_decklink_audio_sink_class_init (GstDecklinkAudioSinkClass * klass) "The serial number (hardware ID) of the Decklink card", NULL, (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (gobject_class, PROP_ALIGNMENT_THRESHOLD, + g_param_spec_uint64 ("alignment-threshold", "Alignment Threshold", + "Timestamp alignment threshold in nanoseconds", 0, + G_MAXUINT64 - 1, DEFAULT_ALIGNMENT_THRESHOLD, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY))); + + 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, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY))); + + g_object_class_install_property (gobject_class, PROP_BUFFER_TIME, + g_param_spec_uint64 ("buffer-time", "Buffer Time", + "Size of audio buffer in microseconds, this is the minimum latency that the sink reports", + 0, G_MAXUINT64, DEFAULT_BUFFER_TIME, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY))); + gst_element_class_add_static_pad_template (element_class, &sink_template); gst_element_class_set_static_metadata (element_class, "Decklink Audio Sink", @@ -591,14 +159,13 @@ gst_decklink_audio_sink_class_init (GstDecklinkAudioSinkClass * klass) static void gst_decklink_audio_sink_init (GstDecklinkAudioSink * self) { - self->device_number = 0; + self->device_number = DEFAULT_DEVICE_NUMBER; + self->stream_align = + gst_audio_stream_align_new (48000, DEFAULT_ALIGNMENT_THRESHOLD, + DEFAULT_DISCONT_WAIT); + self->buffer_time = DEFAULT_BUFFER_TIME * 1000; - // 25.000ms latency time seems to be needed at least, - // everything below can cause drop-outs - // TODO: This is probably related to the video mode that - // is selected, but not directly it seems. Choosing the - // duration of a frame does not work. - GST_AUDIO_BASE_SINK_CAST (self)->latency_time = 25000; + gst_base_sink_set_max_lateness (GST_BASE_SINK_CAST (self), 20 * GST_MSECOND); } void @@ -611,6 +178,23 @@ gst_decklink_audio_sink_set_property (GObject * object, guint property_id, case PROP_DEVICE_NUMBER: self->device_number = g_value_get_int (value); break; + case PROP_ALIGNMENT_THRESHOLD: + GST_OBJECT_LOCK (self); + gst_audio_stream_align_set_alignment_threshold (self->stream_align, + g_value_get_uint64 (value)); + GST_OBJECT_UNLOCK (self); + break; + case PROP_DISCONT_WAIT: + GST_OBJECT_LOCK (self); + gst_audio_stream_align_set_discont_wait (self->stream_align, + g_value_get_uint64 (value)); + GST_OBJECT_UNLOCK (self); + break; + case PROP_BUFFER_TIME: + GST_OBJECT_LOCK (self); + self->buffer_time = g_value_get_uint64 (value) * 1000; + GST_OBJECT_UNLOCK (self); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -627,16 +211,29 @@ gst_decklink_audio_sink_get_property (GObject * object, guint property_id, case PROP_DEVICE_NUMBER: g_value_set_int (value, self->device_number); break; - case PROP_HW_SERIAL_NUMBER:{ - GstDecklinkAudioSinkRingBuffer *buf = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (GST_AUDIO_BASE_SINK_CAST - (self)->ringbuffer); - if (buf && buf->output) - g_value_set_string (value, buf->output->hw_serial_number); + case PROP_HW_SERIAL_NUMBER: + if (self->output) + g_value_set_string (value, self->output->hw_serial_number); else g_value_set_string (value, NULL); break; - } + case PROP_ALIGNMENT_THRESHOLD: + GST_OBJECT_LOCK (self); + g_value_set_uint64 (value, + gst_audio_stream_align_get_alignment_threshold (self->stream_align)); + GST_OBJECT_UNLOCK (self); + break; + case PROP_DISCONT_WAIT: + GST_OBJECT_LOCK (self); + g_value_set_uint64 (value, + gst_audio_stream_align_get_discont_wait (self->stream_align)); + GST_OBJECT_UNLOCK (self); + break; + case PROP_BUFFER_TIME: + GST_OBJECT_LOCK (self); + g_value_set_uint64 (value, self->buffer_time / 1000); + GST_OBJECT_UNLOCK (self); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -646,86 +243,106 @@ gst_decklink_audio_sink_get_property (GObject * object, guint property_id, void gst_decklink_audio_sink_finalize (GObject * object) { + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (object); + + if (self->stream_align) { + gst_audio_stream_align_free (self->stream_align); + self->stream_align = NULL; + } + G_OBJECT_CLASS (parent_class)->finalize (object); } -static GstStateChangeReturn -gst_decklink_audio_sink_change_state (GstElement * element, - GstStateChange transition) +static gboolean +gst_decklink_audio_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) { - GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (element); - GstDecklinkAudioSinkRingBuffer *buf = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (GST_AUDIO_BASE_SINK_CAST - (self)->ringbuffer); - GstStateChangeReturn ret; + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (bsink); + HRESULT ret; + BMDAudioSampleType sample_depth; + GstAudioInfo info; - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - if (ret == GST_STATE_CHANGE_FAILURE) - return ret; + GST_DEBUG_OBJECT (self, "Setting caps %" GST_PTR_FORMAT, caps); - switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - g_mutex_lock (&buf->output->lock); - buf->output->audio_enabled = TRUE; - if (buf->output->start_scheduled_playback && buf->output->videosink) - buf->output->start_scheduled_playback (buf->output->videosink); - g_mutex_unlock (&buf->output->lock); - break; - default: - break; + if (!gst_audio_info_from_caps (&info, caps)) + return FALSE; + + if (self->output->audio_enabled + && (self->info.finfo->format != info.finfo->format + || self->info.channels != info.channels)) { + GST_ERROR_OBJECT (self, "Reconfiguration not supported"); + return FALSE; + } else if (self->output->audio_enabled) { + return TRUE; } - return ret; + if (info.finfo->format == GST_AUDIO_FORMAT_S16LE) { + sample_depth = bmdAudioSampleType16bitInteger; + } else { + sample_depth = bmdAudioSampleType32bitInteger; + } + + ret = self->output->output->EnableAudioOutput (bmdAudioSampleRate48kHz, + sample_depth, info.channels, bmdAudioOutputStreamContinuous); + if (ret != S_OK) { + GST_WARNING_OBJECT (self, "Failed to enable audio output 0x%08lx", + (unsigned long) ret); + return FALSE; + } + + self->output->audio_enabled = TRUE; + self->info = info; + + return TRUE; } static GstCaps * gst_decklink_audio_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) { GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (bsink); - GstDecklinkAudioSinkRingBuffer *buf = - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (GST_AUDIO_BASE_SINK_CAST - (self)->ringbuffer); - GstCaps *caps = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); + GstCaps *caps; - if (buf) { - GST_OBJECT_LOCK (buf); - if (buf->output && buf->output->attributes) { - int64_t max_channels = 0; - HRESULT ret; - GstStructure *s; - GValue arr = G_VALUE_INIT; - GValue v = G_VALUE_INIT; + if ((caps = gst_pad_get_current_caps (GST_BASE_SINK_PAD (bsink)))) + return caps; - ret = - buf->output->attributes->GetInt (BMDDeckLinkMaximumAudioChannels, - &max_channels); - /* 2 should always be supported */ - if (ret != S_OK) { - max_channels = 2; - } + caps = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); - caps = gst_caps_make_writable (caps); - s = gst_caps_get_structure (caps, 0); + GST_OBJECT_LOCK (self); + if (self->output && self->output->attributes) { + int64_t max_channels = 0; + HRESULT ret; + GstStructure *s; + GValue arr = G_VALUE_INIT; + GValue v = G_VALUE_INIT; - g_value_init (&arr, GST_TYPE_LIST); - g_value_init (&v, G_TYPE_INT); - if (max_channels >= 16) { - g_value_set_int (&v, 16); - gst_value_list_append_value (&arr, &v); - } - if (max_channels >= 8) { - g_value_set_int (&v, 8); - gst_value_list_append_value (&arr, &v); - } - g_value_set_int (&v, 2); - gst_value_list_append_value (&arr, &v); - - gst_structure_set_value (s, "channels", &arr); - g_value_unset (&v); - g_value_unset (&arr); + ret = + self->output->attributes->GetInt (BMDDeckLinkMaximumAudioChannels, + &max_channels); + /* 2 should always be supported */ + if (ret != S_OK) { + max_channels = 2; } - GST_OBJECT_UNLOCK (buf); + + caps = gst_caps_make_writable (caps); + s = gst_caps_get_structure (caps, 0); + + g_value_init (&arr, GST_TYPE_LIST); + g_value_init (&v, G_TYPE_INT); + if (max_channels >= 16) { + g_value_set_int (&v, 16); + gst_value_list_append_value (&arr, &v); + } + if (max_channels >= 8) { + g_value_set_int (&v, 8); + gst_value_list_append_value (&arr, &v); + } + g_value_set_int (&v, 2); + gst_value_list_append_value (&arr, &v); + + gst_structure_set_value (s, "channels", &arr); + g_value_unset (&v); + g_value_unset (&arr); } + GST_OBJECT_UNLOCK (self); if (filter) { GstCaps *intersection = @@ -737,19 +354,389 @@ gst_decklink_audio_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) return caps; } -static GstAudioRingBuffer * -gst_decklink_audio_sink_create_ringbuffer (GstAudioBaseSink * absink) +static gboolean +gst_decklink_audio_sink_query (GstBaseSink * bsink, GstQuery * query) { - GstAudioRingBuffer *ret; + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK (bsink); + gboolean res = FALSE; - GST_DEBUG_OBJECT (absink, "Creating ringbuffer"); + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + gboolean live, us_live; + GstClockTime min_l, max_l; - ret = - GST_AUDIO_RING_BUFFER_CAST (g_object_new - (GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER, NULL)); + GST_DEBUG_OBJECT (self, "latency query"); - GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (ret)->sink = - (GstDecklinkAudioSink *) gst_object_ref (absink); + /* ask parent first, it will do an upstream query for us. */ + if ((res = + gst_base_sink_query_latency (GST_BASE_SINK_CAST (self), &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) { + GST_OBJECT_LOCK (self); + if (!self->info.rate) { + GST_OBJECT_UNLOCK (self); + + GST_DEBUG_OBJECT (self, + "we are not negotiated, can't report latency yet"); + res = FALSE; + goto done; + } + + base_latency = self->buffer_time * 1000; + GST_OBJECT_UNLOCK (self); + + /* 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 == + GST_CLOCK_TIME_NONE) ? GST_CLOCK_TIME_NONE : (base_latency + + max_l); + + GST_DEBUG_OBJECT (self, + "peer min %" GST_TIME_FORMAT ", our min latency: %" + GST_TIME_FORMAT, GST_TIME_ARGS (min_l), + GST_TIME_ARGS (min_latency)); + GST_DEBUG_OBJECT (self, + "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 (self, + "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; + } + default: + res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query); + break; + } + +done: + return res; +} + +static GstFlowReturn +gst_decklink_audio_sink_render (GstBaseSink * bsink, GstBuffer * buffer) +{ + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (bsink); + GstDecklinkVideoSink *video_sink; + GstFlowReturn flow_ret; + HRESULT ret; + GstClockTime timestamp, duration; + GstClockTime running_time, running_time_duration; + GstClockTime schedule_time, schedule_time_duration; + GstClockTime latency, render_delay; + GstClockTimeDiff ts_offset; + GstMapInfo map_info; + const guint8 *data; + gsize len, written_all; + + GST_DEBUG_OBJECT (self, "Rendering buffer %p", buffer); + + // FIXME: Handle no timestamps + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { + return GST_FLOW_ERROR; + } + + if (GST_BASE_SINK_CAST (self)->flushing) { + return GST_FLOW_FLUSHING; + } + + video_sink = + GST_DECKLINK_VIDEO_SINK (gst_object_ref (self->output->videosink)); + + timestamp = GST_BUFFER_TIMESTAMP (buffer); + duration = GST_BUFFER_DURATION (buffer); + gst_audio_stream_align_process (self->stream_align, + GST_BUFFER_IS_DISCONT (buffer), timestamp, + gst_buffer_get_size (buffer) / self->info.bpf, ×tamp, &duration, + NULL); + + gst_buffer_map (buffer, &map_info, GST_MAP_READ); + data = map_info.data; + len = map_info.size / self->info.bpf; + written_all = 0; + + do { + GstClockTime timestamp_now = + timestamp + gst_util_uint64_scale (written_all, GST_SECOND, + self->info.rate); + guint32 buffered_samples; + GstClockTime buffered_time; + + if (GST_BASE_SINK_CAST (self)->flushing) { + flow_ret = GST_FLOW_FLUSHING; + break; + } + + running_time = + gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, + GST_FORMAT_TIME, timestamp_now); + running_time_duration = + gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, + GST_FORMAT_TIME, timestamp_now + duration) - running_time; + + /* See gst_base_sink_adjust_time() */ + latency = gst_base_sink_get_latency (bsink); + render_delay = gst_base_sink_get_render_delay (bsink); + ts_offset = gst_base_sink_get_ts_offset (bsink); + running_time += latency; + + if (ts_offset < 0) { + ts_offset = -ts_offset; + if ((GstClockTime) ts_offset < running_time) + running_time -= ts_offset; + else + running_time = 0; + } else { + running_time += ts_offset; + } + + if (running_time > render_delay) + running_time -= render_delay; + else + running_time = 0; + + if (self->output-> + output->GetBufferedAudioSampleFrameCount (&buffered_samples) != S_OK) + buffered_samples = 0; + + buffered_time = + gst_util_uint64_scale (buffered_samples, GST_SECOND, self->info.rate); + GST_DEBUG_OBJECT (self, + "Buffered %" GST_TIME_FORMAT " in the driver (%u samples)", + GST_TIME_ARGS (buffered_time), buffered_samples); + + buffered_time /= GST_BASE_SINK_CAST (self)->segment.rate; + // We start waiting once we have more than buffer-time buffered + if (buffered_time > self->buffer_time) { + GstClockReturn clock_ret; + GstClockTime wait_time = running_time; + + GST_DEBUG_OBJECT (self, + "Buffered enough, wait for preroll or the clock or flushing"); + + if (wait_time < self->buffer_time) + wait_time = 0; + else + wait_time -= self->buffer_time; + + flow_ret = + gst_base_sink_do_preroll (GST_BASE_SINK_CAST (self), + GST_MINI_OBJECT_CAST (buffer)); + if (flow_ret != GST_FLOW_OK) + break; + + clock_ret = + gst_base_sink_wait_clock (GST_BASE_SINK_CAST (self), wait_time, NULL); + if (GST_BASE_SINK_CAST (self)->flushing) { + flow_ret = GST_FLOW_FLUSHING; + break; + } + // Rerun the whole loop again + if (clock_ret == GST_CLOCK_UNSCHEDULED) + continue; + } + + schedule_time = running_time; + schedule_time_duration = running_time_duration; + + gst_decklink_video_sink_convert_to_internal_clock (video_sink, + &schedule_time, &schedule_time_duration); + + if (!self->output->started) { + guint32 written = 0; + GST_LOG_OBJECT (self, "Writing audio frame synchronously because PAUSED"); + + ret = + self->output->output->WriteAudioSamplesSync ((void *) data, len, + &written); + if (ret != S_OK) { + GST_ELEMENT_WARNING (self, STREAM, FAILED, + (NULL), ("Failed to write audio frame synchronously: 0x%08lx", + (unsigned long) ret)); + ret = S_OK; + break; + } + len -= written; + data += written * self->info.bpf; + written_all += written; + } else { + guint32 written = 0; + + GST_LOG_OBJECT (self, "Scheduling audio samples at %" GST_TIME_FORMAT + " with duration %" GST_TIME_FORMAT, GST_TIME_ARGS (schedule_time), + GST_TIME_ARGS (schedule_time_duration)); + + ret = self->output->output->ScheduleAudioSamples ((void *) data, len, + schedule_time, GST_SECOND, &written); + if (ret != S_OK) { + bool is_running = true; + self->output->output->IsScheduledPlaybackRunning (&is_running); + + if (is_running && !GST_BASE_SINK_CAST (self)->flushing && self->output->started) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + (NULL), ("Failed to schedule frame: 0x%08lx", + (unsigned long) ret)); + flow_ret = GST_FLOW_ERROR; + break; + } else { + flow_ret = GST_FLOW_FLUSHING; + break; + } + } + + len -= written; + data += written * self->info.bpf; + written_all += written; + } + + flow_ret = GST_FLOW_OK; + } while (len > 0); + + gst_buffer_unmap (buffer, &map_info); + + return flow_ret; +} + +static gboolean +gst_decklink_audio_sink_open (GstBaseSink * bsink) +{ + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (bsink); + + GST_DEBUG_OBJECT (self, "Stopping"); + + self->output = + gst_decklink_acquire_nth_output (self->device_number, + GST_ELEMENT_CAST (self), TRUE); + if (!self->output) { + GST_ERROR_OBJECT (self, "Failed to acquire output"); + return FALSE; + } + + g_object_notify (G_OBJECT (self), "hw-serial-number"); + + return TRUE; +} + +static gboolean +gst_decklink_audio_sink_close (GstBaseSink * bsink) +{ + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (bsink); + + GST_DEBUG_OBJECT (self, "Closing"); + + if (self->output) { + g_mutex_lock (&self->output->lock); + self->output->mode = NULL; + self->output->audio_enabled = FALSE; + if (self->output->start_scheduled_playback && self->output->videosink) + self->output->start_scheduled_playback (self->output->videosink); + g_mutex_unlock (&self->output->lock); + + self->output->output->DisableAudioOutput (); + gst_decklink_release_nth_output (self->device_number, + GST_ELEMENT_CAST (self), TRUE); + self->output = NULL; + } + + return TRUE; +} + +static gboolean +gst_decklink_audio_sink_stop (GstDecklinkAudioSink * self) +{ + GST_DEBUG_OBJECT (self, "Stopping"); + + if (self->output && self->output->audio_enabled) { + g_mutex_lock (&self->output->lock); + self->output->audio_enabled = FALSE; + g_mutex_unlock (&self->output->lock); + + self->output->output->DisableAudioOutput (); + } + + return TRUE; +} + +static gboolean +gst_decklink_audio_sink_unlock_stop (GstBaseSink * bsink) +{ + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK (bsink); + + if (self->output) { + self->output->output->FlushBufferedAudioSamples (); + } + + return TRUE; +} + +static void +gst_decklink_audio_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 GstStateChangeReturn +gst_decklink_audio_sink_change_state (GstElement * element, + GstStateChange transition) +{ + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_OBJECT_LOCK (self); + gst_audio_stream_align_mark_discont (self->stream_align); + GST_OBJECT_UNLOCK (self); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_decklink_audio_sink_stop (self); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ + g_mutex_lock (&self->output->lock); + if (self->output->start_scheduled_playback) + self->output->start_scheduled_playback (self->output->videosink); + g_mutex_unlock (&self->output->lock); + break; + } + default: + break; + } return ret; } + +static GstClock * +gst_decklink_audio_sink_provide_clock (GstElement * element) +{ + GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (element); + + if (!self->output) + return NULL; + + return GST_CLOCK_CAST (gst_object_ref (self->output->clock)); +} diff --git a/sys/decklink/gstdecklinkaudiosink.h b/sys/decklink/gstdecklinkaudiosink.h index 82d20c392e..c04aee61be 100644 --- a/sys/decklink/gstdecklinkaudiosink.h +++ b/sys/decklink/gstdecklinkaudiosink.h @@ -23,6 +23,7 @@ #define __GST_DECKLINK_AUDIO_SINK_H__ #include +#include #include #include "gstdecklink.h" @@ -46,15 +47,20 @@ typedef struct _GstDecklinkAudioSinkClass GstDecklinkAudioSinkClass; struct _GstDecklinkAudioSink { - GstAudioBaseSink parent; + GstBaseSink parent; GstDecklinkModeEnum mode; gint device_number; + GstClockTime buffer_time; + + GstDecklinkOutput *output; + GstAudioInfo info; + GstAudioStreamAlign *stream_align; }; struct _GstDecklinkAudioSinkClass { - GstAudioBaseSinkClass parent_class; + GstBaseSinkClass parent_class; }; GType gst_decklink_audio_sink_get_type (void); diff --git a/sys/decklink/gstdecklinkvideosink.cpp b/sys/decklink/gstdecklinkvideosink.cpp index ac61637ba8..90bd43e4bd 100644 --- a/sys/decklink/gstdecklinkvideosink.cpp +++ b/sys/decklink/gstdecklinkvideosink.cpp @@ -526,17 +526,16 @@ gst_decklink_video_sink_render (GstBaseSink * bsink, GstBuffer * buffer) return GST_FLOW_OK; } -static void -convert_to_internal_clock (GstDecklinkVideoSink * self, +void +gst_decklink_video_sink_convert_to_internal_clock (GstDecklinkVideoSink * self, GstClockTime * timestamp, GstClockTime * duration) { - GstClock *clock, *audio_clock; + GstClock *clock; g_assert (timestamp != NULL); clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); - audio_clock = gst_decklink_output_get_audio_clock (self->output); - if (clock && clock != self->output->clock && clock != audio_clock) { + if (clock && clock != self->output->clock) { GstClockTime internal, external, rate_n, rate_d; gst_clock_get_calibration (self->output->clock, &internal, &external, &rate_n, &rate_d); @@ -729,7 +728,7 @@ gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer) g_free (tc_str); } - convert_to_internal_clock (self, &running_time, &running_time_duration); + gst_decklink_video_sink_convert_to_internal_clock (self, &running_time, &running_time_duration); if (!self->output->started) { GST_LOG_OBJECT (self, "Showing video frame synchronously because PAUSED"); @@ -882,7 +881,7 @@ gst_decklink_video_sink_start_scheduled_playback (GstElement * element) gst_clock_get_internal_time (self->output->clock); self->external_base_time = gst_clock_get_internal_time (clock); - convert_to_internal_clock (self, &start_time, NULL); + gst_decklink_video_sink_convert_to_internal_clock (self, &start_time, NULL); g_mutex_lock (&self->output->lock); // Check if someone else started in the meantime @@ -970,7 +969,7 @@ gst_decklink_video_sink_stop_scheduled_playback (GstDecklinkVideoSink * self) if (start_time == GST_CLOCK_TIME_NONE) start_time = 0; - convert_to_internal_clock (self, &start_time, NULL); + gst_decklink_video_sink_convert_to_internal_clock (self, &start_time, NULL); // The start time is now the running time when we stopped // playback @@ -1030,17 +1029,14 @@ gst_decklink_video_sink_change_state (GstElement * element, self->output->clock, TRUE)); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ - GstClock *clock, *audio_clock; + GstClock *clock; clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); if (clock) { - audio_clock = gst_decklink_output_get_audio_clock (self->output); - if (clock && clock != self->output->clock && clock != audio_clock) { + if (clock && clock != self->output->clock) { gst_clock_set_master (self->output->clock, clock); } gst_object_unref (clock); - if (audio_clock) - gst_object_unref (audio_clock); } else { GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL), ("Need a clock to go to PLAYING")); diff --git a/sys/decklink/gstdecklinkvideosink.h b/sys/decklink/gstdecklinkvideosink.h index 1b60df196f..08eb0a0ecb 100644 --- a/sys/decklink/gstdecklinkvideosink.h +++ b/sys/decklink/gstdecklinkvideosink.h @@ -71,6 +71,9 @@ struct _GstDecklinkVideoSinkClass GType gst_decklink_video_sink_get_type (void); +void gst_decklink_video_sink_convert_to_internal_clock (GstDecklinkVideoSink * self, + GstClockTime * timestamp, GstClockTime * duration); + G_END_DECLS #endif /* __GST_DECKLINK_VIDEO_SINK_H__ */