gstreamer/gstajasrc.cpp
Sebastian Dröge 99c86891a4 Read colorimetry information from VPID and create caps from detected input format
This does not implement automatic mode selection yet, for which it is
necessary to change the routing at runtime based on the detected
format. It is a first step into that direction though.
2021-07-07 10:36:55 +03:00

1874 lines
70 KiB
C++

/* GStreamer
* Copyright (C) 2021 Sebastian Dröge <sebastian@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
* Boston, MA 02110-1335, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <ajaanc/includes/ancillarydata_cea708.h>
#include <ajaanc/includes/ancillarylist.h>
#include <ajantv2/includes/ntv2rp188.h>
#include <ajantv2/includes/ntv2vpid.h>
#include "gstajacommon.h"
#include "gstajasrc.h"
GST_DEBUG_CATEGORY_STATIC(gst_aja_src_debug);
#define GST_CAT_DEFAULT gst_aja_src_debug
#define DEFAULT_DEVICE_IDENTIFIER ("0")
#define DEFAULT_CHANNEL (::NTV2_CHANNEL1)
// TODO: GST_AJA_VIDEO_FORMAT_AUTO
#define DEFAULT_VIDEO_FORMAT (GST_AJA_VIDEO_FORMAT_1080i_5000)
#define DEFAULT_AUDIO_SYSTEM (GST_AJA_AUDIO_SYSTEM_AUTO)
#define DEFAULT_INPUT_SOURCE (GST_AJA_INPUT_SOURCE_AUTO)
#define DEFAULT_SDI_MODE (GST_AJA_SDI_MODE_SINGLE_LINK)
#define DEFAULT_AUDIO_SOURCE (GST_AJA_AUDIO_SOURCE_EMBEDDED)
#define DEFAULT_TIMECODE_INDEX (GST_AJA_TIMECODE_INDEX_VITC)
#define DEFAULT_REFERENCE_SOURCE (GST_AJA_REFERENCE_SOURCE_FREERUN)
#define DEFAULT_QUEUE_SIZE (16)
#define DEFAULT_CAPTURE_CPU_CORE (G_MAXUINT)
enum {
PROP_0,
PROP_DEVICE_IDENTIFIER,
PROP_CHANNEL,
PROP_VIDEO_FORMAT,
PROP_AUDIO_SYSTEM,
PROP_INPUT_SOURCE,
PROP_SDI_MODE,
PROP_AUDIO_SOURCE,
PROP_TIMECODE_INDEX,
PROP_REFERENCE_SOURCE,
PROP_QUEUE_SIZE,
PROP_CAPTURE_CPU_CORE,
};
typedef enum {
QUEUE_ITEM_TYPE_FRAME,
} QueueItemType;
typedef struct {
QueueItemType type;
// For FRAME
GstClockTime capture_time;
GstBuffer *video_buffer;
GstBuffer *audio_buffer;
GstBuffer *anc_buffer, *anc_buffer2;
NTV2_RP188 tc;
NTV2VideoFormat detected_format;
guint32 vpid;
} QueueItem;
static void gst_aja_src_set_property(GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec);
static void gst_aja_src_get_property(GObject *object, guint property_id,
GValue *value, GParamSpec *pspec);
static void gst_aja_src_finalize(GObject *object);
static GstCaps *gst_aja_src_get_caps(GstBaseSrc *bsrc, GstCaps *filter);
static gboolean gst_aja_src_query(GstBaseSrc *bsrc, GstQuery *query);
static gboolean gst_aja_src_unlock(GstBaseSrc *bsrc);
static gboolean gst_aja_src_unlock_stop(GstBaseSrc *bsrc);
static GstFlowReturn gst_aja_src_create(GstPushSrc *psrc, GstBuffer **buffer);
static gboolean gst_aja_src_open(GstAjaSrc *src);
static gboolean gst_aja_src_close(GstAjaSrc *src);
static gboolean gst_aja_src_start(GstAjaSrc *src);
static gboolean gst_aja_src_stop(GstAjaSrc *src);
static GstStateChangeReturn gst_aja_src_change_state(GstElement *element,
GstStateChange transition);
static void capture_thread_func(AJAThread *thread, void *data);
#define parent_class gst_aja_src_parent_class
G_DEFINE_TYPE(GstAjaSrc, gst_aja_src, GST_TYPE_PUSH_SRC);
static void gst_aja_src_class_init(GstAjaSrcClass *klass) {
GObjectClass *gobject_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;
gobject_class->set_property = gst_aja_src_set_property;
gobject_class->get_property = gst_aja_src_get_property;
gobject_class->finalize = gst_aja_src_finalize;
g_object_class_install_property(
gobject_class, PROP_DEVICE_IDENTIFIER,
g_param_spec_string(
"device-identifier", "Device identifier",
"Input device instance to use", DEFAULT_DEVICE_IDENTIFIER,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_CHANNEL,
g_param_spec_uint(
"channel", "Channel", "Channel to use", 0, NTV2_MAX_NUM_CHANNELS - 1,
DEFAULT_CHANNEL,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_VIDEO_FORMAT,
g_param_spec_enum(
"video-format", "Video Format", "Video format to use",
GST_TYPE_AJA_VIDEO_FORMAT, DEFAULT_VIDEO_FORMAT,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_QUEUE_SIZE,
g_param_spec_uint(
"queue-size", "Queue Size",
"Size of internal queue in number of video frames. "
"Half of this is allocated as device buffers and equal to the "
"latency.",
1, G_MAXINT, DEFAULT_QUEUE_SIZE,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property(
gobject_class, PROP_AUDIO_SYSTEM,
g_param_spec_enum(
"audio-system", "Audio System", "Audio system to use",
GST_TYPE_AJA_AUDIO_SYSTEM, DEFAULT_AUDIO_SYSTEM,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_INPUT_SOURCE,
g_param_spec_enum(
"input-source", "Input Source", "Input source to use",
GST_TYPE_AJA_INPUT_SOURCE, DEFAULT_INPUT_SOURCE,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_SDI_MODE,
g_param_spec_enum(
"sdi-input-mode", "SDI Input Mode", "SDI input mode to use",
GST_TYPE_AJA_SDI_MODE, DEFAULT_SDI_MODE,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_AUDIO_SOURCE,
g_param_spec_enum(
"audio-source", "Audio Source", "Audio source to use",
GST_TYPE_AJA_AUDIO_SOURCE, DEFAULT_AUDIO_SOURCE,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_TIMECODE_INDEX,
g_param_spec_enum(
"timecode-index", "Timecode Index", "Timecode index to use",
GST_TYPE_AJA_TIMECODE_INDEX, DEFAULT_TIMECODE_INDEX,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_REFERENCE_SOURCE,
g_param_spec_enum(
"reference-source", "Reference Source", "Reference source to use",
GST_TYPE_AJA_REFERENCE_SOURCE, DEFAULT_REFERENCE_SOURCE,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
g_object_class_install_property(
gobject_class, PROP_CAPTURE_CPU_CORE,
g_param_spec_uint(
"capture-cpu-core", "Capture CPU Core",
"Sets the affinity of the capture thread to this CPU core "
"(-1=disabled)",
0, G_MAXUINT, DEFAULT_CAPTURE_CPU_CORE,
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
element_class->change_state = GST_DEBUG_FUNCPTR(gst_aja_src_change_state);
basesrc_class->get_caps = GST_DEBUG_FUNCPTR(gst_aja_src_get_caps);
basesrc_class->negotiate = NULL;
basesrc_class->query = GST_DEBUG_FUNCPTR(gst_aja_src_query);
basesrc_class->unlock = GST_DEBUG_FUNCPTR(gst_aja_src_unlock);
basesrc_class->unlock_stop = GST_DEBUG_FUNCPTR(gst_aja_src_unlock_stop);
pushsrc_class->create = GST_DEBUG_FUNCPTR(gst_aja_src_create);
templ_caps = gst_ntv2_supported_caps(DEVICE_ID_INVALID);
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, "AJA audio/video src", "Audio/Video/Src",
"Captures audio/video frames with AJA devices",
"Sebastian Dröge <sebastian@centricular.com>");
GST_DEBUG_CATEGORY_INIT(gst_aja_src_debug, "ajasrc", 0, "AJA src");
}
static void gst_aja_src_init(GstAjaSrc *self) {
g_mutex_init(&self->queue_lock);
g_cond_init(&self->queue_cond);
self->device_identifier = g_strdup(DEFAULT_DEVICE_IDENTIFIER);
self->channel = DEFAULT_CHANNEL;
self->queue_size = DEFAULT_QUEUE_SIZE;
self->video_format_setting = DEFAULT_VIDEO_FORMAT;
self->audio_system_setting = DEFAULT_AUDIO_SYSTEM;
self->input_source = DEFAULT_INPUT_SOURCE;
self->audio_source = DEFAULT_AUDIO_SOURCE;
self->timecode_index = DEFAULT_TIMECODE_INDEX;
self->reference_source = DEFAULT_REFERENCE_SOURCE;
self->capture_cpu_core = DEFAULT_CAPTURE_CPU_CORE;
self->queue =
gst_queue_array_new_for_struct(sizeof(QueueItem), self->queue_size);
gst_base_src_set_live(GST_BASE_SRC_CAST(self), TRUE);
gst_base_src_set_format(GST_BASE_SRC_CAST(self), GST_FORMAT_TIME);
}
void gst_aja_src_set_property(GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec) {
GstAjaSrc *self = GST_AJA_SRC(object);
switch (property_id) {
case PROP_DEVICE_IDENTIFIER:
g_free(self->device_identifier);
self->device_identifier = g_value_dup_string(value);
break;
case PROP_CHANNEL:
self->channel = (NTV2Channel)g_value_get_uint(value);
break;
case PROP_QUEUE_SIZE:
self->queue_size = g_value_get_uint(value);
break;
case PROP_VIDEO_FORMAT:
self->video_format_setting = (GstAjaVideoFormat)g_value_get_enum(value);
break;
case PROP_AUDIO_SYSTEM:
self->audio_system_setting = (GstAjaAudioSystem)g_value_get_enum(value);
break;
case PROP_INPUT_SOURCE:
self->input_source = (GstAjaInputSource)g_value_get_enum(value);
break;
case PROP_SDI_MODE:
self->sdi_mode = (GstAjaSdiMode)g_value_get_enum(value);
break;
case PROP_AUDIO_SOURCE:
self->audio_source = (GstAjaAudioSource)g_value_get_enum(value);
break;
case PROP_TIMECODE_INDEX:
self->timecode_index = (GstAjaTimecodeIndex)g_value_get_enum(value);
break;
case PROP_REFERENCE_SOURCE:
self->reference_source = (GstAjaReferenceSource)g_value_get_enum(value);
break;
case PROP_CAPTURE_CPU_CORE:
self->capture_cpu_core = g_value_get_uint(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
}
}
void gst_aja_src_get_property(GObject *object, guint property_id, GValue *value,
GParamSpec *pspec) {
GstAjaSrc *self = GST_AJA_SRC(object);
switch (property_id) {
case PROP_DEVICE_IDENTIFIER:
g_value_set_string(value, self->device_identifier);
break;
case PROP_CHANNEL:
g_value_set_uint(value, self->channel);
break;
case PROP_QUEUE_SIZE:
g_value_set_uint(value, self->queue_size);
break;
case PROP_VIDEO_FORMAT:
g_value_set_enum(value, self->video_format_setting);
break;
case PROP_AUDIO_SYSTEM:
g_value_set_enum(value, self->audio_system_setting);
break;
case PROP_INPUT_SOURCE:
g_value_set_enum(value, self->input_source);
break;
case PROP_SDI_MODE:
g_value_set_enum(value, self->sdi_mode);
break;
case PROP_AUDIO_SOURCE:
g_value_set_enum(value, self->audio_source);
break;
case PROP_TIMECODE_INDEX:
g_value_set_enum(value, self->timecode_index);
break;
case PROP_REFERENCE_SOURCE:
g_value_set_enum(value, self->reference_source);
break;
case PROP_CAPTURE_CPU_CORE:
g_value_set_uint(value, self->capture_cpu_core);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
}
}
void gst_aja_src_finalize(GObject *object) {
GstAjaSrc *self = GST_AJA_SRC(object);
g_assert(self->device == NULL);
g_assert(gst_queue_array_get_length(self->queue) == 0);
g_clear_pointer(&self->queue, gst_queue_array_free);
g_mutex_clear(&self->queue_lock);
g_cond_clear(&self->queue_cond);
G_OBJECT_CLASS(parent_class)->finalize(object);
}
static gboolean gst_aja_src_open(GstAjaSrc *self) {
GST_DEBUG_OBJECT(self, "Opening device");
g_assert(self->device == NULL);
self->device = gst_aja_device_obtain(self->device_identifier);
if (!self->device) {
GST_ERROR_OBJECT(self, "Failed to open device");
return FALSE;
}
if (!self->device->device->IsDeviceReady(false)) {
g_clear_pointer(&self->device, gst_aja_device_unref);
return FALSE;
}
self->device->device->SetEveryFrameServices(::NTV2_OEM_TASKS);
self->device_id = self->device->device->GetDeviceID();
std::string serial_number;
if (!self->device->device->GetSerialNumberString(serial_number))
serial_number = "none";
GST_DEBUG_OBJECT(self,
"Opened device with ID %d at index %d (%s, version %s, "
"serial number %s, can do VANC %d)",
self->device_id, self->device->device->GetIndexNumber(),
self->device->device->GetDisplayName().c_str(),
self->device->device->GetDeviceVersionString().c_str(),
serial_number.c_str(),
::NTV2DeviceCanDoCustomAnc(self->device_id));
GST_DEBUG_OBJECT(self,
"Using SDK version %d.%d.%d.%d (%s) and driver version %s",
AJA_NTV2_SDK_VERSION_MAJOR, AJA_NTV2_SDK_VERSION_MINOR,
AJA_NTV2_SDK_VERSION_POINT, AJA_NTV2_SDK_BUILD_NUMBER,
AJA_NTV2_SDK_BUILD_DATETIME,
self->device->device->GetDriverVersionString().c_str());
self->device->device->SetMultiFormatMode(true);
self->allocator = gst_aja_allocator_new(self->device);
GST_DEBUG_OBJECT(self, "Opened device");
return TRUE;
}
static gboolean gst_aja_src_close(GstAjaSrc *self) {
gst_clear_object(&self->allocator);
g_clear_pointer(&self->device, gst_aja_device_unref);
self->device_id = DEVICE_ID_INVALID;
GST_DEBUG_OBJECT(self, "Closed device");
return TRUE;
}
static gboolean gst_aja_src_start(GstAjaSrc *self) {
GST_DEBUG_OBJECT(self, "Starting");
{
// Make sure to globally lock here as the routing settings and others are
// global shared state
ShmMutexLocker locker;
#define NEEDS_QUAD_MODE(self) \
(self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_SQD || \
self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_TSI || \
(self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 && \
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4))
self->quad_mode = NEEDS_QUAD_MODE(self);
self->video_format = gst_ntv2_video_format_from_aja_format(
self->video_format_setting, self->quad_mode);
#undef NEEDS_QUAD_MODE
if (self->video_format == NTV2_FORMAT_UNKNOWN) {
GST_ERROR_OBJECT(self, "Unsupported mode");
return FALSE;
}
if (!::NTV2DeviceCanDoVideoFormat(self->device_id, self->video_format)) {
GST_ERROR_OBJECT(self, "Device does not support mode %d",
(int)self->video_format);
return FALSE;
}
if (self->quad_mode) {
if (self->channel != ::NTV2_CHANNEL1 &&
self->channel != ::NTV2_CHANNEL5) {
GST_ERROR_OBJECT(self, "Quad modes require channels 1 or 5");
return FALSE;
}
}
gst_video_info_from_ntv2_video_format(&self->configured_info,
self->video_format);
if (self->quad_mode) {
if (self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
self->device->device->SetQuadQuadFrameEnable(false, self->channel);
self->device->device->SetQuadQuadSquaresEnable(false, self->channel);
self->device->device->Set4kSquaresEnable(true, self->channel);
self->device->device->SetTsiFrameEnable(true, self->channel);
} else {
switch (self->sdi_mode) {
case GST_AJA_SDI_MODE_SINGLE_LINK:
g_assert_not_reached();
break;
case GST_AJA_SDI_MODE_QUAD_LINK_SQD:
if (self->configured_info.height > 2160) {
self->device->device->Set4kSquaresEnable(false, self->channel);
self->device->device->SetTsiFrameEnable(false, self->channel);
self->device->device->SetQuadQuadFrameEnable(true, self->channel);
self->device->device->SetQuadQuadSquaresEnable(true,
self->channel);
} else {
self->device->device->SetQuadQuadFrameEnable(false,
self->channel);
self->device->device->SetQuadQuadSquaresEnable(false,
self->channel);
self->device->device->Set4kSquaresEnable(true, self->channel);
self->device->device->SetTsiFrameEnable(false, self->channel);
}
break;
case GST_AJA_SDI_MODE_QUAD_LINK_TSI:
if (self->configured_info.height > 2160) {
self->device->device->Set4kSquaresEnable(false, self->channel);
self->device->device->SetTsiFrameEnable(false, self->channel);
self->device->device->SetQuadQuadFrameEnable(true, self->channel);
self->device->device->SetQuadQuadSquaresEnable(false,
self->channel);
} else {
self->device->device->SetQuadQuadFrameEnable(false,
self->channel);
self->device->device->SetQuadQuadSquaresEnable(false,
self->channel);
self->device->device->Set4kSquaresEnable(false, self->channel);
self->device->device->SetTsiFrameEnable(true, self->channel);
}
break;
}
}
} else {
self->device->device->Set4kSquaresEnable(false, self->channel);
self->device->device->SetTsiFrameEnable(false, self->channel);
self->device->device->SetQuadQuadFrameEnable(false, self->channel);
self->device->device->SetQuadQuadSquaresEnable(false, self->channel);
}
self->device->device->SetMode(self->channel, NTV2_MODE_CAPTURE, false);
if (self->quad_mode) {
for (int i = 1; i < 4; i++)
self->device->device->SetMode((NTV2Channel)(self->channel + i),
NTV2_MODE_CAPTURE, false);
}
GST_DEBUG_OBJECT(self, "Configuring video format %d on channel %d",
(int)self->video_format, (int)self->channel);
self->device->device->SetVideoFormat(self->video_format, false, false,
self->channel);
if (!::NTV2DeviceCanDoFrameBufferFormat(self->device_id,
::NTV2_FBF_10BIT_YCBCR)) {
GST_ERROR_OBJECT(self, "Device does not support frame buffer format %d",
(int)::NTV2_FBF_10BIT_YCBCR);
return FALSE;
}
self->device->device->SetFrameBufferFormat(self->channel,
::NTV2_FBF_10BIT_YCBCR);
if (self->quad_mode) {
for (int i = 1; i < 4; i++)
self->device->device->SetFrameBufferFormat(
(NTV2Channel)(self->channel + i), ::NTV2_FBF_10BIT_YCBCR);
}
self->device->device->DMABufferAutoLock(false, true, 0);
if (::NTV2DeviceHasBiDirectionalSDI(self->device_id)) {
self->device->device->SetSDITransmitEnable(self->channel, false);
if (self->quad_mode) {
for (int i = 1; i < 4; i++)
self->device->device->SetSDITransmitEnable(
(NTV2Channel)(self->channel + i), false);
}
}
// Always use the framebuffer associated with the channel
NTV2InputCrosspointID framebuffer_id =
::GetFrameBufferInputXptFromChannel(self->channel, false);
NTV2VANCMode vanc_mode;
NTV2InputSource input_source;
NTV2OutputCrosspointID input_source_id;
switch (self->input_source) {
case GST_AJA_INPUT_SOURCE_AUTO:
input_source = ::NTV2ChannelToInputSource(self->channel);
input_source_id =
::GetSDIInputOutputXptFromChannel(self->channel, false);
vanc_mode = ::NTV2DeviceCanDoCustomAnc(self->device_id)
? ::NTV2_VANCMODE_OFF
: ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_ANALOG1:
input_source = ::NTV2_INPUTSOURCE_ANALOG1;
input_source_id = ::NTV2_XptAnalogIn;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_HDMI1:
input_source = ::NTV2_INPUTSOURCE_HDMI1;
input_source_id = ::NTV2_XptHDMIIn1;
vanc_mode = ::NTV2_VANCMODE_OFF;
break;
case GST_AJA_INPUT_SOURCE_HDMI2:
input_source = ::NTV2_INPUTSOURCE_HDMI2;
input_source_id = ::NTV2_XptHDMIIn2;
vanc_mode = ::NTV2_VANCMODE_OFF;
break;
case GST_AJA_INPUT_SOURCE_HDMI3:
input_source = ::NTV2_INPUTSOURCE_HDMI3;
input_source_id = ::NTV2_XptHDMIIn3;
vanc_mode = ::NTV2_VANCMODE_OFF;
break;
case GST_AJA_INPUT_SOURCE_HDMI4:
input_source = ::NTV2_INPUTSOURCE_HDMI4;
input_source_id = ::NTV2_XptHDMIIn4;
vanc_mode = ::NTV2_VANCMODE_OFF;
break;
case GST_AJA_INPUT_SOURCE_SDI1:
input_source = ::NTV2_INPUTSOURCE_SDI1;
input_source_id = ::NTV2_XptSDIIn1;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_SDI2:
input_source = ::NTV2_INPUTSOURCE_SDI2;
input_source_id = ::NTV2_XptSDIIn2;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_SDI3:
input_source = ::NTV2_INPUTSOURCE_SDI3;
input_source_id = ::NTV2_XptSDIIn3;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_SDI4:
input_source = ::NTV2_INPUTSOURCE_SDI4;
input_source_id = ::NTV2_XptSDIIn4;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_SDI5:
input_source = ::NTV2_INPUTSOURCE_SDI5;
input_source_id = ::NTV2_XptSDIIn5;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_SDI6:
input_source = ::NTV2_INPUTSOURCE_SDI6;
input_source_id = ::NTV2_XptSDIIn6;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_SDI7:
input_source = ::NTV2_INPUTSOURCE_SDI7;
input_source_id = ::NTV2_XptSDIIn7;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
case GST_AJA_INPUT_SOURCE_SDI8:
input_source = ::NTV2_INPUTSOURCE_SDI8;
input_source_id = ::NTV2_XptSDIIn8;
vanc_mode = ::NTV2_VANCMODE_TALL;
break;
default:
g_assert_not_reached();
break;
}
self->configured_input_source = input_source;
self->vanc_mode = vanc_mode;
const NTV2Standard standard(
::GetNTV2StandardFromVideoFormat(self->video_format));
self->device->device->SetStandard(standard, self->channel);
if (self->quad_mode) {
for (int i = 1; i < 4; i++)
self->device->device->SetStandard(standard,
(NTV2Channel)(self->channel + i));
}
const NTV2FrameGeometry geometry =
::GetNTV2FrameGeometryFromVideoFormat(self->video_format);
self->vanc_mode =
::HasVANCGeometries(geometry) ? vanc_mode : ::NTV2_VANCMODE_OFF;
if (self->vanc_mode == ::NTV2_VANCMODE_OFF) {
self->device->device->SetFrameGeometry(geometry, false, self->channel);
self->device->device->SetVANCMode(self->vanc_mode, self->channel);
if (self->quad_mode) {
for (int i = 1; i < 4; i++) {
self->device->device->SetFrameGeometry(
geometry, false, (NTV2Channel)(self->channel + i));
self->device->device->SetVANCMode(self->vanc_mode,
(NTV2Channel)(self->channel + i));
}
}
} else {
const NTV2FrameGeometry vanc_geometry =
::GetVANCFrameGeometry(geometry, self->vanc_mode);
self->device->device->SetFrameGeometry(vanc_geometry, false,
self->channel);
self->device->device->SetVANCMode(self->vanc_mode, self->channel);
if (self->quad_mode) {
for (int i = 1; i < 4; i++) {
self->device->device->SetFrameGeometry(
vanc_geometry, false, (NTV2Channel)(self->channel + i));
self->device->device->SetVANCMode(self->vanc_mode,
(NTV2Channel)(self->channel + i));
}
}
}
CNTV2SignalRouter router;
self->device->device->GetRouting(router);
// Need to remove old routes for the output and framebuffer we're going to
// use
NTV2ActualConnections connections = router.GetConnections();
if (self->quad_mode) {
if (self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
// Need to disconnect the 4 inputs corresponding to this channel from
// their framebuffers/muxers, and muxers from their framebuffers
for (auto iter = connections.begin(); iter != connections.end();
iter++) {
if (iter->first == NTV2_XptFrameBuffer1Input ||
iter->first == NTV2_XptFrameBuffer1BInput ||
iter->first == NTV2_XptFrameBuffer2Input ||
iter->first == NTV2_XptFrameBuffer2BInput ||
iter->second == NTV2_Xpt425Mux1AYUV ||
iter->second == NTV2_Xpt425Mux1BYUV ||
iter->second == NTV2_Xpt425Mux2AYUV ||
iter->second == NTV2_Xpt425Mux2BYUV ||
iter->first == NTV2_Xpt425Mux1AInput ||
iter->first == NTV2_Xpt425Mux1BInput ||
iter->first == NTV2_Xpt425Mux2AInput ||
iter->first == NTV2_Xpt425Mux2BInput ||
iter->second == NTV2_XptHDMIIn1 ||
iter->second == NTV2_XptHDMIIn1Q2 ||
iter->second == NTV2_XptHDMIIn1Q3 ||
iter->second == NTV2_XptHDMIIn1Q4)
router.RemoveConnection(iter->first, iter->second);
}
} else if (self->channel == NTV2_CHANNEL1) {
for (auto iter = connections.begin(); iter != connections.end();
iter++) {
if (iter->first == NTV2_XptFrameBuffer1Input ||
iter->first == NTV2_XptFrameBuffer1BInput ||
iter->first == NTV2_XptFrameBuffer1DS2Input ||
iter->first == NTV2_XptFrameBuffer2Input ||
iter->first == NTV2_XptFrameBuffer2BInput ||
iter->first == NTV2_XptFrameBuffer2DS2Input ||
iter->second == NTV2_Xpt425Mux1AYUV ||
iter->second == NTV2_Xpt425Mux1BYUV ||
iter->second == NTV2_Xpt425Mux2AYUV ||
iter->second == NTV2_Xpt425Mux2BYUV ||
iter->first == NTV2_Xpt425Mux1AInput ||
iter->first == NTV2_Xpt425Mux1BInput ||
iter->first == NTV2_Xpt425Mux2AInput ||
iter->first == NTV2_Xpt425Mux2BInput ||
iter->second == NTV2_XptSDIIn1 ||
iter->second == NTV2_XptSDIIn2 ||
iter->second == NTV2_XptSDIIn3 ||
iter->second == NTV2_XptSDIIn4 ||
iter->second == NTV2_XptSDIIn1DS2 ||
iter->second == NTV2_XptSDIIn2DS2 ||
iter->first == NTV2_XptFrameBuffer1Input ||
iter->first == NTV2_XptFrameBuffer2Input ||
iter->first == NTV2_XptFrameBuffer3Input ||
iter->first == NTV2_XptFrameBuffer4Input)
router.RemoveConnection(iter->first, iter->second);
}
} else if (self->channel == NTV2_CHANNEL5) {
for (auto iter = connections.begin(); iter != connections.end();
iter++) {
if (iter->first == NTV2_XptFrameBuffer5Input ||
iter->first == NTV2_XptFrameBuffer5BInput ||
iter->first == NTV2_XptFrameBuffer5DS2Input ||
iter->first == NTV2_XptFrameBuffer6Input ||
iter->first == NTV2_XptFrameBuffer6BInput ||
iter->first == NTV2_XptFrameBuffer6DS2Input ||
iter->second == NTV2_Xpt425Mux3AYUV ||
iter->second == NTV2_Xpt425Mux3BYUV ||
iter->second == NTV2_Xpt425Mux4AYUV ||
iter->second == NTV2_Xpt425Mux4BYUV ||
iter->first == NTV2_Xpt425Mux3AInput ||
iter->first == NTV2_Xpt425Mux3BInput ||
iter->first == NTV2_Xpt425Mux4AInput ||
iter->first == NTV2_Xpt425Mux4BInput ||
iter->second == NTV2_XptSDIIn5 ||
iter->second == NTV2_XptSDIIn6 ||
iter->second == NTV2_XptSDIIn7 ||
iter->second == NTV2_XptSDIIn8 ||
iter->second == NTV2_XptSDIIn5DS2 ||
iter->second == NTV2_XptSDIIn6DS2 ||
iter->first == NTV2_XptFrameBuffer5Input ||
iter->first == NTV2_XptFrameBuffer6Input ||
iter->first == NTV2_XptFrameBuffer7Input ||
iter->first == NTV2_XptFrameBuffer8Input)
router.RemoveConnection(iter->first, iter->second);
}
} else {
g_assert_not_reached();
}
} else {
for (auto iter = connections.begin(); iter != connections.end(); iter++) {
if (iter->first == framebuffer_id || iter->second == input_source_id)
router.RemoveConnection(iter->first, iter->second);
if (((input_source_id == NTV2_XptSDIIn6 ||
input_source_id == NTV2_XptSDIIn8) &&
iter->first == NTV2_XptFrameBuffer6BInput) ||
((input_source_id == NTV2_XptSDIIn5 ||
input_source_id == NTV2_XptSDIIn6) &&
iter->first == NTV2_XptFrameBuffer5BInput) ||
((input_source_id == NTV2_XptSDIIn4 ||
input_source_id == NTV2_XptSDIIn2) &&
iter->first == NTV2_XptFrameBuffer2BInput) ||
((input_source_id == NTV2_XptSDIIn1 ||
input_source_id == NTV2_XptSDIIn2) &&
iter->first == NTV2_XptFrameBuffer1BInput))
router.RemoveConnection(iter->first, iter->second);
}
}
if (self->quad_mode) {
if (self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
input_source_id = NTV2_Xpt425Mux1AYUV;
} else if (self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_TSI &&
!NTV2_IS_QUAD_QUAD_HFR_VIDEO_FORMAT(self->video_format) &&
!NTV2_IS_QUAD_QUAD_FORMAT(self->video_format)) {
if (self->channel == NTV2_CHANNEL1)
input_source_id = NTV2_Xpt425Mux1AYUV;
else if (self->channel == NTV2_CHANNEL5)
input_source_id = NTV2_Xpt425Mux3AYUV;
else
g_assert_not_reached();
}
}
GST_DEBUG_OBJECT(self, "Creating connection %d - %d", framebuffer_id,
input_source_id);
router.AddConnection(framebuffer_id, input_source_id);
if (self->quad_mode) {
if (self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
router.AddConnection(NTV2_XptFrameBuffer1BInput, NTV2_Xpt425Mux1BYUV);
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_Xpt425Mux2AYUV);
router.AddConnection(NTV2_XptFrameBuffer2BInput, NTV2_Xpt425Mux2BYUV);
router.AddConnection(NTV2_Xpt425Mux1AInput, NTV2_XptHDMIIn1);
router.AddConnection(NTV2_Xpt425Mux1BInput, NTV2_XptHDMIIn1Q2);
router.AddConnection(NTV2_Xpt425Mux2AInput, NTV2_XptHDMIIn1Q3);
router.AddConnection(NTV2_Xpt425Mux2BInput, NTV2_XptHDMIIn1Q4);
} else {
if (self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_TSI) {
if (NTV2_IS_QUAD_QUAD_HFR_VIDEO_FORMAT(self->video_format)) {
if (self->channel == NTV2_CHANNEL1) {
router.AddConnection(NTV2_XptFrameBuffer1DS2Input,
NTV2_XptSDIIn2);
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_XptSDIIn3);
router.AddConnection(NTV2_XptFrameBuffer2DS2Input,
NTV2_XptSDIIn4);
} else if (self->channel == NTV2_CHANNEL5) {
router.AddConnection(NTV2_XptFrameBuffer5DS2Input,
NTV2_XptSDIIn6);
router.AddConnection(NTV2_XptFrameBuffer5Input, NTV2_XptSDIIn7);
router.AddConnection(NTV2_XptFrameBuffer6DS2Input,
NTV2_XptSDIIn8);
} else {
g_assert_not_reached();
}
} else if (NTV2_IS_QUAD_QUAD_FORMAT(self->video_format)) {
if (self->channel == NTV2_CHANNEL1) {
router.AddConnection(NTV2_XptFrameBuffer1DS2Input,
NTV2_XptSDIIn1DS2);
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_XptSDIIn2);
router.AddConnection(NTV2_XptFrameBuffer2DS2Input,
NTV2_XptSDIIn2DS2);
} else if (self->channel == NTV2_CHANNEL5) {
router.AddConnection(NTV2_XptFrameBuffer5DS2Input,
NTV2_XptSDIIn5DS2);
router.AddConnection(NTV2_XptFrameBuffer5Input, NTV2_XptSDIIn6);
router.AddConnection(NTV2_XptFrameBuffer6DS2Input,
NTV2_XptSDIIn6DS2);
} else {
g_assert_not_reached();
}
// FIXME: Need special handling of NTV2_IS_4K_HFR_VIDEO_FORMAT for
// TSI?
} else {
if (self->channel == NTV2_CHANNEL1) {
router.AddConnection(NTV2_XptFrameBuffer1BInput,
NTV2_Xpt425Mux1BYUV);
router.AddConnection(NTV2_XptFrameBuffer2Input,
NTV2_Xpt425Mux2AYUV);
router.AddConnection(NTV2_XptFrameBuffer2BInput,
NTV2_Xpt425Mux2BYUV);
router.AddConnection(NTV2_Xpt425Mux1AInput, NTV2_XptSDIIn1);
router.AddConnection(NTV2_Xpt425Mux1BInput, NTV2_XptSDIIn2);
router.AddConnection(NTV2_Xpt425Mux2AInput, NTV2_XptSDIIn3);
router.AddConnection(NTV2_Xpt425Mux2BInput, NTV2_XptSDIIn4);
} else if (self->channel == NTV2_CHANNEL5) {
router.AddConnection(NTV2_XptFrameBuffer5BInput,
NTV2_Xpt425Mux3BYUV);
router.AddConnection(NTV2_XptFrameBuffer6Input,
NTV2_Xpt425Mux4AYUV);
router.AddConnection(NTV2_XptFrameBuffer6BInput,
NTV2_Xpt425Mux4BYUV);
router.AddConnection(NTV2_Xpt425Mux3AInput, NTV2_XptSDIIn5);
router.AddConnection(NTV2_Xpt425Mux3BInput, NTV2_XptSDIIn6);
router.AddConnection(NTV2_Xpt425Mux4AInput, NTV2_XptSDIIn7);
router.AddConnection(NTV2_Xpt425Mux4BInput, NTV2_XptSDIIn8);
} else {
g_assert_not_reached();
}
}
} else {
if (self->channel == NTV2_CHANNEL1) {
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_XptSDIIn2);
router.AddConnection(NTV2_XptFrameBuffer3Input, NTV2_XptSDIIn3);
router.AddConnection(NTV2_XptFrameBuffer4Input, NTV2_XptSDIIn4);
} else if (self->channel == NTV2_CHANNEL5) {
router.AddConnection(NTV2_XptFrameBuffer6Input, NTV2_XptSDIIn6);
router.AddConnection(NTV2_XptFrameBuffer7Input, NTV2_XptSDIIn7);
router.AddConnection(NTV2_XptFrameBuffer8Input, NTV2_XptSDIIn8);
} else {
g_assert_not_reached();
}
}
}
}
{
std::stringstream os;
CNTV2SignalRouter oldRouter;
self->device->device->GetRouting(oldRouter);
oldRouter.Print(os);
GST_DEBUG_OBJECT(self, "Previous routing:\n%s", os.str().c_str());
}
self->device->device->ApplySignalRoute(router, true);
{
std::stringstream os;
CNTV2SignalRouter currentRouter;
self->device->device->GetRouting(currentRouter);
currentRouter.Print(os);
GST_DEBUG_OBJECT(self, "New routing:\n%s", os.str().c_str());
}
switch (self->audio_system_setting) {
case GST_AJA_AUDIO_SYSTEM_1:
self->audio_system = ::NTV2_AUDIOSYSTEM_1;
break;
case GST_AJA_AUDIO_SYSTEM_2:
self->audio_system = ::NTV2_AUDIOSYSTEM_2;
break;
case GST_AJA_AUDIO_SYSTEM_3:
self->audio_system = ::NTV2_AUDIOSYSTEM_3;
break;
case GST_AJA_AUDIO_SYSTEM_4:
self->audio_system = ::NTV2_AUDIOSYSTEM_4;
break;
case GST_AJA_AUDIO_SYSTEM_5:
self->audio_system = ::NTV2_AUDIOSYSTEM_5;
break;
case GST_AJA_AUDIO_SYSTEM_6:
self->audio_system = ::NTV2_AUDIOSYSTEM_6;
break;
case GST_AJA_AUDIO_SYSTEM_7:
self->audio_system = ::NTV2_AUDIOSYSTEM_7;
break;
case GST_AJA_AUDIO_SYSTEM_8:
self->audio_system = ::NTV2_AUDIOSYSTEM_8;
break;
case GST_AJA_AUDIO_SYSTEM_AUTO:
self->audio_system = ::NTV2_AUDIOSYSTEM_1;
if (::NTV2DeviceGetNumAudioSystems(self->device_id) > 1)
self->audio_system = ::NTV2ChannelToAudioSystem(self->channel);
if (!::NTV2DeviceCanDoFrameStore1Display(self->device_id))
self->audio_system = ::NTV2_AUDIOSYSTEM_1;
break;
default:
g_assert_not_reached();
break;
}
GST_DEBUG_OBJECT(self, "Using audio system %d", self->audio_system);
NTV2AudioSource audio_source;
switch (self->audio_source) {
case GST_AJA_AUDIO_SOURCE_EMBEDDED:
audio_source = ::NTV2_AUDIO_EMBEDDED;
break;
case GST_AJA_AUDIO_SOURCE_AES:
audio_source = ::NTV2_AUDIO_AES;
break;
case GST_AJA_AUDIO_SOURCE_ANALOG:
audio_source = ::NTV2_AUDIO_ANALOG;
break;
case GST_AJA_AUDIO_SOURCE_HDMI:
audio_source = ::NTV2_AUDIO_HDMI;
break;
case GST_AJA_AUDIO_SOURCE_MIC:
audio_source = ::NTV2_AUDIO_MIC;
break;
default:
g_assert_not_reached();
break;
}
self->device->device->SetAudioSystemInputSource(
self->audio_system, audio_source,
::NTV2InputSourceToEmbeddedAudioInput(input_source));
self->configured_audio_channels =
::NTV2DeviceGetMaxAudioChannels(self->device_id);
self->device->device->SetNumberAudioChannels(
self->configured_audio_channels, self->audio_system);
self->device->device->SetAudioRate(::NTV2_AUDIO_48K, self->audio_system);
self->device->device->SetAudioBufferSize(::NTV2_AUDIO_BUFFER_BIG,
self->audio_system);
self->device->device->SetAudioLoopBack(::NTV2_AUDIO_LOOPBACK_OFF,
self->audio_system);
self->device->device->SetEmbeddedAudioClock(
::NTV2_EMBEDDED_AUDIO_CLOCK_VIDEO_INPUT, self->audio_system);
NTV2ReferenceSource reference_source;
switch (self->reference_source) {
case GST_AJA_REFERENCE_SOURCE_AUTO:
reference_source = ::NTV2InputSourceToReferenceSource(input_source);
break;
case GST_AJA_REFERENCE_SOURCE_EXTERNAL:
reference_source = ::NTV2_REFERENCE_EXTERNAL;
break;
case GST_AJA_REFERENCE_SOURCE_FREERUN:
reference_source = ::NTV2_REFERENCE_FREERUN;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_1:
reference_source = ::NTV2_REFERENCE_INPUT1;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_2:
reference_source = ::NTV2_REFERENCE_INPUT2;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_3:
reference_source = ::NTV2_REFERENCE_INPUT3;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_4:
reference_source = ::NTV2_REFERENCE_INPUT4;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_5:
reference_source = ::NTV2_REFERENCE_INPUT5;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_6:
reference_source = ::NTV2_REFERENCE_INPUT6;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_7:
reference_source = ::NTV2_REFERENCE_INPUT7;
break;
case GST_AJA_REFERENCE_SOURCE_INPUT_8:
reference_source = ::NTV2_REFERENCE_INPUT8;
break;
default:
g_assert_not_reached();
break;
}
GST_DEBUG_OBJECT(self, "Configuring reference source %d",
(int)reference_source);
self->device->device->SetReference(reference_source);
switch (self->timecode_index) {
case GST_AJA_TIMECODE_INDEX_VITC:
self->tc_index = ::NTV2InputSourceToTimecodeIndex(input_source, false);
break;
case GST_AJA_TIMECODE_INDEX_ATC_LTC:
self->tc_index = ::NTV2InputSourceToTimecodeIndex(input_source, true);
break;
case GST_AJA_TIMECODE_INDEX_LTC1:
self->tc_index = ::NTV2_TCINDEX_LTC1;
break;
case GST_AJA_TIMECODE_INDEX_LTC2:
self->tc_index = ::NTV2_TCINDEX_LTC2;
break;
default:
g_assert_not_reached();
break;
}
}
guint video_buffer_size = ::GetVideoActiveSize(
self->video_format, ::NTV2_FBF_10BIT_YCBCR, self->vanc_mode);
self->buffer_pool = gst_buffer_pool_new();
GstStructure *config = gst_buffer_pool_get_config(self->buffer_pool);
gst_buffer_pool_config_set_params(config, NULL, video_buffer_size,
2 * self->queue_size, 0);
gst_buffer_pool_config_set_allocator(config, self->allocator, NULL);
gst_buffer_pool_set_config(self->buffer_pool, config);
gst_buffer_pool_set_active(self->buffer_pool, TRUE);
guint audio_buffer_size = 401 * 1024;
self->audio_buffer_pool = gst_buffer_pool_new();
config = gst_buffer_pool_get_config(self->audio_buffer_pool);
gst_buffer_pool_config_set_params(config, NULL, audio_buffer_size,
2 * self->queue_size, 0);
gst_buffer_pool_config_set_allocator(config, self->allocator, NULL);
gst_buffer_pool_set_config(self->audio_buffer_pool, config);
gst_buffer_pool_set_active(self->audio_buffer_pool, TRUE);
guint anc_buffer_size = 8 * 1024;
if (self->vanc_mode == ::NTV2_VANCMODE_OFF &&
::NTV2DeviceCanDoCustomAnc(self->device_id)) {
self->anc_buffer_pool = gst_buffer_pool_new();
config = gst_buffer_pool_get_config(self->anc_buffer_pool);
gst_buffer_pool_config_set_params(
config, NULL, anc_buffer_size,
(self->configured_info.interlace_mode ==
GST_VIDEO_INTERLACE_MODE_PROGRESSIVE
? 1
: 2) *
self->queue_size,
0);
gst_buffer_pool_config_set_allocator(config, self->allocator, NULL);
gst_buffer_pool_set_config(self->anc_buffer_pool, config);
gst_buffer_pool_set_active(self->anc_buffer_pool, TRUE);
}
self->capture_thread = new AJAThread();
self->capture_thread->Attach(capture_thread_func, self);
self->capture_thread->SetPriority(AJA_ThreadPriority_High);
self->capture_thread->Start();
g_mutex_lock(&self->queue_lock);
self->shutdown = FALSE;
self->playing = FALSE;
self->flushing = FALSE;
g_cond_signal(&self->queue_cond);
g_mutex_unlock(&self->queue_lock);
gst_element_post_message(GST_ELEMENT_CAST(self),
gst_message_new_latency(GST_OBJECT_CAST(self)));
return TRUE;
}
static gboolean gst_aja_src_stop(GstAjaSrc *self) {
QueueItem *item;
GST_DEBUG_OBJECT(self, "Stopping");
g_mutex_lock(&self->queue_lock);
self->shutdown = TRUE;
self->flushing = TRUE;
self->playing = FALSE;
g_cond_signal(&self->queue_cond);
g_mutex_unlock(&self->queue_lock);
if (self->capture_thread) {
self->capture_thread->Stop();
delete self->capture_thread;
self->capture_thread = NULL;
}
GST_OBJECT_LOCK(self);
memset(&self->current_info, 0, sizeof(self->current_info));
memset(&self->configured_info, 0, sizeof(self->configured_info));
self->configured_audio_channels = 0;
GST_OBJECT_UNLOCK(self);
while ((item = (QueueItem *)gst_queue_array_pop_head_struct(self->queue))) {
if (item->type == QUEUE_ITEM_TYPE_FRAME) {
gst_clear_buffer(&item->video_buffer);
gst_clear_buffer(&item->audio_buffer);
gst_clear_buffer(&item->anc_buffer);
gst_clear_buffer(&item->anc_buffer2);
}
}
if (self->buffer_pool) {
gst_buffer_pool_set_active(self->buffer_pool, FALSE);
gst_clear_object(&self->buffer_pool);
}
if (self->audio_buffer_pool) {
gst_buffer_pool_set_active(self->audio_buffer_pool, FALSE);
gst_clear_object(&self->audio_buffer_pool);
}
if (self->anc_buffer_pool) {
gst_buffer_pool_set_active(self->anc_buffer_pool, FALSE);
gst_clear_object(&self->anc_buffer_pool);
}
GST_DEBUG_OBJECT(self, "Stopped");
return TRUE;
}
static GstStateChangeReturn gst_aja_src_change_state(
GstElement *element, GstStateChange transition) {
GstAjaSrc *self = GST_AJA_SRC(element);
GstStateChangeReturn ret;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (!gst_aja_src_open(self)) return GST_STATE_CHANGE_FAILURE;
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
if (!gst_aja_src_start(self)) return GST_STATE_CHANGE_FAILURE;
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
default:
break;
}
ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
if (ret == GST_STATE_CHANGE_FAILURE) return ret;
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
g_mutex_lock(&self->queue_lock);
self->playing = FALSE;
g_cond_signal(&self->queue_cond);
g_mutex_unlock(&self->queue_lock);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
g_mutex_lock(&self->queue_lock);
self->playing = TRUE;
g_cond_signal(&self->queue_cond);
g_mutex_unlock(&self->queue_lock);
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
if (!gst_aja_src_stop(self)) return GST_STATE_CHANGE_FAILURE;
break;
case GST_STATE_CHANGE_READY_TO_NULL:
if (!gst_aja_src_close(self)) return GST_STATE_CHANGE_FAILURE;
break;
default:
break;
}
return ret;
}
static GstCaps *gst_aja_src_get_caps(GstBaseSrc *bsrc, GstCaps *filter) {
GstAjaSrc *self = GST_AJA_SRC(bsrc);
GstCaps *caps;
if (self->device) {
caps = gst_ntv2_supported_caps(self->device_id);
} else {
caps = gst_pad_get_pad_template_caps(GST_BASE_SRC_PAD(self));
}
if (filter) {
GstCaps *tmp =
gst_caps_intersect_full(filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref(caps);
caps = tmp;
}
return caps;
}
static gboolean gst_aja_src_query(GstBaseSrc *bsrc, GstQuery *query) {
GstAjaSrc *self = GST_AJA_SRC(bsrc);
gboolean ret = TRUE;
switch (GST_QUERY_TYPE(query)) {
case GST_QUERY_LATENCY: {
if (self->current_info.finfo &&
self->current_info.finfo->format != GST_VIDEO_FORMAT_UNKNOWN) {
GstClockTime min, max;
min = gst_util_uint64_scale_ceil(
GST_SECOND, 3 * self->current_info.fps_d, self->current_info.fps_n);
max = self->queue_size * min;
gst_query_set_latency(query, TRUE, min, max);
ret = TRUE;
} else {
ret = FALSE;
}
return ret;
}
default:
return GST_BASE_SRC_CLASS(parent_class)->query(bsrc, query);
break;
}
}
static gboolean gst_aja_src_unlock(GstBaseSrc *bsrc) {
GstAjaSrc *self = GST_AJA_SRC(bsrc);
g_mutex_lock(&self->queue_lock);
self->flushing = TRUE;
g_cond_signal(&self->queue_cond);
g_mutex_unlock(&self->queue_lock);
return TRUE;
}
static gboolean gst_aja_src_unlock_stop(GstBaseSrc *bsrc) {
GstAjaSrc *self = GST_AJA_SRC(bsrc);
g_mutex_lock(&self->queue_lock);
self->flushing = FALSE;
g_mutex_unlock(&self->queue_lock);
return TRUE;
}
static GstFlowReturn gst_aja_src_create(GstPushSrc *psrc, GstBuffer **buffer) {
GstAjaSrc *self = GST_AJA_SRC(psrc);
GstFlowReturn flow_ret = GST_FLOW_OK;
QueueItem item;
g_mutex_lock(&self->queue_lock);
while (gst_queue_array_is_empty(self->queue) && !self->flushing) {
g_cond_wait(&self->queue_cond, &self->queue_lock);
}
if (self->flushing) {
g_mutex_unlock(&self->queue_lock);
GST_DEBUG_OBJECT(self, "Flushing");
return GST_FLOW_FLUSHING;
}
item = *(QueueItem *)gst_queue_array_pop_head_struct(self->queue);
g_mutex_unlock(&self->queue_lock);
*buffer = item.video_buffer;
gst_buffer_add_aja_audio_meta(*buffer, item.audio_buffer);
gst_buffer_unref(item.audio_buffer);
if (item.tc.IsValid()) {
TimecodeFormat tc_format = ::kTCFormatUnknown;
GstVideoTimeCodeFlags flags = GST_VIDEO_TIME_CODE_FLAGS_NONE;
if (self->configured_info.fps_n == 24 && self->configured_info.fps_d == 1) {
tc_format = kTCFormat24fps;
} else if (self->configured_info.fps_n == 25 &&
self->configured_info.fps_d == 1) {
tc_format = kTCFormat25fps;
} else if (self->configured_info.fps_n == 30 &&
self->configured_info.fps_d == 1) {
tc_format = kTCFormat30fps;
} else if (self->configured_info.fps_n == 30000 &&
self->configured_info.fps_d == 1001) {
tc_format = kTCFormat30fpsDF;
flags =
(GstVideoTimeCodeFlags)(flags | GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME);
} else if (self->configured_info.fps_n == 48 &&
self->configured_info.fps_d == 1) {
tc_format = kTCFormat48fps;
} else if (self->configured_info.fps_n == 50 &&
self->configured_info.fps_d == 1) {
tc_format = kTCFormat50fps;
} else if (self->configured_info.fps_n == 60 &&
self->configured_info.fps_d == 1) {
tc_format = kTCFormat60fps;
} else if (self->configured_info.fps_n == 60000 &&
self->configured_info.fps_d == 1001) {
tc_format = kTCFormat60fpsDF;
flags =
(GstVideoTimeCodeFlags)(flags | GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME);
}
if (self->configured_info.interlace_mode !=
GST_VIDEO_INTERLACE_MODE_PROGRESSIVE)
flags =
(GstVideoTimeCodeFlags)(flags | GST_VIDEO_TIME_CODE_FLAGS_INTERLACED);
CRP188 rp188(item.tc, tc_format);
guint hours, minutes, seconds, frames;
rp188.GetRP188Hrs(hours);
rp188.GetRP188Mins(minutes);
rp188.GetRP188Secs(seconds);
rp188.GetRP188Frms(frames);
GstVideoTimeCode tc;
gst_video_time_code_init(&tc, self->configured_info.fps_n,
self->configured_info.fps_d, NULL, flags, hours,
minutes, seconds, frames, 0);
gst_buffer_add_video_time_code_meta(*buffer, &tc);
}
AJAAncillaryList anc_packets;
if (item.anc_buffer) {
GstMapInfo map = GST_MAP_INFO_INIT;
GstMapInfo map2 = GST_MAP_INFO_INIT;
gst_buffer_map(item.anc_buffer, &map, GST_MAP_READ);
if (item.anc_buffer2) gst_buffer_map(item.anc_buffer2, &map2, GST_MAP_READ);
NTV2_POINTER ptr1(map.data, map.size);
NTV2_POINTER ptr2(map2.data, map2.size);
AJAAncillaryList::SetFromDeviceAncBuffers(ptr1, ptr2, anc_packets);
if (item.anc_buffer2) gst_buffer_unmap(item.anc_buffer2, &map2);
gst_buffer_unmap(item.anc_buffer, &map);
} else if (self->vanc_mode != ::NTV2_VANCMODE_OFF) {
GstMapInfo map;
NTV2FormatDescriptor format_desc(self->video_format, ::NTV2_FBF_10BIT_YCBCR,
self->vanc_mode);
gst_buffer_map(item.video_buffer, &map, GST_MAP_READ);
NTV2_POINTER ptr(map.data, map.size);
AJAAncillaryList::SetFromVANCData(ptr, format_desc, anc_packets);
gst_buffer_unmap(item.video_buffer, &map);
guint offset =
format_desc.RasterLineToByteOffset(format_desc.GetFirstActiveLine());
guint size = format_desc.GetVisibleRasterBytes();
gst_buffer_resize(item.video_buffer, offset, size);
}
gst_clear_buffer(&item.anc_buffer);
gst_clear_buffer(&item.anc_buffer2);
// Not using CountAncillaryDataWithType(AJAAncillaryDataType_Cea708) etc
// here because for SD it doesn't recognize the packets. It assumes they
// would only be received on AJAAncillaryDataChannel_Y but for SD it is
// actually AJAAncillaryDataChannel_Both.
//
// See AJA SDK support ticket #4844.
guint32 n_vanc_packets = anc_packets.CountAncillaryData();
for (guint32 i = 0; i < n_vanc_packets; i++) {
AJAAncillaryData *packet = anc_packets.GetAncillaryDataAtIndex(i);
if (packet->GetDID() == AJAAncillaryData_CEA708_DID &&
packet->GetSID() == AJAAncillaryData_CEA708_SID &&
packet->GetPayloadData() && packet->GetPayloadByteCount() &&
AJA_SUCCESS(packet->ParsePayloadData())) {
gst_buffer_add_video_caption_meta(
*buffer, GST_VIDEO_CAPTION_TYPE_CEA708_CDP, packet->GetPayloadData(),
packet->GetPayloadByteCount());
}
}
// TODO: Add AFD/Bar meta
bool caps_changed = false;
CNTV2VPID vpid(item.vpid);
if (vpid.IsValid()) {
GstVideoInfo info;
if (gst_video_info_from_ntv2_video_format(&info, item.detected_format)) {
switch (vpid.GetTransferCharacteristics()) {
default:
case NTV2_VPID_TC_SDR_TV:
// SDR is the default, do nothing here.
break;
case NTV2_VPID_TC_HLG:
info.colorimetry.transfer = GST_VIDEO_TRANSFER_ARIB_STD_B67;
break;
case NTV2_VPID_TC_PQ:
info.colorimetry.transfer = GST_VIDEO_TRANSFER_SMPTE2084;
break;
}
switch (vpid.GetColorimetry()) {
case NTV2_VPID_Color_Rec709:
info.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
info.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
break;
case NTV2_VPID_Color_UHDTV:
info.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
info.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
break;
default:
// Default handling
break;
}
switch (vpid.GetRGBRange()) {
case NTV2_VPID_Range_Full:
info.colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
break;
case NTV2_VPID_Range_Narrow:
info.colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
break;
}
if (!gst_pad_has_current_caps(GST_BASE_SRC_PAD(self)) ||
!gst_video_info_is_equal(&info, &self->current_info)) {
self->current_info = info;
caps_changed = true;
}
}
} else {
GstVideoInfo info;
if (gst_video_info_from_ntv2_video_format(&info, item.detected_format)) {
if (!gst_pad_has_current_caps(GST_BASE_SRC_PAD(self)) ||
!gst_video_info_is_equal(&info, &self->current_info)) {
self->current_info = info;
caps_changed = true;
}
} else if (!gst_pad_has_current_caps(GST_BASE_SRC_PAD(self))) {
self->current_info = self->configured_info;
caps_changed = true;
}
}
if (caps_changed) {
GstCaps *caps = gst_video_info_to_caps(&self->current_info);
gst_caps_set_simple(caps, "audio-channels", G_TYPE_INT,
self->configured_audio_channels, NULL);
GST_DEBUG_OBJECT(self, "Configuring caps %" GST_PTR_FORMAT, caps);
gst_base_src_set_caps(GST_BASE_SRC_CAST(self), caps);
gst_caps_unref(caps);
}
return flow_ret;
}
static void capture_thread_func(AJAThread *thread, void *data) {
GstAjaSrc *self = GST_AJA_SRC(data);
GstClock *clock = NULL;
AUTOCIRCULATE_TRANSFER transfer;
guint64 frames_dropped_last = G_MAXUINT64;
gboolean have_signal = TRUE;
if (self->capture_cpu_core != G_MAXUINT) {
cpu_set_t mask;
pthread_t current_thread = pthread_self();
CPU_ZERO(&mask);
CPU_SET(self->capture_cpu_core, &mask);
if (pthread_setaffinity_np(current_thread, sizeof(mask), &mask) != 0) {
GST_ERROR_OBJECT(self,
"Failed to set affinity for current thread to core %u",
self->capture_cpu_core);
}
}
g_mutex_lock(&self->queue_lock);
restart:
GST_DEBUG_OBJECT(self, "Waiting for playing or shutdown");
while (!self->playing && !self->shutdown)
g_cond_wait(&self->queue_cond, &self->queue_lock);
if (self->shutdown) {
GST_DEBUG_OBJECT(self, "Shutting down");
g_mutex_unlock(&self->queue_lock);
return;
}
GST_DEBUG_OBJECT(self, "Starting capture");
g_mutex_unlock(&self->queue_lock);
// TODO: Wait for stable input signal
if (!self->device->device->EnableChannel(self->channel)) {
GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL),
("Failed to enable channel"));
goto out;
}
if (self->quad_mode) {
for (int i = 1; i < 4; i++) {
if (!self->device->device->EnableChannel(
(NTV2Channel)(self->channel + i))) {
GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL),
("Failed to enable channel"));
goto out;
}
}
}
{
// Make sure to globally lock here as the routing settings and others are
// global shared state
ShmMutexLocker locker;
self->device->device->AutoCirculateStop(self->channel);
self->device->device->EnableInputInterrupt(self->channel);
self->device->device->SubscribeInputVerticalEvent(self->channel);
if (!self->device->device->AutoCirculateInitForInput(
self->channel, self->queue_size / 2, self->audio_system,
AUTOCIRCULATE_WITH_RP188 |
(self->vanc_mode == ::NTV2_VANCMODE_OFF ? AUTOCIRCULATE_WITH_ANC
: 0),
1)) {
GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL),
("Failed to initialize autocirculate"));
goto out;
}
self->device->device->AutoCirculateStart(self->channel);
}
gst_clear_object(&clock);
clock = gst_element_get_clock(GST_ELEMENT_CAST(self));
frames_dropped_last = G_MAXUINT64;
have_signal = TRUE;
g_mutex_lock(&self->queue_lock);
while (self->playing && !self->shutdown) {
// Check for valid signal first
NTV2VideoFormat current_video_format =
self->device->device->GetInputVideoFormat(
self->configured_input_source);
ULWord vpid_a = 0;
ULWord vpid_b = 0;
self->device->device->ReadSDIInVPID(self->channel, vpid_a, vpid_b);
GST_DEBUG_OBJECT(self,
"Detected input video format %u with VPID %08x / %08x",
current_video_format, vpid_a, vpid_b);
NTV2VideoFormat effective_video_format = self->video_format;
// Can't call this unconditionally as it also maps e.g. 3840x2160p to 1080p
if (self->quad_mode) {
effective_video_format =
::GetQuarterSizedVideoFormat(effective_video_format);
}
if (current_video_format == ::NTV2_FORMAT_UNKNOWN) {
GST_DEBUG_OBJECT(self, "No signal, waiting");
g_mutex_unlock(&self->queue_lock);
self->device->device->WaitForInputVerticalInterrupt(self->channel);
frames_dropped_last = G_MAXUINT64;
if (have_signal) {
GST_ELEMENT_WARNING(GST_ELEMENT(self), RESOURCE, READ, ("Signal lost"),
("No input source was detected"));
have_signal = FALSE;
}
g_mutex_lock(&self->queue_lock);
continue;
} else if (current_video_format != effective_video_format &&
current_video_format != self->video_format) {
// TODO: Handle GST_AJA_VIDEO_FORMAT_AUTO here
GST_DEBUG_OBJECT(self,
"Different input format %u than configured %u "
"(effective %u), waiting",
current_video_format, self->video_format,
effective_video_format);
g_mutex_unlock(&self->queue_lock);
self->device->device->WaitForInputVerticalInterrupt(self->channel);
frames_dropped_last = G_MAXUINT64;
if (have_signal) {
GST_ELEMENT_WARNING(GST_ELEMENT(self), RESOURCE, READ, ("Signal lost"),
("Different input source was detected"));
have_signal = FALSE;
}
g_mutex_lock(&self->queue_lock);
continue;
}
if (!have_signal) {
GST_ELEMENT_INFO(GST_ELEMENT(self), RESOURCE, READ, ("Signal recovered"),
("Input source detected"));
have_signal = TRUE;
}
AUTOCIRCULATE_STATUS status;
self->device->device->AutoCirculateGetStatus(self->channel, status);
GST_TRACE_OBJECT(self,
"Start frame %d "
"end frame %d "
"active frame %d "
"start time %" G_GUINT64_FORMAT
" "
"current time %" G_GUINT64_FORMAT
" "
"frames processed %u "
"frames dropped %u "
"buffer level %u",
status.acStartFrame, status.acEndFrame,
status.acActiveFrame, status.acRDTSCStartTime,
status.acRDTSCCurrentTime, status.acFramesProcessed,
status.acFramesDropped, status.acBufferLevel);
if (frames_dropped_last == G_MAXUINT64) {
frames_dropped_last = status.acFramesDropped;
} else if (frames_dropped_last < status.acFramesDropped) {
GST_WARNING_OBJECT(self, "Dropped %" G_GUINT64_FORMAT " frames",
status.acFramesDropped - frames_dropped_last);
GstClockTime timestamp =
gst_util_uint64_scale(status.acFramesProcessed + frames_dropped_last,
self->configured_info.fps_n,
self->configured_info.fps_d * GST_SECOND);
GstClockTime timestamp_end = gst_util_uint64_scale(
status.acFramesProcessed + status.acFramesDropped,
self->configured_info.fps_n,
self->configured_info.fps_d * GST_SECOND);
GstMessage *msg = gst_message_new_qos(
GST_OBJECT_CAST(self), TRUE, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE,
timestamp, timestamp_end - timestamp);
gst_element_post_message(GST_ELEMENT_CAST(self), msg);
frames_dropped_last = status.acFramesDropped;
}
if (status.IsRunning() && status.acBufferLevel > 1) {
GstBuffer *video_buffer = NULL;
GstBuffer *audio_buffer = NULL;
GstBuffer *anc_buffer = NULL, *anc_buffer2 = NULL;
GstMapInfo video_map = GST_MAP_INFO_INIT;
GstMapInfo audio_map = GST_MAP_INFO_INIT;
GstMapInfo anc_map = GST_MAP_INFO_INIT;
GstMapInfo anc_map2 = GST_MAP_INFO_INIT;
AUTOCIRCULATE_TRANSFER transfer;
if (gst_buffer_pool_acquire_buffer(self->buffer_pool, &video_buffer,
NULL) != GST_FLOW_OK) {
GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL),
("Failed to acquire video buffer"));
break;
}
if (gst_buffer_pool_acquire_buffer(self->audio_buffer_pool, &audio_buffer,
NULL) != GST_FLOW_OK) {
gst_buffer_unref(video_buffer);
GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL),
("Failed to acquire audio buffer"));
break;
}
if (self->vanc_mode == ::NTV2_VANCMODE_OFF &&
::NTV2DeviceCanDoCustomAnc(self->device_id)) {
if (gst_buffer_pool_acquire_buffer(self->anc_buffer_pool, &anc_buffer,
NULL) != GST_FLOW_OK) {
gst_buffer_unref(audio_buffer);
gst_buffer_unref(video_buffer);
GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL),
("Failed to acquire anc buffer"));
break;
}
if (self->configured_info.interlace_mode !=
GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) {
if (gst_buffer_pool_acquire_buffer(
self->anc_buffer_pool, &anc_buffer2, NULL) != GST_FLOW_OK) {
gst_buffer_unref(anc_buffer);
gst_buffer_unref(audio_buffer);
gst_buffer_unref(video_buffer);
GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL),
("Failed to acquire anc buffer"));
break;
}
}
}
gst_buffer_map(video_buffer, &video_map, GST_MAP_READWRITE);
gst_buffer_map(audio_buffer, &audio_map, GST_MAP_READWRITE);
if (anc_buffer) gst_buffer_map(anc_buffer, &anc_map, GST_MAP_READWRITE);
if (anc_buffer2)
gst_buffer_map(anc_buffer2, &anc_map2, GST_MAP_READWRITE);
transfer.acFrameBufferFormat = ::NTV2_FBF_10BIT_YCBCR;
transfer.SetVideoBuffer((ULWord *)video_map.data, video_map.size);
transfer.SetAudioBuffer((ULWord *)audio_map.data, audio_map.size);
transfer.SetAncBuffers((ULWord *)anc_map.data, anc_map.size,
(ULWord *)anc_map2.data, anc_map2.size);
g_mutex_unlock(&self->queue_lock);
bool transfered = true;
if (!self->device->device->AutoCirculateTransfer(self->channel,
transfer)) {
GST_WARNING_OBJECT(self, "Failed to transfer frame");
transfered = false;
}
if (anc_buffer2) gst_buffer_unmap(anc_buffer2, &anc_map2);
if (anc_buffer) gst_buffer_unmap(anc_buffer, &anc_map);
gst_buffer_unmap(audio_buffer, &audio_map);
gst_buffer_unmap(video_buffer, &video_map);
g_mutex_lock(&self->queue_lock);
if (!transfered) {
gst_clear_buffer(&anc_buffer2);
gst_clear_buffer(&anc_buffer);
gst_clear_buffer(&audio_buffer);
gst_clear_buffer(&video_buffer);
continue;
}
gst_buffer_set_size(audio_buffer, transfer.GetCapturedAudioByteCount());
if (anc_buffer)
gst_buffer_set_size(anc_buffer,
transfer.GetCapturedAncByteCount(false));
if (anc_buffer2)
gst_buffer_set_size(anc_buffer2,
transfer.GetCapturedAncByteCount(true));
NTV2_RP188 time_code;
transfer.acTransferStatus.acFrameStamp.GetInputTimeCode(time_code,
self->tc_index);
gint64 frame_time = transfer.acTransferStatus.acFrameStamp.acFrameTime;
gint64 now_sys = g_get_real_time();
GstClockTime now_gst = gst_clock_get_time(clock);
if (now_sys * 10 > frame_time) {
GstClockTime diff = now_sys * 1000 - frame_time * 100;
if (now_gst > diff)
now_gst -= diff;
else
now_gst = 0;
}
GstClockTime base_time =
gst_element_get_base_time(GST_ELEMENT_CAST(self));
if (now_gst > base_time)
now_gst -= base_time;
else
now_gst = 0;
GST_BUFFER_PTS(video_buffer) = now_gst;
GST_BUFFER_DURATION(video_buffer) = gst_util_uint64_scale(
GST_SECOND, self->configured_info.fps_d, self->configured_info.fps_n);
GST_BUFFER_PTS(audio_buffer) = now_gst;
GST_BUFFER_DURATION(audio_buffer) = gst_util_uint64_scale(
GST_SECOND, self->configured_info.fps_d, self->configured_info.fps_n);
// TODO: Drift detection and compensation
QueueItem item = {
.type = QUEUE_ITEM_TYPE_FRAME,
.capture_time = now_gst,
.video_buffer = video_buffer,
.audio_buffer = audio_buffer,
.anc_buffer = anc_buffer,
.anc_buffer2 = anc_buffer2,
.tc = time_code,
.detected_format =
(self->quad_mode ? ::GetQuadSizedVideoFormat(current_video_format)
: current_video_format),
.vpid = vpid_a};
while (gst_queue_array_get_length(self->queue) >= self->queue_size) {
QueueItem *tmp =
(QueueItem *)gst_queue_array_pop_head_struct(self->queue);
if (tmp->type == QUEUE_ITEM_TYPE_FRAME) {
GST_WARNING_OBJECT(self, "Element queue overrun, dropping old frame");
GstMessage *msg = gst_message_new_qos(
GST_OBJECT_CAST(self), TRUE, GST_CLOCK_TIME_NONE,
GST_CLOCK_TIME_NONE, tmp->capture_time,
gst_util_uint64_scale(GST_SECOND, self->configured_info.fps_d,
self->configured_info.fps_n));
gst_element_post_message(GST_ELEMENT_CAST(self), msg);
gst_clear_buffer(&tmp->video_buffer);
gst_clear_buffer(&tmp->audio_buffer);
gst_clear_buffer(&tmp->anc_buffer);
gst_clear_buffer(&tmp->anc_buffer2);
}
}
GST_TRACE_OBJECT(self, "Queuing frame %" GST_TIME_FORMAT,
GST_TIME_ARGS(now_gst));
gst_queue_array_push_tail_struct(self->queue, &item);
GST_TRACE_OBJECT(self, "%u frames queued",
gst_queue_array_get_length(self->queue));
g_cond_signal(&self->queue_cond);
} else {
g_mutex_unlock(&self->queue_lock);
self->device->device->WaitForInputVerticalInterrupt(self->channel);
g_mutex_lock(&self->queue_lock);
}
}
out : {
// Make sure to globally lock here as the routing settings and others are
// global shared state
ShmMutexLocker locker;
self->device->device->AutoCirculateStop(self->channel);
self->device->device->UnsubscribeInputVerticalEvent(self->channel);
self->device->device->DisableInputInterrupt(self->channel);
}
if (!self->playing && !self->shutdown) goto restart;
g_mutex_unlock(&self->queue_lock);
gst_clear_object(&clock);
GST_DEBUG_OBJECT(self, "Stopped");
}