diff --git a/gstajacommon.cpp b/gstajacommon.cpp index 2019f7295e..90474a61e0 100644 --- a/gstajacommon.cpp +++ b/gstajacommon.cpp @@ -471,6 +471,67 @@ GType gst_aja_reference_source_get_type(void) { return (GType)id; } +GType gst_aja_input_source_get_type(void) { + static gsize id = 0; + static const GEnumValue modes[] = { + {GST_AJA_INPUT_SOURCE_AUTO, "auto", "Auto (based on selected channel)"}, + {GST_AJA_INPUT_SOURCE_ANALOG1, "analog-1", "Analog Input 1"}, + {GST_AJA_INPUT_SOURCE_SDI1, "sdi-1", "SDI Input 1"}, + {GST_AJA_INPUT_SOURCE_SDI2, "sdi-2", "SDI Input 2"}, + {GST_AJA_INPUT_SOURCE_SDI3, "sdi-3", "SDI Input 3"}, + {GST_AJA_INPUT_SOURCE_SDI4, "sdi-4", "SDI Input 4"}, + {GST_AJA_INPUT_SOURCE_SDI5, "sdi-5", "SDI Input 5"}, + {GST_AJA_INPUT_SOURCE_SDI6, "sdi-6", "SDI Input 6"}, + {GST_AJA_INPUT_SOURCE_SDI7, "sdi-7", "SDI Input 7"}, + {GST_AJA_INPUT_SOURCE_SDI8, "sdi-8", "SDI Input 8"}, + {GST_AJA_INPUT_SOURCE_HDMI1, "hdmi-1", "HDMI Input 1"}, + {GST_AJA_INPUT_SOURCE_HDMI2, "hdmi-2", "HDMI Input 2"}, + {GST_AJA_INPUT_SOURCE_HDMI3, "hdmi-3", "HDMI Input 3"}, + {GST_AJA_INPUT_SOURCE_HDMI4, "hdmi-4", "HDMI Input 4"}, + {0, NULL, NULL}}; + + if (g_once_init_enter(&id)) { + GType tmp = g_enum_register_static("GstAjaInputSource", modes); + g_once_init_leave(&id, tmp); + } + + return (GType)id; +} + +GType gst_aja_video_format_get_type(void) { + static gsize id = 0; + static const GEnumValue modes[] = { + // TODO: Implement: {GST_AJA_VIDEO_FORMAT_AUTO, "auto", "Autodetect"}, + {GST_AJA_VIDEO_FORMAT_1080i_5000, "1080i-5000", "1080i 5000"}, + {GST_AJA_VIDEO_FORMAT_1080i_5994, "1080i-5994", "1080i 5994"}, + {GST_AJA_VIDEO_FORMAT_1080i_6000, "1080i-6000", "1080i 6000"}, + {GST_AJA_VIDEO_FORMAT_720p_5994, "720p-5994", "720p 5994"}, + {GST_AJA_VIDEO_FORMAT_720p_6000, "720p-6000", "720p 6000"}, + {GST_AJA_VIDEO_FORMAT_1080p_2997, "1080p-2997", "1080p 2997"}, + {GST_AJA_VIDEO_FORMAT_1080p_3000, "1080p-3000", "1080p 3000"}, + {GST_AJA_VIDEO_FORMAT_1080p_2500, "1080p-2500", "1080p 2500"}, + {GST_AJA_VIDEO_FORMAT_1080p_2398, "1080p-2398", "1080p 2398"}, + {GST_AJA_VIDEO_FORMAT_1080p_2400, "1080p-2400", "1080p 2400"}, + {GST_AJA_VIDEO_FORMAT_720p_5000, "720p-5000", "720p 5000"}, + {GST_AJA_VIDEO_FORMAT_720p_2398, "720p-2398", "720p 2398"}, + {GST_AJA_VIDEO_FORMAT_720p_2500, "720p-2500", "720p 2500"}, + {GST_AJA_VIDEO_FORMAT_1080p_5000_A, "1080p-5000-a", "1080p 5000 A"}, + {GST_AJA_VIDEO_FORMAT_1080p_5994_A, "1080p-5994-a", "1080p 5994 A"}, + {GST_AJA_VIDEO_FORMAT_1080p_6000_A, "1080p-6000-a", "1080p 6000 A"}, + {GST_AJA_VIDEO_FORMAT_625_5000, "625-5000", "625 5000"}, + {GST_AJA_VIDEO_FORMAT_525_5994, "525-5994", "525 5994"}, + {GST_AJA_VIDEO_FORMAT_525_2398, "525-2398", "525 2398"}, + {GST_AJA_VIDEO_FORMAT_525_2400, "525-2400", "525 2400"}, + {0, NULL, NULL}}; + + if (g_once_init_enter(&id)) { + GType tmp = g_enum_register_static("GstAjaVideoFormat", modes); + g_once_init_leave(&id, tmp); + } + + return (GType)id; +} + void gst_aja_common_init(void) { GST_DEBUG_CATEGORY_INIT(gst_aja_debug, "aja", 0, "Debug category for AJA plugin"); diff --git a/gstajacommon.h b/gstajacommon.h index 965229d505..33f37622f2 100644 --- a/gstajacommon.h +++ b/gstajacommon.h @@ -156,6 +156,55 @@ typedef enum { G_GNUC_INTERNAL GType gst_aja_reference_source_get_type(void); +typedef enum { + GST_AJA_INPUT_SOURCE_AUTO, + GST_AJA_INPUT_SOURCE_ANALOG1, + GST_AJA_INPUT_SOURCE_HDMI1, + GST_AJA_INPUT_SOURCE_HDMI2, + GST_AJA_INPUT_SOURCE_HDMI3, + GST_AJA_INPUT_SOURCE_HDMI4, + GST_AJA_INPUT_SOURCE_SDI1, + GST_AJA_INPUT_SOURCE_SDI2, + GST_AJA_INPUT_SOURCE_SDI3, + GST_AJA_INPUT_SOURCE_SDI4, + GST_AJA_INPUT_SOURCE_SDI5, + GST_AJA_INPUT_SOURCE_SDI6, + GST_AJA_INPUT_SOURCE_SDI7, + GST_AJA_INPUT_SOURCE_SDI8, +} GstAjaInputSource; + +#define GST_TYPE_AJA_INPUT_SOURCE (gst_aja_input_source_get_type()) +G_GNUC_INTERNAL +GType gst_aja_input_source_get_type(void); + +typedef enum { + // TODO: Implement: GST_AJA_VIDEO_FORMAT_AUTO, + GST_AJA_VIDEO_FORMAT_1080i_5000, + GST_AJA_VIDEO_FORMAT_1080i_5994, + GST_AJA_VIDEO_FORMAT_1080i_6000, + GST_AJA_VIDEO_FORMAT_720p_5994, + GST_AJA_VIDEO_FORMAT_720p_6000, + GST_AJA_VIDEO_FORMAT_1080p_2997, + GST_AJA_VIDEO_FORMAT_1080p_3000, + GST_AJA_VIDEO_FORMAT_1080p_2500, + GST_AJA_VIDEO_FORMAT_1080p_2398, + GST_AJA_VIDEO_FORMAT_1080p_2400, + GST_AJA_VIDEO_FORMAT_720p_5000, + GST_AJA_VIDEO_FORMAT_720p_2398, + GST_AJA_VIDEO_FORMAT_720p_2500, + GST_AJA_VIDEO_FORMAT_1080p_5000_A, + GST_AJA_VIDEO_FORMAT_1080p_5994_A, + GST_AJA_VIDEO_FORMAT_1080p_6000_A, + GST_AJA_VIDEO_FORMAT_625_5000, + GST_AJA_VIDEO_FORMAT_525_5994, + GST_AJA_VIDEO_FORMAT_525_2398, + GST_AJA_VIDEO_FORMAT_525_2400, +} GstAjaVideoFormat; + +#define GST_TYPE_AJA_VIDEO_FORMAT (gst_aja_video_format_get_type()) +G_GNUC_INTERNAL +GType gst_aja_video_format_get_type(void); + G_GNUC_INTERNAL void gst_aja_common_init(void); diff --git a/gstajasrc.cpp b/gstajasrc.cpp new file mode 100644 index 0000000000..6c4e4e50cb --- /dev/null +++ b/gstajasrc.cpp @@ -0,0 +1,1280 @@ +/* GStreamer + * Copyright (C) 2021 Sebastian Dröge + * + * 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 +#include +#include + +#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_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_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; +} 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_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 "); + + 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->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_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_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; + + switch (self->video_format_setting) { + // TODO: GST_AJA_VIDEO_FORMAT_AUTO + case GST_AJA_VIDEO_FORMAT_1080i_5000: + self->video_format = ::NTV2_FORMAT_1080i_5000; + break; + case GST_AJA_VIDEO_FORMAT_1080i_5994: + self->video_format = ::NTV2_FORMAT_1080i_5994; + break; + case GST_AJA_VIDEO_FORMAT_1080i_6000: + self->video_format = ::NTV2_FORMAT_1080i_6000; + break; + case GST_AJA_VIDEO_FORMAT_720p_5994: + self->video_format = ::NTV2_FORMAT_720p_5994; + break; + case GST_AJA_VIDEO_FORMAT_720p_6000: + self->video_format = ::NTV2_FORMAT_720p_6000; + break; + case GST_AJA_VIDEO_FORMAT_1080p_2997: + self->video_format = ::NTV2_FORMAT_1080p_2997; + break; + case GST_AJA_VIDEO_FORMAT_1080p_3000: + self->video_format = ::NTV2_FORMAT_1080p_3000; + break; + case GST_AJA_VIDEO_FORMAT_1080p_2500: + self->video_format = ::NTV2_FORMAT_1080p_2500; + break; + case GST_AJA_VIDEO_FORMAT_1080p_2398: + self->video_format = ::NTV2_FORMAT_1080p_2398; + break; + case GST_AJA_VIDEO_FORMAT_1080p_2400: + self->video_format = ::NTV2_FORMAT_1080p_2400; + break; + case GST_AJA_VIDEO_FORMAT_720p_5000: + self->video_format = ::NTV2_FORMAT_720p_5000; + break; + case GST_AJA_VIDEO_FORMAT_720p_2398: + self->video_format = ::NTV2_FORMAT_720p_2398; + break; + case GST_AJA_VIDEO_FORMAT_720p_2500: + self->video_format = ::NTV2_FORMAT_720p_2500; + break; + case GST_AJA_VIDEO_FORMAT_1080p_5000_A: + self->video_format = ::NTV2_FORMAT_1080p_5000_A; + break; + case GST_AJA_VIDEO_FORMAT_1080p_5994_A: + self->video_format = ::NTV2_FORMAT_1080p_5994_A; + break; + case GST_AJA_VIDEO_FORMAT_1080p_6000_A: + self->video_format = ::NTV2_FORMAT_1080p_6000_A; + break; + case GST_AJA_VIDEO_FORMAT_625_5000: + self->video_format = ::NTV2_FORMAT_625_5000; + break; + case GST_AJA_VIDEO_FORMAT_525_5994: + self->video_format = ::NTV2_FORMAT_525_5994; + break; + case GST_AJA_VIDEO_FORMAT_525_2398: + self->video_format = ::NTV2_FORMAT_525_2398; + break; + case GST_AJA_VIDEO_FORMAT_525_2400: + self->video_format = ::NTV2_FORMAT_525_2400; + break; + default: + g_assert_not_reached(); + break; + } + + if (!::NTV2DeviceCanDoVideoFormat(self->device_id, self->video_format)) { + GST_ERROR_OBJECT(self, "Device does not support mode %d", + (int)self->video_format); + return FALSE; + } + + gst_clear_caps(&self->configured_caps); + self->configured_caps = gst_ntv2_video_format_to_caps(self->video_format); + gst_video_info_from_caps(&self->configured_info, self->configured_caps); + + self->device->device->SetMode(self->channel, 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); + + self->device->device->DMABufferAutoLock(false, true, 0); + + if (::NTV2DeviceHasBiDirectionalSDI(self->device_id)) + self->device->device->SetSDITransmitEnable(self->channel, false); + + self->device->device->SetEnableVANCData(false, false, self->channel); + + CNTV2SignalRouter router; + + self->device->device->GetRouting(router); + + // Always use the framebuffer associated with the channel + NTV2InputCrosspointID framebuffer_id = + ::GetFrameBufferInputXptFromChannel(self->channel, false); + + 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); + break; + case GST_AJA_INPUT_SOURCE_ANALOG1: + input_source = ::NTV2_INPUTSOURCE_ANALOG1; + input_source_id = ::NTV2_XptAnalogIn; + break; + case GST_AJA_INPUT_SOURCE_HDMI1: + input_source = ::NTV2_INPUTSOURCE_HDMI1; + input_source_id = ::NTV2_XptHDMIIn1; + break; + case GST_AJA_INPUT_SOURCE_HDMI2: + input_source = ::NTV2_INPUTSOURCE_HDMI2; + input_source_id = ::NTV2_XptHDMIIn2; + break; + case GST_AJA_INPUT_SOURCE_HDMI3: + input_source = ::NTV2_INPUTSOURCE_HDMI3; + input_source_id = ::NTV2_XptHDMIIn3; + break; + case GST_AJA_INPUT_SOURCE_HDMI4: + input_source = ::NTV2_INPUTSOURCE_HDMI4; + input_source_id = ::NTV2_XptHDMIIn4; + break; + case GST_AJA_INPUT_SOURCE_SDI1: + input_source = ::NTV2_INPUTSOURCE_SDI1; + input_source_id = ::NTV2_XptSDIIn1; + break; + case GST_AJA_INPUT_SOURCE_SDI2: + input_source = ::NTV2_INPUTSOURCE_SDI2; + input_source_id = ::NTV2_XptSDIIn2; + break; + case GST_AJA_INPUT_SOURCE_SDI3: + input_source = ::NTV2_INPUTSOURCE_SDI3; + input_source_id = ::NTV2_XptSDIIn3; + break; + case GST_AJA_INPUT_SOURCE_SDI4: + input_source = ::NTV2_INPUTSOURCE_SDI4; + input_source_id = ::NTV2_XptSDIIn4; + break; + case GST_AJA_INPUT_SOURCE_SDI5: + input_source = ::NTV2_INPUTSOURCE_SDI5; + input_source_id = ::NTV2_XptSDIIn5; + break; + case GST_AJA_INPUT_SOURCE_SDI6: + input_source = ::NTV2_INPUTSOURCE_SDI6; + input_source_id = ::NTV2_XptSDIIn6; + break; + case GST_AJA_INPUT_SOURCE_SDI7: + input_source = ::NTV2_INPUTSOURCE_SDI7; + input_source_id = ::NTV2_XptSDIIn7; + break; + case GST_AJA_INPUT_SOURCE_SDI8: + input_source = ::NTV2_INPUTSOURCE_SDI8; + input_source_id = ::NTV2_XptSDIIn8; + break; + default: + g_assert_not_reached(); + break; + } + + // Need to remove old routes for the output and framebuffer we're going to + // use + NTV2ActualConnections connections = router.GetConnections(); + + for (NTV2ActualConnectionsConstIter iter = connections.begin(); + iter != connections.end(); iter++) { + if (iter->first == framebuffer_id || iter->second == input_source_id) + router.RemoveConnection(iter->first, iter->second); + } + + GST_DEBUG_OBJECT(self, "Creating connection %d - %d", framebuffer_id, + input_source_id); + router.AddConnection(framebuffer_id, input_source_id); + + { + 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); + + // TODO: make configurable + self->device->device->SetAudioSystemInputSource( + self->audio_system, NTV2_AUDIO_EMBEDDED, + ::NTV2InputSourceToEmbeddedAudioInput(input_source)); + self->device->device->SetEmbeddedAudioInput( + ::NTV2ChannelToEmbeddedAudioInput(self->channel), self->audio_system); + 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); + + gst_caps_set_simple(self->configured_caps, "audio-channels", G_TYPE_INT, + self->configured_audio_channels, NULL); + + 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); + } + + guint video_buffer_size = ::GetVideoActiveSize( + self->video_format, ::NTV2_FBF_10BIT_YCBCR, ::NTV2_VANCMODE_OFF); + + 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; + + 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->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); + gst_clear_caps(&self->configured_caps); + 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->configured_caps) { + GstClockTime min, max; + + min = gst_util_uint64_scale_ceil(GST_SECOND, + 3 * self->configured_info.fps_d, + self->configured_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); + + *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); + } + + if (item.anc_buffer) { + AJAAncillaryList anc_packets; + 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); + // anc_packets.ParseAllAncillaryData(); + // std::stringstream os; + // anc_packets.Print(os); + // GST_ERROR_OBJECT(self, "meh %u %lu\n%s", + // anc_packets.CountAncillaryData(), + // map.size, os.str().c_str()); + + if (anc_packets.CountAncillaryDataWithType(AJAAncillaryDataType_Cea708)) { + AJAAncillaryData packet = + anc_packets.GetAncillaryDataWithType(AJAAncillaryDataType_Cea708); + + if (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 + + if (item.anc_buffer2) gst_buffer_unmap(item.anc_buffer2, &map2); + gst_buffer_unmap(item.anc_buffer, &map); + } + + gst_clear_buffer(&item.anc_buffer); + gst_clear_buffer(&item.anc_buffer2); + g_mutex_unlock(&self->queue_lock); + + if (!gst_pad_has_current_caps(GST_BASE_SRC_PAD(self))) { + gst_base_src_set_caps(GST_BASE_SRC_CAST(self), self->configured_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; + + 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; + } + + { + // 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 | AUTOCIRCULATE_WITH_ANC, 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)); + + g_mutex_lock(&self->queue_lock); + while (self->playing && !self->shutdown) { + 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); + + // TODO: Drop detection + // TODO: Signal loss detection + + 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 (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); + 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); + 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()); + 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, ::NTV2ChannelToTimecodeIndex(self->channel, false)); + + 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_PTS(audio_buffer) = now_gst; + + // 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}; + + 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"); +} diff --git a/gstajasrc.h b/gstajasrc.h new file mode 100644 index 0000000000..000b77f7bd --- /dev/null +++ b/gstajasrc.h @@ -0,0 +1,90 @@ +/* GStreamer + * Copyright (C) 2021 Sebastian Dröge + * + * 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 +#include +#include + +#include "gstajacommon.h" + +G_BEGIN_DECLS + +#define GST_TYPE_AJA_SRC (gst_aja_src_get_type()) +#define GST_AJA_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AJA_SRC, GstAjaSrc)) +#define GST_AJA_SRC_CAST(obj) ((GstAjaSrc *)obj) +#define GST_AJA_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AJA_SRC, GstAjaSrcClass)) +#define GST_IS_AJA_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AJA_SRC)) +#define GST_IS_AJA_SRC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AJA_SRC)) + +typedef struct _GstAjaSrc GstAjaSrc; +typedef struct _GstAjaSrcClass GstAjaSrcClass; + +struct _GstAjaSrc { + GstPushSrc parent; + + // Everything below protected by queue lock + GMutex queue_lock; + GCond queue_cond; + GstQueueArray *queue; + gboolean playing; + gboolean shutdown; + gboolean flushing; + + GstAjaDevice *device; + NTV2DeviceID device_id; + GstAllocator *allocator; + GstBufferPool *buffer_pool; + GstBufferPool *audio_buffer_pool; + GstBufferPool *anc_buffer_pool; + + // Properties + gchar *device_identifier; + NTV2Channel channel; + GstAjaAudioSystem audio_system_setting; + GstAjaVideoFormat video_format_setting; + GstAjaInputSource input_source; + GstAjaReferenceSource reference_source; + guint queue_size; + guint capture_cpu_core; + + NTV2AudioSystem audio_system; + NTV2VideoFormat video_format; + guint32 f2_start_line; + + GstCaps *configured_caps; + GstVideoInfo configured_info; + gint configured_audio_channels; + + AJAThread *capture_thread; +}; + +struct _GstAjaSrcClass { + GstPushSrcClass parent_class; +}; + +G_GNUC_INTERNAL +GType gst_aja_src_get_type(void); + +G_END_DECLS diff --git a/gstajasrcdemux.cpp b/gstajasrcdemux.cpp new file mode 100644 index 0000000000..67edc6dd52 --- /dev/null +++ b/gstajasrcdemux.cpp @@ -0,0 +1,162 @@ +/* GStreamer + * Copyright (C) 2021 Sebastian Dröge + * + * 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 + +#include "gstajacommon.h" +#include "gstajasrcdemux.h" + +GST_DEBUG_CATEGORY_STATIC(gst_aja_src_demux_debug); +#define GST_CAT_DEFAULT gst_aja_src_demux_debug + +static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE( + "video", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS("video/x-raw")); + +static GstStaticPadTemplate audio_src_template = GST_STATIC_PAD_TEMPLATE( + "audio", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS("audio/x-raw")); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE( + "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS("video/x-raw")); + +static GstFlowReturn gst_aja_src_demux_sink_chain(GstPad *pad, + GstObject *parent, + GstBuffer *buffer); +static gboolean gst_aja_src_demux_sink_event(GstPad *pad, GstObject *parent, + GstEvent *event); + +#define parent_class gst_aja_src_demux_parent_class +G_DEFINE_TYPE(GstAjaSrcDemux, gst_aja_src_demux, GST_TYPE_ELEMENT); + +static void gst_aja_src_demux_class_init(GstAjaSrcDemuxClass *klass) { + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + gst_element_class_add_static_pad_template(element_class, &sink_template); + gst_element_class_add_static_pad_template(element_class, &video_src_template); + gst_element_class_add_static_pad_template(element_class, &audio_src_template); + + gst_element_class_set_static_metadata( + element_class, "AJA audio/video source demuxer", "Audio/Video/Demux", + "Demuxes audio/video from video buffers", + "Sebastian Dröge "); + + GST_DEBUG_CATEGORY_INIT(gst_aja_src_demux_debug, "ajasrcdemux", 0, + "AJA source demuxer"); +} + +static void gst_aja_src_demux_init(GstAjaSrcDemux *self) { + self->sink = gst_pad_new_from_static_template(&sink_template, "sink"); + gst_pad_set_chain_function(self->sink, + GST_DEBUG_FUNCPTR(gst_aja_src_demux_sink_chain)); + gst_pad_set_event_function(self->sink, + GST_DEBUG_FUNCPTR(gst_aja_src_demux_sink_event)); + gst_element_add_pad(GST_ELEMENT(self), self->sink); + + self->audio_src = + gst_pad_new_from_static_template(&audio_src_template, "audio"); + gst_pad_use_fixed_caps(self->audio_src); + gst_element_add_pad(GST_ELEMENT(self), self->audio_src); + + self->video_src = + gst_pad_new_from_static_template(&video_src_template, "video"); + gst_pad_use_fixed_caps(self->video_src); + gst_element_add_pad(GST_ELEMENT(self), self->video_src); +} + +static GstFlowReturn gst_aja_src_demux_sink_chain(GstPad *pad, + GstObject *parent, + GstBuffer *buffer) { + GstAjaSrcDemux *self = GST_AJA_SRC_DEMUX(parent); + GstAjaAudioMeta *meta = gst_buffer_get_aja_audio_meta(buffer); + GstFlowReturn audio_flow_ret = GST_FLOW_OK; + GstFlowReturn video_flow_ret = GST_FLOW_OK; + + if (meta) { + GstBuffer *audio_buffer; + buffer = gst_buffer_make_writable(buffer); + meta = gst_buffer_get_aja_audio_meta(buffer); + audio_buffer = gst_buffer_ref(meta->buffer); + gst_buffer_remove_meta(buffer, GST_META_CAST(meta)); + + audio_flow_ret = gst_pad_push(self->audio_src, audio_buffer); + } else { + GstEvent *event = + gst_event_new_gap(GST_BUFFER_PTS(buffer), GST_BUFFER_DURATION(buffer)); + gst_pad_push_event(self->audio_src, event); + } + + video_flow_ret = gst_pad_push(self->video_src, buffer); + + // Combine flows the way it makes sense + if (video_flow_ret == GST_FLOW_NOT_LINKED && + audio_flow_ret == GST_FLOW_NOT_LINKED) + return GST_FLOW_NOT_LINKED; + if (video_flow_ret == GST_FLOW_EOS && audio_flow_ret == GST_FLOW_EOS) + return GST_FLOW_EOS; + if (video_flow_ret == GST_FLOW_FLUSHING || + video_flow_ret <= GST_FLOW_NOT_NEGOTIATED) + return video_flow_ret; + if (audio_flow_ret == GST_FLOW_FLUSHING || + audio_flow_ret <= GST_FLOW_NOT_NEGOTIATED) + return audio_flow_ret; + return GST_FLOW_OK; +} + +static gboolean gst_aja_src_demux_sink_event(GstPad *pad, GstObject *parent, + GstEvent *event) { + GstAjaSrcDemux *self = GST_AJA_SRC_DEMUX(parent); + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_CAPS: { + GstCaps *caps; + GstStructure *s; + GstAudioInfo audio_info; + gint audio_channels = 0; + + gst_event_parse_caps(event, &caps); + s = gst_caps_get_structure(caps, 0); + + gst_structure_get_int(s, "audio-channels", &audio_channels); + + GstCaps *audio_caps, *video_caps; + + gst_audio_info_init(&audio_info); + gst_audio_info_set_format(&audio_info, GST_AUDIO_FORMAT_S32LE, 48000, + audio_channels ? audio_channels : 1, NULL); + audio_caps = gst_audio_info_to_caps(&audio_info); + gst_pad_set_caps(self->audio_src, audio_caps); + gst_caps_unref(audio_caps); + + video_caps = gst_caps_ref(caps); + gst_event_unref(event); + video_caps = gst_caps_make_writable(video_caps); + s = gst_caps_get_structure(video_caps, 0); + gst_structure_remove_field(s, "audio-channels"); + gst_pad_set_caps(self->video_src, video_caps); + gst_caps_unref(video_caps); + + return TRUE; + } + default: + return gst_pad_event_default(pad, parent, event); + } +} diff --git a/gstajasrcdemux.h b/gstajasrcdemux.h new file mode 100644 index 0000000000..fe1b791729 --- /dev/null +++ b/gstajasrcdemux.h @@ -0,0 +1,59 @@ +/* GStreamer + * Copyright (C) 2021 Sebastian Dröge + * + * 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 +#include +#include + +#include "gstajacommon.h" + +G_BEGIN_DECLS + +#define GST_TYPE_AJA_SRC_DEMUX (gst_aja_src_demux_get_type()) +#define GST_AJA_SRC_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AJA_SRC_DEMUX, GstAjaSrcDemux)) +#define GST_AJA_SRC_DEMUX_CAST(obj) ((GstAjaSrcDemux *)obj) +#define GST_AJA_SRC_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AJA_SRC_DEMUX, \ + GstAjaSrcDemuxClass)) +#define GST_IS_AJA_SRC_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AJA_SRC_DEMUX)) +#define GST_IS_AJA_SRC_DEMUX_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AJA_SRC_DEMUX)) + +typedef struct _GstAjaSrcDemux GstAjaSrcDemux; +typedef struct _GstAjaSrcDemuxClass GstAjaSrcDemuxClass; + +struct _GstAjaSrcDemux { + GstElement parent; + + GstPad *sink; + GstPad *video_src, *audio_src; +}; + +struct _GstAjaSrcDemuxClass { + GstElementClass parent_class; +}; + +G_GNUC_INTERNAL +GType gst_aja_src_demux_get_type(void); + +G_END_DECLS diff --git a/meson.build b/meson.build index d1d9ce33c1..66cc7c2858 100644 --- a/meson.build +++ b/meson.build @@ -82,6 +82,8 @@ gstaja = library('gstaja', 'gstajacommon.cpp', 'gstajasink.cpp', 'gstajasinkcombiner.cpp', + 'gstajasrc.cpp', + 'gstajasrcdemux.cpp', ], cpp_args : [ aja_includedirs, diff --git a/plugin.cpp b/plugin.cpp index d6367c4877..1423c69c7e 100644 --- a/plugin.cpp +++ b/plugin.cpp @@ -17,15 +17,23 @@ * Boston, MA 02110-1335, USA. */ +#include #include #include "gstajacommon.h" #include "gstajasink.h" #include "gstajasinkcombiner.h" +#include "gstajasrc.h" +#include "gstajasrcdemux.h" static gboolean plugin_init(GstPlugin* plugin) { + AJADebug::Open(); + gst_aja_common_init(); + gst_element_register(plugin, "ajasrc", GST_RANK_NONE, GST_TYPE_AJA_SRC); + gst_element_register(plugin, "ajasrcdemux", GST_RANK_NONE, + GST_TYPE_AJA_SRC_DEMUX); gst_element_register(plugin, "ajasink", GST_RANK_NONE, GST_TYPE_AJA_SINK); gst_element_register(plugin, "ajasinkcombiner", GST_RANK_NONE, GST_TYPE_AJA_SINK_COMBINER);