| /* GStreamer |
| * Copyright (C) 2011 David Schleef <ds@entropywave.com> |
| * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com> |
| * |
| * 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 Street, Suite 500, |
| * Boston, MA 02110-1335, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstdecklinkaudiosink.h" |
| |
| 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 QueryInterface (REFIID, LPVOID *) |
| { |
| return E_NOINTERFACE; |
| } |
| |
| virtual ULONG AddRef (void) |
| { |
| ULONG ret; |
| |
| g_mutex_lock (&m_mutex); |
| m_refcount++; |
| ret = m_refcount; |
| g_mutex_unlock (&m_mutex); |
| |
| return ret; |
| } |
| |
| virtual ULONG 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 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%08x", written_sum, |
| 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; |
| |
| if (self->output) { |
| if (self->output->output->GetBufferedAudioSampleFrameCount (&ret) != S_OK) |
| ret = 0; |
| } |
| |
| GST_DEBUG_OBJECT (self->sink, "Delay: %u", ret); |
| |
| 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, 2, bmdAudioOutputStreamContinuous); |
| if (ret != S_OK) { |
| GST_WARNING_OBJECT (self->sink, "Failed to enable audio output 0x%08x", |
| 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%08x", 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; |
| } |
| |
| 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; |
| } |
| |
| enum |
| { |
| PROP_0, |
| PROP_DEVICE_NUMBER |
| }; |
| |
| static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS |
| ("audio/x-raw, format={S16LE,S32LE}, channels=2, rate=48000, " |
| "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 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); |
| |
| static void |
| gst_decklink_audio_sink_class_init (GstDecklinkAudioSinkClass * klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| GstElementClass *element_class = GST_ELEMENT_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; |
| gobject_class->finalize = gst_decklink_audio_sink_finalize; |
| |
| element_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_change_state); |
| |
| audiobasesink_class->create_ringbuffer = |
| GST_DEBUG_FUNCPTR (gst_decklink_audio_sink_create_ringbuffer); |
| |
| 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, |
| (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | |
| G_PARAM_CONSTRUCT))); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&sink_template)); |
| |
| gst_element_class_set_static_metadata (element_class, "Decklink Audio Sink", |
| "Audio/Sink", "Decklink Sink", "David Schleef <ds@entropywave.com>, " |
| "Sebastian Dröge <sebastian@centricular.com>"); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_decklink_audio_sink_debug, "decklinkaudiosink", |
| 0, "debug category for decklinkaudiosink element"); |
| } |
| |
| static void |
| gst_decklink_audio_sink_init (GstDecklinkAudioSink * self) |
| { |
| self->device_number = 0; |
| |
| // 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; |
| } |
| |
| void |
| gst_decklink_audio_sink_set_property (GObject * object, guint property_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (object); |
| |
| switch (property_id) { |
| case PROP_DEVICE_NUMBER: |
| self->device_number = g_value_get_int (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
| break; |
| } |
| } |
| |
| void |
| gst_decklink_audio_sink_get_property (GObject * object, guint property_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstDecklinkAudioSink *self = GST_DECKLINK_AUDIO_SINK_CAST (object); |
| |
| switch (property_id) { |
| case PROP_DEVICE_NUMBER: |
| g_value_set_int (value, self->device_number); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
| break; |
| } |
| } |
| |
| void |
| gst_decklink_audio_sink_finalize (GObject * object) |
| { |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static GstStateChangeReturn |
| gst_decklink_audio_sink_change_state (GstElement * element, |
| GstStateChange transition) |
| { |
| 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; |
| |
| 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_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; |
| } |
| |
| return ret; |
| } |
| |
| static GstAudioRingBuffer * |
| gst_decklink_audio_sink_create_ringbuffer (GstAudioBaseSink * absink) |
| { |
| GstAudioRingBuffer *ret; |
| |
| GST_DEBUG_OBJECT (absink, "Creating ringbuffer"); |
| |
| ret = |
| GST_AUDIO_RING_BUFFER_CAST (g_object_new |
| (GST_TYPE_DECKLINK_AUDIO_SINK_RING_BUFFER, NULL)); |
| |
| GST_DECKLINK_AUDIO_SINK_RING_BUFFER_CAST (ret)->sink = |
| (GstDecklinkAudioSink *) gst_object_ref (absink); |
| |
| return ret; |
| } |