decklink2: Add new Blackmagic DeckLink plugin

Key differences from the old decklink plugin:
* Single source/sink element instead of separate audio/video source/sink
* Supports old driver (version 10.11 or newer)
* Windows DeckLink SDK build is integrated into plugin build
This commit is contained in:
Seungha Yang 2023-11-04 00:25:23 +09:00
parent 6c7fe649ab
commit 1a02d4ad19
26 changed files with 10912 additions and 0 deletions

View file

@ -7,6 +7,7 @@ subprojects/gst-plugins-bad/gst-libs/gst/winrt
subprojects/gst-plugins-bad/sys/amfcodec
subprojects/gst-plugins-bad/sys/d3d11
subprojects/gst-plugins-bad/sys/d3d12
^(subprojects/gst-plugins-bad/sys/decklink2/)+(\w)+([^/])+(cpp$)
subprojects/gst-plugins-bad/sys/dwrite
subprojects/gst-plugins-bad/sys/mediafoundation
subprojects/gst-plugins-bad/sys/nvcodec

File diff suppressed because one or more lines are too long

View file

@ -106,6 +106,7 @@ option('d3d12', type : 'feature', value : 'auto', description : 'Direct3D12 plug
option('dash', type : 'feature', value : 'auto', description : 'DASH demuxer plugin')
option('dc1394', type : 'feature', value : 'auto', description : 'libdc1394 IIDC camera source plugin')
option('decklink', type : 'feature', value : 'auto', description : 'DeckLink audio/video source/sink plugin')
option('decklink2', type : 'feature', value : 'auto', description : 'DeckLink plugin')
option('directfb', type : 'feature', value : 'auto', description : 'DirectFB video sink plugin')
option('directsound', type : 'feature', value : 'auto', description : 'Directsound audio source plugin')
option('directshow', type : 'feature', value : 'auto', description : 'Directshow audio/video plugins')

View file

@ -0,0 +1,683 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdecklink2combiner.h"
#include "gstdecklink2utils.h"
GST_DEBUG_CATEGORY_STATIC (gst_decklink2_combiner_debug);
#define GST_CAT_DEFAULT gst_decklink2_combiner_debug
static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw, format = (string) { S16LE, S32LE }, "
"rate = (int) 48000, channels = (int) { 2, 8, 16 }, "
"layout = (string) interleaved"));
struct _GstDeckLink2Combiner
{
GstAggregator parent;
GstAggregatorPad *video_pad;
GstAggregatorPad *audio_pad;
GstCaps *video_caps;
GstCaps *audio_caps;
GstVideoInfo video_info;
GstAudioInfo audio_info;
GstAdapter *audio_buffers;
GstClockTime video_start_time;
GstClockTime audio_start_time;
GstClockTime video_running_time;
GstClockTime audio_running_time;
};
static void gst_decklink2_combiner_dispose (GObject * object);
static gboolean gst_decklink2_combiner_sink_event (GstAggregator * agg,
GstAggregatorPad * pad, GstEvent * event);
static gboolean gst_decklink2_combiner_sink_query (GstAggregator * agg,
GstAggregatorPad * aggpad, GstQuery * query);
static GstFlowReturn gst_decklink2_combiner_aggregate (GstAggregator * agg,
gboolean timeout);
static gboolean gst_decklink2_combiner_start (GstAggregator * agg);
static gboolean gst_decklink2_combiner_stop (GstAggregator * agg);
static GstBuffer *gst_decklink2_combiner_clip (GstAggregator * agg,
GstAggregatorPad * aggpad, GstBuffer * buffer);
#define gst_decklink2_combiner_parent_class parent_class
G_DEFINE_TYPE (GstDeckLink2Combiner, gst_decklink2_combiner,
GST_TYPE_AGGREGATOR);
GST_ELEMENT_REGISTER_DEFINE (decklink2combiner, "decklink2combiner",
GST_RANK_NONE, GST_TYPE_DECKLINK2_COMBINER);
static void
gst_decklink2_combiner_class_init (GstDeckLink2CombinerClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstAggregatorClass *agg_class = GST_AGGREGATOR_CLASS (klass);
GstCaps *templ_caps;
object_class->dispose = gst_decklink2_combiner_dispose;
gst_element_class_add_static_pad_template_with_gtype (element_class,
&audio_template, GST_TYPE_AGGREGATOR_PAD);
templ_caps = gst_decklink2_get_default_template_caps ();
gst_element_class_add_pad_template (element_class,
gst_pad_template_new_with_gtype ("video", GST_PAD_SINK, GST_PAD_ALWAYS,
templ_caps, GST_TYPE_AGGREGATOR_PAD));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new_with_gtype ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
templ_caps, GST_TYPE_AGGREGATOR_PAD));
gst_caps_unref (templ_caps);
gst_element_class_set_static_metadata (element_class,
"DeckLink2 Combiner",
"Combiner", "Combines video and audio frames",
"Seungha Yang <seungha@centricular.com>");
agg_class->sink_event = GST_DEBUG_FUNCPTR (gst_decklink2_combiner_sink_event);
agg_class->sink_query = GST_DEBUG_FUNCPTR (gst_decklink2_combiner_sink_query);
agg_class->aggregate = GST_DEBUG_FUNCPTR (gst_decklink2_combiner_aggregate);
agg_class->start = GST_DEBUG_FUNCPTR (gst_decklink2_combiner_start);
agg_class->stop = GST_DEBUG_FUNCPTR (gst_decklink2_combiner_stop);
agg_class->clip = GST_DEBUG_FUNCPTR (gst_decklink2_combiner_clip);
agg_class->get_next_time =
GST_DEBUG_FUNCPTR (gst_aggregator_simple_get_next_time);
/* No negotiation needed */
agg_class->negotiate = NULL;
GST_DEBUG_CATEGORY_INIT (gst_decklink2_combiner_debug,
"decklink2combiner", 0, "decklink2combiner");
}
static void
gst_decklink2_combiner_init (GstDeckLink2Combiner * self)
{
GstPadTemplate *templ;
GstElementClass *klass = GST_ELEMENT_GET_CLASS (self);
templ = gst_element_class_get_pad_template (klass, "video");
self->video_pad = (GstAggregatorPad *)
g_object_new (GST_TYPE_AGGREGATOR_PAD, "name", "video", "direction",
GST_PAD_SINK, "template", templ, NULL);
gst_object_unref (templ);
gst_element_add_pad (GST_ELEMENT_CAST (self), GST_PAD_CAST (self->video_pad));
templ = gst_static_pad_template_get (&audio_template);
self->audio_pad = (GstAggregatorPad *)
g_object_new (GST_TYPE_AGGREGATOR_PAD, "name", "audio", "direction",
GST_PAD_SINK, "template", templ, NULL);
gst_object_unref (templ);
gst_element_add_pad (GST_ELEMENT_CAST (self), GST_PAD_CAST (self->audio_pad));
self->audio_buffers = gst_adapter_new ();
}
static void
gst_decklink2_combiner_dispose (GObject * object)
{
GstDeckLink2Combiner *self = GST_DECKLINK2_COMBINER (object);
g_clear_object (&self->audio_buffers);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static gboolean
gst_decklink2_combiner_sink_event (GstAggregator * agg,
GstAggregatorPad * aggpad, GstEvent * event)
{
GstDeckLink2Combiner *self = GST_DECKLINK2_COMBINER (agg);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
GST_DEBUG_OBJECT (self, "Got caps from %s pad %" GST_PTR_FORMAT,
aggpad == self->video_pad ? "video" : "audio", caps);
if (aggpad == self->video_pad) {
gst_caps_replace (&self->video_caps, caps);
gst_video_info_from_caps (&self->video_info, caps);
} else {
/* FIXME: flush audio if audio info is changed or disallow audio update */
gst_caps_replace (&self->audio_caps, caps);
gst_audio_info_from_caps (&self->audio_info, caps);
}
if (self->video_caps) {
gint fps_n, fps_d;
GstClockTime latency;
caps = gst_caps_copy (self->video_caps);
if (GST_AUDIO_INFO_IS_VALID (&self->audio_info)) {
gst_caps_set_simple (caps, "audio-channels", G_TYPE_INT,
self->audio_info.channels,
"audio-format", G_TYPE_STRING,
gst_audio_format_to_string (self->audio_info.finfo->format),
NULL);
} else {
gst_caps_set_simple (caps, "audio-channels", G_TYPE_INT, 0, NULL);
}
GST_DEBUG_OBJECT (self, "Set caps %" GST_PTR_FORMAT, caps);
if (self->video_info.fps_n > 0 && self->video_info.fps_d > 0) {
fps_n = self->video_info.fps_n;
fps_d = self->video_info.fps_d;
} else {
fps_n = 30;
fps_d = 1;
}
latency = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n);
gst_aggregator_set_latency (agg, latency, latency);
gst_aggregator_set_src_caps (agg, caps);
gst_caps_unref (caps);
}
break;
}
case GST_EVENT_SEGMENT:
{
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
/* pass through video segment as-is */
gst_aggregator_update_segment (agg, segment);
break;
}
default:
break;
}
return GST_AGGREGATOR_CLASS (parent_class)->sink_event (agg, aggpad, event);
}
static gboolean
gst_decklink2_combiner_sink_query (GstAggregator * agg,
GstAggregatorPad * aggpad, GstQuery * query)
{
GstDeckLink2Combiner *self = GST_DECKLINK2_COMBINER (agg);
gboolean ret;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CAPS:
{
GstQuery *caps_query;
GstCaps *filter = NULL;
GstStructure *s;
GstCaps *caps = NULL;
GstCaps *templ_caps = gst_pad_get_pad_template_caps (GST_PAD (aggpad));
gst_query_parse_caps (query, &filter);
GST_LOG_OBJECT (aggpad, "Handle query caps with filter %" GST_PTR_FORMAT,
filter);
if (filter)
caps_query = gst_query_new_caps (filter);
else
caps_query = gst_query_new_caps (templ_caps);
ret = gst_pad_peer_query (GST_AGGREGATOR_SRC_PAD (agg), caps_query);
gst_query_parse_caps_result (caps_query, &caps);
GST_LOG_OBJECT (aggpad, "Downstream query caps result %d, %"
GST_PTR_FORMAT, ret, caps);
if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
if (filter) {
GstCaps *temp = gst_caps_intersect_full (filter, templ_caps,
GST_CAPS_INTERSECT_FIRST);
gst_query_set_caps_result (query, temp);
gst_caps_unref (temp);
} else {
gst_query_set_caps_result (query, templ_caps);
}
gst_caps_unref (templ_caps);
gst_query_unref (caps_query);
return TRUE;
}
caps = gst_caps_copy (caps);
gst_query_unref (caps_query);
if (aggpad == self->video_pad) {
/* Remove audio related fields */
for (guint i = 0; i < gst_caps_get_size (caps); i++) {
s = gst_caps_get_structure (caps, i);
gst_structure_remove_field (s, "audio-channels");
}
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
} else {
GstCaps *audio_caps = gst_caps_copy (templ_caps);
const GValue *ch;
/* construct caps with updated channels field */
s = gst_caps_get_structure (caps, 0);
ch = gst_structure_get_value (s, "audio-channels");
if (ch)
gst_caps_set_value (audio_caps, "channels", ch);
gst_caps_unref (caps);
if (filter) {
GstCaps *temp = gst_caps_intersect_full (filter, audio_caps,
GST_CAPS_INTERSECT_FIRST);
gst_query_set_caps_result (query, temp);
gst_caps_unref (temp);
} else {
gst_query_set_caps_result (query, audio_caps);
}
gst_caps_unref (audio_caps);
}
gst_caps_unref (templ_caps);
return TRUE;
}
case GST_QUERY_ACCEPT_CAPS:
GST_DEBUG_OBJECT (aggpad, "Handle accept caps");
if (aggpad == self->video_pad) {
ret = gst_pad_peer_query (GST_AGGREGATOR_SRC_PAD (agg), query);
GST_DEBUG_OBJECT (aggpad, "Video accept caps result %d", ret);
} else {
GstQuery *caps_query;
GstCaps *audio_caps;
GstCaps *caps = NULL;
const GValue *ch;
GstStructure *s;
caps_query = gst_query_new_caps (NULL);
ret = gst_pad_peer_query (GST_AGGREGATOR_SRC_PAD (agg), caps_query);
gst_query_parse_caps_result (caps_query, &caps);
GST_LOG_OBJECT (aggpad, "Downstream query caps result %d, %"
GST_PTR_FORMAT, ret, caps);
if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
gst_query_unref (caps_query);
gst_query_set_accept_caps_result (query, TRUE);
return TRUE;
}
audio_caps = gst_static_pad_template_get_caps (&audio_template);
/* construct caps with updated channels field */
audio_caps = gst_caps_copy (audio_caps);
s = gst_caps_get_structure (caps, 0);
ch = gst_structure_get_value (s, "audio-channels");
if (ch)
gst_caps_set_value (audio_caps, "channels", ch);
gst_query_unref (caps_query);
gst_query_parse_accept_caps (query, &caps);
gst_query_set_accept_caps_result (query, gst_caps_is_subset (caps,
audio_caps));
gst_caps_unref (audio_caps);
ret = TRUE;
}
return ret;
default:
break;
}
return GST_AGGREGATOR_CLASS (parent_class)->sink_query (agg, aggpad, query);
}
static void
gst_decklink2_combiner_reset (GstDeckLink2Combiner * self)
{
gst_clear_caps (&self->video_caps);
gst_clear_caps (&self->audio_caps);
gst_adapter_clear (self->audio_buffers);
gst_video_info_init (&self->video_info);
gst_audio_info_init (&self->audio_info);
self->video_start_time = GST_CLOCK_TIME_NONE;
self->audio_start_time = GST_CLOCK_TIME_NONE;
self->video_running_time = GST_CLOCK_TIME_NONE;
self->audio_running_time = GST_CLOCK_TIME_NONE;
}
static gboolean
gst_decklink2_combiner_start (GstAggregator * agg)
{
GstDeckLink2Combiner *self = GST_DECKLINK2_COMBINER (agg);
gst_decklink2_combiner_reset (self);
return TRUE;
}
static gboolean
gst_decklink2_combiner_stop (GstAggregator * agg)
{
GstDeckLink2Combiner *self = GST_DECKLINK2_COMBINER (agg);
gst_decklink2_combiner_reset (self);
return TRUE;
}
static GstBuffer *
gst_decklink2_combiner_clip (GstAggregator * agg, GstAggregatorPad * aggpad,
GstBuffer * buffer)
{
GstDeckLink2Combiner *self = GST_DECKLINK2_COMBINER (agg);
GstClockTime pts;
pts = GST_BUFFER_PTS (buffer);
if (!GST_CLOCK_TIME_IS_VALID (pts)) {
GST_ERROR_OBJECT (self, "Only buffers with PTS supported");
return buffer;
}
if (aggpad == self->video_pad) {
GstClockTime dur;
GstClockTime start, stop, cstart, cstop;
dur = GST_BUFFER_DURATION (buffer);
if (!GST_CLOCK_TIME_IS_VALID (dur) &&
self->video_info.fps_n > 0 && self->video_info.fps_d > 0) {
dur = gst_util_uint64_scale_int (GST_SECOND, self->video_info.fps_d,
self->video_info.fps_n);
}
start = pts;
if (GST_CLOCK_TIME_IS_VALID (dur))
stop = start + dur;
else
stop = GST_CLOCK_TIME_NONE;
if (!gst_segment_clip (&aggpad->segment, GST_FORMAT_TIME, start, stop,
&cstart, &cstop)) {
GST_LOG_OBJECT (self, "Dropping buffer outside segment");
gst_buffer_unref (buffer);
return NULL;
}
if (GST_BUFFER_PTS (buffer) != cstart) {
buffer = gst_buffer_make_writable (buffer);
GST_BUFFER_PTS (buffer) = cstart;
}
if (GST_CLOCK_TIME_IS_VALID (stop) && GST_CLOCK_TIME_IS_VALID (cstop)) {
dur = cstop - cstart;
if (GST_BUFFER_DURATION (buffer) != dur)
buffer = gst_buffer_make_writable (buffer);
GST_BUFFER_DURATION (buffer) = dur;
}
} else {
buffer = gst_audio_buffer_clip (buffer, &aggpad->segment,
self->audio_info.rate, self->audio_info.bpf);
}
return buffer;
}
static GstFlowReturn
gst_decklink2_combiner_aggregate (GstAggregator * agg, gboolean timeout)
{
GstDeckLink2Combiner *self = GST_DECKLINK2_COMBINER (agg);
GstBuffer *video_buf = NULL;
GstBuffer *audio_buf = NULL;
gsize audio_buf_size;
GstDeckLink2AudioMeta *meta;
GstClockTime video_running_time = GST_CLOCK_TIME_NONE;
GstClockTime video_running_time_end = GST_CLOCK_TIME_NONE;
video_buf = gst_aggregator_pad_peek_buffer (self->video_pad);
if (!video_buf) {
if (gst_aggregator_pad_is_eos (self->video_pad)) {
/* Follow video stream's timeline */
GST_DEBUG_OBJECT (self, "Video pad is EOS");
return GST_FLOW_EOS;
}
/* Need to know video start time */
if (!GST_CLOCK_TIME_IS_VALID (self->video_start_time)) {
GST_LOG_OBJECT (self, "Waiting for first video buffer");
goto again;
}
GST_LOG_OBJECT (self, "Video is not ready");
} else {
/* Drop empty buffer */
if (gst_buffer_get_size (video_buf) == 0) {
GST_LOG_OBJECT (self, "Dropping empty video buffer");
gst_aggregator_pad_drop_buffer (self->video_pad);
goto again;
}
video_running_time = video_running_time_end =
gst_segment_to_running_time (&self->video_pad->segment,
GST_FORMAT_TIME, GST_BUFFER_PTS (video_buf));
if (GST_BUFFER_DURATION_IS_VALID (video_buf)) {
video_running_time_end += GST_BUFFER_DURATION (video_buf);
} else if (self->video_info.fps_n > 0 && self->video_info.fps_d > 0) {
video_running_time_end += gst_util_uint64_scale_int (GST_SECOND,
self->video_info.fps_d, self->video_info.fps_n);
} else {
/* XXX: shouldn't happen */
video_running_time_end = video_running_time;
}
if (!GST_CLOCK_TIME_IS_VALID (self->video_start_time)) {
self->video_start_time = video_running_time;
GST_DEBUG_OBJECT (self, "Video start time %" GST_TIME_FORMAT,
GST_TIME_ARGS (self->video_start_time));
}
self->video_running_time = video_running_time_end;
}
audio_buf = gst_aggregator_pad_peek_buffer (self->audio_pad);
if (!audio_buf) {
if (gst_adapter_available (self->audio_buffers) == 0 &&
!gst_aggregator_pad_is_eos (self->audio_pad) &&
self->audio_running_time < self->video_running_time) {
GST_LOG_OBJECT (self, "Waiting for audio buffer");
goto again;
}
} else if (gst_buffer_get_size (audio_buf) == 0) {
GST_LOG_OBJECT (self, "Dropping empty audio buffer");
gst_aggregator_pad_drop_buffer (self->audio_pad);
goto again;
} else {
GstClockTime audio_running_time, audio_running_time_end;
audio_running_time = gst_segment_to_running_time (&self->audio_pad->segment,
GST_FORMAT_TIME, GST_BUFFER_PTS (audio_buf));
if (GST_BUFFER_DURATION_IS_VALID (audio_buf)) {
audio_running_time_end = audio_running_time +
GST_BUFFER_DURATION (audio_buf);
} else {
audio_running_time_end = gst_util_uint64_scale (GST_SECOND,
gst_buffer_get_size (audio_buf),
self->audio_info.rate * self->audio_info.bpf);
audio_running_time_end += audio_running_time;
}
self->audio_running_time = audio_running_time_end;
/* Do initial video/audio align */
if (!GST_CLOCK_TIME_IS_VALID (self->audio_start_time)) {
GST_DEBUG_OBJECT (self, "Initial audio running time %" GST_TIME_FORMAT,
GST_TIME_ARGS (audio_running_time));
if (audio_running_time_end <= self->video_start_time) {
GST_DEBUG_OBJECT (self, "audio running-time end %" GST_TIME_FORMAT
" < video-start-time %" GST_TIME_FORMAT,
GST_TIME_ARGS (self->video_start_time),
GST_TIME_ARGS (audio_running_time_end));
/* completely outside */
gst_aggregator_pad_drop_buffer (self->audio_pad);
goto again;
} else if (audio_running_time < self->video_start_time &&
audio_running_time_end >= self->video_start_time) {
/* partial overlap */
GstClockTime diff;
gsize in_samples, diff_samples;
GstAudioMeta *meta;
GstBuffer *trunc_buf;
meta = gst_buffer_get_audio_meta (audio_buf);
in_samples = meta ? meta->samples :
gst_buffer_get_size (audio_buf) / self->audio_info.bpf;
diff = self->video_start_time - audio_running_time;
diff_samples = gst_util_uint64_scale (diff,
self->audio_info.rate, GST_SECOND);
GST_DEBUG_OBJECT (self, "Truncate initial audio buffer duration %"
GST_TIME_FORMAT, GST_TIME_ARGS (diff));
trunc_buf = gst_audio_buffer_truncate (
(GstBuffer *) g_steal_pointer (&audio_buf),
self->audio_info.bpf, diff_samples, in_samples - diff_samples);
gst_aggregator_pad_drop_buffer (self->audio_pad);
if (!trunc_buf) {
GST_DEBUG_OBJECT (self, "Empty truncated buffer");
gst_aggregator_pad_drop_buffer (self->audio_pad);
goto again;
}
self->audio_start_time = self->video_start_time;
gst_adapter_push (self->audio_buffers, trunc_buf);
} else if (audio_running_time >= self->video_start_time) {
/* fill silence if needed */
GstClockTime diff;
gsize diff_samples;
diff = audio_running_time - self->video_start_time;
if (diff > 0) {
gsize fill_size;
diff_samples = gst_util_uint64_scale (diff,
self->audio_info.rate, GST_SECOND);
fill_size = diff_samples * self->audio_info.bpf;
if (fill_size > 0) {
GstBuffer *fill_buf;
GstMapInfo map;
GST_DEBUG_OBJECT (self, "Fill initial %" G_GSIZE_FORMAT
" audio samples", diff_samples);
fill_buf = gst_buffer_new_and_alloc (fill_size);
gst_buffer_map (fill_buf, &map, GST_MAP_WRITE);
gst_audio_format_info_fill_silence (self->audio_info.finfo,
map.data, map.size);
gst_buffer_unmap (fill_buf, &map);
gst_adapter_push (self->audio_buffers, fill_buf);
}
}
self->audio_start_time = self->video_start_time;
gst_adapter_push (self->audio_buffers,
(GstBuffer *) g_steal_pointer (&audio_buf));
gst_aggregator_pad_drop_buffer (self->audio_pad);
}
} else {
GST_LOG_OBJECT (self, "Pushing audio buffer to adapter, %" GST_PTR_FORMAT,
audio_buf);
gst_adapter_push (self->audio_buffers,
(GstBuffer *) g_steal_pointer (&audio_buf));
gst_aggregator_pad_drop_buffer (self->audio_pad);
}
}
if (!video_buf) {
GST_LOG_OBJECT (self, "Waiting for video");
goto again;
} else if (!gst_aggregator_pad_is_eos (self->audio_pad) &&
self->audio_running_time < self->video_running_time) {
GST_LOG_OBJECT (self, "Waiting for audio, audio running time %"
GST_TIME_FORMAT " < video running time %" GST_TIME_FORMAT,
GST_TIME_ARGS (self->audio_running_time),
GST_TIME_ARGS (self->video_running_time));
goto again;
}
gst_aggregator_pad_drop_buffer (self->video_pad);
video_buf = gst_buffer_make_writable (video_buf);
/* Remove external audio meta if any */
meta = gst_buffer_get_decklink2_audio_meta (video_buf);
if (meta) {
GST_LOG_OBJECT (self, "Removing old audio meta");
gst_buffer_remove_meta (video_buf, GST_META_CAST (meta));
}
audio_buf_size = gst_adapter_available (self->audio_buffers);
if (audio_buf_size > 0) {
GstSample *audio_sample;
audio_buf = gst_adapter_take_buffer (self->audio_buffers, audio_buf_size);
audio_sample = gst_sample_new (audio_buf, self->audio_caps, NULL, NULL);
GST_LOG_OBJECT (self, "Adding meta with size %" G_GSIZE_FORMAT,
gst_buffer_get_size (audio_buf));
gst_buffer_unref (audio_buf);
gst_buffer_add_decklink2_audio_meta (video_buf, audio_sample);
gst_sample_unref (audio_sample);
} else {
GST_LOG_OBJECT (self, "No audio meta");
}
GST_LOG_OBJECT (self, "Finish buffer %" GST_PTR_FORMAT, video_buf);
GST_AGGREGATOR_PAD (agg->srcpad)->segment.position = self->video_running_time;
return gst_aggregator_finish_buffer (agg, video_buf);
again:
gst_clear_buffer (&video_buf);
gst_clear_buffer (&audio_buf);
return GST_AGGREGATOR_FLOW_NEED_DATA;
}

