mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-10-22 02:13:46 +00:00
820894a871
Otherwise we're going to starve other elements if the decklink clock is slower than the pipeline clock, or starts much later. Of course this will still cause problems if the decklink clock and ours are completely out of sync, or running at a very different rate. But this at least works better now.
821 lines
24 KiB
C++
821 lines
24 KiB
C++
/* GStreamer
|
|
* Copyright (C) 2011 David Schleef <ds@entropywave.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.
|
|
*/
|
|
/**
|
|
* SECTION:element-gstdecklinksink
|
|
*
|
|
* The decklinksink element is a sink element for BlackMagic DeckLink
|
|
* cards.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 -v videotestsrc ! decklinksink
|
|
* ]|
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/video/video.h>
|
|
#include "gstdecklink.h"
|
|
#include "gstdecklinksink.h"
|
|
#include <string.h>
|
|
|
|
/* FIXME:
|
|
* - handle ALLOCATION query
|
|
* - provide buffer pool with suitable strides/alignment for video
|
|
* - handle video meta
|
|
*/
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_decklink_sink_debug_category);
|
|
#define GST_CAT_DEFAULT gst_decklink_sink_debug_category
|
|
|
|
static void gst_decklink_sink_set_property (GObject * object,
|
|
guint property_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_decklink_sink_get_property (GObject * object,
|
|
guint property_id, GValue * value, GParamSpec * pspec);
|
|
static void gst_decklink_sink_finalize (GObject * object);
|
|
|
|
static GstStateChangeReturn
|
|
gst_decklink_sink_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
|
|
static GstFlowReturn gst_decklink_sink_videosink_chain (GstPad * pad,
|
|
GstObject * parent, GstBuffer * buffer);
|
|
static gboolean gst_decklink_sink_videosink_event (GstPad * pad,
|
|
GstObject * parent, GstEvent * event);
|
|
static gboolean gst_decklink_sink_videosink_query (GstPad * pad,
|
|
GstObject * parent, GstQuery * query);
|
|
|
|
static GstFlowReturn gst_decklink_sink_audiosink_chain (GstPad * pad,
|
|
GstObject * parent, GstBuffer * buffer);
|
|
static gboolean gst_decklink_sink_audiosink_event (GstPad * pad,
|
|
GstObject * parent, GstEvent * event);
|
|
static gboolean gst_decklink_sink_audiosink_query (GstPad * pad,
|
|
GstObject * parent, GstQuery * query);
|
|
|
|
#ifdef _MSC_VER
|
|
/* COM initialization/uninitialization thread */
|
|
static void gst_decklink_sink_com_thread (GstDecklinkSink * sink);
|
|
#endif /* _MSC_VER */
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_MODE,
|
|
PROP_DEVICE_NUMBER
|
|
};
|
|
|
|
/* pad templates */
|
|
|
|
/* the video sink pad template is created on the fly */
|
|
|
|
static GstStaticPadTemplate gst_decklink_sink_audiosink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("audiosink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-raw, format=S16LE, channels=2, rate=48000, "
|
|
"layout=interleaved")
|
|
);
|
|
|
|
#define parent_class gst_decklink_sink_parent_class
|
|
G_DEFINE_TYPE (GstDecklinkSink, gst_decklink_sink, GST_TYPE_ELEMENT);
|
|
|
|
static void
|
|
gst_decklink_sink_class_init (GstDecklinkSinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
|
|
gobject_class->set_property = gst_decklink_sink_set_property;
|
|
gobject_class->get_property = gst_decklink_sink_get_property;
|
|
gobject_class->finalize = gst_decklink_sink_finalize;
|
|
|
|
element_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_decklink_sink_change_state);
|
|
|
|
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)));
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_pad_template_new ("videosink", GST_PAD_SINK, GST_PAD_ALWAYS,
|
|
gst_decklink_mode_get_template_caps ()));
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_decklink_sink_audiosink_template));
|
|
|
|
gst_element_class_set_static_metadata (element_class, "Decklink Sink",
|
|
"Video/Sink", "Decklink Sink", "David Schleef <ds@entropywave.com>");
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_decklink_sink_debug_category, "decklinksink", 0,
|
|
"debug category for decklinksink element");
|
|
}
|
|
|
|
static void
|
|
gst_decklink_sink_init (GstDecklinkSink * decklinksink)
|
|
{
|
|
GstDecklinkSinkClass *decklinksink_class;
|
|
|
|
decklinksink_class = GST_DECKLINK_SINK_GET_CLASS (decklinksink);
|
|
|
|
decklinksink->videosinkpad =
|
|
gst_pad_new_from_template (gst_element_class_get_pad_template
|
|
(GST_ELEMENT_CLASS (decklinksink_class), "videosink"), "videosink");
|
|
gst_pad_set_chain_function (decklinksink->videosinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_decklink_sink_videosink_chain));
|
|
gst_pad_set_event_function (decklinksink->videosinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_decklink_sink_videosink_event));
|
|
gst_pad_set_query_function (decklinksink->videosinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_decklink_sink_videosink_query));
|
|
gst_element_add_pad (GST_ELEMENT (decklinksink), decklinksink->videosinkpad);
|
|
|
|
decklinksink->audiosinkpad =
|
|
gst_pad_new_from_static_template (&gst_decklink_sink_audiosink_template,
|
|
"audiosink");
|
|
gst_pad_set_chain_function (decklinksink->audiosinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_decklink_sink_audiosink_chain));
|
|
gst_pad_set_event_function (decklinksink->audiosinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_decklink_sink_audiosink_event));
|
|
gst_pad_set_query_function (decklinksink->audiosinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_decklink_sink_audiosink_query));
|
|
gst_element_add_pad (GST_ELEMENT (decklinksink), decklinksink->audiosinkpad);
|
|
|
|
GST_OBJECT_FLAG_SET (decklinksink, GST_ELEMENT_FLAG_SINK);
|
|
|
|
g_cond_init (&decklinksink->cond);
|
|
g_mutex_init (&decklinksink->mutex);
|
|
g_mutex_init (&decklinksink->audio_mutex);
|
|
g_cond_init (&decklinksink->audio_cond);
|
|
|
|
decklinksink->mode = GST_DECKLINK_MODE_NTSC;
|
|
decklinksink->device_number = 0;
|
|
|
|
#ifdef _MSC_VER
|
|
g_mutex_init (&decklinksink->com_init_lock);
|
|
g_mutex_init (&decklinksink->com_deinit_lock);
|
|
g_cond_init (&decklinksink->com_initialized);
|
|
g_cond_init (&decklinksink->com_uninitialize);
|
|
g_cond_init (&decklinksink->com_uninitialized);
|
|
|
|
g_mutex_lock (&decklinksink->com_init_lock);
|
|
|
|
/* create the COM initialization thread */
|
|
g_thread_create ((GThreadFunc) gst_decklink_sink_com_thread,
|
|
decklinksink, FALSE, NULL);
|
|
|
|
/* wait until the COM thread signals that COM has been initialized */
|
|
g_cond_wait (&decklinksink->com_initialized, &decklinksink->com_init_lock);
|
|
g_mutex_unlock (&decklinksink->com_init_lock);
|
|
#endif /* _MSC_VER */
|
|
}
|
|
|
|
void
|
|
gst_decklink_sink_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDecklinkSink *decklinksink;
|
|
|
|
g_return_if_fail (GST_IS_DECKLINK_SINK (object));
|
|
decklinksink = GST_DECKLINK_SINK (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_MODE:
|
|
decklinksink->mode = (GstDecklinkModeEnum) g_value_get_enum (value);
|
|
break;
|
|
case PROP_DEVICE_NUMBER:
|
|
decklinksink->device_number = g_value_get_int (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_decklink_sink_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstDecklinkSink *decklinksink;
|
|
|
|
g_return_if_fail (GST_IS_DECKLINK_SINK (object));
|
|
decklinksink = GST_DECKLINK_SINK (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_MODE:
|
|
g_value_set_enum (value, decklinksink->mode);
|
|
break;
|
|
case PROP_DEVICE_NUMBER:
|
|
g_value_set_int (value, decklinksink->device_number);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
static void
|
|
gst_decklink_sink_com_thread (GstDecklinkSink * sink)
|
|
{
|
|
HRESULT res;
|
|
|
|
g_mutex_lock (sink->com_init_lock);
|
|
|
|
/* Initialize COM with a MTA for this process. This thread will
|
|
* be the first one to enter the apartement and the last one to leave
|
|
* it, unitializing COM properly */
|
|
|
|
res = CoInitializeEx (0, COINIT_MULTITHREADED);
|
|
if (res == S_FALSE)
|
|
GST_WARNING_OBJECT (sink,
|
|
"COM has been already initialized in the same process");
|
|
else if (res == RPC_E_CHANGED_MODE)
|
|
GST_WARNING_OBJECT (sink, "The concurrency model of COM has changed.");
|
|
else
|
|
GST_INFO_OBJECT (sink, "COM intialized succesfully");
|
|
|
|
sink->comInitialized = TRUE;
|
|
|
|
/* Signal other threads waiting on this condition that COM was initialized */
|
|
g_cond_signal (sink->com_initialized);
|
|
|
|
g_mutex_unlock (sink->com_init_lock);
|
|
|
|
/* Wait until the unitialize condition is met to leave the COM apartement */
|
|
g_mutex_lock (sink->com_deinit_lock);
|
|
g_cond_wait (sink->com_uninitialize, sink->com_deinit_lock);
|
|
|
|
CoUninitialize ();
|
|
GST_INFO_OBJECT (sink, "COM unintialized succesfully");
|
|
sink->comInitialized = FALSE;
|
|
g_cond_signal (sink->com_uninitialized);
|
|
g_mutex_unlock (sink->com_deinit_lock);
|
|
}
|
|
#endif /* _MSC_VER */
|
|
|
|
void
|
|
gst_decklink_sink_finalize (GObject * object)
|
|
{
|
|
GstDecklinkSink *decklinksink;
|
|
|
|
decklinksink = GST_DECKLINK_SINK (object);
|
|
|
|
g_cond_clear (&decklinksink->cond);
|
|
g_mutex_clear (&decklinksink->mutex);
|
|
g_cond_clear (&decklinksink->audio_cond);
|
|
g_mutex_clear (&decklinksink->audio_mutex);
|
|
|
|
#ifdef _MSC_VER
|
|
/* signal the COM thread that it should uninitialize COM */
|
|
if (decklinksink->comInitialized) {
|
|
g_mutex_lock (&decklinksink->com_deinit_lock);
|
|
g_cond_signal (&decklinksink->com_uninitialize);
|
|
g_cond_wait (&decklinksink->com_uninitialized,
|
|
&decklinksink->com_deinit_lock);
|
|
g_mutex_unlock (&decklinksink->com_deinit_lock);
|
|
}
|
|
|
|
g_mutex_clear (&decklinksink->com_init_lock);
|
|
g_mutex_clear (&decklinksink->com_deinit_lock);
|
|
g_cond_clear (&decklinksink->com_initialized);
|
|
g_cond_clear (&decklinksink->com_uninitialize);
|
|
g_cond_clear (&decklinksink->com_uninitialized);
|
|
#endif /* _MSC_VER */
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
/* FIXME: post error messages for the misc. failures */
|
|
static gboolean
|
|
gst_decklink_sink_start (GstDecklinkSink * decklinksink)
|
|
{
|
|
HRESULT ret;
|
|
const GstDecklinkMode *mode;
|
|
BMDAudioSampleType sample_depth;
|
|
|
|
decklinksink->decklink =
|
|
gst_decklink_get_nth_device (decklinksink->device_number);
|
|
if (!decklinksink->decklink) {
|
|
GST_WARNING ("failed to get device %d", decklinksink->device_number);
|
|
return FALSE;
|
|
}
|
|
|
|
decklinksink->output =
|
|
gst_decklink_get_nth_output (decklinksink->device_number);
|
|
if (!decklinksink->output) {
|
|
GST_WARNING ("no output for device %d", decklinksink->device_number);
|
|
return FALSE;
|
|
}
|
|
|
|
decklinksink->callback = new Output;
|
|
decklinksink->callback->decklinksink = decklinksink;
|
|
|
|
decklinksink->output->SetAudioCallback (decklinksink->callback);
|
|
|
|
mode = gst_decklink_get_mode (decklinksink->mode);
|
|
|
|
ret = decklinksink->output->EnableVideoOutput (mode->mode,
|
|
bmdVideoOutputFlagDefault);
|
|
if (ret != S_OK) {
|
|
GST_WARNING ("failed to enable video output");
|
|
return FALSE;
|
|
}
|
|
//decklinksink->video_enabled = TRUE;
|
|
|
|
decklinksink->output->SetScheduledFrameCompletionCallback (decklinksink->callback);
|
|
|
|
sample_depth = bmdAudioSampleType16bitInteger;
|
|
ret = decklinksink->output->EnableAudioOutput (bmdAudioSampleRate48kHz,
|
|
sample_depth, 2, bmdAudioOutputStreamContinuous);
|
|
if (ret != S_OK) {
|
|
GST_WARNING ("failed to enable audio output");
|
|
return FALSE;
|
|
}
|
|
decklinksink->audio_adapter = gst_adapter_new ();
|
|
|
|
decklinksink->num_frames = 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_sink_force_stop (GstDecklinkSink * decklinksink)
|
|
{
|
|
g_mutex_lock (&decklinksink->mutex);
|
|
decklinksink->stop = TRUE;
|
|
g_cond_signal (&decklinksink->cond);
|
|
g_mutex_unlock (&decklinksink->mutex);
|
|
|
|
g_mutex_lock (&decklinksink->audio_mutex);
|
|
g_cond_signal (&decklinksink->audio_cond);
|
|
g_mutex_unlock (&decklinksink->audio_mutex);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_sink_stop (GstDecklinkSink * decklinksink)
|
|
{
|
|
decklinksink->output->StopScheduledPlayback (0, NULL, 0);
|
|
decklinksink->output->DisableAudioOutput ();
|
|
decklinksink->output->DisableVideoOutput ();
|
|
|
|
decklinksink->output->SetAudioCallback (NULL);
|
|
decklinksink->output->SetScheduledFrameCompletionCallback (NULL);
|
|
delete decklinksink->callback;
|
|
decklinksink->callback = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_decklink_sink_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstDecklinkSink *decklinksink;
|
|
GstStateChangeReturn ret;
|
|
|
|
decklinksink = GST_DECKLINK_SINK (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
if (!gst_decklink_sink_start (decklinksink)) {
|
|
ret = GST_STATE_CHANGE_FAILURE;
|
|
goto out;
|
|
}
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
gst_segment_init (&decklinksink->audio_segment, GST_FORMAT_TIME);
|
|
gst_segment_init (&decklinksink->video_segment, GST_FORMAT_TIME);
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
gst_decklink_sink_force_stop (decklinksink);
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
gst_decklink_sink_stop (decklinksink);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_decklink_sink_videosink_chain (GstPad * pad, GstObject * parent,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstDecklinkSink *decklinksink;
|
|
IDeckLinkMutableVideoFrame *frame;
|
|
void *data;
|
|
GstFlowReturn flow_ret;
|
|
const GstDecklinkMode *mode;
|
|
HRESULT ret;
|
|
GstClockTime timestamp, duration;
|
|
GstClockTime running_time;
|
|
GstClockTime running_time_duration;
|
|
GstClock *clock;
|
|
GstClockID clock_id;
|
|
GstClockTime base_time;
|
|
|
|
decklinksink = GST_DECKLINK_SINK (parent);
|
|
|
|
#if 0
|
|
if (!decklinksink->video_enabled) {
|
|
HRESULT ret;
|
|
ret = decklinksink->output->EnableVideoOutput (decklinksink->display_mode,
|
|
bmdVideoOutputFlagDefault);
|
|
if (ret != S_OK) {
|
|
GST_WARNING ("failed to enable video output");
|
|
//return FALSE;
|
|
}
|
|
decklinksink->video_enabled = TRUE;
|
|
}
|
|
#endif
|
|
|
|
mode = gst_decklink_get_mode (decklinksink->mode);
|
|
|
|
ret = decklinksink->output->CreateVideoFrame (mode->width,
|
|
mode->height, mode->width * 2, decklinksink->pixel_format,
|
|
bmdFrameFlagDefault, &frame);
|
|
if (ret != S_OK) {
|
|
GST_ELEMENT_ERROR (decklinksink, STREAM, FAILED,
|
|
(NULL), ("Failed to create video frame: 0x%08x", ret));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
frame->GetBytes (&data);
|
|
gst_buffer_extract (buffer, 0, data, gst_buffer_get_size (buffer));
|
|
timestamp = GST_BUFFER_TIMESTAMP (buffer);
|
|
duration = GST_BUFFER_DURATION (buffer);
|
|
if (duration == GST_CLOCK_TIME_NONE) {
|
|
duration = gst_util_uint64_scale_int (GST_SECOND, mode->fps_d, mode->fps_n);
|
|
}
|
|
running_time = gst_segment_to_running_time (&decklinksink->video_segment, GST_FORMAT_TIME, timestamp);
|
|
running_time_duration = gst_segment_to_running_time (&decklinksink->video_segment, GST_FORMAT_TIME, timestamp + duration) - running_time;
|
|
gst_buffer_unref (buffer);
|
|
|
|
#if 0
|
|
g_mutex_lock (&decklinksink->mutex);
|
|
/* FIXME: This is not correct as it assumes that the decklink clock
|
|
* has the exact same value as the pipeline clock at any time. Not
|
|
* only the same rate!
|
|
*/
|
|
while (decklinksink->queued_frames > 2 && !decklinksink->stop) {
|
|
g_cond_wait (&decklinksink->cond, &decklinksink->mutex);
|
|
}
|
|
#else
|
|
/* XXX: Wait until the pipeline clock reaches our running time to
|
|
* throttle filling the decklinksink internal queue */
|
|
clock = gst_element_get_clock (GST_ELEMENT (decklinksink));
|
|
base_time = gst_element_get_base_time (GST_ELEMENT (decklinksink));
|
|
if (clock) {
|
|
if (base_time != GST_CLOCK_TIME_NONE) {
|
|
clock_id = gst_clock_new_single_shot_id (clock, base_time + running_time);
|
|
gst_clock_id_wait (clock_id, NULL);
|
|
gst_clock_id_unref (clock_id);
|
|
}
|
|
|
|
gst_object_unref (clock);
|
|
}
|
|
#endif
|
|
if (!decklinksink->stop) {
|
|
decklinksink->queued_frames++;
|
|
}
|
|
#if 0
|
|
g_mutex_unlock (&decklinksink->mutex);
|
|
#endif
|
|
|
|
if (!decklinksink->stop) {
|
|
ret = decklinksink->output->ScheduleVideoFrame (frame,
|
|
running_time, running_time_duration, GST_SECOND);
|
|
if (ret != S_OK) {
|
|
GST_ELEMENT_ERROR (decklinksink, STREAM, FAILED,
|
|
(NULL), ("Failed to schedule frame: 0x%08x", ret));
|
|
flow_ret = GST_FLOW_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
decklinksink->num_frames++;
|
|
|
|
if (!decklinksink->sched_started) {
|
|
ret = decklinksink->output->StartScheduledPlayback (0, mode->fps_d, 1.0);
|
|
if (ret != S_OK) {
|
|
GST_ELEMENT_ERROR (decklinksink, STREAM, FAILED,
|
|
(NULL), ("Failed to start scheduled playback: 0x%08x", ret));
|
|
flow_ret = GST_FLOW_ERROR;
|
|
goto out;
|
|
}
|
|
decklinksink->sched_started = TRUE;
|
|
}
|
|
|
|
flow_ret = GST_FLOW_OK;
|
|
} else {
|
|
flow_ret = GST_FLOW_FLUSHING;
|
|
}
|
|
|
|
out:
|
|
frame->Release ();
|
|
|
|
return flow_ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_sink_videosink_event (GstPad * pad, GstObject * parent,
|
|
GstEvent * event)
|
|
{
|
|
gboolean res;
|
|
GstDecklinkSink *decklinksink;
|
|
|
|
decklinksink = GST_DECKLINK_SINK (parent);
|
|
|
|
GST_DEBUG_OBJECT (pad, "event: %" GST_PTR_FORMAT, event);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_CAPS:{
|
|
decklinksink->pixel_format = bmdFormat8BitYUV;
|
|
res = TRUE;
|
|
/* FIXME: this makes no sense, template caps don't contain v210 */
|
|
#if 0
|
|
GstCaps *caps;
|
|
|
|
gst_event_parse_caps (event, &caps);
|
|
ret = gst_video_format_parse_caps (caps, &format, &width, &height);
|
|
if (ret) {
|
|
if (format == GST_VIDEO_FORMAT_v210) {
|
|
decklinksink->pixel_format = bmdFormat10BitYUV;
|
|
} else {
|
|
decklinksink->pixel_format = bmdFormat8BitYUV;
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
case GST_EVENT_EOS:
|
|
/* FIXME: EOS aggregation with audio pad looks wrong */
|
|
decklinksink->video_eos = TRUE;
|
|
decklinksink->video_seqnum = gst_event_get_seqnum (event);
|
|
{
|
|
GstMessage *message;
|
|
|
|
message = gst_message_new_eos (GST_OBJECT_CAST (decklinksink));
|
|
gst_message_set_seqnum (message, decklinksink->video_seqnum);
|
|
gst_element_post_message (GST_ELEMENT_CAST (decklinksink), message);
|
|
}
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
case GST_EVENT_SEGMENT:
|
|
gst_event_copy_segment (event, &decklinksink->video_segment);
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
gst_segment_init (&decklinksink->video_segment, GST_FORMAT_TIME);
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
default:
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_sink_videosink_query (GstPad * pad, GstObject * parent,
|
|
GstQuery * query)
|
|
{
|
|
gboolean res;
|
|
GstDecklinkSink *decklinksink;
|
|
|
|
decklinksink = GST_DECKLINK_SINK (parent);
|
|
|
|
GST_DEBUG_OBJECT (decklinksink, "query");
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CAPS:{
|
|
GstCaps *mode_caps, *filter, *caps;
|
|
|
|
/* FIXME: do we change mode if incoming caps change? If yes, we
|
|
* should probably return the template caps instead */
|
|
mode_caps = gst_decklink_mode_get_caps (decklinksink->mode);
|
|
gst_query_parse_caps (query, &filter);
|
|
if (filter) {
|
|
caps =
|
|
gst_caps_intersect_full (filter, mode_caps,
|
|
GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (mode_caps);
|
|
} else {
|
|
caps = mode_caps;
|
|
}
|
|
gst_query_set_caps_result (query, caps);
|
|
gst_caps_unref (caps);
|
|
res = TRUE;
|
|
break;
|
|
}
|
|
default:
|
|
res = gst_pad_query_default (pad, parent, query);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_decklink_sink_audiosink_chain (GstPad * pad, GstObject * parent,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstDecklinkSink *decklinksink;
|
|
GstFlowReturn ret;
|
|
|
|
decklinksink = GST_DECKLINK_SINK (parent);
|
|
|
|
if (decklinksink->stop)
|
|
return GST_FLOW_FLUSHING;
|
|
|
|
g_mutex_lock (&decklinksink->audio_mutex);
|
|
/* FIXME: This is not correct as it assumes that the decklink clock
|
|
* has the exact same value as the pipeline clock at any time. Not
|
|
* only the same rate!
|
|
*/
|
|
while (!decklinksink->stop &&
|
|
gst_adapter_available (decklinksink->audio_adapter) > 1600 * 4 * 2) {
|
|
g_cond_wait (&decklinksink->audio_cond, &decklinksink->audio_mutex);
|
|
}
|
|
gst_adapter_push (decklinksink->audio_adapter, buffer);
|
|
g_mutex_unlock (&decklinksink->audio_mutex);
|
|
|
|
ret = GST_FLOW_OK;
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_sink_audiosink_event (GstPad * pad, GstObject * parent,
|
|
GstEvent * event)
|
|
{
|
|
gboolean res;
|
|
GstDecklinkSink *decklinksink;
|
|
|
|
decklinksink = GST_DECKLINK_SINK (parent);
|
|
|
|
GST_DEBUG_OBJECT (pad, "event: %" GST_PTR_FORMAT, event);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_EOS:
|
|
/* FIXME: EOS aggregation with video pad looks wrong */
|
|
decklinksink->audio_eos = TRUE;
|
|
decklinksink->audio_seqnum = gst_event_get_seqnum (event);
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
case GST_EVENT_SEGMENT:
|
|
gst_event_copy_segment (event, &decklinksink->audio_segment);
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
gst_segment_init (&decklinksink->audio_segment, GST_FORMAT_TIME);
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
default:
|
|
res = gst_pad_event_default (pad, parent, event);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
gst_decklink_sink_audiosink_query (GstPad * pad, GstObject * parent,
|
|
GstQuery * query)
|
|
{
|
|
gboolean res;
|
|
|
|
GST_DEBUG_OBJECT (pad, "query: %" GST_PTR_FORMAT, query);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
default:
|
|
res = gst_pad_query_default (pad, parent, query);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
HRESULT
|
|
Output::ScheduledFrameCompleted (IDeckLinkVideoFrame * completedFrame,
|
|
BMDOutputFrameCompletionResult result)
|
|
{
|
|
GST_DEBUG ("ScheduledFrameCompleted");
|
|
|
|
g_mutex_lock (&decklinksink->mutex);
|
|
g_cond_signal (&decklinksink->cond);
|
|
decklinksink->queued_frames--;
|
|
g_mutex_unlock (&decklinksink->mutex);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
Output::ScheduledPlaybackHasStopped ()
|
|
{
|
|
GST_DEBUG ("ScheduledPlaybackHasStopped");
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
Output::RenderAudioSamples (bool preroll)
|
|
{
|
|
uint32_t samplesWritten;
|
|
HRESULT ret;
|
|
|
|
// guint64 samplesToWrite;
|
|
|
|
if (decklinksink->stop) {
|
|
GST_DEBUG ("decklinksink->stop set TRUE!");
|
|
decklinksink->output->BeginAudioPreroll ();
|
|
// running = true;
|
|
} else {
|
|
gconstpointer data;
|
|
int n;
|
|
|
|
g_mutex_lock (&decklinksink->audio_mutex);
|
|
|
|
n = gst_adapter_available (decklinksink->audio_adapter);
|
|
if (n > 0) {
|
|
data = gst_adapter_map (decklinksink->audio_adapter, n);
|
|
|
|
/* FIXME: This is not correct. We assume here that the decklink
|
|
* clock runs at the same rate as the pipeline clock and that
|
|
* the input stream has no discontinuities at all
|
|
*/
|
|
ret = decklinksink->output->ScheduleAudioSamples ((void *) data, n / 4,
|
|
0, 0, &samplesWritten);
|
|
|
|
gst_adapter_unmap (decklinksink->audio_adapter);
|
|
gst_adapter_flush (decklinksink->audio_adapter, samplesWritten * 4);
|
|
if (ret != S_OK) {
|
|
GST_ELEMENT_ERROR (decklinksink, STREAM, FAILED,
|
|
(NULL), ("Failed to schedule audio samples: 0x%08x", ret));
|
|
} else {
|
|
GST_DEBUG ("wrote %d samples, %d available", samplesWritten, n / 4);
|
|
}
|
|
|
|
g_cond_signal (&decklinksink->audio_cond);
|
|
} else {
|
|
if (decklinksink->audio_eos) {
|
|
GstMessage *message;
|
|
|
|
message = gst_message_new_eos (GST_OBJECT_CAST (decklinksink));
|
|
gst_message_set_seqnum (message, decklinksink->audio_seqnum);
|
|
gst_element_post_message (GST_ELEMENT_CAST (decklinksink), message);
|
|
}
|
|
}
|
|
g_mutex_unlock (&decklinksink->audio_mutex);
|
|
|
|
}
|
|
|
|
GST_DEBUG ("RenderAudioSamples");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
Output::~Output() {
|
|
}
|