mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-13 12:51:16 +00:00
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:
parent
6c7fe649ab
commit
1a02d4ad19
26 changed files with 10912 additions and 0 deletions
|
@ -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
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
275
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2demux.cpp
Normal file
275
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2demux.cpp
Normal 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);
|
||||
}
|
|
@ -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
|
|
@ -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 ();
|
||||
}
|
|
@ -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
|
2082
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2input.cpp
Normal file
2082
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2input.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
712
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2object.cpp
Normal file
712
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2object.cpp
Normal 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;
|
||||
}
|
|
@ -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
|
1806
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.cpp
Normal file
1806
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2output.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
901
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.cpp
Normal file
901
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.cpp
Normal 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, ¶ms);
|
||||
|
||||
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, ¶ms);
|
||||
else
|
||||
gst_query_add_allocation_param (query, allocator, ¶ms);
|
||||
|
||||
pool = gst_video_buffer_pool_new ();
|
||||
|
||||
structure = gst_buffer_pool_get_config (pool);
|
||||
gst_buffer_pool_config_set_params (structure, caps, size, 0, 0);
|
||||
gst_buffer_pool_config_set_allocator (structure, allocator, ¶ms);
|
||||
|
||||
if (allocator)
|
||||
gst_object_unref (allocator);
|
||||
|
||||
if (!gst_buffer_pool_set_config (pool, structure)) {
|
||||
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;
|
||||
}
|
34
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.h
Normal file
34
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2sink.h
Normal 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
|
674
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2src.cpp
Normal file
674
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2src.cpp
Normal 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)));
|
||||
}
|
36
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2src.h
Normal file
36
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2src.h
Normal 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
|
206
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2srcbin.cpp
Normal file
206
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2srcbin.cpp
Normal 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));
|
||||
}
|
|
@ -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
|
1228
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2utils.cpp
Normal file
1228
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2utils.cpp
Normal file
File diff suppressed because it is too large
Load diff
334
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2utils.h
Normal file
334
subprojects/gst-plugins-bad/sys/decklink2/gstdecklink2utils.h
Normal 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 */
|
96
subprojects/gst-plugins-bad/sys/decklink2/meson.build
Normal file
96
subprojects/gst-plugins-bad/sys/decklink2/meson.build
Normal 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]
|
74
subprojects/gst-plugins-bad/sys/decklink2/plugin.cpp
Normal file
74
subprojects/gst-plugins-bad/sys/decklink2/plugin.cpp
Normal 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)
|
|
@ -8,6 +8,7 @@ subdir('d3d11')
|
|||
subdir('d3d12')
|
||||
subdir('d3dvideosink')
|
||||
subdir('decklink')
|
||||
subdir('decklink2')
|
||||
subdir('directsound')
|
||||
subdir('directshow')
|
||||
subdir('dvb')
|
||||
|
|
Loading…
Reference in a new issue