View file

@ -0,0 +1,34 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/base.h>
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_COMBINER (gst_decklink2_combiner_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Combiner, gst_decklink2_combiner,
GST, DECKLINK2_COMBINER, GstAggregator);
GST_ELEMENT_REGISTER_DECLARE (decklink2combiner);
G_END_DECLS

View file

@ -0,0 +1,275 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdecklink2demux.h"
#include "gstdecklink2utils.h"
#include "gstdecklink2object.h"
GST_DEBUG_CATEGORY_STATIC (gst_decklink2_demux_debug);
#define GST_CAT_DEFAULT gst_decklink2_demux_debug
static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("audio/x-raw, format = (string) { S16LE, S32LE }, "
"rate = (int) 48000, channels = (int) { 2, 8, 16 }, "
"layout = (string) interleaved"));
struct _GstDeckLink2Demux
{
GstElement parent;
GstPad *sink_pad;
GstPad *video_pad;
GstPad *audio_pad;
GstFlowCombiner *flow_combiner;
GstCaps *audio_caps;
};
static void gst_decklink2_demux_finalize (GObject * object);
static GstStateChangeReturn
gst_decklink2_demux_change_state (GstElement * element,
GstStateChange transition);
static GstFlowReturn gst_decklink2_demux_chain (GstPad * sinkpad,
GstObject * parent, GstBuffer * inbuf);
static gboolean gst_decklink2_demux_sink_event (GstPad * sinkpad,
GstObject * parent, GstEvent * event);
#define gst_decklink2_demux_parent_class parent_class
G_DEFINE_TYPE (GstDeckLink2Demux, gst_decklink2_demux, GST_TYPE_ELEMENT);
GST_ELEMENT_REGISTER_DEFINE (decklink2demux, "decklink2demux",
GST_RANK_NONE, GST_TYPE_DECKLINK2_DEMUX);
static void
gst_decklink2_demux_class_init (GstDeckLink2DemuxClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
object_class->finalize = gst_decklink2_demux_finalize;
GstCaps *templ_caps = gst_decklink2_get_default_template_caps ();
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, templ_caps));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("video", GST_PAD_SRC, GST_PAD_ALWAYS, templ_caps));
gst_caps_unref (templ_caps);
gst_element_class_add_static_pad_template (element_class, &audio_template);
gst_element_class_set_static_metadata (element_class,
"Decklink2 Demux", "Video/Audio/Demuxer/Hardware", "Decklink2 Demux",
"Seungha Yang <seungha@centricular.com>");
element_class->change_state =
GST_DEBUG_FUNCPTR (gst_decklink2_demux_change_state);
GST_DEBUG_CATEGORY_INIT (gst_decklink2_demux_debug, "decklink2demux",
0, "decklink2demux");
}
static void
gst_decklink2_demux_init (GstDeckLink2Demux * self)
{
GstPadTemplate *templ;
GstElementClass *klass = GST_ELEMENT_GET_CLASS (self);
templ = gst_element_class_get_pad_template (klass, "sink");
self->sink_pad = gst_pad_new_from_template (templ, "sink");
gst_pad_set_chain_function (self->sink_pad, gst_decklink2_demux_chain);
gst_pad_set_event_function (self->sink_pad, gst_decklink2_demux_sink_event);
gst_element_add_pad (GST_ELEMENT (self), self->sink_pad);
gst_object_unref (templ);
templ = gst_element_class_get_pad_template (klass, "video");
self->video_pad = gst_pad_new_from_template (templ, "video");
gst_element_add_pad (GST_ELEMENT (self), self->video_pad);
gst_pad_use_fixed_caps (self->video_pad);
gst_object_unref (templ);
self->flow_combiner = gst_flow_combiner_new ();
gst_flow_combiner_add_pad (self->flow_combiner, self->video_pad);
}
static void
gst_decklink2_demux_finalize (GObject * object)
{
GstDeckLink2Demux *self = GST_DECKLINK2_DEMUX (object);
gst_flow_combiner_free (self->flow_combiner);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static GstStateChangeReturn
gst_decklink2_demux_change_state (GstElement * element,
GstStateChange transition)
{
GstDeckLink2Demux *self = GST_DECKLINK2_DEMUX (element);
GstStateChangeReturn ret;
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
gst_clear_caps (&self->audio_caps);
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
if (self->audio_pad) {
gst_flow_combiner_remove_pad (self->flow_combiner, self->audio_pad);
gst_element_remove_pad (element, self->audio_pad);
self->audio_pad = NULL;
}
gst_clear_caps (&self->audio_caps);
break;
default:
break;
}
return ret;
}
static GstFlowReturn
gst_decklink2_demux_chain (GstPad * sinkpad, GstObject * parent,
GstBuffer * inbuf)
{
GstDeckLink2Demux *self = GST_DECKLINK2_DEMUX (parent);
GstDeckLink2AudioMeta *meta;
GstSample *audio_sample = NULL;
GstFlowReturn ret;
meta = gst_buffer_get_decklink2_audio_meta (inbuf);
if (meta) {
audio_sample = gst_sample_ref (meta->sample);
inbuf = gst_buffer_make_writable (inbuf);
gst_buffer_remove_meta (inbuf, GST_META_CAST (meta));
}
if (audio_sample) {
GstCaps *audio_caps = gst_sample_get_caps (audio_sample);
GstBuffer *audio_buf = gst_sample_get_buffer (audio_sample);
if (!audio_caps) {
GST_WARNING_OBJECT (self, "Audio sample without caps");
gst_sample_unref (audio_sample);
audio_sample = NULL;
goto out;
}
if (!audio_buf) {
GST_WARNING_OBJECT (self, "Audio sample without buffer");
gst_sample_unref (audio_sample);
audio_sample = NULL;
goto out;
}
if (!self->audio_pad) {
GstEvent *event;
self->audio_pad = gst_pad_new_from_static_template (&audio_template,
"audio");
gst_pad_set_active (self->audio_pad, TRUE);
event = gst_pad_get_sticky_event (self->sink_pad,
GST_EVENT_STREAM_START, 0);
gst_pad_store_sticky_event (self->audio_pad, event);
gst_event_unref (event);
gst_caps_replace (&self->audio_caps, audio_caps);
event = gst_event_new_caps (self->audio_caps);
gst_pad_store_sticky_event (self->audio_pad, event);
gst_event_unref (event);
event = gst_pad_get_sticky_event (self->sink_pad, GST_EVENT_SEGMENT, 0);
if (event) {
gst_pad_store_sticky_event (self->audio_pad, event);
gst_event_unref (event);
}
gst_element_add_pad (GST_ELEMENT (self), self->audio_pad);
gst_flow_combiner_add_pad (self->flow_combiner, self->audio_pad);
gst_element_no_more_pads (GST_ELEMENT (self));
} else if (!self->audio_caps || !gst_caps_is_equal (self->audio_caps,
audio_caps)) {
GstEvent *event;
gst_caps_replace (&self->audio_caps, audio_caps);
event = gst_event_new_caps (self->audio_caps);
gst_pad_push_event (self->audio_pad, event);
}
}
out:
GST_LOG_OBJECT (self, "Pushing video buffer %" GST_PTR_FORMAT, inbuf);
ret = gst_pad_push (self->video_pad, inbuf);
ret = gst_flow_combiner_update_pad_flow (self->flow_combiner,
self->video_pad, ret);
if (audio_sample) {
GstBuffer *audio_buf = gst_sample_get_buffer (audio_sample);
gst_buffer_ref (audio_buf);
gst_sample_unref (audio_sample);
GST_LOG_OBJECT (self, "Pushing audio buffer %" GST_PTR_FORMAT, audio_buf);
ret = gst_pad_push (self->audio_pad, audio_buf);
ret = gst_flow_combiner_update_pad_flow (self->flow_combiner,
self->audio_pad, ret);
}
return ret;
}
static gboolean
gst_decklink2_demux_sink_event (GstPad * sinkpad, GstObject * parent,
GstEvent * event)
{
GstDeckLink2Demux *self = GST_DECKLINK2_DEMUX (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
GST_DEBUG_OBJECT (self, "Forwarding %" GST_PTR_FORMAT, caps);
return gst_pad_push_event (self->video_pad, event);
}
case GST_EVENT_FLUSH_STOP:
gst_flow_combiner_reset (self->flow_combiner);
break;
default:
break;
}
return gst_pad_event_default (sinkpad, parent, event);
}

View file

@ -0,0 +1,34 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/base.h>
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_DEMUX (gst_decklink2_demux_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Demux, gst_decklink2_demux,
GST, DECKLINK2_DEMUX, GstElement);
GST_ELEMENT_REGISTER_DECLARE (decklink2demux);
G_END_DECLS

View file

