diff --git a/sys/decklink/Makefile.am b/sys/decklink/Makefile.am index c96c2b9444..b836ae9d82 100644 --- a/sys/decklink/Makefile.am +++ b/sys/decklink/Makefile.am @@ -8,6 +8,7 @@ libgstdecklink_la_CPPFLAGS = \ $(DECKLINK_CXXFLAGS) libgstdecklink_la_LIBADD = \ $(GST_PLUGINS_BASE_LIBS) \ + -lgstaudio-@GST_API_VERSION@ \ -lgstvideo-@GST_API_VERSION@ \ $(GST_BASE_LIBS) \ $(GST_LIBS) \ @@ -23,10 +24,9 @@ endif libgstdecklink_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) libgstdecklink_la_SOURCES = \ - gstdecklinksink.cpp \ - gstdecklinksrc.cpp \ gstdecklink.cpp \ - capture.cpp + gstdecklinkaudiosink.cpp \ + gstdecklinkvideosink.cpp if DECKLINK_OSX libgstdecklink_la_SOURCES += \ @@ -38,9 +38,8 @@ endif noinst_HEADERS = \ gstdecklink.h \ - gstdecklinksrc.h \ - gstdecklinksink.h \ - capture.h \ + gstdecklinkaudiosink.h \ + gstdecklinkvideosink.h \ linux/DeckLinkAPIConfiguration.h \ linux/DeckLinkAPIDeckControl.h \ linux/DeckLinkAPIDiscovery.h \ diff --git a/sys/decklink/gstdecklink.cpp b/sys/decklink/gstdecklink.cpp index b660374a8e..30e95d3ee1 100644 --- a/sys/decklink/gstdecklink.cpp +++ b/sys/decklink/gstdecklink.cpp @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) 2011 David Schleef + * Copyright (C) 2014 Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -23,8 +24,11 @@ #include #include "gstdecklink.h" -#include "gstdecklinksrc.h" -#include "gstdecklinksink.h" +#include "gstdecklinkaudiosink.h" +#include "gstdecklinkvideosink.h" + +GST_DEBUG_CATEGORY_STATIC (gst_decklink_debug); +#define GST_CAT_DEFAULT gst_decklink_debug GType gst_decklink_mode_get_type (void) @@ -199,56 +203,93 @@ gst_decklink_mode_get_template_caps (void) return caps; } +#define GST_TYPE_DECKLINK_CLOCK \ + (gst_decklink_clock_get_type()) +#define GST_DECKLINK_CLOCK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DECKLINK_CLOCK,GstDecklinkClock)) +#define GST_DECKLINK_CLOCK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DECKLINK_CLOCK,GstDecklinkClockClass)) +#define GST_IS_Decklink_CLOCK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DECKLINK_CLOCK)) +#define GST_IS_Decklink_CLOCK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DECKLINK_CLOCK)) +#define GST_DECKLINK_CLOCK_CAST(obj) \ + ((GstDecklinkClock*)(obj)) + +typedef struct _GstDecklinkClock GstDecklinkClock; +typedef struct _GstDecklinkClockClass GstDecklinkClockClass; + +struct _GstDecklinkClock +{ + GstSystemClock clock; + + IDeckLinkInput *input; + IDeckLinkOutput *output; +}; + +struct _GstDecklinkClockClass +{ + GstSystemClockClass parent_class; +}; + +GType gst_decklink_clock_get_type (void); + typedef struct _Device Device; struct _Device { - IDeckLink *decklink; - IDeckLinkInput *input; - IDeckLinkOutput *output; - IDeckLinkConfiguration *config; + GstDecklinkOutput output; + GstDecklinkInput input; }; +static GOnce devices_once = G_ONCE_INIT; static int n_devices; static Device devices[10]; -static void -init_devices (void) +static gpointer +init_devices (gpointer data) { IDeckLinkIterator *iterator; IDeckLink *decklink = NULL; HRESULT ret; int i; - static gboolean inited = FALSE; - - if (inited) - return; - inited = TRUE; iterator = CreateDeckLinkIteratorInstance (); if (iterator == NULL) { GST_ERROR ("no driver"); - return; + return NULL; } i = 0; ret = iterator->Next (&decklink); while (ret == S_OK) { - devices[i].decklink = decklink; - ret = decklink->QueryInterface (IID_IDeckLinkInput, - (void **) &devices[i].input); + (void **) &devices[i].input.input); if (ret != S_OK) { GST_WARNING ("selected device does not have input interface"); + } else { + devices[i].input.device = decklink; + devices[i].input.clock = + GST_CLOCK_CAST (g_object_new (GST_TYPE_DECKLINK_CLOCK, "name", + "GstDecklinkInputClock", NULL)); + GST_DECKLINK_CLOCK_CAST (devices[i].input.clock)->input = + devices[i].input.input; } ret = decklink->QueryInterface (IID_IDeckLinkOutput, - (void **) &devices[i].output); + (void **) &devices[i].output.output); if (ret != S_OK) { GST_WARNING ("selected device does not have output interface"); + } else { + devices[i].output.device = decklink; + devices[i].output.clock = + GST_CLOCK_CAST (g_object_new (GST_TYPE_DECKLINK_CLOCK, "name", + "GstDecklinkOutputClock", NULL)); + GST_DECKLINK_CLOCK_CAST (devices[i].output.clock)->output = + devices[i].output.output; } ret = decklink->QueryInterface (IID_IDeckLinkConfiguration, - (void **) &devices[i].config); + (void **) &devices[i].input.config); if (ret != S_OK) { GST_WARNING ("selected device does not have config interface"); } @@ -265,46 +306,166 @@ init_devices (void) n_devices = i; iterator->Release (); + + return NULL; } -IDeckLink * -gst_decklink_get_nth_device (int n) +GstDecklinkOutput * +gst_decklink_acquire_nth_output (gint n, GstElement * sink, gboolean is_audio) { - init_devices (); - return devices[n].decklink; + GstDecklinkOutput *output; + + g_once (&devices_once, init_devices, NULL); + + if (n >= n_devices) + return NULL; + + output = &devices[n].output; + if (!output->output) { + GST_ERROR ("Device %d has no output", n); + return NULL; + } + + g_mutex_lock (&output->lock); + if (is_audio && !output->audiosink) { + output->audiosink = GST_ELEMENT_CAST (gst_object_ref (sink)); + g_mutex_unlock (&output->lock); + return output; + } else if (!output->videosink) { + output->videosink = GST_ELEMENT_CAST (gst_object_ref (sink)); + g_mutex_unlock (&output->lock); + return output; + } + g_mutex_unlock (&output->lock); + + GST_ERROR ("Output device %d (audio: %d) in use already", n, is_audio); + return NULL; } -IDeckLinkInput * -gst_decklink_get_nth_input (int n) +void +gst_decklink_release_nth_output (gint n, GstElement * sink, gboolean is_audio) { - init_devices (); - return devices[n].input; + GstDecklinkOutput *output; + + if (n >= n_devices) + return; + + output = &devices[n].output; + g_assert (output->output); + + g_mutex_lock (&output->lock); + if (is_audio) { + g_assert (output->audiosink == sink); + gst_object_unref (sink); + output->audiosink = NULL; + } else { + g_assert (output->videosink == sink); + gst_object_unref (sink); + output->videosink = NULL; + } + g_mutex_unlock (&output->lock); } -IDeckLinkOutput * -gst_decklink_get_nth_output (int n) +void +gst_decklink_output_set_audio_clock (GstDecklinkOutput * output, + GstClock * clock) { - init_devices (); - return devices[n].output; + 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); } -IDeckLinkConfiguration * -gst_decklink_get_nth_config (int n) + +GstClock * +gst_decklink_output_get_audio_clock (GstDecklinkOutput * output) { - init_devices (); - return devices[n].config; + 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; +} + +G_DEFINE_TYPE (GstDecklinkClock, gst_decklink_clock, GST_TYPE_SYSTEM_CLOCK); + +static void gst_decklink_clock_class_init (GstDecklinkClockClass * klass); +static void gst_decklink_clock_init (GstDecklinkClock * clock); + +static GstClockTime gst_decklink_clock_get_internal_time (GstClock * clock); + +static void +gst_decklink_clock_class_init (GstDecklinkClockClass * klass) +{ + GstClockClass *clock_class = (GstClockClass *) klass; + + clock_class->get_internal_time = gst_decklink_clock_get_internal_time; +} + +static void +gst_decklink_clock_init (GstDecklinkClock * clock) +{ + GST_OBJECT_FLAG_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER); +} + +GstDecklinkClock * +gst_decklink_clock_new (const gchar * name) +{ + GstDecklinkClock *self = + GST_DECKLINK_CLOCK (g_object_new (GST_TYPE_DECKLINK_CLOCK, "name", name, + "clock-type", GST_CLOCK_TYPE_OTHER, NULL)); + + return self; +} + +static GstClockTime +gst_decklink_clock_get_internal_time (GstClock * clock) +{ + GstDecklinkClock *self = GST_DECKLINK_CLOCK (clock); + GstClockTime result; + BMDTimeValue time; + HRESULT ret; + + GST_OBJECT_LOCK (clock); + if (self->input != NULL) { + ret = + self->input->GetHardwareReferenceClock (GST_SECOND, &time, NULL, NULL); + if (ret == S_OK && time >= 0) + result = time; + else + result = GST_CLOCK_TIME_NONE; + } else if (self->output != NULL) { + ret = + self->output->GetHardwareReferenceClock (GST_SECOND, &time, NULL, NULL); + if (ret == S_OK && time >= 0) + result = time; + else + result = GST_CLOCK_TIME_NONE; + } else { + result = GST_CLOCK_TIME_NONE; + } + GST_OBJECT_UNLOCK (clock); + GST_LOG_OBJECT (clock, "result %" GST_TIME_FORMAT, GST_TIME_ARGS (result)); + + return result; } static gboolean plugin_init (GstPlugin * plugin) { + GST_DEBUG_CATEGORY_INIT (gst_decklink_debug, "decklink", 0, + "debug category for decklink plugin"); - gst_element_register (plugin, "decklinksrc", GST_RANK_NONE, - gst_decklink_src_get_type ()); - - gst_element_register (plugin, "decklinksink", GST_RANK_NONE, - gst_decklink_sink_get_type ()); - + gst_element_register (plugin, "decklinkaudiosink", GST_RANK_NONE, + GST_TYPE_DECKLINK_AUDIO_SINK); + gst_element_register (plugin, "decklinkvideosink", GST_RANK_NONE, + GST_TYPE_DECKLINK_VIDEO_SINK); return TRUE; } diff --git a/sys/decklink/gstdecklink.h b/sys/decklink/gstdecklink.h index ea892a1ccc..bffac2938c 100644 --- a/sys/decklink/gstdecklink.h +++ b/sys/decklink/gstdecklink.h @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) 2011 David Schleef + * Copyright (C) 2014 Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -111,9 +112,45 @@ const GstDecklinkMode * gst_decklink_get_mode (GstDecklinkModeEnum e); GstCaps * gst_decklink_mode_get_caps (GstDecklinkModeEnum e); GstCaps * gst_decklink_mode_get_template_caps (void); -IDeckLink * gst_decklink_get_nth_device (int n); -IDeckLinkInput * gst_decklink_get_nth_input (int n); -IDeckLinkOutput * gst_decklink_get_nth_output (int n); -IDeckLinkConfiguration * gst_decklink_get_nth_config (int n); +typedef struct _GstDecklinkOutput GstDecklinkOutput; +struct _GstDecklinkOutput { + IDeckLink *device; + IDeckLinkOutput *output; + GstClock *clock; + + /* Everything below protected by mutex */ + GMutex lock; + + /* Set by the audio sink */ + GstClock *audio_clock; + + /* */ + GstElement *audiosink; + GstElement *videosink; +}; + +typedef struct _GstDecklinkInput GstDecklinkInput; +struct _GstDecklinkInput { + IDeckLink *device; + IDeckLinkInput *input; + IDeckLinkConfiguration *config; + GstClock *clock; + + /* Everything below protected by mutex */ + GMutex lock; + + /* Set by the audio source */ + GstClock *audio_clock; + + /* */ + GstElement *audiosrc; + GstElement *videosrc; +}; + +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); #endif diff --git a/sys/decklink/gstdecklinkaudiosink.cpp b/sys/decklink/gstdecklinkaudiosink.cpp new file mode 100644 index 0000000000..683a17d7a3 --- /dev/null +++ b/sys/decklink/gstdecklinkaudiosink.cpp @@ -0,0 +1,561 @@ +/* GStreamer + * Copyright (C) 2011 David Schleef + * Copyright (C) 2014 Sebastian Dröge + * + * 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; +}; + +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) +{ +} + +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_OBJECT_CLASS (ringbuffer_parent_class)->finalize (object); +} + +class GStreamerAudioOutputCallback:public IDeckLinkAudioOutputCallback +{ +public: + GStreamerAudioOutputCallback (GstDecklinkAudioSinkRingBuffer * ringbuffer) + { + 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; + + GST_LOG_OBJECT (m_ringbuffer->sink, "Writing audio samples (preroll: %d)", + preroll); + + 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; +} + +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; + +} + +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; + } 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; + } + + 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) + 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_MODE, + 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 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; + + 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 , " + "Sebastian Dröge "); + + 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; + + // 25ms latency time seems to be needed at least, + // everything below can cause drop-outs + 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 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; +} diff --git a/sys/decklink/gstdecklinkaudiosink.h b/sys/decklink/gstdecklinkaudiosink.h new file mode 100644 index 0000000000..82d20c392e --- /dev/null +++ b/sys/decklink/gstdecklinkaudiosink.h @@ -0,0 +1,64 @@ +/* GStreamer + * + * Copyright (C) 2011 David Schleef + * Copyright (C) 2014 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_DECKLINK_AUDIO_SINK_H__ +#define __GST_DECKLINK_AUDIO_SINK_H__ + +#include +#include +#include "gstdecklink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_DECKLINK_AUDIO_SINK \ + (gst_decklink_audio_sink_get_type()) +#define GST_DECKLINK_AUDIO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_DECKLINK_AUDIO_SINK, GstDecklinkAudioSink)) +#define GST_DECKLINK_AUDIO_SINK_CAST(obj) \ + ((GstDecklinkAudioSink*)obj) +#define GST_DECKLINK_AUDIO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_DECKLINK_AUDIO_SINK, GstDecklinkAudioSinkClass)) +#define GST_IS_DECKLINK_AUDIO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_DECKLINK_AUDIO_SINK)) +#define GST_IS_DECKLINK_AUDIO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_DECKLINK_AUDIO_SINK)) + +typedef struct _GstDecklinkAudioSink GstDecklinkAudioSink; +typedef struct _GstDecklinkAudioSinkClass GstDecklinkAudioSinkClass; + +struct _GstDecklinkAudioSink +{ + GstAudioBaseSink parent; + + GstDecklinkModeEnum mode; + gint device_number; +}; + +struct _GstDecklinkAudioSinkClass +{ + GstAudioBaseSinkClass parent_class; +}; + +GType gst_decklink_audio_sink_get_type (void); + +G_END_DECLS + +#endif /* __GST_DECKLINK_AUDIO_SINK_H__ */ diff --git a/sys/decklink/gstdecklinkvideosink.cpp b/sys/decklink/gstdecklinkvideosink.cpp new file mode 100644 index 0000000000..c627b568b2 --- /dev/null +++ b/sys/decklink/gstdecklinkvideosink.cpp @@ -0,0 +1,526 @@ +/* GStreamer + * Copyright (C) 2011 David Schleef + * Copyright (C) 2014 Sebastian Dröge + * + * 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 "gstdecklinkvideosink.h" +#include + +GST_DEBUG_CATEGORY_STATIC (gst_decklink_video_sink_debug); +#define GST_CAT_DEFAULT gst_decklink_video_sink_debug + +enum +{ + PROP_0, + PROP_MODE, + PROP_DEVICE_NUMBER +}; + +static void gst_decklink_video_sink_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec); +static void gst_decklink_video_sink_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static void gst_decklink_video_sink_finalize (GObject * object); + +static GstStateChangeReturn +gst_decklink_video_sink_change_state (GstElement * element, + GstStateChange transition); +static GstClock *gst_decklink_video_sink_provide_clock (GstElement * element); + +static GstCaps *gst_decklink_video_sink_get_caps (GstBaseSink * bsink, + GstCaps * filter); +static GstFlowReturn gst_decklink_video_sink_prepare (GstBaseSink * bsink, + GstBuffer * buffer); +static GstFlowReturn gst_decklink_video_sink_render (GstBaseSink * bsink, + GstBuffer * buffer); +static gboolean gst_decklink_video_sink_open (GstBaseSink * bsink); +static gboolean gst_decklink_video_sink_close (GstBaseSink * bsink); +static gboolean gst_decklink_video_sink_propose_allocation (GstBaseSink * bsink, + GstQuery * query); + +#define parent_class gst_decklink_video_sink_parent_class +G_DEFINE_TYPE (GstDecklinkVideoSink, gst_decklink_video_sink, + GST_TYPE_BASE_SINK); + +static void +gst_decklink_video_sink_class_init (GstDecklinkVideoSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass); + GstCaps *templ_caps; + + gobject_class->set_property = gst_decklink_video_sink_set_property; + gobject_class->get_property = gst_decklink_video_sink_get_property; + gobject_class->finalize = gst_decklink_video_sink_finalize; + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_decklink_video_sink_change_state); + element_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_decklink_video_sink_provide_clock); + + basesink_class->get_caps = + GST_DEBUG_FUNCPTR (gst_decklink_video_sink_get_caps); + basesink_class->prepare = GST_DEBUG_FUNCPTR (gst_decklink_video_sink_prepare); + basesink_class->render = GST_DEBUG_FUNCPTR (gst_decklink_video_sink_render); + // FIXME: These are misnamed in basesink! + basesink_class->start = GST_DEBUG_FUNCPTR (gst_decklink_video_sink_open); + basesink_class->stop = GST_DEBUG_FUNCPTR (gst_decklink_video_sink_close); + basesink_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_decklink_video_sink_propose_allocation); + + g_object_class_install_property (gobject_class, PROP_MODE, + g_param_spec_enum ("mode", "Playback Mode", + "Video Mode to use for playback", + GST_TYPE_DECKLINK_MODE, GST_DECKLINK_MODE_NTSC, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT))); + + 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))); + + templ_caps = gst_decklink_mode_get_template_caps (); + gst_element_class_add_pad_template (element_class, + gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, templ_caps)); + gst_caps_unref (templ_caps); + + gst_element_class_set_static_metadata (element_class, "Decklink Video Sink", + "Video/Sink", "Decklink Sink", "David Schleef , " + "Sebastian Dröge "); + + GST_DEBUG_CATEGORY_INIT (gst_decklink_video_sink_debug, "decklinkvideosink", + 0, "debug category for decklinkvideosink element"); +} + +static void +gst_decklink_video_sink_init (GstDecklinkVideoSink * self) +{ + self->mode = GST_DECKLINK_MODE_NTSC; + self->device_number = 0; +} + +void +gst_decklink_video_sink_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (object); + + switch (property_id) { + case PROP_MODE: + self->mode = (GstDecklinkModeEnum) g_value_get_enum (value); + break; + 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_video_sink_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (object); + + switch (property_id) { + case PROP_MODE: + g_value_set_enum (value, self->mode); + break; + 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_video_sink_finalize (GObject * object) +{ + //GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (object); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstCaps * +gst_decklink_video_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); + GstCaps *mode_caps, *caps; + + mode_caps = gst_decklink_mode_get_caps (self->mode); + if (filter) { + caps = + gst_caps_intersect_full (filter, mode_caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (mode_caps); + } else { + caps = mode_caps; + } + + return caps; +} + +static GstFlowReturn +gst_decklink_video_sink_render (GstBaseSink * bsink, GstBuffer * buffer) +{ + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); + GstVideoFrame vframe; + IDeckLinkMutableVideoFrame *frame; + guint8 *outdata, *indata; + GstFlowReturn flow_ret; + HRESULT ret; + GstClockTime timestamp, duration, running_time, running_time_duration; + gint i; + GstClock *clock = NULL, *audio_clock = NULL; + + GST_DEBUG_OBJECT (self, "Preparing buffer %p", buffer); + + // FIXME: Handle no timestamps + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { + return GST_FLOW_ERROR; + } + + ret = self->output->output->CreateVideoFrame (self->info.width, + self->info.height, self->info.stride[0], bmdFormat8BitYUV, + bmdFrameFlagDefault, &frame); + if (ret != S_OK) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + (NULL), ("Failed to create video frame: 0x%08x", ret)); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_map (&vframe, &self->info, buffer, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Failed to map video frame"); + flow_ret = GST_FLOW_ERROR; + goto out; + } + + frame->GetBytes ((void **) &outdata); + indata = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0); + for (i = 0; i < self->info.height; i++) { + memcpy (outdata, indata, GST_VIDEO_FRAME_WIDTH (&vframe) * 2); + indata += GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0); + outdata += frame->GetRowBytes (); + } + gst_video_frame_unmap (&vframe); + + timestamp = GST_BUFFER_TIMESTAMP (buffer); + duration = GST_BUFFER_DURATION (buffer); + if (duration == GST_CLOCK_TIME_NONE) { + duration = + gst_util_uint64_scale_int (GST_SECOND, self->info.fps_d, + self->info.fps_n); + } + running_time = + gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, + GST_FORMAT_TIME, timestamp); + running_time_duration = + gst_segment_to_running_time (&GST_BASE_SINK_CAST (self)->segment, + GST_FORMAT_TIME, timestamp + duration) - running_time; + + 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) { + // TODO: Adjust time if pipeline clock is not our clock + //g_assert_not_reached (); + } + + GST_LOG_OBJECT (self, "Scheduling video frame at %" GST_TIME_FORMAT + " with duration %" GST_TIME_FORMAT, GST_TIME_ARGS (running_time), + GST_TIME_ARGS (running_time_duration)); + + ret = self->output->output->ScheduleVideoFrame (frame, + running_time, running_time_duration, GST_SECOND); + if (ret != S_OK) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + (NULL), ("Failed to schedule frame: 0x%08x", ret)); + flow_ret = GST_FLOW_ERROR; + goto out; + } + + flow_ret = GST_FLOW_OK; + +out: + + if (clock) + gst_object_unref (clock); + if (audio_clock) + gst_object_unref (audio_clock); + + frame->Release (); + + return flow_ret; +} + +static gboolean +gst_decklink_video_sink_open (GstBaseSink * bsink) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); + const GstDecklinkMode *mode; + GstCaps *caps; + HRESULT ret; + + GST_DEBUG_OBJECT (self, "Starting"); + + self->output = + gst_decklink_acquire_nth_output (self->device_number, + GST_ELEMENT_CAST (self), FALSE); + if (!self->output) { + GST_ERROR_OBJECT (self, "Failed to acquire output"); + return FALSE; + } + + mode = gst_decklink_get_mode (self->mode); + g_assert (mode != NULL); + + ret = self->output->output->EnableVideoOutput (mode->mode, + bmdVideoOutputFlagDefault); + if (ret != S_OK) { + GST_WARNING_OBJECT (self, "Failed to enable video output"); + gst_decklink_release_nth_output (self->device_number, + GST_ELEMENT_CAST (self), FALSE); + return FALSE; + } + + caps = gst_decklink_mode_get_caps (self->mode); + gst_video_info_from_caps (&self->info, caps); + gst_caps_unref (caps); + + return TRUE; +} + +static gboolean +gst_decklink_video_sink_close (GstBaseSink * bsink) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink); + + GST_DEBUG_OBJECT (self, "Stopping"); + + if (self->output) { + self->output->output->DisableVideoOutput (); + gst_decklink_release_nth_output (self->device_number, + GST_ELEMENT_CAST (self), FALSE); + self->output = NULL; + } + + return TRUE; +} + +static GstStateChangeReturn +gst_decklink_video_sink_change_state (GstElement * element, + GstStateChange transition) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); + GstStateChangeReturn ret; + + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_element_post_message (element, + gst_message_new_clock_provide (GST_OBJECT_CAST (element), + self->output->clock, TRUE)); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ + GstClock *clock, *audio_clock; + + 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) { + gst_clock_set_master (self->output->clock, clock); + } + if (clock) + gst_object_unref (clock); + if (audio_clock) + gst_object_unref (audio_clock); + + 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_element_post_message (element, + gst_message_new_clock_lost (GST_OBJECT_CAST (element), + self->output->clock)); + gst_clock_set_master (self->output->clock, NULL); + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED:{ + GstClockTime start_time = gst_element_get_start_time (element); + HRESULT res; + GstClock *clock, *audio_clock; + + // FIXME: This will probably not work + if (start_time == GST_CLOCK_TIME_NONE) + start_time = 0; + + 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) { + // TODO: Adjust time if pipeline clock is not our clock + //g_assert_not_reached (); + } + if (clock) + gst_object_unref (clock); + if (audio_clock) + gst_object_unref (audio_clock); + + // The start time is now the running time when we stopped + // playback + + GST_DEBUG_OBJECT (self, + "Stopping scheduled playback at %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time)); + res = + self->output->output->StopScheduledPlayback (start_time, 0, + GST_SECOND); + if (res != S_OK) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + (NULL), ("Failed to stop scheduled playback: 0x%08x", res)); + ret = GST_STATE_CHANGE_FAILURE; + } + break; + } + case GST_STATE_CHANGE_PAUSED_TO_PLAYING:{ + GstClockTime start_time = gst_element_get_start_time (element); + HRESULT res; + GstClock *clock, *audio_clock; + + // FIXME: This will probably not work + if (start_time == GST_CLOCK_TIME_NONE) + start_time = 0; + + 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) { + // TODO: Adjust time if pipeline clock is not our clock + //g_assert_not_reached (); + } + if (clock) + gst_object_unref (clock); + if (audio_clock) + gst_object_unref (audio_clock); + + GST_DEBUG_OBJECT (self, + "Starting scheduled playback at %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time)); + + res = + self->output->output->StartScheduledPlayback (start_time, + GST_SECOND, 1.0); + if (res != S_OK) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + (NULL), ("Failed to start scheduled playback: 0x%08x", res)); + ret = GST_STATE_CHANGE_FAILURE; + } + break; + } + default: + break; + } + + return ret; +} + +static GstClock * +gst_decklink_video_sink_provide_clock (GstElement * element) +{ + GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (element); + + if (!self->output) + return NULL; + + return GST_CLOCK_CAST (gst_object_ref (self->output->clock)); +} + +static gboolean +gst_decklink_video_sink_propose_allocation (GstBaseSink * bsink, + GstQuery * query) +{ + GstCaps *caps; + GstVideoInfo info; + GstBufferPool *pool; + guint size; + + gst_query_parse_allocation (query, &caps, NULL); + + if (caps == NULL) + return FALSE; + + if (!gst_video_info_from_caps (&info, caps)) + return FALSE; + + size = GST_VIDEO_INFO_SIZE (&info); + + if (gst_query_get_n_allocation_pools (query) == 0) { + GstStructure *structure; + GstAllocator *allocator = NULL; + GstAllocationParams params = { (GstMemoryFlags) 0, 15, 0, 0 }; + + if (gst_query_get_n_allocation_params (query) > 0) + gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); + else + gst_query_add_allocation_param (query, allocator, ¶ms); + + pool = gst_video_buffer_pool_new (); + + structure = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_set_params (structure, caps, size, 0, 0); + gst_buffer_pool_config_set_allocator (structure, allocator, ¶ms); + + if (allocator) + gst_object_unref (allocator); + + if (!gst_buffer_pool_set_config (pool, structure)) + goto config_failed; + + gst_query_add_allocation_pool (query, pool, size, 0, 0); + gst_object_unref (pool); + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + } + + return TRUE; + // ERRORS +config_failed: + { + GST_ERROR_OBJECT (bsink, "failed to set config"); + gst_object_unref (pool); + return FALSE; + } +} diff --git a/sys/decklink/gstdecklinkvideosink.h b/sys/decklink/gstdecklinkvideosink.h new file mode 100644 index 0000000000..01abfd7f79 --- /dev/null +++ b/sys/decklink/gstdecklinkvideosink.h @@ -0,0 +1,69 @@ +/* GStreamer + * + * Copyright (C) 2011 David Schleef + * Copyright (C) 2014 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_DECKLINK_VIDEO_SINK_H__ +#define __GST_DECKLINK_VIDEO_SINK_H__ + +#include +#include +#include +#include "gstdecklink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_DECKLINK_VIDEO_SINK \ + (gst_decklink_video_sink_get_type()) +#define GST_DECKLINK_VIDEO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_DECKLINK_VIDEO_SINK, GstDecklinkVideoSink)) +#define GST_DECKLINK_VIDEO_SINK_CAST(obj) \ + ((GstDecklinkVideoSink*)obj) +#define GST_DECKLINK_VIDEO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_DECKLINK_VIDEO_SINK, GstDecklinkVideoSinkClass)) +#define GST_IS_DECKLINK_VIDEO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_DECKLINK_VIDEO_SINK)) +#define GST_IS_DECKLINK_VIDEO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_DECKLINK_VIDEO_SINK)) + +typedef struct _GstDecklinkVideoSink GstDecklinkVideoSink; +typedef struct _GstDecklinkVideoSinkClass GstDecklinkVideoSinkClass; + +struct _GstDecklinkVideoSink +{ + GstBaseSink parent; + + GstDecklinkModeEnum mode; + gint device_number; + + GstVideoInfo info; + + GstDecklinkOutput *output; +}; + +struct _GstDecklinkVideoSinkClass +{ + GstBaseSinkClass parent_class; +}; + +GType gst_decklink_video_sink_get_type (void); + +G_END_DECLS + +#endif /* __GST_DECKLINK_VIDEO_SINK_H__ */