mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-23 10:11:08 +00:00
decklink: Initial version of the audio/video sink rewrite
This commit is contained in:
parent
f9739870ce
commit
57d46fe9e1
7 changed files with 1468 additions and 51 deletions
|
@ -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 \
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2011 David Schleef <ds@schleef.org>
|
||||
* 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
|
||||
|
@ -23,8 +24,11 @@
|
|||
|
||||
#include <gst/gst.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* GStreamer
|
||||
* Copyright (C) 2011 David Schleef <ds@schleef.org>
|
||||
* 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
|
||||
|
@ -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;
|
||||
|
||||
/* <private> */
|
||||
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;
|
||||
|
||||
/* <private> */
|
||||
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
|
||||
|
|
561
sys/decklink/gstdecklinkaudiosink.cpp
Normal file
561
sys/decklink/gstdecklinkaudiosink.cpp
Normal file
|
@ -0,0 +1,561 @@
|
|||
/* 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;
|
||||
};
|
||||
|
||||
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 <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;
|
||||
|
||||
// 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;
|
||||
}
|
64
sys/decklink/gstdecklinkaudiosink.h
Normal file
64
sys/decklink/gstdecklinkaudiosink.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/* GStreamer
|
||||
*
|
||||
* Copyright (C) 2011 David Schleef <ds@schleef.org>
|
||||
* 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 St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_DECKLINK_AUDIO_SINK_H__
|
||||
#define __GST_DECKLINK_AUDIO_SINK_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/audio.h>
|
||||
#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__ */
|
526
sys/decklink/gstdecklinkvideosink.cpp
Normal file
526
sys/decklink/gstdecklinkvideosink.cpp
Normal file
|
@ -0,0 +1,526 @@
|
|||
/* 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 "gstdecklinkvideosink.h"
|
||||
#include <string.h>
|
||||
|
||||
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 <ds@entropywave.com>, "
|
||||
"Sebastian Dröge <sebastian@centricular.com>");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
69
sys/decklink/gstdecklinkvideosink.h
Normal file
69
sys/decklink/gstdecklinkvideosink.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/* GStreamer
|
||||
*
|
||||
* Copyright (C) 2011 David Schleef <ds@schleef.org>
|
||||
* 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 St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_DECKLINK_VIDEO_SINK_H__
|
||||
#define __GST_DECKLINK_VIDEO_SINK_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/base.h>
|
||||
#include <gst/video/video.h>
|
||||
#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__ */
|
Loading…
Reference in a new issue