@ -0,0 +1,158 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdecklink2deviceprovider.h"
#include <string.h>
GST_DEBUG_CATEGORY_EXTERN (gst_decklink2_debug);
#define GST_CAT_DEFAULT gst_decklink2_debug
struct _GstDeckLink2Device
{
GstDevice parent;
gboolean is_src;
guint device_number;
gint64 persistent_id;
};
static GstElement *gst_decklink2_device_create_element (GstDevice * device,
const gchar * name);
G_DEFINE_TYPE (GstDeckLink2Device, gst_decklink2_device, GST_TYPE_DEVICE);
static void
gst_decklink2_device_class_init (GstDeckLink2DeviceClass * klass)
{
GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
dev_class->create_element =
GST_DEBUG_FUNCPTR (gst_decklink2_device_create_element);
}
static void
gst_decklink2_device_init (GstDeckLink2Device * self)
{
}
static GstElement *
gst_decklink2_device_create_element (GstDevice * device, const gchar * name)
{
GstDeckLink2Device *self = GST_DECKLINK2_DEVICE (device);
GstElement *elem;
if (self->is_src) {
elem = gst_element_factory_make ("decklink2src", name);
} else {
elem = gst_element_factory_make ("decklink2sink", name);
}
g_object_set (elem, "persistent-id", self->persistent_id, NULL);
return elem;
}
GstDevice *
gst_decklink2_device_new (gboolean is_src, const gchar * model_name,
const gchar * display_name, const gchar * serial_number, GstCaps * caps,
gint64 persistent_id, guint device_number, guint max_audio_channels,
const gchar * driver_ver, const gchar * api_ver)
{
GstDevice *device;
GstDeckLink2Device *self;
const gchar *device_class;
GstStructure *props;
if (is_src)
device_class = "Video/Audio/Source/Hardware";
else
device_class = "Video/Audio/Sink/Hardware";
props = gst_structure_new ("properties",
"driver-version", G_TYPE_STRING, driver_ver,
"api-version", G_TYPE_STRING, api_ver,
"device-number", G_TYPE_UINT, device_number,
"persistent-id", G_TYPE_INT64, persistent_id, NULL);
if (max_audio_channels > 0) {
gst_structure_set (props,
"max-channels", G_TYPE_UINT, max_audio_channels, NULL);
}
if (serial_number && serial_number[0] != '\0') {
gst_structure_set (props,
"serial-number", G_TYPE_STRING, serial_number, NULL);
}
device = (GstDevice *) g_object_new (GST_TYPE_DECKLINK2_DEVICE,
"display-name", display_name, "device-class", device_class,
"caps", caps, "properties", props, NULL);
self = GST_DECKLINK2_DEVICE (device);
self->device_number = device_number;
self->persistent_id = persistent_id;
self->is_src = is_src;
return device;
}
struct _GstDeckLink2DeviceProvider
{
GstDeviceProvider parent;
};
static GList *gst_decklink2_device_provider_probe (GstDeviceProvider *
provider);
G_DEFINE_TYPE (GstDeckLink2DeviceProvider, gst_decklink2_device_provider,
GST_TYPE_DEVICE_PROVIDER);
GST_DEVICE_PROVIDER_REGISTER_DEFINE (decklink2deviceprovider,
"decklink2deviceprovider", GST_RANK_SECONDARY,
GST_TYPE_DECKLINK2_DEVICE_PROVIDER);
static void
gst_decklink2_device_provider_class_init (GstDeckLink2DeviceProviderClass *
klass)
{
GstDeviceProviderClass *provider_class = GST_DEVICE_PROVIDER_CLASS (klass);
provider_class->probe =
GST_DEBUG_FUNCPTR (gst_decklink2_device_provider_probe);
gst_device_provider_class_set_static_metadata (provider_class,
"Decklink Device Provider", "Hardware/Source/Sink/Audio/Video",
"Lists and provides Decklink devices",
"Seungha Yang <seungha@centricular.com>");
}
static void
gst_decklink2_device_provider_init (GstDeckLink2DeviceProvider * self)
{
}
static GList *
gst_decklink2_device_provider_probe (GstDeviceProvider * provider)
{
return gst_decklink2_get_devices ();
}

View file

@ -0,0 +1,50 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include "gstdecklink2utils.h"
#include "gstdecklink2object.h"
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_DEVICE (gst_decklink2_device_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Device, gst_decklink2_device,
GST, DECKLINK2_DEVICE, GstDevice);
#define GST_TYPE_DECKLINK2_DEVICE_PROVIDER (gst_decklink2_device_provider_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2DeviceProvider, gst_decklink2_device_provider,
GST, DECKLINK2_DEVICE_PROVIDER, GstDeviceProvider);
GstDevice * gst_decklink2_device_new (gboolean is_src,
const gchar * model_name,
const gchar * display_name,
const gchar * serial_number,
GstCaps * caps,
gint64 persistent_id,
guint device_number,
guint max_audio_channels,
const gchar * driver_ver,
const gchar * api_ver);
GST_DEVICE_PROVIDER_REGISTER_DECLARE (decklink2deviceprovider);
G_END_DECLS

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include "gstdecklink2utils.h"
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_INPUT (gst_decklink2_input_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Input, gst_decklink2_input,
GST, DECKLINK2_INPUT, GstObject);
typedef struct _GstDeckLink2InputVideoConfig
{
BMDVideoConnection connection;
GstDeckLink2DisplayMode display_mode;
BMDPixelFormat pixel_format;
gboolean auto_detect;
gboolean output_cc;
gboolean output_afd_bar;
} GstDeckLink2InputVideoConfig;
typedef struct _GstDeckLink2InputAudioConfig
{
BMDAudioConnection connection;
BMDAudioSampleType sample_type;
GstDeckLink2AudioChannels channels;
} GstDeckLink2InputAudioConfig;
GstDeckLink2Input * gst_decklink2_input_new (IDeckLink * device,
GstDeckLink2APILevel api_level);
GstCaps * gst_decklink2_input_get_caps (GstDeckLink2Input * input,
BMDDisplayMode mode,
BMDPixelFormat format);
gboolean gst_decklink2_input_get_display_mode (GstDeckLink2Input * input,
const GstVideoInfo * info,
GstDeckLink2DisplayMode * display_mode);
HRESULT gst_decklink2_input_start (GstDeckLink2Input * input,
GstElement * client,
BMDProfileID profile_id,
guint buffer_size,
const GstDeckLink2InputVideoConfig * video_config,
const GstDeckLink2InputAudioConfig * audio_config);
void gst_decklink2_input_stop (GstDeckLink2Input * input);
void gst_decklink2_input_set_flushing (GstDeckLink2Input * input,
gboolean flush);
GstFlowReturn gst_decklink2_input_get_sample (GstDeckLink2Input * input,
GstSample ** sample);
gboolean gst_decklink2_input_has_signal (GstDeckLink2Input * input);
G_END_DECLS

View file

@ -0,0 +1,712 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdecklink2object.h"
#include "gstdecklink2deviceprovider.h"
#include <stdlib.h>
#include <atomic>
#include <mutex>
#include <vector>
#include <algorithm>
#include <string>
GST_DEBUG_CATEGORY_EXTERN (gst_decklink2_debug);
#define GST_CAT_DEFAULT gst_decklink2_debug
/* *INDENT-OFF* */
static std::vector<GstDeckLink2Object *> device_list;
static std::mutex device_lock;
/* *INDENT-ON* */
struct _GstDeckLink2Object
{
GstObject parent;
GstDeckLink2APILevel api_level;
IDeckLink *device;
IDeckLinkProfileAttributes *attr;
IDeckLinkAttributes_v10_11 *attr_10_11;
IDeckLinkConfiguration *config;
IDeckLinkConfiguration_v10_11 *config_10_11;
IDeckLinkProfileManager *profile_manager;
GstDeckLink2Input *input;
GstDevice *input_device;
GstDeckLink2Output *output;
GstDevice *output_device;
guint device_number;
gint64 persistent_id;
gchar *serial_number;
gchar *model_name;
gchar *display_name;
gboolean input_acquired;
gboolean output_acquired;
};
static void gst_decklink2_object_dispose (GObject * object);
static void gst_decklink2_object_finalize (GObject * object);
#define gst_decklink2_object_parent_class parent_class
G_DEFINE_TYPE (GstDeckLink2Object, gst_decklink2_object, GST_TYPE_OBJECT);
static void
gst_decklink2_object_class_init (GstDeckLink2ObjectClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gst_decklink2_object_dispose;
object_class->finalize = gst_decklink2_object_finalize;
}
static void
gst_decklink2_object_init (GstDeckLink2Object * self)
{
}
static void
gst_decklink2_object_dispose (GObject * object)
{
GstDeckLink2Object *self = GST_DECKLINK2_OBJECT (object);
if (self->input) {
gst_object_unparent (GST_OBJECT (self->input));
self->input = NULL;
}
if (self->output) {
gst_object_unparent (GST_OBJECT (self->output));
self->output = NULL;
}
gst_clear_object (&self->input_device);
gst_clear_object (&self->output_device);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_decklink2_object_finalize (GObject * object)
{
GstDeckLink2Object *self = GST_DECKLINK2_OBJECT (object);
GST_DECKLINK2_CLEAR_COM (self->attr);
GST_DECKLINK2_CLEAR_COM (self->attr_10_11);
GST_DECKLINK2_CLEAR_COM (self->config);
GST_DECKLINK2_CLEAR_COM (self->config_10_11);
GST_DECKLINK2_CLEAR_COM (self->profile_manager);
GST_DECKLINK2_CLEAR_COM (self->device);
g_free (self->serial_number);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static GstDeckLink2Object *
gst_decklink2_object_new (IDeckLink * device, guint index,
GstDeckLink2APILevel api_level, const gchar * api_ver_str,
const gchar * driver_ver_str)
{
HRESULT hr;
GstDeckLink2Input *input;
GstDeckLink2Output *output;
GstDeckLink2Object *self;
dlstring_t str;
gint64 max_num_audio_channels = 0;
GstCaps *caps = NULL;
input = gst_decklink2_input_new (device, api_level);
output = gst_decklink2_output_new (device, api_level);
if (!output && !input)
return NULL;
self = (GstDeckLink2Object *)
g_object_new (GST_TYPE_DECKLINK2_OBJECT, NULL);
gst_object_ref_sink (self);
self->api_level = api_level;
if (input)
gst_object_set_parent (GST_OBJECT (input), GST_OBJECT (self));
if (output)
gst_object_set_parent (GST_OBJECT (output), GST_OBJECT (self));
self->input = input;
self->output = output;
self->device_number = index;
self->device = device;
device->AddRef ();
if (api_level == GST_DECKLINK2_API_LEVEL_10_11) {
IDeckLinkConfiguration_v10_11 *config_10_11 = NULL;
hr = device->QueryInterface (IID_IDeckLinkConfiguration_v10_11,
(void **) &config_10_11);
if (!gst_decklink2_result (hr)) {
GST_WARNING_OBJECT (self, "Couldn't get config object");
gst_object_unref (self);
return NULL;
}
hr = config_10_11->GetString
(bmdDeckLinkConfigDeviceInformationSerialNumber, &str);
if (gst_decklink2_result (hr)) {
std::string serial_number = DlToStdString (str);
DeleteString (str);
self->serial_number = g_strdup (serial_number.c_str ());
GST_DEBUG_OBJECT (self, "device %d has serial number %s", index,
GST_STR_NULL (self->serial_number));
}
self->config_10_11 = config_10_11;
} else {
IDeckLinkConfiguration *config = NULL;
hr = device->QueryInterface (IID_IDeckLinkConfiguration, (void **) &config);
if (!gst_decklink2_result (hr)) {
GST_WARNING_OBJECT (self, "Couldn't get config object");
gst_object_unref (self);
return NULL;
}
hr = config->GetString (bmdDeckLinkConfigDeviceInformationSerialNumber,
&str);
if (gst_decklink2_result (hr)) {
std::string serial_number = DlToStdString (str);
DeleteString (str);
self->serial_number = g_strdup (serial_number.c_str ());
GST_DEBUG_OBJECT (self, "device %d has serial number %s", index,
GST_STR_NULL (self->serial_number));
}
self->config = config;
device->QueryInterface (IID_IDeckLinkProfileManager,
(void **) &self->profile_manager);
}
if (api_level == GST_DECKLINK2_API_LEVEL_10_11) {
hr = device->QueryInterface (IID_IDeckLinkAttributes_v10_11,
(void **) &self->attr_10_11);
} else {
hr = device->QueryInterface (IID_IDeckLinkProfileAttributes,
(void **) &self->attr);
}
if (!gst_decklink2_result (hr)) {
GST_WARNING_OBJECT (self,
"IDeckLinkProfileAttributes interface is not available");
self->persistent_id = self->device_number;
} else {
hr = E_FAIL;
if (self->attr) {
hr = self->attr->GetInt (BMDDeckLinkPersistentID, &self->persistent_id);
if (!gst_decklink2_result (hr))
self->persistent_id = self->device_number;
hr = self->attr->GetInt (BMDDeckLinkMaximumAudioChannels,
&max_num_audio_channels);
} else if (self->attr_10_11) {
hr = self->attr_10_11->GetInt (BMDDeckLinkPersistentID,
&self->persistent_id);
if (!gst_decklink2_result (hr))
self->persistent_id = self->device_number;
hr = self->attr_10_11->GetInt (BMDDeckLinkMaximumAudioChannels,
&max_num_audio_channels);
}
if (!gst_decklink2_result (hr)) {
GST_WARNING_OBJECT (self, "Couldn't query max audio channels");
max_num_audio_channels = 0;
}
}
hr = device->GetModelName (&str);
if (gst_decklink2_result (hr)) {
std::string model_name = DlToStdString (str);
DeleteString (str);
self->model_name = g_strdup (model_name.c_str ());
}
hr = device->GetDisplayName (&str);
if (gst_decklink2_result (hr)) {
std::string display_name = DlToStdString (str);
DeleteString (str);
self->display_name = g_strdup (display_name.c_str ());
}
if (self->input) {
caps = gst_decklink2_input_get_caps (self->input,
bmdModeUnknown, bmdFormatUnspecified);
self->input_device = gst_decklink2_device_new (TRUE, self->model_name,
self->display_name, self->serial_number, caps, self->persistent_id,
self->device_number, (guint) max_num_audio_channels, driver_ver_str,
api_ver_str);
gst_clear_caps (&caps);
gst_object_ref_sink (self->input_device);
}
if (self->output) {
caps = gst_decklink2_output_get_caps (self->output,
bmdModeUnknown, bmdFormatUnspecified);
self->output_device = gst_decklink2_device_new (FALSE, self->model_name,
self->display_name, self->serial_number, caps, self->persistent_id,
self->device_number, (guint) max_num_audio_channels, driver_ver_str,
api_ver_str);
gst_clear_caps (&caps);
gst_object_ref_sink (self->output_device);
}
return self;
}
#ifdef G_OS_WIN32
static IDeckLinkIterator *
CreateDeckLinkIteratorInstance (void)
{
IDeckLinkIterator *iter = NULL;
CoCreateInstance (CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL,
IID_IDeckLinkIterator, (void **) &iter);
return iter;
}
static IDeckLinkIterator *
CreateDeckLinkIteratorInstance_v10_11 (void)
{
IDeckLinkIterator *iter = NULL;
CoCreateInstance (CLSID_CDeckLinkIterator_v10_11, NULL, CLSCTX_ALL,
IID_IDeckLinkIterator, (void **) &iter);
return iter;
}
#endif
static void
gst_decklink2_device_init (void)
{
GstDeckLink2APILevel api_level = gst_decklink2_get_api_level ();
IDeckLinkIterator *iter = NULL;
HRESULT hr = S_OK;
guint major, minor, sub, extra;
std::string driver_version;
std::string api_version;
if (api_level == GST_DECKLINK2_API_LEVEL_UNKNOWN)
return;
api_version = gst_decklink2_api_level_to_string (api_level);
gst_decklink2_get_api_version (&major, &minor, &sub, &extra);
driver_version = std::to_string (major) + "." + std::to_string (minor)
+ "." + std::to_string (sub) + "." + std::to_string (extra);
if (api_level == GST_DECKLINK2_API_LEVEL_10_11) {
iter = CreateDeckLinkIteratorInstance_v10_11 ();
} else {
iter = CreateDeckLinkIteratorInstance ();
}
if (!iter) {
GST_DEBUG ("Couldn't create device iterator");
return;
}
guint i = 0;
do {
IDeckLink *device = NULL;
GstDeckLink2Object *object;
hr = iter->Next (&device);
if (!gst_decklink2_result (hr))
break;
object = gst_decklink2_object_new (device, i, api_level,
api_version.c_str (), driver_version.c_str ());
device->Release ();
if (object)
device_list.push_back (object);
i++;
} while (gst_decklink2_result (hr));
iter->Release ();
std::sort (device_list.begin (), device_list.end (),
[](const GstDeckLink2Object * a, const GstDeckLink2Object * b)->bool
{
{
return a->persistent_id < b->persistent_id;
}
}
);
GST_DEBUG ("Found %u device", (guint) device_list.size ());
}
static void
gst_decklink2_device_init_once (void)
{
GST_DECKLINK2_CALL_ONCE_BEGIN {
std::lock_guard < std::mutex > lk (device_lock);
gst_decklink2_device_init ();
} GST_DECKLINK2_CALL_ONCE_END;
}
GstDeckLink2Input *
gst_decklink2_acquire_input (guint device_number, gint64 persistent_id)
{
GstDeckLink2Object *target = NULL;
gst_decklink2_device_init_once ();
std::lock_guard < std::mutex > lk (device_lock);
/* *INDENT-OFF* */
if (persistent_id != -1) {
auto object = std::find_if (device_list.begin (), device_list.end (),
[&](const GstDeckLink2Object * obj) {
return obj->persistent_id == persistent_id;
});
if (object == device_list.end ()) {
GST_WARNING ("Couldn't find object for persistent id %" G_GINT64_FORMAT,
persistent_id);
return NULL;
}
target = *object;
}
if (!target) {
auto object = std::find_if (device_list.begin (), device_list.end (),
[&](const GstDeckLink2Object * obj) {
return obj->device_number == device_number;
});
if (object == device_list.end ()) {
GST_WARNING ("Couldn't find object for device number %u", device_number);
return NULL;
}
target = *object;
}
/* *INDENT-ON* */
if (!target->input) {
GST_WARNING_OBJECT (target, "Device does not support input");
return NULL;
}
if (target->input_acquired) {
GST_WARNING_OBJECT (target, "Input was already acquired");
return NULL;
}
target->input_acquired = TRUE;
return (GstDeckLink2Input *) gst_object_ref (target->input);
}
GstDeckLink2Output *
gst_decklink2_acquire_output (guint device_number, gint64 persistent_id)
{
GstDeckLink2Object *target = NULL;
gst_decklink2_device_init_once ();
std::lock_guard < std::mutex > lk (device_lock);
/* *INDENT-OFF* */
if (persistent_id != -1) {
auto object = std::find_if (device_list.begin (), device_list.end (),
[&](const GstDeckLink2Object * obj) {
return obj->persistent_id == persistent_id;
});
if (object == device_list.end ()) {
GST_WARNING ("Couldn't find object for persistent id %" G_GINT64_FORMAT,
persistent_id);
return NULL;
}
target = *object;
}
if (!target) {
auto object = std::find_if (device_list.begin (), device_list.end (),
[&](const GstDeckLink2Object * obj) {
return obj->device_number == device_number;
});
if (object == device_list.end ()) {
GST_WARNING ("Couldn't find object for device number %u", device_number);
return NULL;
}
target = *object;
}
/* *INDENT-ON* */
if (!target->output) {
GST_WARNING_OBJECT (target, "Device does not support output");
return NULL;
}
if (target->output_acquired) {
GST_WARNING_OBJECT (target, "Output was already acquired");
return NULL;
}
target->output_acquired = TRUE;
return (GstDeckLink2Output *) gst_object_ref (target->output);
}
void
gst_decklink2_release_input (GstDeckLink2Input * input)
{
std::unique_lock < std::mutex > lk (device_lock);
auto object = std::find_if (device_list.begin (), device_list.end (),
[&](const GstDeckLink2Object * obj) {
return obj->input == input;
}
);
if (object == device_list.end ()) {
GST_ERROR_OBJECT (input, "Couldn't find parent object");
} else {
(*object)->input_acquired = FALSE;
}
lk.unlock ();
gst_object_unref (input);
}
void
gst_decklink2_release_output (GstDeckLink2Output * output)
{
std::unique_lock < std::mutex > lk (device_lock);
auto object = std::find_if (device_list.begin (), device_list.end (),
[&](const GstDeckLink2Object * obj) {
return obj->output == output;
}
);
if (object == device_list.end ()) {
GST_ERROR_OBJECT (output, "Couldn't find parent object");
} else {
(*object)->output_acquired = FALSE;
}
lk.unlock ();
gst_object_unref (output);
}
void
gst_decklink2_object_deinit (void)
{
std::lock_guard < std::mutex > lk (device_lock);
/* *INDENT-OFF* */
for (auto iter : device_list)
gst_object_unref (iter);
/* *INDENT-ON* */
device_list.clear ();
}
GList *
gst_decklink2_get_devices (void)
{
GList *list = NULL;
gst_decklink2_device_init_once ();
std::lock_guard < std::mutex > lk (device_lock);
/* *INDENT-OFF* */
for (auto iter : device_list) {
if (iter->input_device)
list = g_list_append (list, gst_object_ref (iter->input_device));
if (iter->output_device)
list = g_list_append (list, gst_object_ref (iter->output_device));
}
/* *INDENT-ON* */
return list;
}
static HRESULT
gst_decklink2_set_duplex_mode (gint64 persistent_id, BMDDuplexMode_v10_11 mode)
{
GstDeckLink2Object *object = NULL;
HRESULT hr = E_FAIL;
dlbool_t duplex_supported = FALSE;
std::lock_guard < std::mutex > lk (device_lock);
/* *INDENT-OFF* */
for (auto iter : device_list) {
if (iter->persistent_id == persistent_id) {
object = iter;
break;
}
}
/* *INDENT-ON* */
if (!object) {
GST_ERROR ("Couldn't find device for persistent id %" G_GINT64_FORMAT,
persistent_id);
return E_FAIL;
}
if (!object->attr_10_11 || !object->config_10_11) {
GST_WARNING_OBJECT (object,
"Couldn't set duplex mode, missing required interface");
return E_FAIL;
}
hr = object->attr_10_11->GetFlag ((BMDDeckLinkAttributeID)
BMDDeckLinkSupportsDuplexModeConfiguration_v10_11, &duplex_supported);
if (!gst_decklink2_result (hr)) {
GST_WARNING_OBJECT (object, "Couldn't query duplex mode support");
return hr;
}
if (!duplex_supported) {
GST_WARNING_OBJECT (object, "Duplex mode is not supported");
return E_FAIL;
}
return object->config_10_11->SetInt ((BMDDeckLinkConfigurationID)
bmdDeckLinkConfigDuplexMode_v10_11, mode);
}
HRESULT
gst_decklink2_object_set_profile_id (GstDeckLink2Object * object,
BMDProfileID profile_id)
{
gchar *profile_id_str = NULL;
HRESULT hr = E_FAIL;
g_return_val_if_fail (GST_IS_DECKLINK2_OBJECT (object), E_INVALIDARG);
if (profile_id == bmdProfileDefault)
return S_OK;
profile_id_str = g_enum_to_string (GST_TYPE_DECKLINK2_PROFILE_ID, profile_id);
GST_DEBUG_OBJECT (object, "Setting profile id \"%s\"", profile_id_str);
if (object->api_level == GST_DECKLINK2_API_LEVEL_10_11) {
dlbool_t duplex_supported = FALSE;
BMDDuplexMode_v10_11 duplex_mode = bmdDuplexModeHalf_v10_11;
if (!object->attr_10_11 || !object->config_10_11) {
GST_WARNING_OBJECT (object,
"Couldn't set duplex mode, missing required interface");
hr = E_FAIL;
goto out;
}
switch (profile_id) {
case bmdProfileOneSubDeviceHalfDuplex:
case bmdProfileTwoSubDevicesHalfDuplex:
case bmdProfileFourSubDevicesHalfDuplex:
duplex_mode = bmdDuplexModeHalf_v10_11;
GST_DEBUG_OBJECT (object, "Mapping \"%s\" to bmdDuplexModeHalf");
break;
default:
GST_DEBUG_OBJECT (object, "Mapping \"%s\" to bmdDuplexModeFull");
duplex_mode = bmdDuplexModeFull_v10_11;
break;
}
hr = object->attr_10_11->GetFlag ((BMDDeckLinkAttributeID)
BMDDeckLinkSupportsDuplexModeConfiguration_v10_11, &duplex_supported);
if (!gst_decklink2_result (hr))
duplex_supported = FALSE;
if (!duplex_supported) {
gint64 paired_device_id = 0;
if (duplex_mode == bmdDuplexModeFull_v10_11) {
GST_WARNING_OBJECT (object, "Device does not support Full-Duplex-Mode");
goto out;
}
hr = object->attr_10_11->GetInt ((BMDDeckLinkAttributeID)
BMDDeckLinkPairedDevicePersistentID_v10_11, &paired_device_id);
if (gst_decklink2_result (hr)) {
GST_DEBUG_OBJECT (object,
"Device has paired device, Setting duplex mode to paired device");
hr = gst_decklink2_set_duplex_mode (paired_device_id, duplex_mode);
} else {
GST_WARNING_OBJECT (object, "Device does not support Half-Duplex-Mode");
}
} else {
hr = object->config_10_11->SetInt ((BMDDeckLinkConfigurationID)
bmdDeckLinkConfigDuplexMode_v10_11, duplex_mode);
}
if (!gst_decklink2_result (hr)) {
GST_WARNING_OBJECT (object,
"Couldn't set profile \"%s\"", profile_id_str);
} else {
GST_DEBUG_OBJECT (object, "Profile \"%s\" is configured", profile_id_str);
}
} else {
IDeckLinkProfile *profile = NULL;
if (!object->profile_manager) {
GST_WARNING_OBJECT (object,
"Profile \"%s\" is requested but profile manager is not available",
profile_id_str);
goto out;
}
hr = object->profile_manager->GetProfile (profile_id, &profile);
if (gst_decklink2_result (hr)) {
hr = profile->SetActive ();
profile->Release ();
}
if (!gst_decklink2_result (hr)) {
GST_WARNING_OBJECT (object,
"Couldn't set profile \"%s\"", profile_id_str);
} else {
GST_DEBUG_OBJECT (object, "Profile \"%s\" is configured", profile_id_str);
}
}
out:
g_free (profile_id_str);
return hr;
}

View file

@ -0,0 +1,51 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include "gstdecklink2utils.h"
#include "gstdecklink2input.h"
#include "gstdecklink2output.h"
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_OBJECT (gst_decklink2_object_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Object, gst_decklink2_object,
GST, DECKLINK2_OBJECT, GstObject);
GstDeckLink2Input * gst_decklink2_acquire_input (guint device_number,
gint64 persistent_id);
GstDeckLink2Output * gst_decklink2_acquire_output (guint device_number,
gint64 persistent_id);
void gst_decklink2_release_input (GstDeckLink2Input * input);
void gst_decklink2_release_output (GstDeckLink2Output * output);
void gst_decklink2_object_deinit (void);
GList * gst_decklink2_get_devices (void);
HRESULT gst_decklink2_object_set_profile_id (GstDeckLink2Object * object,
BMDProfileID profile_id);
G_END_DECLS

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include "gstdecklink2utils.h"
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_OUTPUT (gst_decklink2_output_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Output, gst_decklink2_output,
GST, DECKLINK2_OUTPUT, GstObject);
GstDeckLink2Output * gst_decklink2_output_new (IDeckLink * device,
GstDeckLink2APILevel api_level);
GstCaps * gst_decklink2_output_get_caps (GstDeckLink2Output * output,
BMDDisplayMode mode,
BMDPixelFormat format);
gboolean gst_decklink2_output_get_display_mode (GstDeckLink2Output * output,
const GstVideoInfo * info,
GstDeckLink2DisplayMode * display_mode);
guint gst_decklink2_output_get_max_audio_channels (GstDeckLink2Output * output);
HRESULT gst_decklink2_output_configure (GstDeckLink2Output * output,
guint n_preroll_frames,
guint min_buffered,
guint max_buffered,
const GstDeckLink2DisplayMode * display_mode,
BMDVideoOutputFlags output_flags,
BMDProfileID profile_id,
GstDeckLink2KeyerMode keyer_mode,
guint8 keyer_level,
GstDeckLink2MappingFormat mapping_format,
BMDAudioSampleType audio_sample_type,
guint audio_channels);
IDeckLinkVideoFrame * gst_decklink2_output_upload (GstDeckLink2Output * output,
const GstVideoInfo * info,
GstBuffer * buffer,
gint caption_line,
gint afd_bar_line);
HRESULT gst_decklink2_output_schedule_stream (GstDeckLink2Output * output,
IDeckLinkVideoFrame * frame,
guint8 *audio_buf,
gsize audio_buf_size);
HRESULT gst_decklink2_output_stop (GstDeckLink2Output * output);
G_END_DECLS

View file

@ -0,0 +1,901 @@
/*
* GStreamer
* Copyright (C) 2021 Mathieu Duponchelle <mathieu@centricular.com>
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdecklink2sink.h"
#include "gstdecklink2utils.h"
#include "gstdecklink2object.h"
#include <string.h>
#include <mutex>
GST_DEBUG_CATEGORY_STATIC (gst_decklink2_sink_debug);
#define GST_CAT_DEFAULT gst_decklink2_sink_debug
enum
{
PROP_0,
PROP_MODE,
PROP_DEVICE_NUMBER,
PROP_VIDEO_FORMAT,
PROP_PROFILE_ID,
PROP_TIMECODE_FORMAT,
PROP_KEYER_MODE,
PROP_KEYER_LEVEL,
PROP_CC_LINE,
PROP_AFD_BAR_LINE,
PROP_MAPPING_FORMAT,
PROP_PERSISTENT_ID,
PROP_N_PREROLL_FRAMES,
PROP_MIN_BUFFERED_FRAMES,
PROP_MAX_BUFFERED_FRAMES,
};
#define DEFAULT_MODE bmdModeUnknown
#define DEFAULT_DEVICE_NUMBER 0
#define DEFAULT_PERSISTENT_ID -1
#define DEFAULT_VIDEO_FORMAT bmdFormat8BitYUV
#define DEFAULT_PROFILE_ID bmdProfileDefault
#define DEFAULT_TIMECODE_FORMAT bmdTimecodeRP188Any
#define DEFAULT_KEYER_MODE GST_DECKLINK2_KEYER_MODE_OFF
#define DEFAULT_KEYER_LEVEL 255
#define DEFAULT_CC_LINE 0
#define DEFAULT_AFD_BAR_LINE 0
#define DEFAULT_MAPPING_FORMAT GST_DECKLINK2_MAPPING_FORMAT_DEFAULT
#define DEFAULT_N_PREROLL_FRAMES 7
#define DEFAULT_MIN_BUFFERED_FRAMES 3
#define DEFAULT_MAX_BUFFERED_FRAMES 14
struct GstDeckLink2SinkPrivate
{
std::mutex lock;
};
struct _GstDeckLink2Sink
{
GstBaseSink parent;
GstDeckLink2SinkPrivate *priv;
GstDeckLink2Output *output;
GstVideoInfo video_info;
GstDeckLink2DisplayMode selected_mode;
BMDAudioSampleType audio_sample_type;
gint audio_channels;
gboolean configured;
GstBufferPool *fallback_pool;
IDeckLinkVideoFrame *prepared_frame;
/* properties */
BMDDisplayMode display_mode;
gint device_number;
gint64 persistent_id;
BMDPixelFormat video_format;
BMDProfileID profile_id;
BMDTimecodeFormat timecode_format;
GstDeckLink2KeyerMode keyer_mode;
gint keyer_level;
gint caption_line;
gint afd_bar_line;
GstDeckLink2MappingFormat mapping_format;
guint n_preroll_frames;
guint min_buffered_frames;
guint max_buffered_frames;
};
static void gst_decklink2_sink_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_decklink2_sink_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static void gst_decklink2_sink_finalize (GObject * object);
static gboolean gst_decklink2_sink_query (GstBaseSink * sink, GstQuery * query);
static GstCaps *gst_decklink2_sink_get_caps (GstBaseSink * sink,
GstCaps * filter);
static gboolean gst_decklink2_sink_set_caps (GstBaseSink * sink,
GstCaps * caps);
static gboolean gst_decklink2_sink_propose_allocation (GstBaseSink * sink,
GstQuery * query);
static gboolean gst_decklink2_sink_start (GstBaseSink * sink);
static gboolean gst_decklink2_sink_stop (GstBaseSink * sink);
static gboolean gst_decklink2_sink_unlock_stop (GstBaseSink * sink);
static GstFlowReturn gst_decklink2_sink_prepare (GstBaseSink * sink,
GstBuffer * buffer);
static GstFlowReturn gst_decklink2_sink_render (GstBaseSink * sink,
GstBuffer * buffer);
#define gst_decklink2_sink_parent_class parent_class
G_DEFINE_TYPE (GstDeckLink2Sink, gst_decklink2_sink, GST_TYPE_BASE_SINK);
GST_ELEMENT_REGISTER_DEFINE (decklink2sink, "decklink2sink",
GST_RANK_NONE, GST_TYPE_DECKLINK2_SINK);
static void
gst_decklink2_sink_class_init (GstDeckLink2SinkClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
GParamFlags param_flags = (GParamFlags) (G_PARAM_READWRITE |
GST_PARAM_MUTABLE_READY | G_PARAM_STATIC_STRINGS);
object_class->finalize = gst_decklink2_sink_finalize;
object_class->set_property = gst_decklink2_sink_set_property;
object_class->get_property = gst_decklink2_sink_get_property;
g_object_class_install_property (object_class, PROP_MODE,
g_param_spec_enum ("mode", "Playback Mode",
"Video Mode to use for playback",
GST_TYPE_DECKLINK2_MODE, DEFAULT_MODE, param_flags));
g_object_class_install_property (object_class, PROP_DEVICE_NUMBER,
g_param_spec_int ("device-number", "Device number",
"Output device instance to use", 0, G_MAXINT, DEFAULT_DEVICE_NUMBER,
param_flags));
g_object_class_install_property (object_class, PROP_PERSISTENT_ID,
g_param_spec_int64 ("persistent-id", "Persistent id",
"Output device instance to use. Higher priority than \"device-number\".",
DEFAULT_PERSISTENT_ID, G_MAXINT64, DEFAULT_PERSISTENT_ID,
param_flags));
g_object_class_install_property (object_class, PROP_VIDEO_FORMAT,
g_param_spec_enum ("video-format", "Video format",
"Video format type to use for playback",
GST_TYPE_DECKLINK2_VIDEO_FORMAT, DEFAULT_VIDEO_FORMAT, param_flags));
g_object_class_install_property (object_class, PROP_PROFILE_ID,
g_param_spec_enum ("profile", "Profile",
"Certain DeckLink devices such as the DeckLink 8K Pro, the DeckLink "
"Quad 2 and the DeckLink Duo 2 support multiple profiles to "
"configure the capture and playback behavior of its sub-devices."
"For the DeckLink Duo 2 and DeckLink Quad 2, a profile is shared "
"between any 2 sub-devices that utilize the same connectors. For the "
"DeckLink 8K Pro, a profile is shared between all 4 sub-devices. Any "
"sub-devices that share a profile are considered to be part of the "
"same profile group."
"DeckLink Duo 2 support configuration of the duplex mode of "
"individual sub-devices.",
GST_TYPE_DECKLINK2_PROFILE_ID, DEFAULT_PROFILE_ID, param_flags));
g_object_class_install_property (object_class, PROP_TIMECODE_FORMAT,
g_param_spec_enum ("timecode-format", "Timecode format",
"Timecode format type to use for playback",
GST_TYPE_DECKLINK2_TIMECODE_FORMAT, DEFAULT_TIMECODE_FORMAT,
param_flags));
g_object_class_install_property (object_class, PROP_KEYER_MODE,
g_param_spec_enum ("keyer-mode", "Keyer mode",
"Keyer mode to be enabled",
GST_TYPE_DECKLINK2_KEYER_MODE, DEFAULT_KEYER_MODE, param_flags));
g_object_class_install_property (object_class, PROP_KEYER_LEVEL,
g_param_spec_int ("keyer-level", "Keyer level",
"Keyer level", 0, 255, DEFAULT_KEYER_LEVEL, param_flags));
g_object_class_install_property (object_class, PROP_CC_LINE,
g_param_spec_int ("cc-line", "CC Line",
"Line number to use for inserting closed captions (0 = disabled)",
0, 22, DEFAULT_CC_LINE, param_flags));
g_object_class_install_property (object_class, PROP_AFD_BAR_LINE,
g_param_spec_int ("afd-bar-line", "AFD/Bar Line",
"Line number to use for inserting AFD/Bar data (0 = disabled)",
0, 10000, DEFAULT_AFD_BAR_LINE, param_flags));
g_object_class_install_property (object_class, PROP_MAPPING_FORMAT,
g_param_spec_enum ("mapping-format", "3G-SDI Mapping Format",
"3G-SDI Mapping Format (Level A/B)",
GST_TYPE_DECKLINK2_MAPPING_FORMAT, DEFAULT_MAPPING_FORMAT,
param_flags));
g_object_class_install_property (object_class, PROP_N_PREROLL_FRAMES,
g_param_spec_int ("n-preroll-frames", "Number of preroll frames",
"How many frames to preroll before starting scheduled playback",
0, 16, DEFAULT_N_PREROLL_FRAMES, param_flags));
g_object_class_install_property (object_class, PROP_MIN_BUFFERED_FRAMES,
g_param_spec_int ("min-buffered-frames", "Min number of buffered frames",
"Min number of frames to buffer before duplicating",
0, 16, DEFAULT_MIN_BUFFERED_FRAMES, param_flags));
g_object_class_install_property (object_class, PROP_MAX_BUFFERED_FRAMES,
g_param_spec_int ("max-buffered-frames", "Max number of buffered frames",
"Max number of frames to buffer before dropping",
0, 16, DEFAULT_MAX_BUFFERED_FRAMES, param_flags));
GstCaps *templ_caps = gst_decklink2_get_default_template_caps ();
templ_caps = gst_caps_make_writable (templ_caps);
GValue ch_list = G_VALUE_INIT;
gint ch[] = { 0, 2, 8, 16 };
g_value_init (&ch_list, GST_TYPE_LIST);
for (guint i = 0; i < G_N_ELEMENTS (ch); i++) {
GValue ch_val = G_VALUE_INIT;
g_value_init (&ch_val, G_TYPE_INT);
g_value_set_int (&ch_val, ch[i]);
gst_value_list_append_and_take_value (&ch_list, &ch_val);
}
gst_caps_set_value (templ_caps, "audio-channels", &ch_list);
g_value_unset (&ch_list);
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,
"Decklink2 Sink", "Video/Audio/Sink/Hardware", "Decklink2 Sink",
"Seungha Yang <seungha@centricular.com>");
basesink_class->query = GST_DEBUG_FUNCPTR (gst_decklink2_sink_query);
basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_decklink2_sink_get_caps);
basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_decklink2_sink_set_caps);
basesink_class->propose_allocation =
GST_DEBUG_FUNCPTR (gst_decklink2_sink_propose_allocation);
basesink_class->start = GST_DEBUG_FUNCPTR (gst_decklink2_sink_start);
basesink_class->stop = GST_DEBUG_FUNCPTR (gst_decklink2_sink_stop);
basesink_class->unlock_stop =
GST_DEBUG_FUNCPTR (gst_decklink2_sink_unlock_stop);
basesink_class->prepare = GST_DEBUG_FUNCPTR (gst_decklink2_sink_prepare);
basesink_class->render = GST_DEBUG_FUNCPTR (gst_decklink2_sink_render);
GST_DEBUG_CATEGORY_INIT (gst_decklink2_sink_debug, "decklink2sink",
0, "decklink2sink");
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_MODE, (GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_VIDEO_FORMAT,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_PROFILE_ID,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_TIMECODE_FORMAT,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_KEYER_MODE,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_MAPPING_FORMAT,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_MAPPING_FORMAT,
(GstPluginAPIFlags) 0);
}
static void
gst_decklink2_sink_init (GstDeckLink2Sink * self)
{
self->display_mode = DEFAULT_MODE;
self->device_number = DEFAULT_DEVICE_NUMBER;
self->persistent_id = DEFAULT_PERSISTENT_ID;
self->video_format = DEFAULT_VIDEO_FORMAT;
self->profile_id = DEFAULT_PROFILE_ID;
self->timecode_format = DEFAULT_TIMECODE_FORMAT;
self->keyer_mode = DEFAULT_KEYER_MODE;
self->keyer_level = DEFAULT_KEYER_LEVEL;
self->caption_line = DEFAULT_CC_LINE;
self->afd_bar_line = DEFAULT_AFD_BAR_LINE;
self->mapping_format = DEFAULT_MAPPING_FORMAT;
self->n_preroll_frames = DEFAULT_N_PREROLL_FRAMES;
self->min_buffered_frames = DEFAULT_MIN_BUFFERED_FRAMES;
self->max_buffered_frames = DEFAULT_MAX_BUFFERED_FRAMES;
self->priv = new GstDeckLink2SinkPrivate ();
}
static void
gst_decklink2_sink_finalize (GObject * object)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (object);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_decklink2_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (object);
GstDeckLink2SinkPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_MODE:
self->display_mode = (BMDDisplayMode) g_value_get_enum (value);
break;
case PROP_DEVICE_NUMBER:
self->device_number = g_value_get_int (value);
break;
case PROP_PERSISTENT_ID:
self->persistent_id = g_value_get_int64 (value);
break;
case PROP_VIDEO_FORMAT:
self->video_format = (BMDPixelFormat) g_value_get_enum (value);
break;
case PROP_PROFILE_ID:
self->profile_id = (BMDProfileID) g_value_get_enum (value);
break;
case PROP_TIMECODE_FORMAT:
self->timecode_format = (BMDTimecodeFormat) g_value_get_enum (value);
break;
case PROP_KEYER_MODE:
self->keyer_mode = (GstDeckLink2KeyerMode) g_value_get_enum (value);
break;
case PROP_KEYER_LEVEL:
self->keyer_level = g_value_get_int (value);
break;
case PROP_CC_LINE:
self->caption_line = g_value_get_int (value);
break;
case PROP_AFD_BAR_LINE:
self->afd_bar_line = g_value_get_int (value);
break;
case PROP_MAPPING_FORMAT:
self->mapping_format =
(GstDeckLink2MappingFormat) g_value_get_enum (value);
break;
case PROP_N_PREROLL_FRAMES:
self->n_preroll_frames = g_value_get_int (value);
break;
case PROP_MIN_BUFFERED_FRAMES:
self->min_buffered_frames = g_value_get_int (value);
break;
case PROP_MAX_BUFFERED_FRAMES:
self->max_buffered_frames = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_decklink2_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (object);
GstDeckLink2SinkPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_MODE:
g_value_set_enum (value, self->display_mode);
break;
case PROP_DEVICE_NUMBER:
g_value_set_int (value, self->device_number);
break;
case PROP_PERSISTENT_ID:
g_value_set_int64 (value, self->persistent_id);
break;
case PROP_VIDEO_FORMAT:
g_value_set_enum (value, self->video_format);
break;
case PROP_PROFILE_ID:
g_value_set_enum (value, self->profile_id);
break;
case PROP_TIMECODE_FORMAT:
g_value_set_enum (value, self->timecode_format);
break;
case PROP_KEYER_MODE:
g_value_set_enum (value, self->keyer_mode);
break;
case PROP_KEYER_LEVEL:
g_value_set_int (value, self->keyer_level);
break;
case PROP_CC_LINE:
g_value_set_int (value, self->caption_line);
break;
case PROP_AFD_BAR_LINE:
g_value_set_int (value, self->afd_bar_line);
break;
case PROP_MAPPING_FORMAT:
g_value_set_enum (value, self->mapping_format);
break;
case PROP_N_PREROLL_FRAMES:
g_value_set_int (value, self->n_preroll_frames);
break;
case PROP_MIN_BUFFERED_FRAMES:
g_value_set_int (value, self->min_buffered_frames);
break;
case PROP_MAX_BUFFERED_FRAMES:
g_value_set_int (value, self->max_buffered_frames);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
gst_decklink2_sink_query (GstBaseSink * sink, GstQuery * query)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_ACCEPT_CAPS:
{
GstCaps *caps, *allowed;
gboolean can_intercept;
gst_query_parse_accept_caps (query, &caps);
allowed = gst_decklink2_sink_get_caps (sink, NULL);
can_intercept = gst_caps_can_intersect (caps, allowed);
GST_DEBUG_OBJECT (self, "Checking if requested caps %" GST_PTR_FORMAT
" are intersectable of pad caps %" GST_PTR_FORMAT " result %d", caps,
allowed, can_intercept);
gst_caps_unref (allowed);
gst_query_set_accept_caps_result (query, can_intercept);
return TRUE;
}
default:
break;
}
return GST_BASE_SINK_CLASS (parent_class)->query (sink, query);
}
static GstCaps *
gst_decklink2_sink_get_caps (GstBaseSink * sink, GstCaps * filter)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
GstDeckLink2SinkPrivate *priv = self->priv;
GstCaps *caps;
GstCaps *ret;
std::lock_guard < std::mutex > lk (priv->lock);
if (!self->output) {
GST_DEBUG_OBJECT (self, "Output is not configured yet");
caps = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (self));
} else {
caps = gst_decklink2_output_get_caps (self->output, self->display_mode,
self->video_format);
}
if (!caps) {
GST_WARNING_OBJECT (self, "Couldn't get caps");
caps = gst_caps_new_empty ();
} else if (self->output) {
guint max_ch;
GValue ch_list = G_VALUE_INIT;
GValue ch_val = G_VALUE_INIT;
max_ch = gst_decklink2_output_get_max_audio_channels (self->output);
caps = gst_caps_make_writable (caps);
g_value_init (&ch_list, GST_TYPE_LIST);
g_value_init (&ch_val, G_TYPE_INT);
g_value_set_int (&ch_val, 0);
gst_value_list_append_and_take_value (&ch_list, &ch_val);
g_value_init (&ch_val, G_TYPE_INT);
g_value_set_int (&ch_val, 2);
gst_value_list_append_and_take_value (&ch_list, &ch_val);
if (max_ch >= 8) {
g_value_init (&ch_val, G_TYPE_INT);
g_value_set_int (&ch_val, 8);
gst_value_list_append_and_take_value (&ch_list, &ch_val);
}
if (max_ch >= 16) {
g_value_init (&ch_val, G_TYPE_INT);
g_value_set_int (&ch_val, 16);
gst_value_list_append_and_take_value (&ch_list, &ch_val);
}
gst_caps_set_value (caps, "audio-channels", &ch_list);
g_value_unset (&ch_list);
}
if (filter) {
ret = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
} else {
ret = caps;
}
GST_LOG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, ret);
return ret;
}
static gboolean
gst_decklink2_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
HRESULT hr;
GstVideoInfo info;
GstDeckLink2DisplayMode mode;
GstStructure *config;
GstAllocationParams params = { (GstMemoryFlags) 0, 15, 0, 0 };
GstStructure *s;
GstAudioFormat audio_format = GST_AUDIO_FORMAT_UNKNOWN;
const gchar *audio_format_str;
gint audio_channels = 0;
BMDAudioSampleType audio_sample_type = bmdAudioSampleType16bitInteger;
GST_DEBUG_OBJECT (self, "Set caps %" GST_PTR_FORMAT, caps);
if (!self->output) {
GST_ERROR_OBJECT (self, "output has not been configured yet");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_ERROR_OBJECT (self, "Invalid caps");
return FALSE;
}
if (!gst_decklink2_output_get_display_mode (self->output, &info, &mode)) {
GST_ERROR_OBJECT (self, "Couldn't get display mode");
return FALSE;
}
self->video_info = info;
s = gst_caps_get_structure (caps, 0);
audio_format_str = gst_structure_get_string (s, "audio-format");
if (audio_format_str)
audio_format = gst_audio_format_from_string (audio_format_str);
gst_structure_get_int (s, "audio-channels", &audio_channels);
if (audio_format == GST_AUDIO_FORMAT_S16LE) {
audio_sample_type = bmdAudioSampleType16bitInteger;
} else if (audio_format == GST_AUDIO_FORMAT_S32LE) {
audio_sample_type = bmdAudioSampleType32bitInteger;
} else {
audio_channels = 0;
}
if (self->configured) {
if (self->selected_mode.mode == mode.mode &&
self->audio_sample_type == audio_sample_type &&
self->audio_channels == audio_channels) {
return TRUE;
}
GST_DEBUG_OBJECT (self, "Configuration changed");
gst_decklink2_output_stop (self->output);
self->configured = FALSE;
}
self->selected_mode = mode;
self->audio_sample_type = audio_sample_type;
self->audio_channels = audio_channels;
/* The timecode_format itself is used when we embed the actual timecode data
* into the frame. Now we only need to know which of the two standards the
* timecode format will adhere to: VITC or RP188, and send the appropriate
* flag to EnableVideoOutput. The exact format is specified later.
*
* Note that this flag will have no effect in practice if the video stream
* does not contain timecode metadata.
*/
BMDVideoOutputFlags output_flags;
if (self->timecode_format == bmdTimecodeVITC ||
self->timecode_format == bmdTimecodeVITCField2) {
output_flags = bmdVideoOutputVITC;
} else {
output_flags = bmdVideoOutputRP188;
}
if (self->caption_line > 0 || self->afd_bar_line > 0)
output_flags = (BMDVideoOutputFlags) (output_flags | bmdVideoOutputVANC);
GST_DEBUG_OBJECT (self, "Configuring output, mode %" GST_FOURCC_FORMAT
", audio-sample-type %d, audio-channles %d",
GST_DECKLINK2_FOURCC_ARGS (self->selected_mode.mode),
self->audio_sample_type, self->audio_channels);
hr = gst_decklink2_output_configure (self->output, self->n_preroll_frames,
self->min_buffered_frames, self->max_buffered_frames,
&self->selected_mode, output_flags, self->profile_id, self->keyer_mode,
(guint8) self->keyer_level, self->mapping_format,
self->audio_sample_type, self->audio_channels);
if (hr != S_OK) {
GST_ERROR_OBJECT (self, "Couldn't configure output");
return FALSE;
}
if (self->fallback_pool) {
gst_buffer_pool_set_active (self->fallback_pool, FALSE);
gst_object_unref (self->fallback_pool);
}
self->fallback_pool = gst_video_buffer_pool_new ();
config = gst_buffer_pool_get_config (self->fallback_pool);
gst_buffer_pool_config_set_params (config, caps, info.size, 0, 0);
gst_buffer_pool_config_set_allocator (config, NULL, &params);
if (!gst_buffer_pool_set_config (self->fallback_pool, config)) {
GST_ERROR_OBJECT (self, "Couldn't set pool config");
goto error;
}
if (!gst_buffer_pool_set_active (self->fallback_pool, TRUE)) {
GST_ERROR_OBJECT (self, "Couldn't set active state to pool");
goto error;
}
self->configured = TRUE;
return TRUE;
error:
gst_clear_object (&self->fallback_pool);
return FALSE;
}
static gboolean
gst_decklink2_sink_propose_allocation (GstBaseSink * sink, GstQuery * query)
{
GstCaps *caps;
GstVideoInfo info;
GstBufferPool *pool;
guint size;
gst_query_parse_allocation (query, &caps, NULL);
if (!caps)
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, &params);
else
gst_query_add_allocation_param (query, allocator, &params);
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, &params);
if (allocator)
gst_object_unref (allocator);
if (!gst_buffer_pool_set_config (pool, structure)) {
GST_ERROR_OBJECT (sink, "Couldn't set pool config");
gst_object_unref (pool);
return FALSE;
}
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;
}
static gboolean
gst_decklink2_sink_start (GstBaseSink * sink)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
GstDeckLink2SinkPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
GST_DEBUG_OBJECT (self, "Start");
self->output = gst_decklink2_acquire_output (self->device_number,
self->persistent_id);
if (!self->output) {
GST_ERROR_OBJECT (self, "Couldn't acquire output object");
return FALSE;
}
if (self->n_preroll_frames < self->min_buffered_frames ||
self->n_preroll_frames > self->max_buffered_frames ||
self->max_buffered_frames < self->min_buffered_frames) {
GST_WARNING_OBJECT (self, "Invalid buffering configuration");
self->n_preroll_frames = DEFAULT_N_PREROLL_FRAMES;
self->min_buffered_frames = DEFAULT_MIN_BUFFERED_FRAMES;
self->max_buffered_frames = DEFAULT_MAX_BUFFERED_FRAMES;
}
memset (&self->selected_mode, 0, sizeof (GstDeckLink2DisplayMode));
self->audio_sample_type = bmdAudioSampleType16bitInteger;
self->audio_channels = 0;
self->configured = FALSE;
return TRUE;
}
static gboolean
gst_decklink2_sink_stop (GstBaseSink * sink)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
GstDeckLink2SinkPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
GST_DECKLINK2_CLEAR_COM (self->prepared_frame);
if (self->output) {
gst_decklink2_output_stop (self->output);
gst_decklink2_release_output (self->output);
self->output = NULL;
}
if (self->fallback_pool) {
gst_buffer_pool_set_active (self->fallback_pool, FALSE);
gst_clear_object (&self->fallback_pool);
}
return TRUE;
}
static gboolean
gst_decklink2_sink_unlock_stop (GstBaseSink * sink)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
GST_DECKLINK2_CLEAR_COM (self->prepared_frame);
return TRUE;
}
static gboolean
buffer_is_pbo_memory (GstBuffer * buffer)
{
GstMemory *mem;
mem = gst_buffer_peek_memory (buffer, 0);
if (mem->allocator
&& g_strcmp0 (mem->allocator->mem_type, "GLMemoryPBO") == 0)
return TRUE;
return FALSE;
}
static IDeckLinkVideoFrame *
gst_decklink2_sink_upload_frame (GstDeckLink2Sink * self, GstBuffer * buffer)
{
GstBuffer *uploaded_buffer = buffer;
if (buffer_is_pbo_memory (buffer)) {
GstVideoFrame other_frame;
GstVideoFrame vframe;
GstBuffer *fallback;
if (!gst_video_frame_map (&vframe, &self->video_info, buffer, GST_MAP_READ)) {
GST_ERROR_OBJECT (self, "Failed to map video frame");
return NULL;
}
GstFlowReturn ret = gst_buffer_pool_acquire_buffer (self->fallback_pool,
&fallback, NULL);
if (ret != GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Couldn't acquire fallback buffer");
gst_video_frame_unmap (&vframe);
return NULL;
}
if (!gst_video_frame_map (&other_frame,
&self->video_info, fallback, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (self, "Couldn't map fallback buffer");
gst_video_frame_unmap (&vframe);
gst_buffer_unref (fallback);
return NULL;
}
if (!gst_video_frame_copy (&other_frame, &vframe)) {
GST_ERROR_OBJECT (self, "Couldn't copy to fallback buffer");
gst_video_frame_unmap (&vframe);
gst_video_frame_unmap (&other_frame);
gst_buffer_unref (fallback);
return NULL;
}
gst_video_frame_unmap (&vframe);
gst_video_frame_unmap (&other_frame);
gst_buffer_copy_into (fallback, buffer, GST_BUFFER_COPY_META, 0, -1);
uploaded_buffer = fallback;
}
IDeckLinkVideoFrame *frame =
gst_decklink2_output_upload (self->output, &self->video_info,
uploaded_buffer, self->caption_line, self->afd_bar_line);
/* frame will hold buffer */
if (uploaded_buffer != buffer)
gst_buffer_unref (uploaded_buffer);
return frame;
}
static GstFlowReturn
gst_decklink2_sink_prepare (GstBaseSink * sink, GstBuffer * buffer)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
GST_DECKLINK2_CLEAR_COM (self->prepared_frame);
self->prepared_frame = gst_decklink2_sink_upload_frame (self, buffer);
if (!self->prepared_frame) {
GST_ERROR_OBJECT (self, "Couldn't upload frame");
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
static GstFlowReturn
gst_decklink2_sink_render (GstBaseSink * sink, GstBuffer * buffer)
{
GstDeckLink2Sink *self = GST_DECKLINK2_SINK (sink);
HRESULT hr;
GstBuffer *audio_buf = NULL;
GstMapInfo info;
guint8 *audio_data = NULL;
gsize audio_data_size = 0;
if (!self->prepared_frame) {
GST_ERROR_OBJECT (self, "No prepared frame");
return GST_FLOW_ERROR;
}
if (self->audio_channels > 0) {
GstDeckLink2AudioMeta *meta = gst_buffer_get_decklink2_audio_meta (buffer);
if (meta)
audio_buf = gst_sample_get_buffer (meta->sample);
if (audio_buf) {
if (!gst_buffer_map (audio_buf, &info, GST_MAP_READ)) {
GST_ERROR_OBJECT (self, "Couldn't map audio buffer");
return GST_FLOW_ERROR;
}
audio_data = info.data;
audio_data_size = info.size;
} else {
GST_DEBUG_OBJECT (self, "Received buffer without audio meta");
}
}
GST_LOG_OBJECT (self, "schedule frame %p with audio buffer size %"
G_GSIZE_FORMAT, self->prepared_frame, audio_data_size);
hr = gst_decklink2_output_schedule_stream (self->output,
self->prepared_frame, audio_data, audio_data_size);
if (audio_buf)
gst_buffer_unmap (audio_buf, &info);
if (hr != S_OK) {
GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL),
("Failed to schedule frame: 0x%x", (guint) hr));
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}

View file

@ -0,0 +1,34 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/base.h>
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_SINK (gst_decklink2_sink_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Sink, gst_decklink2_sink,
GST, DECKLINK2_SINK, GstBaseSink);
GST_ELEMENT_REGISTER_DECLARE (decklink2sink);
G_END_DECLS

View file

@ -0,0 +1,674 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdecklink2src.h"
#include "gstdecklink2utils.h"
#include "gstdecklink2object.h"
#include <mutex>
#include <string.h>
GST_DEBUG_CATEGORY_STATIC (gst_decklink2_src_debug);
#define GST_CAT_DEFAULT gst_decklink2_src_debug
enum
{
PROP_0,
PROP_MODE,
PROP_DEVICE_NUMBER,
PROP_PERSISTENT_ID,
PROP_VIDEO_CONNECTION,
PROP_AUDIO_CONNECTION,
PROP_VIDEO_FORMAT,
PROP_AUDIO_CHANNELS,
PROP_PROFILE_ID,
PROP_TIMECODE_FORMAT,
PROP_OUTPUT_CC,
PROP_OUTPUT_AFD_BAR,
PROP_BUFFER_SIZE,
PROP_SIGNAL,
};
#define DEFAULT_MODE bmdModeUnknown
#define DEFAULT_DEVICE_NUMBER 0
#define DEFAULT_PERSISTENT_ID -1
#define DEFAULT_VIDEO_CONNECTION bmdVideoConnectionUnspecified
#define DEFAULT_AUDIO_CONNECTION bmdAudioConnectionUnspecified
#define DEFAULT_VIDEO_FORMAT bmdFormat8BitYUV
#define DEFAULT_PROFILE_ID bmdProfileDefault
#define DEFAULT_TIMECODE_FORMAT bmdTimecodeRP188Any
#define DEFAULT_OUTPUT_CC FALSE
#define DEFAULT_OUTPUT_AFD_BAR FALSE
#define DEFAULT_BUFFER_SIZE 5
#define DEFAULT_AUDIO_CHANNELS GST_DECKLINK2_AUDIO_CHANNELS_2
struct GstDeckLink2SrcPrivate
{
std::mutex lock;
};
struct _GstDeckLink2Src
{
GstPushSrc parent;
GstDeckLink2SrcPrivate *priv;
GstVideoInfo video_info;
GstDeckLink2Input *input;
GstDeckLink2DisplayMode selected_mode;
GstCaps *selected_caps;
gboolean is_gap_buf;
gboolean running;
/* properties */
BMDDisplayMode display_mode;
gint device_number;
gint64 persistent_id;
BMDVideoConnection video_conn;
BMDAudioConnection audio_conn;
BMDPixelFormat video_format;
GstDeckLink2AudioChannels audio_channels;
BMDProfileID profile_id;
BMDTimecodeFormat timecode_format;
gboolean output_cc;
gboolean output_afd_bar;
guint buffer_size;
};
static void gst_decklink2_src_finalize (GObject * object);
static void gst_decklink2_src_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_decklink2_src_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static GstCaps *gst_decklink2_src_get_caps (GstBaseSrc * src, GstCaps * filter);
static gboolean gst_decklink2_src_set_caps (GstBaseSrc * src, GstCaps * caps);
static gboolean gst_decklink2_src_query (GstBaseSrc * src, GstQuery * query);
static gboolean gst_decklink2_src_start (GstBaseSrc * src);
static gboolean gst_decklink2_src_stop (GstBaseSrc * src);
static gboolean gst_decklink2_src_unlock (GstBaseSrc * src);
static gboolean gst_decklink2_src_unlock_stop (GstBaseSrc * src);
static GstFlowReturn gst_decklink2_src_create (GstPushSrc * src,
GstBuffer ** buffer);
#define gst_decklink2_src_parent_class parent_class
G_DEFINE_TYPE (GstDeckLink2Src, gst_decklink2_src, GST_TYPE_PUSH_SRC);
GST_ELEMENT_REGISTER_DEFINE (decklink2src, "decklink2src",
GST_RANK_NONE, GST_TYPE_DECKLINK2_SRC);
static void
gst_decklink2_src_class_init (GstDeckLink2SrcClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS (klass);
GstPushSrcClass *pushsrc_class = GST_PUSH_SRC_CLASS (klass);
GstCaps *templ_caps;
object_class->finalize = gst_decklink2_src_finalize;
object_class->set_property = gst_decklink2_src_set_property;
object_class->get_property = gst_decklink2_src_get_property;
gst_decklink2_src_install_properties (object_class);
templ_caps = gst_decklink2_get_default_template_caps ();
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, templ_caps));
gst_caps_unref (templ_caps);
gst_element_class_set_static_metadata (element_class,
"Decklink2 Source", "Video/Audio/Source/Hardware", "Decklink2 Source",
"Seungha Yang <seungha@centricular.com>");
basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_decklink2_src_get_caps);
basesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_decklink2_src_set_caps);
basesrc_class->query = GST_DEBUG_FUNCPTR (gst_decklink2_src_query);
basesrc_class->start = GST_DEBUG_FUNCPTR (gst_decklink2_src_start);
basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_decklink2_src_stop);
basesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_decklink2_src_unlock);
basesrc_class->unlock_stop =
GST_DEBUG_FUNCPTR (gst_decklink2_src_unlock_stop);
pushsrc_class->create = GST_DEBUG_FUNCPTR (gst_decklink2_src_create);
GST_DEBUG_CATEGORY_INIT (gst_decklink2_src_debug, "decklink2src",
0, "decklink2src");
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_MODE, (GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_VIDEO_CONNECTION,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_AUDIO_CONNECTION,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_VIDEO_FORMAT,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_AUDIO_CHANNELS,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_PROFILE_ID,
(GstPluginAPIFlags) 0);
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK2_TIMECODE_FORMAT,
(GstPluginAPIFlags) 0);
}
static void
gst_decklink2_src_init (GstDeckLink2Src * self)
{
self->display_mode = DEFAULT_MODE;
self->device_number = DEFAULT_DEVICE_NUMBER;
self->persistent_id = DEFAULT_PERSISTENT_ID;
self->video_conn = DEFAULT_VIDEO_CONNECTION;
self->audio_conn = DEFAULT_AUDIO_CONNECTION;
self->video_format = DEFAULT_VIDEO_FORMAT;
self->profile_id = DEFAULT_PROFILE_ID;
self->audio_channels = DEFAULT_AUDIO_CHANNELS;
self->timecode_format = DEFAULT_TIMECODE_FORMAT;
self->output_cc = DEFAULT_OUTPUT_CC;
self->output_afd_bar = DEFAULT_OUTPUT_AFD_BAR;
self->buffer_size = DEFAULT_BUFFER_SIZE;
self->is_gap_buf = FALSE;
self->priv = new GstDeckLink2SrcPrivate ();
gst_base_src_set_live (GST_BASE_SRC (self), TRUE);
gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME);
}
static void
gst_decklink2_src_finalize (GObject * object)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (object);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_decklink2_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (object);
GstDeckLink2SrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_MODE:
self->display_mode = (BMDDisplayMode) g_value_get_enum (value);
break;
case PROP_DEVICE_NUMBER:
self->device_number = g_value_get_int (value);
break;
case PROP_PERSISTENT_ID:
self->persistent_id = g_value_get_int64 (value);
break;
case PROP_VIDEO_CONNECTION:
self->video_conn = (BMDVideoConnection) g_value_get_enum (value);
break;
case PROP_AUDIO_CONNECTION:
self->audio_conn = (BMDAudioConnection) g_value_get_enum (value);
break;
case PROP_VIDEO_FORMAT:
self->video_format = (BMDPixelFormat) g_value_get_enum (value);
break;
case PROP_AUDIO_CHANNELS:
self->audio_channels =
(GstDeckLink2AudioChannels) g_value_get_enum (value);
break;
case PROP_PROFILE_ID:
self->profile_id = (BMDProfileID) g_value_get_enum (value);
break;
case PROP_TIMECODE_FORMAT:
self->timecode_format = (BMDTimecodeFormat) g_value_get_enum (value);
break;
case PROP_OUTPUT_CC:
self->output_cc = g_value_get_boolean (value);
break;
case PROP_OUTPUT_AFD_BAR:
self->output_afd_bar = g_value_get_boolean (value);
break;
case PROP_BUFFER_SIZE:
self->buffer_size = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_decklink2_src_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (object);
GstDeckLink2SrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
switch (prop_id) {
case PROP_MODE:
g_value_set_enum (value, self->display_mode);
break;
case PROP_DEVICE_NUMBER:
g_value_set_int (value, self->device_number);
break;
case PROP_PERSISTENT_ID:
g_value_set_int64 (value, self->persistent_id);
break;
case PROP_VIDEO_CONNECTION:
g_value_set_enum (value, self->video_conn);
break;
case PROP_AUDIO_CONNECTION:
g_value_set_enum (value, self->audio_conn);
break;
case PROP_VIDEO_FORMAT:
g_value_set_enum (value, self->video_format);
break;
case PROP_AUDIO_CHANNELS:
g_value_set_enum (value, self->audio_channels);
break;
case PROP_PROFILE_ID:
g_value_set_enum (value, self->profile_id);
break;
case PROP_TIMECODE_FORMAT:
g_value_set_enum (value, self->timecode_format);
break;
case PROP_OUTPUT_CC:
g_value_set_boolean (value, self->output_cc);
break;
case PROP_OUTPUT_AFD_BAR:
g_value_set_boolean (value, self->output_afd_bar);
break;
case PROP_BUFFER_SIZE:
g_value_set_uint (value, self->buffer_size);
break;
case PROP_SIGNAL:
{
gboolean has_signal = FALSE;
if (self->input)
has_signal = gst_decklink2_input_has_signal (self->input);
g_value_set_boolean (value, has_signal);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstCaps *
gst_decklink2_src_get_caps (GstBaseSrc * src, GstCaps * filter)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstDeckLink2SrcPrivate *priv = self->priv;
GstCaps *caps;
GstCaps *ret;
std::lock_guard < std::mutex > lk (priv->lock);
if (!self->input)
return GST_BASE_SRC_CLASS (parent_class)->get_caps (src, filter);
if (self->selected_caps) {
caps = gst_caps_ref (self->selected_caps);
} else {
caps = gst_decklink2_input_get_caps (self->input, self->display_mode,
self->video_format);
}
if (!caps) {
GST_WARNING_OBJECT (self, "Couldn't get caps");
caps = gst_caps_new_empty ();
}
if (filter) {
ret = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
} else {
ret = caps;
}
GST_DEBUG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, ret);
return ret;
}
static gboolean
gst_decklink2_src_set_caps (GstBaseSrc * src, GstCaps * caps)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstDeckLink2SrcPrivate *priv = self->priv;
BMDPixelFormat pixel_format;
std::lock_guard < std::mutex > lk (priv->lock);
GST_DEBUG_OBJECT (self, "Set caps %" GST_PTR_FORMAT, caps);
if (!self->input) {
GST_WARNING_OBJECT (self,
"Couldn't accept caps without configured input object");
return FALSE;
}
if (self->running)
return TRUE;
if (!gst_video_info_from_caps (&self->video_info, caps)) {
GST_WARNING_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
if (!gst_decklink2_input_get_display_mode (self->input, &self->video_info,
&self->selected_mode)) {
GST_ERROR_OBJECT (self, "Not a supported caps");
return FALSE;
}
gst_clear_caps (&self->selected_caps);
pixel_format =
gst_decklink2_pixel_format_from_video_format (GST_VIDEO_INFO_FORMAT
(&self->video_info));
self->selected_caps =
gst_decklink2_input_get_caps (self->input, self->selected_mode.mode,
pixel_format);
if (!self->selected_caps) {
GST_ERROR_OBJECT (self, "Couldn't get caps from selected mode");
return FALSE;
}
return TRUE;
}
static gboolean
gst_decklink2_src_query (GstBaseSrc * src, GstQuery * query)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstDeckLink2SrcPrivate *priv = self->priv;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_LATENCY:
{
std::lock_guard < std::mutex > lk (priv->lock);
gint fps_n, fps_d;
GstClockTime min, max;
if (self->selected_mode.fps_n > 0 && self->selected_mode.fps_d > 0) {
fps_n = self->selected_mode.fps_n;
fps_d = self->selected_mode.fps_d;
} else {
fps_n = 30;
fps_d = 1;
}
min = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n);
max = self->buffer_size * min;
gst_query_set_latency (query, TRUE, min, max);
return TRUE;
}
default:
break;
}
return GST_BASE_SRC_CLASS (parent_class)->query (src, query);
}
static gboolean
gst_decklink2_src_start (GstBaseSrc * src)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstDeckLink2SrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
self->running = FALSE;
memset (&self->selected_mode, 0, sizeof (GstDeckLink2DisplayMode));
gst_clear_caps (&self->selected_caps);
self->input = gst_decklink2_acquire_input (self->device_number,
self->persistent_id);
if (!self->input) {
GST_ERROR_OBJECT (self, "Couldn't acquire input object");
return FALSE;
}
return TRUE;
}
static gboolean
gst_decklink2_src_stop (GstBaseSrc * src)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstDeckLink2SrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
if (self->input) {
gst_decklink2_input_stop (self->input);
gst_decklink2_release_input (self->input);
self->input = NULL;
}
gst_clear_caps (&self->selected_caps);
memset (&self->selected_mode, 0, sizeof (GstDeckLink2DisplayMode));
self->running = FALSE;
return TRUE;
}
static gboolean
gst_decklink2_src_unlock (GstBaseSrc * src)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstDeckLink2SrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
if (self->input)
gst_decklink2_input_set_flushing (self->input, TRUE);
return TRUE;
}
static gboolean
gst_decklink2_src_unlock_stop (GstBaseSrc * src)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstDeckLink2SrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
if (self->input)
gst_decklink2_input_set_flushing (self->input, FALSE);
return TRUE;
}
static gboolean
gst_decklink2_src_run (GstDeckLink2Src * self)
{
HRESULT hr;
GstDeckLink2InputVideoConfig video_config;
GstDeckLink2InputAudioConfig audio_config;
GstDeckLink2SrcPrivate *priv = self->priv;
std::lock_guard < std::mutex > lk (priv->lock);
if (self->running)
return TRUE;
if (!self->input) {
GST_ERROR_OBJECT (self, "Input object was not configured");
return FALSE;
}
video_config.connection = self->video_conn;
video_config.display_mode = self->selected_mode;
video_config.pixel_format = self->video_format;
video_config.auto_detect = self->display_mode == bmdModeUnknown;
video_config.output_cc = self->output_cc;
video_config.output_afd_bar = self->output_afd_bar;
audio_config.connection = self->audio_conn;
audio_config.sample_type = bmdAudioSampleType32bitInteger;
audio_config.channels = self->audio_channels;
hr = gst_decklink2_input_start (self->input, GST_ELEMENT (self),
self->profile_id, self->buffer_size, &video_config, &audio_config);
if (!gst_decklink2_result (hr)) {
GST_ERROR_OBJECT (self, "Couldn't start stream, hr: 0x%x", (guint) hr);
return FALSE;
}
self->running = TRUE;
return TRUE;
}
static GstFlowReturn
gst_decklink2_src_create (GstPushSrc * src, GstBuffer ** buffer)
{
GstDeckLink2Src *self = GST_DECKLINK2_SRC (src);
GstSample *sample;
GstCaps *caps;
GstFlowReturn ret;
GstDeckLink2SrcPrivate *priv = self->priv;
gboolean is_gap_buf = FALSE;
if (!gst_decklink2_src_run (self)) {
GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL),
("Failed to start stream"));
return GST_FLOW_ERROR;
}
ret = gst_decklink2_input_get_sample (self->input, &sample);
if (ret != GST_FLOW_OK)
return ret;
std::unique_lock < std::mutex > lk (priv->lock);
caps = gst_sample_get_caps (sample);
if (caps && !gst_caps_is_equal (caps, self->selected_caps)) {
GST_DEBUG_OBJECT (self, "Set updated caps %" GST_PTR_FORMAT, caps);
gst_caps_replace (&self->selected_caps, caps);
lk.unlock ();
if (!gst_pad_set_caps (GST_BASE_SRC_PAD (self), caps)) {
GST_ERROR_OBJECT (self, "Couldn't set caps");
gst_sample_unref (sample);
return GST_FLOW_NOT_NEGOTIATED;
}
}
*buffer = gst_sample_get_buffer (sample);
if (GST_BUFFER_FLAG_IS_SET (*buffer, GST_BUFFER_FLAG_GAP))
is_gap_buf = TRUE;
if (is_gap_buf != self->is_gap_buf) {
self->is_gap_buf = is_gap_buf;
g_object_notify (G_OBJECT (self), "signal");
}
gst_buffer_ref (*buffer);
gst_sample_unref (sample);
return GST_FLOW_OK;
}
void
gst_decklink2_src_install_properties (GObjectClass * object_class)
{
GParamFlags param_flags = (GParamFlags) (G_PARAM_READWRITE |
GST_PARAM_MUTABLE_READY | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_MODE,
g_param_spec_enum ("mode", "Playback Mode",
"Video Mode to use for playback",
GST_TYPE_DECKLINK2_MODE, DEFAULT_MODE, param_flags));
g_object_class_install_property (object_class, PROP_DEVICE_NUMBER,
g_param_spec_int ("device-number", "Device number",
"Output device instance to use", 0, G_MAXINT, DEFAULT_DEVICE_NUMBER,
param_flags));
g_object_class_install_property (object_class, PROP_PERSISTENT_ID,
g_param_spec_int64 ("persistent-id", "Persistent id",
"Output device instance to use. Higher priority than \"device-number\".",
DEFAULT_PERSISTENT_ID, G_MAXINT64, DEFAULT_PERSISTENT_ID,
param_flags));
g_object_class_install_property (object_class, PROP_VIDEO_CONNECTION,
g_param_spec_enum ("video-connection", "Video Connection",
"Video input connection to use",
GST_TYPE_DECKLINK2_VIDEO_CONNECTION, DEFAULT_VIDEO_CONNECTION,
param_flags));
g_object_class_install_property (object_class, PROP_AUDIO_CONNECTION,
g_param_spec_enum ("audio-connection", "Audio Connection",
"Audio input connection to use",
GST_TYPE_DECKLINK2_AUDIO_CONNECTION, DEFAULT_AUDIO_CONNECTION,
param_flags));
g_object_class_install_property (object_class, PROP_VIDEO_FORMAT,
g_param_spec_enum ("video-format", "Video format",
"Video format type to use for playback",
GST_TYPE_DECKLINK2_VIDEO_FORMAT, DEFAULT_VIDEO_FORMAT, param_flags));
g_object_class_install_property (object_class, PROP_AUDIO_CHANNELS,
g_param_spec_enum ("audio-channels", "Audio Channels",
"Audio Channels",
GST_TYPE_DECKLINK2_AUDIO_CHANNELS, DEFAULT_AUDIO_CHANNELS,
param_flags));
g_object_class_install_property (object_class, PROP_PROFILE_ID,
g_param_spec_enum ("profile", "Profile",
"Certain DeckLink devices such as the DeckLink 8K Pro, the DeckLink "
"Quad 2 and the DeckLink Duo 2 support multiple profiles to "
"configure the capture and playback behavior of its sub-devices."
"For the DeckLink Duo 2 and DeckLink Quad 2, a profile is shared "
"between any 2 sub-devices that utilize the same connectors. For the "
"DeckLink 8K Pro, a profile is shared between all 4 sub-devices. Any "
"sub-devices that share a profile are considered to be part of the "
"same profile group."
"DeckLink Duo 2 support configuration of the duplex mode of "
"individual sub-devices.",
GST_TYPE_DECKLINK2_PROFILE_ID, DEFAULT_PROFILE_ID, param_flags));
g_object_class_install_property (object_class, PROP_TIMECODE_FORMAT,
g_param_spec_enum ("timecode-format", "Timecode format",
"Timecode format type to use for playback",
GST_TYPE_DECKLINK2_TIMECODE_FORMAT, DEFAULT_TIMECODE_FORMAT,
param_flags));
g_object_class_install_property (object_class, PROP_OUTPUT_CC,
g_param_spec_boolean ("output-cc", "Output Closed Caption",
"Extract and output CC as GstMeta (if present)",
DEFAULT_OUTPUT_CC, param_flags));
g_object_class_install_property (object_class, PROP_OUTPUT_AFD_BAR,
g_param_spec_boolean ("output-afd-bar", "Output AFD/Bar data",
"Extract and output AFD/Bar as GstMeta (if present)",
DEFAULT_OUTPUT_AFD_BAR, param_flags));
g_object_class_install_property (object_class, PROP_BUFFER_SIZE,
g_param_spec_uint ("buffer-size", "Buffer Size",
"Size of internal buffer in number of video frames", 1,
16, DEFAULT_BUFFER_SIZE, param_flags));
g_object_class_install_property (object_class, PROP_SIGNAL,
g_param_spec_boolean ("signal", "Signal",
"True if there is a valid input signal available",
FALSE, (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
}

View file

@ -0,0 +1,36 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/base.h>
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_SRC (gst_decklink2_src_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2Src, gst_decklink2_src,
GST, DECKLINK2_SRC, GstPushSrc);
GST_ELEMENT_REGISTER_DECLARE (decklink2src);
void gst_decklink2_src_install_properties (GObjectClass * object_class);
G_END_DECLS

View file

@ -0,0 +1,206 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstdecklink2srcbin.h"
#include "gstdecklink2src.h"
#include "gstdecklink2utils.h"
GST_DEBUG_CATEGORY_STATIC (gst_decklink2_src_bin_debug);
#define GST_CAT_DEFAULT gst_decklink2_src_bin_debug
static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("audio/x-raw, format = (string) { S16LE, S32LE }, "
"rate = (int) 48000, channels = (int) { 2, 8, 16 }, "
"layout = (string) interleaved"));
struct _GstDeckLink2SrcBin
{
GstBin parent;
GstElement *src;
GstElement *demux;
};
static void gst_decklink2_src_bin_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_decklink2_src_bin_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static void on_signal (GObject * object, GParamSpec * pspec, GstElement * self);
static void on_pad_added (GstElement * demux, GstPad * pad,
GstDeckLink2SrcBin * self);
static void on_pad_removed (GstElement * demux, GstPad * pad,
GstDeckLink2SrcBin * self);
static void on_no_more_pads (GstElement * demux, GstDeckLink2SrcBin * self);
#define gst_decklink2_src_bin_parent_class parent_class
G_DEFINE_TYPE (GstDeckLink2SrcBin, gst_decklink2_src_bin, GST_TYPE_BIN);
GST_ELEMENT_REGISTER_DEFINE (decklink2srcbin, "decklink2srcbin",
GST_RANK_NONE, GST_TYPE_DECKLINK2_SRC_BIN);
static void
gst_decklink2_src_bin_class_init (GstDeckLink2SrcBinClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstCaps *templ_caps;
object_class->set_property = gst_decklink2_src_bin_set_property;
object_class->get_property = gst_decklink2_src_bin_get_property;
gst_decklink2_src_install_properties (object_class);
templ_caps = gst_decklink2_get_default_template_caps ();
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("video", GST_PAD_SRC, GST_PAD_ALWAYS, templ_caps));
gst_caps_unref (templ_caps);
gst_element_class_add_static_pad_template (element_class, &audio_template);
gst_element_class_set_static_metadata (element_class,
"Decklink2 Source Bin", "Video/Audio/Source/Hardware",
"Decklink2 Source Bin", "Seungha Yang <seungha@centricular.com>");
GST_DEBUG_CATEGORY_INIT (gst_decklink2_src_bin_debug, "decklink2srcbin",
0, "decklink2srcbin");
}
static void
gst_decklink2_src_bin_init (GstDeckLink2SrcBin * self)
{
GstPad *pad;
GstPad *gpad;
self->src = gst_element_factory_make ("decklink2src", NULL);
self->demux = gst_element_factory_make ("decklink2demux", NULL);
gst_bin_add_many (GST_BIN (self), self->src, self->demux, NULL);
gst_element_link (self->src, self->demux);
pad = gst_element_get_static_pad (self->demux, "video");
gpad = gst_ghost_pad_new ("video", pad);
gst_object_unref (pad);
gst_element_add_pad (GST_ELEMENT (self), gpad);
g_signal_connect (self->src, "notify::signal", G_CALLBACK (on_signal), self);
g_signal_connect (self->demux, "pad-added", G_CALLBACK (on_pad_added), self);
g_signal_connect (self->demux,
"pad-removed", G_CALLBACK (on_pad_removed), self);
g_signal_connect (self->demux, "no-more-pads",
G_CALLBACK (on_no_more_pads), self);
gst_bin_set_suppressed_flags (GST_BIN (self),
(GstElementFlags) (GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK));
GST_OBJECT_FLAG_SET (self, GST_ELEMENT_FLAG_SOURCE);
}
static void
gst_decklink2_src_bin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstDeckLink2SrcBin *self = GST_DECKLINK2_SRC_BIN (object);
g_object_set_property (G_OBJECT (self->src), pspec->name, value);
}
static void
gst_decklink2_src_bin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstDeckLink2SrcBin *self = GST_DECKLINK2_SRC_BIN (object);
g_object_get_property (G_OBJECT (self->src), pspec->name, value);
}
static gboolean
copy_sticky_events (GstPad * pad, GstEvent ** event, GstPad * gpad)
{
gst_pad_store_sticky_event (gpad, *event);
return TRUE;
}
static void
on_signal (GObject * object, GParamSpec * pspec, GstElement * self)
{
g_object_notify (G_OBJECT (self), "signal");
}
static void
on_pad_added (GstElement * demux, GstPad * pad, GstDeckLink2SrcBin * self)
{
GstPad *gpad;
gchar *pad_name;
GST_DEBUG_OBJECT (self, "Pad added %" GST_PTR_FORMAT, pad);
if (!GST_PAD_IS_SRC (pad))
return;
pad_name = gst_pad_get_name (pad);
if (g_strcmp0 (pad_name, "audio") != 0) {
g_free (pad_name);
return;
}
g_free (pad_name);
gpad = gst_ghost_pad_new ("audio", pad);
g_object_set_data (G_OBJECT (pad), "decklink2srcbin.ghostpad", gpad);
gst_pad_set_active (gpad, TRUE);
gst_pad_sticky_events_foreach (pad,
(GstPadStickyEventsForeachFunction) copy_sticky_events, gpad);
gst_element_add_pad (GST_ELEMENT (self), gpad);
}
static void
on_pad_removed (GstElement * demux, GstPad * pad, GstDeckLink2SrcBin * self)
{
GstPad *gpad;
GST_DEBUG_OBJECT (self, "Pad removed %" GST_PTR_FORMAT, pad);
if (!GST_PAD_IS_SRC (pad))
return;
gpad = (GstPad *) g_object_get_data (G_OBJECT (pad),
"decklink2srcbin.ghostpad");
if (!gpad) {
GST_DEBUG_OBJECT (self, "No ghost pad found");
return;
}
gst_element_remove_pad (GST_ELEMENT (self), gpad);
}
static void
on_no_more_pads (GstElement * demux, GstDeckLink2SrcBin * self)
{
gst_element_no_more_pads (GST_ELEMENT (self));
}

View file

@ -0,0 +1,34 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/base.h>
G_BEGIN_DECLS
#define GST_TYPE_DECKLINK2_SRC_BIN (gst_decklink2_src_bin_get_type())
G_DECLARE_FINAL_TYPE (GstDeckLink2SrcBin, gst_decklink2_src_bin,
GST, DECKLINK2_SRC_BIN, GstBin);
GST_ELEMENT_REGISTER_DECLARE (decklink2srcbin);
G_END_DECLS

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,334 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
#pragma once
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/audio/audio.h>
#ifdef G_OS_WIN32
#include <windows.h>
#ifndef INITGUID
#include <initguid.h>
#endif /* INITGUID */
#include <DeckLinkAPI_i.c>
#else
#include <DeckLinkAPI_v10_11.h>
#include <DeckLinkAPI_v11_5.h>
#include <DeckLinkAPI_v11_5_1.h>
#include <DeckLinkAPIConfiguration_v10_11.h>
#include <DeckLinkAPIVideoInput_v10_11.h>
#include <DeckLinkAPIVideoInput_v11_4.h>
#include <DeckLinkAPIVideoInput_v11_5_1.h>
#include <DeckLinkAPIVideoOutput_v10_11.h>
#include <DeckLinkAPIVideoOutput_v11_4.h>
#endif /* G_OS_WIN32 */
#include <DeckLinkAPI.h>
#if defined(G_OS_WIN32)
#define dlbool_t BOOL
#define dlstring_t BSTR
#elif defined(__APPLE__)
#define dlbool_t bool
#define dlstring_t CFStringRef
#else
#define dlbool_t bool
#define dlstring_t const char*
#endif
G_BEGIN_DECLS
typedef struct _GstDeckLink2AudioMeta GstDeckLink2AudioMeta;
typedef struct _GstDeckLink2DisplayMode GstDeckLink2DisplayMode;
typedef enum
{
GST_DECKLINK2_API_LEVEL_UNKNOWN,
GST_DECKLINK2_API_LEVEL_10_11,
GST_DECKLINK2_API_LEVEL_11_4,
GST_DECKLINK2_API_LEVEL_11_5_1,
GST_DECKLINK2_API_LEVEL_LATEST,
} GstDeckLink2APILevel;
/* defines custom display mode for wide screen */
#define bmdModeNTSC_W ((BMDDisplayMode) 0x4E545343) /* 'NTSC' */
#define bmdModeNTSC2398_W ((BMDDisplayMode) 0x4E543233) /* 'NT23' */
#define bmdModePAL_W ((BMDDisplayMode) 0x50414C20) /* 'PAL ' */
#define bmdModeNTSCp_W ((BMDDisplayMode) 0x4E545350) /* 'NTSP' */
#define bmdModePALp_W ((BMDDisplayMode) 0x50414C50) /* 'PALP' */
#define GST_TYPE_DECKLINK2_MODE (gst_decklink2_mode_get_type ())
GType gst_decklink2_mode_get_type (void);
#define GST_TYPE_DECKLINK2_VIDEO_FORMAT (gst_decklink2_video_format_get_type ())
GType gst_decklink2_video_format_get_type (void);
#define bmdProfileDefault ((BMDProfileID) 0)
#define GST_TYPE_DECKLINK2_PROFILE_ID (gst_decklink2_profile_id_get_type ())
GType gst_decklink2_profile_id_get_type (void);
typedef enum
{
GST_DECKLINK2_KEYER_MODE_OFF,
GST_DECKLINK2_KEYER_MODE_INTERNAL,
GST_DECKLINK2_KEYER_MODE_EXTERNAL
} GstDeckLink2KeyerMode;
#define GST_TYPE_DECKLINK2_KEYER_MODE (gst_decklink2_keyer_mode_get_type ())
GType gst_decklink2_keyer_mode_get_type (void);
typedef enum
{
GST_DECKLINK2_MAPPING_FORMAT_DEFAULT,
GST_DECKLINK2_MAPPING_FORMAT_LEVEL_A, /* bmdDeckLinkConfigSMPTELevelAOutput = true */
GST_DECKLINK2_MAPPING_FORMAT_LEVEL_B, /* bmdDeckLinkConfigSMPTELevelAOutput = false */
} GstDeckLink2MappingFormat;
#define GST_TYPE_DECKLINK2_MAPPING_FORMAT (gst_decklink2_mapping_format_get_type ())
GType gst_decklink2_mapping_format_get_type (void);
#define GST_TYPE_DECKLINK2_TIMECODE_FORMAT (gst_decklink2_timecode_format_get_type ())
GType gst_decklink2_timecode_format_get_type (void);
#define GST_TYPE_DECKLINK2_VIDEO_CONNECTION (gst_decklink2_video_connection_get_type ())
GType gst_decklink2_video_connection_get_type (void);
#define bmdAudioConnectionUnspecified ((BMDAudioConnection) 0)
#define GST_TYPE_DECKLINK2_AUDIO_CONNECTION (gst_decklink2_audio_connection_get_type ())
GType gst_decklink2_audio_connection_get_type (void);
typedef enum
{
GST_DECKLINK2_AUDIO_CHANNELS_DISABLED = -1,
GST_DECKLINK2_AUDIO_CHANNELS_MAX = 0,
GST_DECKLINK2_AUDIO_CHANNELS_2 = 2,
GST_DECKLINK2_AUDIO_CHANNELS_8 = 8,
GST_DECKLINK2_AUDIO_CHANNELS_16 = 16,
} GstDeckLink2AudioChannels;
#define GST_TYPE_DECKLINK2_AUDIO_CHANNELS (gst_decklink2_audio_channels_get_type ())
GType gst_decklink2_audio_channels_get_type (void);
struct _GstDeckLink2DisplayMode
{
BMDDisplayMode mode;
gint width;
gint height;
gint fps_n;
gint fps_d;
gboolean interlaced;
gint par_n;
gint par_d;
gboolean tff;
};
gboolean gst_decklink2_init_once (void);
void gst_decklink2_deinit (void);
gboolean gst_decklink2_get_api_version (guint * major,
guint * minor,
guint * sub,
guint * extra);
GstDeckLink2APILevel gst_decklink2_get_api_level (void);
const gchar * gst_decklink2_api_level_to_string (GstDeckLink2APILevel level);
GstCaps * gst_decklink2_get_default_template_caps (void);
BMDDisplayMode gst_decklink2_get_real_display_mode (BMDDisplayMode mode);
typedef gboolean (*GstDeckLink2DoesSupportVideoMode) (GstObject * object,
BMDDisplayMode mode,
BMDPixelFormat format);
GstCaps * gst_decklink2_build_caps (GstObject * io_object,
IDeckLinkDisplayModeIterator * iter,
BMDDisplayMode requested_mode,
BMDPixelFormat format,
GstDeckLink2DoesSupportVideoMode func);
GstCaps * gst_decklink2_build_template_caps (GstObject * io_object,
IDeckLinkDisplayModeIterator * iter,
GstDeckLink2DoesSupportVideoMode func,
GArray * format_table);
GstCaps * gst_decklink2_get_caps_from_mode (const GstDeckLink2DisplayMode * mode);
GstVideoFormat gst_decklink2_video_format_from_pixel_format (BMDPixelFormat format);
BMDPixelFormat gst_decklink2_pixel_format_from_video_format (GstVideoFormat format);
struct _GstDeckLink2AudioMeta
{
GstMeta meta;
GstSample *sample;
};
GType gst_decklink2_audio_meta_api_get_type (void);
#define GST_DECKLINK2_AUDIO_META_API_TYPE (gst_decklink2_audio_meta_api_get_type())
const GstMetaInfo *gst_decklink2_audio_meta_get_info (void);
#define GST_DECKLINK2_AUDIO_META_INFO (gst_decklink2_audio_meta_get_info())
#define gst_buffer_get_decklink2_audio_meta(b) \
((GstDeckLink2AudioMeta*)gst_buffer_get_meta((b),GST_DECKLINK2_AUDIO_META_API_TYPE))
GstDeckLink2AudioMeta * gst_buffer_add_decklink2_audio_meta (GstBuffer * buffer,
GstSample * audio_sample);
#ifndef GST_DISABLE_GST_DEBUG
static inline gboolean
_gst_decklink2_result (HRESULT hr, GstDebugCategory * cat, const gchar * file,
const gchar * function, gint line)
{
if (hr == S_OK)
return TRUE;
#ifdef G_OS_WIN32
{
gchar *error_text = g_win32_error_message ((guint) hr);
gst_debug_log (cat, GST_LEVEL_WARNING, file, function, line, NULL,
"DeckLink call failed: 0x%x (%s)", (guint) hr,
GST_STR_NULL (error_text));
g_free (error_text);
}
#else
gst_debug_log (cat, GST_LEVEL_WARNING, file, function, line, NULL,
"DeckLink call failed: 0x%x", (guint) hr);
#endif /* G_OS_WIN32 */
return FALSE;
}
#define gst_decklink2_result(hr) \
_gst_decklink2_result (hr, GST_CAT_DEFAULT, __FILE__, GST_FUNCTION, __LINE__)
#else /* GST_DISABLE_GST_DEBUG */
static inline gboolean
gst_decklink2_result (HRESULT hr)
{
if (hr == S_OK)
return TRUE;
return FALSE;
}
#endif /* GST_DISABLE_GST_DEBUG */
#define GST_DECKLINK2_PRINT_CHAR(c) \
g_ascii_isprint(c) ? (c) : '.'
#define GST_DECKLINK2_FOURCC_ARGS(fourcc) \
GST_DECKLINK2_PRINT_CHAR(((fourcc) >> 24) & 0xff), \
GST_DECKLINK2_PRINT_CHAR(((fourcc) >> 16) & 0xff), \
GST_DECKLINK2_PRINT_CHAR(((fourcc) >> 8) & 0xff), \
GST_DECKLINK2_PRINT_CHAR((fourcc) & 0xff)
G_END_DECLS
#ifdef __cplusplus
#include <string>
#include <functional>
#include <stdint.h>
#include <mutex>
#define GST_DECKLINK2_CALL_ONCE_BEGIN \
static std::once_flag __once_flag; \
std::call_once (__once_flag, [&]()
#define GST_DECKLINK2_CALL_ONCE_END )
#define GST_DECKLINK2_CLEAR_COM(obj) G_STMT_START { \
if (obj) { \
(obj)->Release (); \
(obj) = NULL; \
} \
} G_STMT_END
#ifndef G_OS_WIN32
#include <string.h>
inline bool operator==(REFIID a, REFIID b)
{
if (memcmp (&a, &b, sizeof (REFIID)) != 0)
return false;
return true;
}
#endif
#if defined(G_OS_WIN32)
const std::function<void(dlstring_t)> DeleteString = SysFreeString;
const std::function<std::string(dlstring_t)> DlToStdString = [](dlstring_t dl_str) -> std::string
{
int wlen = ::SysStringLen(dl_str);
int mblen = ::WideCharToMultiByte(CP_ACP, 0, (wchar_t*)dl_str, wlen, NULL, 0, NULL, NULL);
std::string ret_str(mblen, '\0');
mblen = ::WideCharToMultiByte(CP_ACP, 0, (wchar_t*)dl_str, wlen, &ret_str[0], mblen, NULL, NULL);
return ret_str;
};
const std::function<dlstring_t(std::string)> StdToDlString = [](std::string std_str) -> dlstring_t
{
int wlen = ::MultiByteToWideChar(CP_ACP, 0, std_str.data(), (int)std_str.length(), NULL, 0);
dlstring_t ret_str = ::SysAllocStringLen(NULL, wlen);
::MultiByteToWideChar(CP_ACP, 0, std_str.data(), (int)std_str.length(), ret_str, wlen);
return ret_str;
};
#elif defined(__APPLE__)
const auto DeleteString = CFRelease;
const auto DlToStdString = [](dlstring_t dl_str) -> std::string
{
std::string returnString("");
char stringBuffer[1024];
if (CFStringGetCString(dl_str, stringBuffer, 1024, kCFStringEncodingUTF8))
returnString = stringBuffer;
return returnString;
};
const auto StdToDlString = [](std::string std_str) -> dlstring_t
{
return CFStringCreateWithCString(kCFAllocatorMalloc, std_str.c_str(), kCFStringEncodingUTF8);
};
#else
#include <string.h>
const std::function<void(dlstring_t)> DeleteString = [](dlstring_t dl_str)
{
free((void*)dl_str);
};
const std::function<std::string(dlstring_t)> DlToStdString = [](dlstring_t dl_str) -> std::string
{
return dl_str;
};
const std::function<dlstring_t(std::string)> StdToDlString = [](std::string std_str) -> dlstring_t
{
return strcpy((char*)malloc(std_str.length()+1), std_str.c_str());
};
#endif
#endif /* __cplusplus */

View file

@ -0,0 +1,96 @@
decklink2_sources = [
'gstdecklink2combiner.cpp',
'gstdecklink2demux.cpp',
'gstdecklink2deviceprovider.cpp',
'gstdecklink2input.cpp',
'gstdecklink2object.cpp',
'gstdecklink2output.cpp',
'gstdecklink2sink.cpp',
'gstdecklink2src.cpp',
'gstdecklink2srcbin.cpp',
'gstdecklink2utils.cpp',
'plugin.cpp',
]
decklink2_sdk = []
decklink2_deps = []
decklink2_extra_args = ['-DGST_USE_UNSTABLE_API']
decklink2_ldflags = []
decklink2_incl = []
decklink2_option = get_option('decklink2')
if decklink2_option.disabled()
subdir_done()
endif
# Build SDK headers using midl compiler on Windows
if host_system == 'windows'
midl = find_program('midl', required: decklink2_option)
if not midl.found()
subdir_done()
endif
if host_machine.cpu_family() == 'x86'
midl_env = 'win32'
elif host_machine.cpu_family() == 'x86_64'
midl_env = 'win64'
else
# ARM64 support?
if decklink2_option.enabled()
error(host_machine.cpu_family() + ' is not supported')
endif
subdir_done()
endif
decklink2_sdk = custom_target('DeckLinkAPI.h',
output : ['DeckLinkAPI_i.c', 'DeckLinkAPI.h'],
input : 'win/DeckLinkAPI.idl',
command : [midl, '/h', 'DeckLinkAPI.h',
'/env', midl_env,
'/target', 'NT50',
'/notlb',
'/out', meson.current_build_dir(),
'@INPUT@']
)
else
libdl = cc.find_library('dl', required: decklink2_option)
have_pthread_h = cc.has_header('pthread.h', required: decklink2_option)
if not libdl.found() or not have_pthread_h
subdir_done()
endif
decklink2_extra_args += cc.get_supported_arguments([
'-Wno-missing-declarations',
])
decklink2_deps += [libdl, dependency('threads')]
if host_system == 'linux'
decklink2_sdk += ['linux/DeckLinkAPIDispatch.cpp',
'linux/DeckLinkAPIDispatch_v10_11.cpp']
decklink2_incl = include_directories('linux')
elif host_system in ['darwin', 'ios']
decklink2_sdk += ['mac/DeckLinkAPIDispatch.cpp',
'mac/DeckLinkAPIDispatch_v10_11.cpp']
decklink2_ldflags += ['-Wl,-framework,CoreFoundation']
decklink2_incl = include_directories('mac')
else
if decklink2_option.enabled()
error('Host system "@0@" is not supported'.format(host_system))
endif
subdir_done()
endif
endif
decklink2 = library('gstdecklink2',
decklink2_sources + decklink2_sdk,
c_args : gst_plugins_bad_args + decklink2_extra_args,
cpp_args : gst_plugins_bad_args + decklink2_extra_args,
link_args : decklink2_ldflags + noseh_link_args,
include_directories : [configinc] + decklink2_incl,
dependencies : [gstvideo_dep, gstaudio_dep, gstbase_dep, gst_dep] + decklink2_deps,
override_options : ['cpp_std=c++14'],
install : true,
install_dir : plugins_install_dir,
)
plugins += [decklink2]

View file

@ -0,0 +1,74 @@
/*
* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@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.
*/
/**
* plugin-decklink2:
*
* Since: 1.24
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include "gstdecklink2combiner.h"
#include "gstdecklink2demux.h"
#include "gstdecklink2deviceprovider.h"
#include "gstdecklink2sink.h"
#include "gstdecklink2src.h"
#include "gstdecklink2srcbin.h"
#include "gstdecklink2utils.h"
GST_DEBUG_CATEGORY (gst_decklink2_debug);
static void
plugin_deinit (gpointer data)
{
gst_decklink2_deinit ();
}
static gboolean
plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (gst_decklink2_debug, "decklink2", 0, "decklink2");
gst_decklink2_init_once ();
GST_ELEMENT_REGISTER (decklink2combiner, plugin);
GST_ELEMENT_REGISTER (decklink2demux, plugin);
GST_ELEMENT_REGISTER (decklink2sink, plugin);
GST_ELEMENT_REGISTER (decklink2src, plugin);
GST_ELEMENT_REGISTER (decklink2srcbin, plugin);
GST_DEVICE_PROVIDER_REGISTER (decklink2deviceprovider, plugin);
g_object_set_data_full (G_OBJECT (plugin),
"plugin-decklink2-shutdown", (gpointer) "shutdown-data",
(GDestroyNotify) plugin_deinit);
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
decklink2,
"Blackmagic Decklink plugin",
plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN)

View file

@ -8,6 +8,7 @@ subdir('d3d11')
subdir('d3d12')
subdir('d3dvideosink')
subdir('decklink')
subdir('decklink2')
subdir('directsound')
subdir('directshow')
subdir('dvb')