| /* 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. | |
| */ | |
| /** | |
| * SECTION:element-ajasrc | |
| * | |
| * Source element for [AJA](https://www.aja.com) output cards. | |
| * | |
| * ## Example usage | |
| * | |
| * Output a 1080p2997 test audio/video stream | |
| * | |
| * ```sh | |
| * gst-launch-1.0 videotestsrc pattern=ball ! video/x-raw,format=v210,width=1920,height=1080,framerate=30000/1001,interlace-mode=progressive ! timeoverlay ! timecodestamper ! combiner.video \ | |
| * audiotestsrc freq=440 ! audio/x-raw,format=S32LE,rate=48000,channels=16 ! audiobuffersplit output-buffer-duration=1/30 ! combiner.audio \ | |
| * ajasinkcombiner name=combiner ! ajasink channel=0 | |
| * ``` | |
| * | |
| * Capture 1080p30 audio/video and directly output it again on the same card | |
| * | |
| * ```sh | |
| * gst-launch-1.0 ajasrc video-format=1080p-3000 channel=1 input-source=sdi-1 audio-system=2 ! ajasrcdemux name=d \ | |
| * d.video ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=1000000000 ! c.video \ | |
| * d.audio ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=1000000000 ! c.audio \ | |
| * ajasinkcombiner name=c ! ajasink channel=0 reference-source=input-1 | |
| * ``` | |
| * | |
| * Since: 1.24 | |
| * | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include "config.h" | |
| #endif | |
| #include <ajaanc/includes/ancillarydata_cea608_vanc.h> | |
| #include <ajaanc/includes/ancillarydata_cea708.h> | |
| #include <ajaanc/includes/ancillarylist.h> | |
| #include <ajantv2/includes/ntv2rp188.h> | |
| #include "gstajacommon.h" | |
| #include "gstajasink.h" | |
| GST_DEBUG_CATEGORY_STATIC(gst_aja_sink_debug); | |
| #define GST_CAT_DEFAULT gst_aja_sink_debug | |
| #define DEFAULT_DEVICE_IDENTIFIER ("0") | |
| #define DEFAULT_CHANNEL (::NTV2_CHANNEL1) | |
| #define DEFAULT_AUDIO_SYSTEM (GST_AJA_AUDIO_SYSTEM_AUTO) | |
| #define DEFAULT_OUTPUT_DESTINATION (GST_AJA_OUTPUT_DESTINATION_AUTO) | |
| #define DEFAULT_SDI_MODE (GST_AJA_SDI_MODE_SINGLE_LINK) | |
| #define DEFAULT_TIMECODE_INDEX (GST_AJA_TIMECODE_INDEX_VITC) | |
| #define DEFAULT_RP188 (TRUE) | |
| #define DEFAULT_REFERENCE_SOURCE (GST_AJA_REFERENCE_SOURCE_AUTO) | |
| #define DEFAULT_CEA608_LINE_NUMBER (12) | |
| #define DEFAULT_CEA708_LINE_NUMBER (12) | |
| #define DEFAULT_QUEUE_SIZE (16) | |
| #define DEFAULT_START_FRAME (0) | |
| #define DEFAULT_END_FRAME (0) | |
| #define DEFAULT_OUTPUT_CPU_CORE (G_MAXUINT) | |
| #define DEFAULT_HANDLE_ANCILLARY_META (FALSE) | |
| enum { | |
| PROP_0, | |
| PROP_DEVICE_IDENTIFIER, | |
| PROP_CHANNEL, | |
| PROP_AUDIO_SYSTEM, | |
| PROP_OUTPUT_DESTINATION, | |
| PROP_SDI_MODE, | |
| PROP_TIMECODE_INDEX, | |
| PROP_RP188, | |
| PROP_REFERENCE_SOURCE, | |
| PROP_CEA608_LINE_NUMBER, | |
| PROP_CEA708_LINE_NUMBER, | |
| PROP_QUEUE_SIZE, | |
| PROP_START_FRAME, | |
| PROP_END_FRAME, | |
| PROP_OUTPUT_CPU_CORE, | |
| PROP_HANDLE_ANCILLARY_META, | |
| }; | |
| typedef enum { | |
| QUEUE_ITEM_TYPE_FRAME, | |
| } QueueItemType; | |
| typedef struct { | |
| QueueItemType type; | |
| // For FRAME | |
| GstBuffer *video_buffer; | |
| GstMapInfo video_map; | |
| GstBuffer *audio_buffer; | |
| GstMapInfo audio_map; | |
| NTV2_RP188 tc; | |
| GstBuffer *anc_buffer; | |
| GstMapInfo anc_map; | |
| GstBuffer *anc_buffer2; | |
| GstMapInfo anc_map2; | |
| } QueueItem; | |
| static void gst_aja_sink_set_property(GObject *object, guint property_id, | |
| const GValue *value, GParamSpec *pspec); | |
| static void gst_aja_sink_get_property(GObject *object, guint property_id, | |
| GValue *value, GParamSpec *pspec); | |
| static void gst_aja_sink_finalize(GObject *object); | |
| static gboolean gst_aja_sink_set_caps(GstBaseSink *bsink, GstCaps *caps); | |
| static GstCaps *gst_aja_sink_get_caps(GstBaseSink *bsink, GstCaps *filter); | |
| static gboolean gst_aja_sink_event(GstBaseSink *bsink, GstEvent *event); | |
| static gboolean gst_aja_sink_propose_allocation(GstBaseSink *bsink, | |
| GstQuery *query); | |
| static GstFlowReturn gst_aja_sink_render(GstBaseSink *bsink, GstBuffer *buffer); | |
| static gboolean gst_aja_sink_open(GstAjaSink *sink); | |
| static gboolean gst_aja_sink_close(GstAjaSink *sink); | |
| static gboolean gst_aja_sink_start(GstAjaSink *sink); | |
| static gboolean gst_aja_sink_stop(GstAjaSink *sink); | |
| static GstStateChangeReturn gst_aja_sink_change_state( | |
| GstElement *element, GstStateChange transition); | |
| static void output_thread_func(AJAThread *thread, void *data); | |
| #define parent_class gst_aja_sink_parent_class | |
| G_DEFINE_TYPE(GstAjaSink, gst_aja_sink, GST_TYPE_BASE_SINK); | |
| static void gst_aja_sink_class_init(GstAjaSinkClass *klass) { | |
| GObjectClass *gobject_class = G_OBJECT_CLASS(klass); | |
| GstElementClass *element_class = GST_ELEMENT_CLASS(klass); | |
| GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass); | |
| GstCaps *templ_caps; | |
| gobject_class->set_property = gst_aja_sink_set_property; | |
| gobject_class->get_property = gst_aja_sink_get_property; | |
| gobject_class->finalize = gst_aja_sink_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_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_START_FRAME, | |
| g_param_spec_uint( | |
| "start-frame", "Start Frame", | |
| "Start frame buffer to be used for output (auto if same number as " | |
| "end-frame).", | |
| 0, G_MAXINT, DEFAULT_START_FRAME, | |
| (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); | |
| g_object_class_install_property( | |
| gobject_class, PROP_END_FRAME, | |
| g_param_spec_uint( | |
| "end-frame", "End Frame", | |
| "End frame buffer to be used for output (auto if same number as " | |
| "start-frame).", | |
| 0, G_MAXINT, DEFAULT_END_FRAME, | |
| (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_OUTPUT_DESTINATION, | |
| g_param_spec_enum( | |
| "output-destination", "Output Destination", | |
| "Output destination to use", GST_TYPE_AJA_OUTPUT_DESTINATION, | |
| DEFAULT_OUTPUT_DESTINATION, | |
| (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-mode", "SDI Mode", "SDI 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_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_RP188, | |
| g_param_spec_boolean( | |
| "rp188", "RP188", "Enable RP188 timecode transmission", DEFAULT_RP188, | |
| (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_CEA608_LINE_NUMBER, | |
| g_param_spec_uint( | |
| "cea608-line-number", "CEA608 Line Number", | |
| "Sets the line number to use for CEA608 S334-1 Annex A Closed " | |
| "Captions " | |
| "(-1=disabled)", | |
| 0, G_MAXUINT, DEFAULT_CEA608_LINE_NUMBER, | |
| (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | | |
| G_PARAM_CONSTRUCT))); | |
| g_object_class_install_property( | |
| gobject_class, PROP_CEA708_LINE_NUMBER, | |
| g_param_spec_uint( | |
| "cea708-line-number", "CEA708 Line Number", | |
| "Sets the line number to use for CEA708 S334-2 Closed Captions " | |
| "(-1=disabled)", | |
| 0, G_MAXUINT, DEFAULT_CEA608_LINE_NUMBER, | |
| (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | | |
| G_PARAM_CONSTRUCT))); | |
| g_object_class_install_property( | |
| gobject_class, PROP_OUTPUT_CPU_CORE, | |
| g_param_spec_uint( | |
| "output-cpu-core", "Output CPU Core", | |
| "Sets the affinity of the output thread to this CPU core " | |
| "(-1=disabled)", | |
| 0, G_MAXUINT, DEFAULT_OUTPUT_CPU_CORE, | |
| (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | | |
| G_PARAM_CONSTRUCT))); | |
| /** | |
| * GstAjaSink:handle-ancillary-meta: | |
| * | |
| * If set to %TRUE handle any #GstAncillaryMeta present on buffers | |
| * | |
| * Since: 1.24 | |
| */ | |
| g_object_class_install_property( | |
| gobject_class, PROP_HANDLE_ANCILLARY_META, | |
| g_param_spec_boolean( | |
| "handle-ancillary-meta", "Handle Ancillary Meta", | |
| "Handle ancillary meta on video frames", | |
| DEFAULT_HANDLE_ANCILLARY_META, | |
| (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | | |
| G_PARAM_CONSTRUCT))); | |
| element_class->change_state = GST_DEBUG_FUNCPTR(gst_aja_sink_change_state); | |
| basesink_class->set_caps = GST_DEBUG_FUNCPTR(gst_aja_sink_set_caps); | |
| basesink_class->get_caps = GST_DEBUG_FUNCPTR(gst_aja_sink_get_caps); | |
| basesink_class->event = GST_DEBUG_FUNCPTR(gst_aja_sink_event); | |
| basesink_class->propose_allocation = | |
| GST_DEBUG_FUNCPTR(gst_aja_sink_propose_allocation); | |
| basesink_class->render = GST_DEBUG_FUNCPTR(gst_aja_sink_render); | |
| templ_caps = gst_ntv2_supported_caps(DEVICE_ID_INVALID); | |
| gst_element_class_add_pad_template( | |
| element_class, | |
| gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, templ_caps)); | |
| gst_caps_unref(templ_caps); | |
| gst_element_class_set_static_metadata( | |
| element_class, "AJA audio/video sink", "Audio/Video/Sink", | |
| "Outputs audio/video frames with AJA devices", | |
| "Sebastian Dröge <sebastian@centricular.com>"); | |
| GST_DEBUG_CATEGORY_INIT(gst_aja_sink_debug, "ajasink", 0, "AJA sink"); | |
| } | |
| static void gst_aja_sink_init(GstAjaSink *self) { | |
| g_mutex_init(&self->queue_lock); | |
| g_cond_init(&self->queue_cond); | |
| g_cond_init(&self->drain_cond); | |
| self->device_identifier = g_strdup(DEFAULT_DEVICE_IDENTIFIER); | |
| self->channel = DEFAULT_CHANNEL; | |
| self->queue_size = DEFAULT_QUEUE_SIZE; | |
| self->start_frame = DEFAULT_START_FRAME; | |
| self->end_frame = DEFAULT_END_FRAME; | |
| self->audio_system_setting = DEFAULT_AUDIO_SYSTEM; | |
| self->output_destination = DEFAULT_OUTPUT_DESTINATION; | |
| self->timecode_index = DEFAULT_TIMECODE_INDEX; | |
| self->reference_source = DEFAULT_REFERENCE_SOURCE; | |
| self->output_cpu_core = DEFAULT_OUTPUT_CPU_CORE; | |
| self->handle_ancillary_meta = DEFAULT_HANDLE_ANCILLARY_META; | |
| self->queue = | |
| gst_queue_array_new_for_struct(sizeof(QueueItem), self->queue_size); | |
| } | |
| void gst_aja_sink_set_property(GObject *object, guint property_id, | |
| const GValue *value, GParamSpec *pspec) { | |
| GstAjaSink *self = GST_AJA_SINK(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_START_FRAME: | |
| self->start_frame = g_value_get_uint(value); | |
| break; | |
| case PROP_END_FRAME: | |
| self->end_frame = g_value_get_uint(value); | |
| break; | |
| case PROP_AUDIO_SYSTEM: | |
| self->audio_system_setting = (GstAjaAudioSystem)g_value_get_enum(value); | |
| break; | |
| case PROP_OUTPUT_DESTINATION: | |
| self->output_destination = | |
| (GstAjaOutputDestination)g_value_get_enum(value); | |
| break; | |
| case PROP_SDI_MODE: | |
| self->sdi_mode = (GstAjaSdiMode)g_value_get_enum(value); | |
| break; | |
| case PROP_TIMECODE_INDEX: | |
| self->timecode_index = (GstAjaTimecodeIndex)g_value_get_enum(value); | |
| break; | |
| case PROP_RP188: | |
| self->rp188 = g_value_get_boolean(value); | |
| break; | |
| case PROP_REFERENCE_SOURCE: | |
| self->reference_source = (GstAjaReferenceSource)g_value_get_enum(value); | |
| break; | |
| case PROP_CEA608_LINE_NUMBER: | |
| self->cea608_line_number = g_value_get_uint(value); | |
| break; | |
| case PROP_CEA708_LINE_NUMBER: | |
| self->cea708_line_number = g_value_get_uint(value); | |
| break; | |
| case PROP_OUTPUT_CPU_CORE: | |
| self->output_cpu_core = g_value_get_uint(value); | |
| break; | |
| case PROP_HANDLE_ANCILLARY_META: | |
| self->handle_ancillary_meta = g_value_get_boolean(value); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); | |
| break; | |
| } | |
| } | |
| void gst_aja_sink_get_property(GObject *object, guint property_id, | |
| GValue *value, GParamSpec *pspec) { | |
| GstAjaSink *self = GST_AJA_SINK(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_START_FRAME: | |
| g_value_set_uint(value, self->start_frame); | |
| break; | |
| case PROP_END_FRAME: | |
| g_value_set_uint(value, self->end_frame); | |
| break; | |
| case PROP_AUDIO_SYSTEM: | |
| g_value_set_enum(value, self->audio_system_setting); | |
| break; | |
| case PROP_OUTPUT_DESTINATION: | |
| g_value_set_enum(value, self->output_destination); | |
| break; | |
| case PROP_SDI_MODE: | |
| g_value_set_enum(value, self->sdi_mode); | |
| break; | |
| case PROP_TIMECODE_INDEX: | |
| g_value_set_enum(value, self->timecode_index); | |
| break; | |
| case PROP_RP188: | |
| g_value_set_boolean(value, self->rp188); | |
| break; | |
| case PROP_REFERENCE_SOURCE: | |
| g_value_set_enum(value, self->reference_source); | |
| break; | |
| case PROP_CEA608_LINE_NUMBER: | |
| g_value_set_uint(value, self->cea608_line_number); | |
| break; | |
| case PROP_CEA708_LINE_NUMBER: | |
| g_value_set_uint(value, self->cea708_line_number); | |
| break; | |
| case PROP_OUTPUT_CPU_CORE: | |
| g_value_set_uint(value, self->output_cpu_core); | |
| break; | |
| case PROP_HANDLE_ANCILLARY_META: | |
| g_value_set_boolean(value, self->handle_ancillary_meta); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); | |
| break; | |
| } | |
| } | |
| void gst_aja_sink_finalize(GObject *object) { | |
| GstAjaSink *self = GST_AJA_SINK(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_cond_clear(&self->drain_cond); | |
| G_OBJECT_CLASS(parent_class)->finalize(object); | |
| } | |
| static gboolean gst_aja_sink_open(GstAjaSink *self) { | |
| GST_DEBUG_OBJECT(self, "Opening device"); | |
| g_assert(self->device == NULL); | |
| self->device = gst_aja_ntv2_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_ntv2_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_sink_close(GstAjaSink *self) { | |
| gst_clear_object(&self->allocator); | |
| g_clear_pointer(&self->device, gst_aja_ntv2_device_unref); | |
| self->device_id = DEVICE_ID_INVALID; | |
| GST_DEBUG_OBJECT(self, "Closed device"); | |
| return TRUE; | |
| } | |
| static gboolean gst_aja_sink_start(GstAjaSink *self) { | |
| GST_DEBUG_OBJECT(self, "Starting"); | |
| self->output_thread = new AJAThread(); | |
| self->output_thread->Attach(output_thread_func, self); | |
| self->output_thread->SetPriority(AJA_ThreadPriority_High); | |
| self->output_thread->Start(); | |
| g_mutex_lock(&self->queue_lock); | |
| self->shutdown = FALSE; | |
| self->playing = FALSE; | |
| self->eos = FALSE; | |
| g_cond_signal(&self->queue_cond); | |
| g_mutex_unlock(&self->queue_lock); | |
| return TRUE; | |
| } | |
| static gboolean gst_aja_sink_stop(GstAjaSink *self) { | |
| QueueItem *item; | |
| GST_DEBUG_OBJECT(self, "Stopping"); | |
| g_mutex_lock(&self->queue_lock); | |
| self->shutdown = TRUE; | |
| self->playing = FALSE; | |
| g_cond_signal(&self->queue_cond); | |
| g_mutex_unlock(&self->queue_lock); | |
| if (self->output_thread) { | |
| self->output_thread->Stop(); | |
| delete self->output_thread; | |
| self->output_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_buffer_unmap(item->video_buffer, &item->video_map); | |
| gst_buffer_unref(item->video_buffer); | |
| if (item->audio_buffer) { | |
| gst_buffer_unmap(item->audio_buffer, &item->audio_map); | |
| gst_buffer_unref(item->audio_buffer); | |
| } | |
| if (item->anc_buffer) { | |
| gst_buffer_unmap(item->anc_buffer, &item->anc_map); | |
| gst_buffer_unref(item->anc_buffer); | |
| } | |
| if (item->anc_buffer2) { | |
| gst_buffer_unmap(item->anc_buffer2, &item->anc_map2); | |
| gst_buffer_unref(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); | |
| } | |
| if (self->tc_indexes) { | |
| delete self->tc_indexes; | |
| self->tc_indexes = NULL; | |
| } | |
| GST_DEBUG_OBJECT(self, "Stopped"); | |
| return TRUE; | |
| } | |
| static GstStateChangeReturn gst_aja_sink_change_state( | |
| GstElement *element, GstStateChange transition) { | |
| GstAjaSink *self = GST_AJA_SINK(element); | |
| GstStateChangeReturn ret; | |
| switch (transition) { | |
| case GST_STATE_CHANGE_NULL_TO_READY: | |
| if (!gst_aja_sink_open(self)) return GST_STATE_CHANGE_FAILURE; | |
| break; | |
| case GST_STATE_CHANGE_READY_TO_PAUSED: | |
| if (!gst_aja_sink_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_sink_stop(self)) return GST_STATE_CHANGE_FAILURE; | |
| break; | |
| case GST_STATE_CHANGE_READY_TO_NULL: | |
| if (!gst_aja_sink_close(self)) return GST_STATE_CHANGE_FAILURE; | |
| break; | |
| default: | |
| break; | |
| } | |
| return ret; | |
| } | |
| static gboolean gst_aja_sink_set_caps(GstBaseSink *bsink, GstCaps *caps) { | |
| GstAjaSink *self = GST_AJA_SINK(bsink); | |
| const GstStructure *s; | |
| NTV2VideoFormat video_format = ::NTV2_FORMAT_UNKNOWN; | |
| GST_DEBUG_OBJECT(self, "Configuring caps %" GST_PTR_FORMAT, caps); | |
| GST_OBJECT_LOCK(self); | |
| if (self->configured_caps) { | |
| if (!gst_caps_can_intersect(self->configured_caps, caps)) { | |
| GST_DEBUG_OBJECT(self, "Need to reconfigure, waiting for draining"); | |
| GST_OBJECT_UNLOCK(self); | |
| g_mutex_lock(&self->queue_lock); | |
| self->draining = TRUE; | |
| g_cond_signal(&self->queue_cond); | |
| while (self->draining && !self->flushing && !self->shutdown) { | |
| g_cond_wait(&self->drain_cond, &self->queue_lock); | |
| } | |
| if (self->flushing || self->shutdown) { | |
| g_mutex_unlock(&self->queue_lock); | |
| GST_DEBUG_OBJECT(self, "Flushing"); | |
| return FALSE; | |
| } | |
| g_mutex_unlock(&self->queue_lock); | |
| GST_OBJECT_LOCK(self); | |
| } else { | |
| GST_OBJECT_UNLOCK(self); | |
| GST_DEBUG_OBJECT(self, | |
| "Compatible caps with previous caps, not reconfiguring"); | |
| return TRUE; | |
| } | |
| } | |
| if (!gst_video_info_from_caps(&self->configured_info, caps)) { | |
| GST_OBJECT_UNLOCK(self); | |
| GST_FIXME_OBJECT(self, "Failed to parse caps"); | |
| return FALSE; | |
| } | |
| self->configured_audio_channels = 0; | |
| s = gst_caps_get_structure(caps, 0); | |
| gst_structure_get_int(s, "audio-channels", &self->configured_audio_channels); | |
| gst_caps_replace(&self->configured_caps, caps); | |
| GST_OBJECT_UNLOCK(self); | |
| bool quad_mode = (self->sdi_mode != GST_AJA_SDI_MODE_SINGLE_LINK); | |
| video_format = gst_ntv2_video_format_from_caps(caps, quad_mode); | |
| if (video_format == NTV2_FORMAT_UNKNOWN) { | |
| GST_ERROR_OBJECT(self, "Unsupported caps %" GST_PTR_FORMAT, caps); | |
| return FALSE; | |
| } | |
| if (quad_mode) { | |
| if (self->output_destination != GST_AJA_OUTPUT_DESTINATION_AUTO) { | |
| GST_ERROR_OBJECT(self, | |
| "Quad modes require usage of the channel's default " | |
| "output destination"); | |
| return FALSE; | |
| } | |
| if (self->channel != ::NTV2_CHANNEL1 && self->channel != ::NTV2_CHANNEL5) { | |
| GST_ERROR_OBJECT(self, "Quad modes require channels 1 or 5"); | |
| return FALSE; | |
| } | |
| } | |
| self->quad_mode = quad_mode; | |
| self->video_format = video_format; | |
| g_assert(self->device != NULL); | |
| // Make sure to globally lock here as the routing settings and others are | |
| // global shared state | |
| GstAjaNtv2DeviceLocker locker(self->device); | |
| if (!::NTV2DeviceCanDoVideoFormat(self->device_id, video_format)) { | |
| GST_ERROR_OBJECT(self, "Device does not support mode %d", | |
| (int)video_format); | |
| return FALSE; | |
| } | |
| self->device->device->SetMode(self->channel, NTV2_MODE_DISPLAY, false); | |
| if (self->quad_mode) { | |
| for (int i = 1; i < 4; i++) | |
| self->device->device->SetMode((NTV2Channel)(self->channel + i), | |
| NTV2_MODE_DISPLAY, false); | |
| } | |
| GST_DEBUG_OBJECT(self, "Configuring video format %d on channel %d", | |
| (int)video_format, (int)self->channel); | |
| self->device->device->SetVideoFormat(video_format, false, false, | |
| self->channel); | |
| if (self->quad_mode) { | |
| for (int i = 1; i < 4; i++) | |
| self->device->device->SetVideoFormat(video_format, false, false, | |
| (NTV2Channel)(self->channel + i)); | |
| } | |
| 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); | |
| } | |
| NTV2ReferenceSource reference_source; | |
| switch (self->reference_source) { | |
| case GST_AJA_REFERENCE_SOURCE_EXTERNAL: | |
| reference_source = ::NTV2_REFERENCE_EXTERNAL; | |
| break; | |
| case GST_AJA_REFERENCE_SOURCE_FREERUN: | |
| case GST_AJA_REFERENCE_SOURCE_AUTO: | |
| 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->SetFramePulseReference(reference_source); | |
| self->device->device->DMABufferAutoLock(false, true, 0); | |
| if (::NTV2DeviceHasBiDirectionalSDI(self->device_id)) | |
| self->device->device->SetSDITransmitEnable(self->channel, true); | |
| if (self->quad_mode) { | |
| for (int i = 1; i < 4; i++) | |
| self->device->device->SetSDITransmitEnable( | |
| (NTV2Channel)(self->channel + i), true); | |
| } | |
| if (self->configured_audio_channels) { | |
| 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); | |
| 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->SetSDIOutputAudioSystem(self->channel, | |
| self->audio_system); | |
| self->device->device->SetSDIOutputDS2AudioSystem(self->channel, | |
| self->audio_system); | |
| if (self->quad_mode) { | |
| for (int i = 1; i < 4; i++) { | |
| self->device->device->SetSDIOutputAudioSystem( | |
| (NTV2Channel)(self->channel + i), self->audio_system); | |
| self->device->device->SetSDIOutputDS2AudioSystem( | |
| (NTV2Channel)(self->channel + i), self->audio_system); | |
| } | |
| } | |
| self->device->device->SetAudioLoopBack(::NTV2_AUDIO_LOOPBACK_OFF, | |
| self->audio_system); | |
| } else { | |
| self->audio_system = ::NTV2_AUDIOSYSTEM_INVALID; | |
| } | |
| // Always use the framebuffer associated with the channel | |
| NTV2OutputCrosspointID framebuffer_id = | |
| ::GetFrameBufferOutputXptFromChannel(self->channel, false, false); | |
| NTV2VANCMode vanc_mode; | |
| NTV2TCIndex tc_indexes_vitc[2] = {::NTV2_TCINDEX_INVALID, | |
| ::NTV2_TCINDEX_INVALID}; | |
| NTV2TCIndex tc_index_atc_ltc = ::NTV2_TCINDEX_INVALID; | |
| NTV2InputCrosspointID output_destination_id; | |
| switch (self->output_destination) { | |
| case GST_AJA_OUTPUT_DESTINATION_AUTO: | |
| tc_indexes_vitc[0] = | |
| ::NTV2ChannelToTimecodeIndex(self->channel, false, false); | |
| tc_indexes_vitc[1] = | |
| ::NTV2ChannelToTimecodeIndex(self->channel, false, true); | |
| tc_index_atc_ltc = | |
| ::NTV2ChannelToTimecodeIndex(self->channel, false, true); | |
| output_destination_id = ::GetSDIOutputInputXpt(self->channel, false); | |
| vanc_mode = ::NTV2DeviceCanDoCustomAnc(self->device_id) | |
| ? ::NTV2_VANCMODE_OFF | |
| : ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI1: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI1; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI1_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI1_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut1Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI2: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI2; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI2_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI2_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut2Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI3: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI3; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI3_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI3_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut3Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI4: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI4; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI4_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI4_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut4Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI5: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI5; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI5_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI5_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut5Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI6: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI6; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI6_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI6_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut6Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI7: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI7; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI7_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI7_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut7Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_SDI8: | |
| tc_indexes_vitc[0] = ::NTV2_TCINDEX_SDI8; | |
| tc_indexes_vitc[1] = ::NTV2_TCINDEX_SDI8_2; | |
| tc_index_atc_ltc = ::NTV2_TCINDEX_SDI8_LTC; | |
| output_destination_id = ::NTV2_XptSDIOut8Input; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_ANALOG: | |
| output_destination_id = ::NTV2_XptAnalogOutInput; | |
| vanc_mode = ::NTV2_VANCMODE_TALL; | |
| break; | |
| case GST_AJA_OUTPUT_DESTINATION_HDMI: | |
| output_destination_id = ::NTV2_XptHDMIOutInput; | |
| vanc_mode = ::NTV2_VANCMODE_OFF; | |
| break; | |
| default: | |
| g_assert_not_reached(); | |
| break; | |
| } | |
| if (!self->tc_indexes) self->tc_indexes = new NTV2TCIndexes; | |
| switch (self->timecode_index) { | |
| case GST_AJA_TIMECODE_INDEX_VITC: | |
| self->tc_indexes->insert(tc_indexes_vitc[0]); | |
| if (self->configured_info.interlace_mode != | |
| GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) | |
| self->tc_indexes->insert(tc_indexes_vitc[1]); | |
| break; | |
| case GST_AJA_TIMECODE_INDEX_ATC_LTC: | |
| self->tc_indexes->insert(tc_index_atc_ltc); | |
| break; | |
| case GST_AJA_TIMECODE_INDEX_LTC1: | |
| self->tc_indexes->insert(::NTV2_TCINDEX_LTC1); | |
| break; | |
| case GST_AJA_TIMECODE_INDEX_LTC2: | |
| self->tc_indexes->insert(::NTV2_TCINDEX_LTC2); | |
| break; | |
| default: | |
| g_assert_not_reached(); | |
| break; | |
| } | |
| const NTV2Standard standard(::GetNTV2StandardFromVideoFormat(video_format)); | |
| self->device->device->SetSDIOutputStandard(self->channel, standard); | |
| if (self->quad_mode) { | |
| for (int i = 1; i < 4; i++) | |
| self->device->device->SetSDIOutputStandard( | |
| (NTV2Channel)(self->channel + i), standard); | |
| } | |
| const NTV2FrameGeometry geometry = | |
| ::GetNTV2FrameGeometryFromVideoFormat(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)); | |
| } | |
| } | |
| } | |
| bool had_quad_enabled = false, had_quad_quad_enabled = false; | |
| if (self->channel < ::NTV2_CHANNEL5) { | |
| self->device->device->GetQuadFrameEnable(had_quad_enabled, ::NTV2_CHANNEL1); | |
| self->device->device->GetQuadQuadFrameEnable(had_quad_quad_enabled, | |
| ::NTV2_CHANNEL1); | |
| } else { | |
| self->device->device->GetQuadFrameEnable(had_quad_enabled, ::NTV2_CHANNEL5); | |
| self->device->device->GetQuadQuadFrameEnable(had_quad_quad_enabled, | |
| ::NTV2_CHANNEL5); | |
| } | |
| if (self->quad_mode) { | |
| 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 { | |
| NTV2Channel quad_channel; | |
| if (self->channel < ::NTV2_CHANNEL5) | |
| quad_channel = ::NTV2_CHANNEL1; | |
| else | |
| quad_channel = ::NTV2_CHANNEL5; | |
| self->device->device->Set4kSquaresEnable(false, quad_channel); | |
| self->device->device->SetTsiFrameEnable(false, quad_channel); | |
| self->device->device->SetQuadQuadFrameEnable(false, quad_channel); | |
| self->device->device->SetQuadQuadSquaresEnable(false, quad_channel); | |
| } | |
| NTV2SmpteLineNumber smpte_line_num_info = ::GetSmpteLineNumber(standard); | |
| self->f2_start_line = | |
| (smpte_line_num_info.GetLastLine( | |
| smpte_line_num_info.firstFieldTop ? NTV2_FIELD0 : NTV2_FIELD1) + | |
| 1); | |
| CNTV2SignalRouter router; | |
| // If any channels are currently running, initialize the router with the | |
| // existing routing setup. Otherwise overwrite the whole routing table. | |
| { | |
| bool have_channels_running = false; | |
| for (NTV2Channel c = ::NTV2_CHANNEL1; c < NTV2_MAX_NUM_CHANNELS; | |
| c = (NTV2Channel)(c + 1)) { | |
| AUTOCIRCULATE_STATUS ac_status; | |
| if (c == self->channel) continue; | |
| if (self->device->device->AutoCirculateGetStatus(c, ac_status) && | |
| !ac_status.IsStopped()) { | |
| have_channels_running = true; | |
| break; | |
| } | |
| } | |
| if (have_channels_running) 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->channel == NTV2_CHANNEL1) { | |
| for (auto iter = connections.begin(); iter != connections.end(); iter++) { | |
| if (iter->first == NTV2_XptSDIOut1Input || | |
| iter->first == NTV2_XptSDIOut1InputDS2 || | |
| iter->first == NTV2_XptSDIOut2Input || | |
| iter->first == NTV2_XptSDIOut2InputDS2 || | |
| iter->first == NTV2_XptSDIOut3Input || | |
| iter->first == NTV2_XptSDIOut4Input || | |
| 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_XptFrameBuffer1YUV || | |
| iter->second == NTV2_XptFrameBuffer2YUV || | |
| iter->second == NTV2_XptFrameBuffer3YUV || | |
| iter->second == NTV2_XptFrameBuffer4YUV || | |
| iter->second == NTV2_XptFrameBuffer1_DS2YUV || | |
| iter->second == NTV2_XptFrameBuffer2_DS2YUV || | |
| iter->first == NTV2_XptSDIOut1Input || | |
| iter->first == NTV2_XptSDIOut2Input || | |
| iter->first == NTV2_XptSDIOut3Input || | |
| iter->first == NTV2_XptSDIOut4Input) | |
| 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_XptSDIOut5Input || | |
| iter->first == NTV2_XptSDIOut5InputDS2 || | |
| iter->first == NTV2_XptSDIOut6Input || | |
| iter->first == NTV2_XptSDIOut6InputDS2 || | |
| iter->first == NTV2_XptSDIOut7Input || | |
| iter->first == NTV2_XptSDIOut8Input || | |
| 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_XptFrameBuffer5YUV || | |
| iter->second == NTV2_XptFrameBuffer6YUV || | |
| iter->second == NTV2_XptFrameBuffer7YUV || | |
| iter->second == NTV2_XptFrameBuffer8YUV || | |
| iter->second == NTV2_XptFrameBuffer3_DS2YUV || | |
| iter->second == NTV2_XptFrameBuffer4_DS2YUV || | |
| iter->second == NTV2_XptFrameBuffer5_DS2YUV || | |
| iter->second == NTV2_XptFrameBuffer6_DS2YUV || | |
| iter->first == NTV2_XptSDIOut5Input || | |
| iter->first == NTV2_XptSDIOut6Input || | |
| iter->first == NTV2_XptSDIOut7Input || | |
| iter->first == NTV2_XptSDIOut8Input) | |
| router.RemoveConnection(iter->first, iter->second); | |
| } | |
| } else { | |
| g_assert_not_reached(); | |
| } | |
| } else { | |
| // This also removes all connections for any previous quad mode on the | |
| // corresponding channels. | |
| NTV2InputCrosspointID quad_output_source_ids[10]; | |
| if (output_destination_id == NTV2_XptSDIOut1Input || | |
| output_destination_id == NTV2_XptSDIOut2Input || | |
| output_destination_id == NTV2_XptSDIOut3Input || | |
| output_destination_id == NTV2_XptSDIOut4Input) { | |
| quad_output_source_ids[0] = NTV2_XptSDIOut1Input; | |
| quad_output_source_ids[1] = NTV2_XptSDIOut2Input; | |
| quad_output_source_ids[2] = NTV2_XptSDIOut3Input; | |
| quad_output_source_ids[3] = NTV2_XptSDIOut4Input; | |
| quad_output_source_ids[4] = NTV2_XptSDIOut1InputDS2; | |
| quad_output_source_ids[5] = NTV2_XptSDIOut2InputDS2; | |
| quad_output_source_ids[6] = NTV2_Xpt425Mux1AInput; | |
| quad_output_source_ids[7] = NTV2_Xpt425Mux1BInput; | |
| quad_output_source_ids[8] = NTV2_Xpt425Mux2AInput; | |
| quad_output_source_ids[9] = NTV2_Xpt425Mux2BInput; | |
| } else if (output_destination_id == NTV2_XptSDIOut5Input || | |
| output_destination_id == NTV2_XptSDIOut6Input || | |
| output_destination_id == NTV2_XptSDIOut7Input || | |
| output_destination_id == NTV2_XptSDIOut8Input) { | |
| quad_output_source_ids[0] = NTV2_XptSDIOut5Input; | |
| quad_output_source_ids[1] = NTV2_XptSDIOut6Input; | |
| quad_output_source_ids[2] = NTV2_XptSDIOut7Input; | |
| quad_output_source_ids[3] = NTV2_XptSDIOut8Input; | |
| quad_output_source_ids[4] = NTV2_XptSDIOut5InputDS2; | |
| quad_output_source_ids[5] = NTV2_XptSDIOut6InputDS2; | |
| quad_output_source_ids[6] = NTV2_Xpt425Mux3AInput; | |
| quad_output_source_ids[7] = NTV2_Xpt425Mux3BInput; | |
| quad_output_source_ids[8] = NTV2_Xpt425Mux4AInput; | |
| quad_output_source_ids[9] = NTV2_Xpt425Mux4BInput; | |
| } | |
| for (auto iter = connections.begin(); iter != connections.end(); iter++) { | |
| if (had_quad_enabled || had_quad_quad_enabled) { | |
| for (auto quad_output_source_id : quad_output_source_ids) { | |
| if (iter->first == quad_output_source_id) | |
| router.RemoveConnection(iter->first, iter->second); | |
| } | |
| } else { | |
| if (iter->first == output_destination_id || | |
| iter->second == framebuffer_id) | |
| router.RemoveConnection(iter->first, iter->second); | |
| } | |
| } | |
| } | |
| if (self->quad_mode) { | |
| 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) | |
| framebuffer_id = NTV2_Xpt425Mux1AYUV; | |
| else if (self->channel == NTV2_CHANNEL5) | |
| framebuffer_id = NTV2_Xpt425Mux3AYUV; | |
| else | |
| g_assert_not_reached(); | |
| } | |
| } | |
| GST_DEBUG_OBJECT(self, "Creating connection %d - %d", output_destination_id, | |
| framebuffer_id); | |
| router.AddConnection(output_destination_id, framebuffer_id); | |
| if (self->quad_mode) { | |
| 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_XptSDIOut2Input, | |
| NTV2_XptFrameBuffer1_DS2YUV); | |
| router.AddConnection(NTV2_XptSDIOut3Input, NTV2_XptFrameBuffer2YUV); | |
| router.AddConnection(NTV2_XptSDIOut4Input, | |
| NTV2_XptFrameBuffer2_DS2YUV); | |
| } else if (self->channel == NTV2_CHANNEL5) { | |
| router.AddConnection(NTV2_XptSDIOut6Input, | |
| NTV2_XptFrameBuffer3_DS2YUV); | |
| router.AddConnection(NTV2_XptSDIOut7Input, NTV2_XptFrameBuffer4YUV); | |
| router.AddConnection(NTV2_XptSDIOut8Input, | |
| NTV2_XptFrameBuffer4_DS2YUV); | |
| } else { | |
| g_assert_not_reached(); | |
| } | |
| } else if (NTV2_IS_QUAD_QUAD_FORMAT(self->video_format)) { | |
| if (self->channel == NTV2_CHANNEL1) { | |
| router.AddConnection(NTV2_XptSDIOut1InputDS2, | |
| NTV2_XptFrameBuffer1_DS2YUV); | |
| router.AddConnection(NTV2_XptSDIOut2Input, NTV2_XptFrameBuffer2YUV); | |
| router.AddConnection(NTV2_XptSDIOut2InputDS2, | |
| NTV2_XptFrameBuffer2_DS2YUV); | |
| } else if (self->channel == NTV2_CHANNEL5) { | |
| router.AddConnection(NTV2_XptSDIOut5InputDS2, | |
| NTV2_XptFrameBuffer3_DS2YUV); | |
| router.AddConnection(NTV2_XptSDIOut6Input, NTV2_XptFrameBuffer4YUV); | |
| router.AddConnection(NTV2_XptSDIOut6InputDS2, | |
| NTV2_XptFrameBuffer4_DS2YUV); | |
| } else { | |
| g_assert_not_reached(); | |
| } | |
| } else if (NTV2_IS_4K_HFR_VIDEO_FORMAT(self->video_format)) { | |
| if (self->channel == NTV2_CHANNEL1) { | |
| router.AddConnection(NTV2_XptSDIOut2Input, NTV2_Xpt425Mux1BYUV); | |
| router.AddConnection(NTV2_XptSDIOut3Input, NTV2_Xpt425Mux2AYUV); | |
| router.AddConnection(NTV2_XptSDIOut4Input, NTV2_Xpt425Mux2BYUV); | |
| router.AddConnection(NTV2_Xpt425Mux1AInput, NTV2_XptFrameBuffer1YUV); | |
| router.AddConnection(NTV2_Xpt425Mux1BInput, | |
| NTV2_XptFrameBuffer1_DS2YUV); | |
| router.AddConnection(NTV2_Xpt425Mux2AInput, NTV2_XptFrameBuffer2YUV); | |
| router.AddConnection(NTV2_Xpt425Mux2BInput, | |
| NTV2_XptFrameBuffer2_DS2YUV); | |
| } else if (self->channel == NTV2_CHANNEL5) { | |
| router.AddConnection(NTV2_XptSDIOut6Input, NTV2_Xpt425Mux3BYUV); | |
| router.AddConnection(NTV2_XptSDIOut7Input, NTV2_Xpt425Mux4AYUV); | |
| router.AddConnection(NTV2_XptSDIOut8Input, NTV2_Xpt425Mux4BYUV); | |
| router.AddConnection(NTV2_Xpt425Mux3AInput, NTV2_XptFrameBuffer5YUV); | |
| router.AddConnection(NTV2_Xpt425Mux3BInput, | |
| NTV2_XptFrameBuffer5_DS2YUV); | |
| router.AddConnection(NTV2_Xpt425Mux4AInput, NTV2_XptFrameBuffer6YUV); | |
| router.AddConnection(NTV2_Xpt425Mux4BInput, | |
| NTV2_XptFrameBuffer6_DS2YUV); | |
| } else { | |
| g_assert_not_reached(); | |
| } | |
| } else { | |
| if (self->channel == NTV2_CHANNEL1) { | |
| router.AddConnection(NTV2_XptSDIOut1InputDS2, NTV2_Xpt425Mux1BYUV); | |
| router.AddConnection(NTV2_XptSDIOut2Input, NTV2_Xpt425Mux2AYUV); | |
| router.AddConnection(NTV2_XptSDIOut2InputDS2, NTV2_Xpt425Mux2BYUV); | |
| router.AddConnection(NTV2_Xpt425Mux1AInput, NTV2_XptFrameBuffer1YUV); | |
| router.AddConnection(NTV2_Xpt425Mux1BInput, | |
| NTV2_XptFrameBuffer1_DS2YUV); | |
| router.AddConnection(NTV2_Xpt425Mux2AInput, NTV2_XptFrameBuffer2YUV); | |
| router.AddConnection(NTV2_Xpt425Mux2BInput, | |
| NTV2_XptFrameBuffer2_DS2YUV); | |
| } else if (self->channel == NTV2_CHANNEL5) { | |
| router.AddConnection(NTV2_XptSDIOut5InputDS2, NTV2_Xpt425Mux3BYUV); | |
| router.AddConnection(NTV2_XptSDIOut6Input, NTV2_Xpt425Mux4AYUV); | |
| router.AddConnection(NTV2_XptSDIOut6InputDS2, NTV2_Xpt425Mux4BYUV); | |
| router.AddConnection(NTV2_Xpt425Mux3AInput, NTV2_XptFrameBuffer5YUV); | |
| router.AddConnection(NTV2_Xpt425Mux3BInput, | |
| NTV2_XptFrameBuffer5_DS2YUV); | |
| router.AddConnection(NTV2_Xpt425Mux4AInput, NTV2_XptFrameBuffer6YUV); | |
| router.AddConnection(NTV2_Xpt425Mux4BInput, | |
| NTV2_XptFrameBuffer6_DS2YUV); | |
| } else { | |
| g_assert_not_reached(); | |
| } | |
| } | |
| } else if (self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_SQD) { | |
| if (self->channel == NTV2_CHANNEL1) { | |
| router.AddConnection(NTV2_XptSDIOut2Input, NTV2_XptFrameBuffer2YUV); | |
| router.AddConnection(NTV2_XptSDIOut3Input, NTV2_XptFrameBuffer3YUV); | |
| router.AddConnection(NTV2_XptSDIOut4Input, NTV2_XptFrameBuffer4YUV); | |
| } else if (self->channel == NTV2_CHANNEL5) { | |
| router.AddConnection(NTV2_XptSDIOut6Input, NTV2_XptFrameBuffer6YUV); | |
| router.AddConnection(NTV2_XptSDIOut7Input, NTV2_XptFrameBuffer7YUV); | |
| router.AddConnection(NTV2_XptSDIOut8Input, NTV2_XptFrameBuffer8YUV); | |
| } 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()); | |
| } | |
| return TRUE; | |
| } | |
| static GstCaps *gst_aja_sink_get_caps(GstBaseSink *bsink, GstCaps *filter) { | |
| GstAjaSink *self = GST_AJA_SINK(bsink); | |
| GstCaps *caps; | |
| if (self->device) { | |
| caps = gst_ntv2_supported_caps(self->device_id); | |
| } else { | |
| caps = gst_pad_get_pad_template_caps(GST_BASE_SINK_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_sink_event(GstBaseSink *bsink, GstEvent *event) { | |
| GstAjaSink *self = GST_AJA_SINK(bsink); | |
| switch (GST_EVENT_TYPE(event)) { | |
| case GST_EVENT_EOS: { | |
| GST_DEBUG_OBJECT(self, "Signalling EOS"); | |
| g_mutex_lock(&self->queue_lock); | |
| self->eos = TRUE; | |
| g_cond_signal(&self->queue_cond); | |
| g_mutex_unlock(&self->queue_lock); | |
| break; | |
| } | |
| case GST_EVENT_FLUSH_START: { | |
| g_mutex_lock(&self->queue_lock); | |
| self->flushing = TRUE; | |
| self->draining = FALSE; | |
| g_cond_signal(&self->drain_cond); | |
| g_mutex_unlock(&self->queue_lock); | |
| break; | |
| } | |
| case GST_EVENT_FLUSH_STOP: { | |
| QueueItem *item; | |
| g_mutex_lock(&self->queue_lock); | |
| while ( | |
| (item = (QueueItem *)gst_queue_array_pop_head_struct(self->queue))) { | |
| if (item->type == QUEUE_ITEM_TYPE_FRAME) { | |
| gst_buffer_unmap(item->video_buffer, &item->video_map); | |
| gst_buffer_unref(item->video_buffer); | |
| if (item->audio_buffer) { | |
| gst_buffer_unmap(item->audio_buffer, &item->audio_map); | |
| gst_buffer_unref(item->audio_buffer); | |
| } | |
| if (item->anc_buffer) { | |
| gst_buffer_unmap(item->anc_buffer, &item->anc_map); | |
| gst_buffer_unref(item->anc_buffer); | |
| } | |
| if (item->anc_buffer2) { | |
| gst_buffer_unmap(item->anc_buffer2, &item->anc_map2); | |
| gst_buffer_unref(item->anc_buffer2); | |
| } | |
| } | |
| } | |
| g_cond_signal(&self->queue_cond); | |
| self->flushing = FALSE; | |
| g_cond_signal(&self->drain_cond); | |
| g_mutex_unlock(&self->queue_lock); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return GST_BASE_SINK_CLASS(parent_class)->event(bsink, event); | |
| } | |
| static gboolean gst_aja_sink_propose_allocation(GstBaseSink *bsink, | |
| GstQuery *query) { | |
| GstAjaSink *self = GST_AJA_SINK(bsink); | |
| if (self->allocator && self->vanc_mode == ::NTV2_VANCMODE_OFF) { | |
| GstAllocationParams params; | |
| gst_allocation_params_init(¶ms); | |
| params.prefix = 0; | |
| params.padding = 0; | |
| params.align = 4095; | |
| gst_query_add_allocation_param(query, self->allocator, ¶ms); | |
| } | |
| return TRUE; | |
| } | |
| static GstFlowReturn gst_aja_sink_render(GstBaseSink *bsink, | |
| GstBuffer *buffer) { | |
| GstAjaSink *self = GST_AJA_SINK(bsink); | |
| GstFlowReturn flow_ret = GST_FLOW_OK; | |
| GstAjaAudioMeta *meta; | |
| GstBuffer *item_buffer = NULL, *item_audio_buffer = NULL; | |
| GstVideoTimeCodeMeta *tc_meta; | |
| QueueItem item = { | |
| .type = QUEUE_ITEM_TYPE_FRAME, | |
| .video_buffer = NULL, | |
| .video_map = GST_MAP_INFO_INIT, | |
| .audio_buffer = NULL, | |
| .audio_map = GST_MAP_INFO_INIT, | |
| .tc = NTV2_RP188(), | |
| .anc_buffer = NULL, | |
| .anc_map = GST_MAP_INFO_INIT, | |
| .anc_buffer2 = NULL, | |
| .anc_map2 = GST_MAP_INFO_INIT, | |
| }; | |
| guint video_buffer_size = ::GetVideoActiveSize( | |
| self->video_format, ::NTV2_FBF_10BIT_YCBCR, self->vanc_mode); | |
| NTV2FormatDescriptor format_desc(self->video_format, ::NTV2_FBF_10BIT_YCBCR, | |
| self->vanc_mode); | |
| meta = gst_buffer_get_aja_audio_meta(buffer); | |
| tc_meta = gst_buffer_get_video_time_code_meta(buffer); | |
| if (self->vanc_mode == ::NTV2_VANCMODE_OFF && | |
| gst_buffer_n_memory(buffer) == 1) { | |
| GstMemory *mem = gst_buffer_peek_memory(buffer, 0); | |
| gsize offset; | |
| if (gst_memory_get_sizes(mem, &offset, NULL) == video_buffer_size && | |
| offset == 0 && | |
| strcmp(mem->allocator->mem_type, GST_AJA_ALLOCATOR_MEMTYPE) == 0 && | |
| GST_AJA_ALLOCATOR(mem->allocator)->device->device->GetIndexNumber() == | |
| self->device->device->GetIndexNumber()) { | |
| item_buffer = gst_buffer_ref(buffer); | |
| } | |
| } | |
| if (!item_buffer) { | |
| GstVideoFrame in_frame; | |
| GST_DEBUG_OBJECT(self, "Allocating new video buffer"); | |
| if (!self->buffer_pool) { | |
| 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, | |
| 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); | |
| } | |
| if (!gst_video_frame_map(&in_frame, &self->configured_info, buffer, | |
| GST_MAP_READ)) { | |
| GST_ERROR_OBJECT(self, "Failed to map buffer"); | |
| return GST_FLOW_ERROR; | |
| } | |
| flow_ret = | |
| gst_buffer_pool_acquire_buffer(self->buffer_pool, &item_buffer, NULL); | |
| if (flow_ret != GST_FLOW_OK) { | |
| gst_video_frame_unmap(&in_frame); | |
| return flow_ret; | |
| } | |
| item.type = QUEUE_ITEM_TYPE_FRAME; | |
| item.video_buffer = item_buffer; | |
| gst_buffer_map(item.video_buffer, &item.video_map, GST_MAP_WRITE); | |
| guint offset = | |
| format_desc.RasterLineToByteOffset(format_desc.GetFirstActiveLine()); | |
| guint size = format_desc.GetVisibleRasterBytes(); | |
| if (offset != 0) | |
| ::SetRasterLinesBlack(::NTV2_FBF_10BIT_YCBCR, item.video_map.data, | |
| format_desc.GetBytesPerRow(), | |
| format_desc.GetFirstActiveLine()); | |
| memcpy(item.video_map.data + offset, | |
| GST_VIDEO_FRAME_PLANE_DATA(&in_frame, 0), size); | |
| gst_video_frame_unmap(&in_frame); | |
| } else { | |
| item.type = QUEUE_ITEM_TYPE_FRAME; | |
| item.video_buffer = item_buffer; | |
| gst_buffer_map(item.video_buffer, &item.video_map, GST_MAP_READ); | |
| } | |
| if (meta) { | |
| if (gst_buffer_n_memory(meta->buffer) == 1) { | |
| GstMemory *mem = gst_buffer_peek_memory(meta->buffer, 0); | |
| if (strcmp(mem->allocator->mem_type, GST_AJA_ALLOCATOR_MEMTYPE) == 0 && | |
| GST_AJA_ALLOCATOR(mem->allocator)->device->device->GetIndexNumber() == | |
| self->device->device->GetIndexNumber()) { | |
| item_audio_buffer = gst_buffer_ref(meta->buffer); | |
| } | |
| } | |
| if (!item_audio_buffer) { | |
| GstMapInfo audio_map; | |
| GST_DEBUG_OBJECT(self, "Allocating new audio buffer"); | |
| if (!self->audio_buffer_pool) { | |
| guint audio_buffer_size = 1UL * 1024UL * 1024UL; | |
| self->audio_buffer_pool = gst_buffer_pool_new(); | |
| GstStructure *config = | |
| gst_buffer_pool_get_config(self->audio_buffer_pool); | |
| gst_buffer_pool_config_set_params(config, NULL, audio_buffer_size, | |
| 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); | |
| } | |
| flow_ret = gst_buffer_pool_acquire_buffer(self->audio_buffer_pool, | |
| &item_audio_buffer, NULL); | |
| if (flow_ret != GST_FLOW_OK) { | |
| gst_buffer_unmap(item.video_buffer, &item.video_map); | |
| gst_buffer_unref(item.video_buffer); | |
| return flow_ret; | |
| } | |
| gst_buffer_set_size(item_audio_buffer, gst_buffer_get_size(meta->buffer)); | |
| gst_buffer_map(meta->buffer, &audio_map, GST_MAP_READ); | |
| gst_buffer_map(item_audio_buffer, &item.audio_map, GST_MAP_READWRITE); | |
| memcpy(item.audio_map.data, audio_map.data, audio_map.size); | |
| gst_buffer_unmap(meta->buffer, &audio_map); | |
| item.audio_buffer = item_audio_buffer; | |
| } else { | |
| gst_buffer_map(item_audio_buffer, &item.audio_map, GST_MAP_READ); | |
| item.audio_buffer = item_audio_buffer; | |
| } | |
| } else { | |
| item.audio_buffer = NULL; | |
| } | |
| if (tc_meta) { | |
| TimecodeFormat tc_format = ::kTCFormatUnknown; | |
| if (tc_meta->tc.config.fps_n == 24 && tc_meta->tc.config.fps_d == 1) { | |
| tc_format = kTCFormat24fps; | |
| } else if (tc_meta->tc.config.fps_n == 25 && | |
| tc_meta->tc.config.fps_d == 1) { | |
| tc_format = kTCFormat25fps; | |
| } else if (tc_meta->tc.config.fps_n == 30 && | |
| tc_meta->tc.config.fps_d == 1) { | |
| tc_format = kTCFormat30fps; | |
| } else if (tc_meta->tc.config.fps_n == 30000 && | |
| tc_meta->tc.config.fps_d == 1001) { | |
| tc_format = kTCFormat30fpsDF; | |
| } else if (tc_meta->tc.config.fps_n == 48 && | |
| tc_meta->tc.config.fps_d == 1) { | |
| tc_format = kTCFormat48fps; | |
| } else if (tc_meta->tc.config.fps_n == 50 && | |
| tc_meta->tc.config.fps_d == 1) { | |
| tc_format = kTCFormat50fps; | |
| } else if (tc_meta->tc.config.fps_n == 60 && | |
| tc_meta->tc.config.fps_d == 1) { | |
| tc_format = kTCFormat60fps; | |
| } else if (tc_meta->tc.config.fps_n == 60000 && | |
| tc_meta->tc.config.fps_d == 1001) { | |
| tc_format = kTCFormat60fpsDF; | |
| } | |
| const CRP188 rp188(tc_meta->tc.frames, tc_meta->tc.seconds, | |
| tc_meta->tc.minutes, tc_meta->tc.hours, tc_format); | |
| rp188.GetRP188Reg(item.tc); | |
| } else { | |
| item.tc.fDBB = 0xffffffff; | |
| } | |
| AJAAncillaryList anc_packet_list; | |
| // TODO: Handle AFD/Bar meta | |
| #if 0 | |
| if (bar_meta || afd_meta) { | |
| const uint16_t kF1PktLineNumAFDBAR(11); | |
| const AJAAncillaryDataLocation kAFDBARLocF1( | |
| AJAAncillaryDataLink_A, AJAAncillaryDataVideoStream_Y, | |
| AJAAncillaryDataSpace_VANC, kF1PktLineNumAFDBAR, | |
| AJAAncDataHorizOffset_AnyVanc); | |
| const uint16_t kF2PktLineNumAFDBAR(self->f2_start_line + 11); | |
| const AJAAncillaryDataLocation kAFDBARLocF2( | |
| AJAAncillaryDataLink_A, AJAAncillaryDataVideoStream_Y, | |
| AJAAncillaryDataSpace_VANC, kF2PktLineNumAFDBAR, | |
| AJAAncDataHorizOffset_AnyVanc); | |
| AJAAncillaryData pkt; | |
| pkt.SetFromSMPTE334(NULL, 0, kAFDBARLocF1); | |
| anc_packet_list.AddAncillaryData(pkt); | |
| if (self->configured_info.interlace_mode != GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) { | |
| AJAAncillaryData pkt2; | |
| pkt.SetFromSMPTE334(NULL, 0, kAFDBARLocF2); | |
| anc_packet_list.AddAncillaryData(pkt); | |
| } | |
| } | |
| #endif | |
| GstVideoCaptionMeta *caption_meta; | |
| gpointer iter = NULL; | |
| while ( | |
| (caption_meta = (GstVideoCaptionMeta *)gst_buffer_iterate_meta_filtered( | |
| buffer, &iter, GST_VIDEO_CAPTION_META_API_TYPE))) { | |
| if (caption_meta->caption_type == GST_VIDEO_CAPTION_TYPE_CEA708_CDP) { | |
| if (self->cea708_line_number != -1) { | |
| const AJAAncillaryDataLocation kCEA708LocF1( | |
| AJAAncillaryDataLink_A, AJAAncillaryDataVideoStream_Y, | |
| AJAAncillaryDataSpace_VANC, self->cea708_line_number, | |
| AJAAncDataHorizOffset_AnyVanc); | |
| AJAAncillaryData_Cea708 pkt; | |
| pkt.SetDID(AJAAncillaryData_CEA708_DID); | |
| pkt.SetSID(AJAAncillaryData_CEA708_SID); | |
| pkt.SetDataLocation(kCEA708LocF1); | |
| pkt.SetDataCoding(AJAAncillaryDataCoding_Digital); | |
| pkt.SetPayloadData(caption_meta->data, caption_meta->size); | |
| GST_TRACE_OBJECT( | |
| self, | |
| "Adding CEA708 CDP VANC of %" G_GSIZE_FORMAT " bytes at line %u", | |
| pkt.GetPayloadByteCount(), pkt.GetLocationLineNumber()); | |
| anc_packet_list.AddAncillaryData(pkt); | |
| } | |
| } else if (caption_meta->caption_type == | |
| GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A) { | |
| if (self->cea608_line_number != -1) { | |
| const AJAAncillaryDataLocation kCEA608LocF1( | |
| AJAAncillaryDataLink_A, AJAAncillaryDataVideoStream_Y, | |
| AJAAncillaryDataSpace_VANC, self->cea608_line_number, | |
| AJAAncDataHorizOffset_AnyVanc); | |
| AJAAncillaryData_Cea608_Vanc pkt; | |
| pkt.SetDID(AJAAncillaryData_Cea608_Vanc_DID); | |
| pkt.SetSID(AJAAncillaryData_Cea608_Vanc_SID); | |
| pkt.SetDataLocation(kCEA608LocF1); | |
| pkt.SetDataCoding(AJAAncillaryDataCoding_Digital); | |
| pkt.SetPayloadData(caption_meta->data, caption_meta->size); | |
| pkt.ParsePayloadData(); | |
| GST_TRACE_OBJECT( | |
| self, "Adding CEA608 VANC of %" G_GSIZE_FORMAT " bytes at line %u", | |
| pkt.GetPayloadByteCount(), pkt.GetLocationLineNumber()); | |
| anc_packet_list.AddAncillaryData(pkt); | |
| } | |
| } else { | |
| GST_WARNING_OBJECT(self, "Unhandled caption type %d", | |
| caption_meta->caption_type); | |
| } | |
| } | |
| if (self->handle_ancillary_meta) { | |
| GstAncillaryMeta *anc_meta; | |
| iter = NULL; | |
| while ((anc_meta = (GstAncillaryMeta *)gst_buffer_iterate_meta_filtered( | |
| buffer, &iter, GST_ANCILLARY_META_API_TYPE))) { | |
| const AJAAncillaryDataLocation loc( | |
| AJAAncillaryDataLink_A, | |
| anc_meta->c_not_y_channel ? AJAAncillaryDataChannel_C | |
| : AJAAncillaryDataVideoStream_Y, | |
| AJAAncillaryDataSpace_VANC, anc_meta->line, anc_meta->offset); | |
| AJAAncillaryData pkt; | |
| guint8 data[256]; | |
| pkt.SetDID(anc_meta->DID); | |
| pkt.SetSID(anc_meta->SDID_block_number); | |
| pkt.SetDataLocation(loc); | |
| pkt.SetDataCoding(AJAAncillaryDataCoding_Digital); | |
| for (gsize i = 0; i < (anc_meta->data_count & 0xff); i++) { | |
| data[i] = anc_meta->data[i] & 0xff; | |
| } | |
| pkt.SetPayloadData(data, anc_meta->data_count & 0xff); | |
| GST_TRACE_OBJECT(self, | |
| "Adding ANC of %" G_GSIZE_FORMAT " bytes at (%u,%u)", | |
| pkt.GetPayloadByteCount(), pkt.GetLocationLineNumber(), | |
| pkt.GetLocationHorizOffset()); | |
| anc_packet_list.AddAncillaryData(pkt); | |
| } | |
| } | |
| if (!anc_packet_list.IsEmpty()) { | |
| if (self->vanc_mode == ::NTV2_VANCMODE_OFF && | |
| ::NTV2DeviceCanDoCustomAnc(self->device_id)) { | |
| if (!self->anc_buffer_pool) { | |
| self->anc_buffer_pool = gst_buffer_pool_new(); | |
| GstStructure *config = | |
| gst_buffer_pool_get_config(self->anc_buffer_pool); | |
| gst_buffer_pool_config_set_params( | |
| config, NULL, 8 * 1024, | |
| (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); | |
| } | |
| flow_ret = gst_buffer_pool_acquire_buffer(self->anc_buffer_pool, | |
| &item.anc_buffer, NULL); | |
| if (flow_ret != GST_FLOW_OK) { | |
| gst_buffer_unmap(item.video_buffer, &item.video_map); | |
| gst_buffer_unref(item.video_buffer); | |
| if (item.audio_buffer) { | |
| gst_buffer_unmap(item.audio_buffer, &item.audio_map); | |
| gst_buffer_unref(item.audio_buffer); | |
| } | |
| return flow_ret; | |
| } | |
| gst_buffer_map(item.anc_buffer, &item.anc_map, GST_MAP_READWRITE); | |
| if (self->configured_info.interlace_mode != | |
| GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) { | |
| flow_ret = gst_buffer_pool_acquire_buffer(self->anc_buffer_pool, | |
| &item.anc_buffer2, NULL); | |
| if (flow_ret != GST_FLOW_OK) { | |
| gst_buffer_unmap(item.video_buffer, &item.video_map); | |
| gst_buffer_unref(item.video_buffer); | |
| if (item.audio_buffer) { | |
| gst_buffer_unmap(item.audio_buffer, &item.audio_map); | |
| gst_buffer_unref(item.audio_buffer); | |
| } | |
| if (item.anc_buffer) { | |
| gst_buffer_unmap(item.anc_buffer, &item.anc_map); | |
| gst_buffer_unref(item.anc_buffer); | |
| } | |
| return flow_ret; | |
| } | |
| gst_buffer_map(item.anc_buffer2, &item.anc_map2, GST_MAP_READWRITE); | |
| } | |
| NTV2_POINTER anc_ptr1(item.anc_map.data, item.anc_map.size); | |
| NTV2_POINTER anc_ptr2(item.anc_map2.data, item.anc_map2.size); | |
| anc_ptr1.Fill(ULWord(0)); | |
| anc_ptr2.Fill(ULWord(0)); | |
| anc_packet_list.GetTransmitData(anc_ptr1, anc_ptr2, | |
| self->configured_info.interlace_mode != | |
| GST_VIDEO_INTERLACE_MODE_PROGRESSIVE, | |
| self->f2_start_line); | |
| } else { | |
| NTV2_POINTER ptr(item.video_map.data, item.video_map.size); | |
| anc_packet_list.GetVANCTransmitData(ptr, format_desc); | |
| } | |
| } | |
| g_mutex_lock(&self->queue_lock); | |
| 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, | |
| GST_BUFFER_PTS(tmp->video_buffer), | |
| 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_buffer_unmap(tmp->video_buffer, &tmp->video_map); | |
| gst_buffer_unref(tmp->video_buffer); | |
| if (tmp->audio_buffer) { | |
| gst_buffer_unmap(tmp->audio_buffer, &tmp->audio_map); | |
| gst_buffer_unref(tmp->audio_buffer); | |
| } | |
| if (tmp->anc_buffer) { | |
| gst_buffer_unmap(tmp->anc_buffer, &tmp->anc_map); | |
| gst_buffer_unref(tmp->anc_buffer); | |
| } | |
| if (tmp->anc_buffer2) { | |
| gst_buffer_unmap(tmp->anc_buffer2, &tmp->anc_map2); | |
| gst_buffer_unref(tmp->anc_buffer2); | |
| } | |
| } | |
| } | |
| GST_TRACE_OBJECT(self, "Queuing frame video %p audio %p", item.video_map.data, | |
| item.audio_buffer ? item.audio_map.data : NULL); | |
| 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); | |
| g_mutex_unlock(&self->queue_lock); | |
| return flow_ret; | |
| } | |
| static void output_thread_func(AJAThread *thread, void *data) { | |
| GstAjaSink *self = GST_AJA_SINK(data); | |
| GstClock *clock = NULL; | |
| guint64 frames_rendered_start = G_MAXUINT64; | |
| GstClockTime frames_rendered_start_time = GST_CLOCK_TIME_NONE; | |
| guint64 frames_dropped_last = G_MAXUINT64; | |
| AUTOCIRCULATE_TRANSFER transfer; | |
| if (self->output_cpu_core != G_MAXUINT) { | |
| cpu_set_t mask; | |
| pthread_t current_thread = pthread_self(); | |
| CPU_ZERO(&mask); | |
| CPU_SET(self->output_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->output_cpu_core); | |
| } | |
| } | |
| g_mutex_lock(&self->queue_lock); | |
| restart: | |
| if (self->draining && gst_queue_array_get_length(self->queue) == 0) { | |
| GST_DEBUG_OBJECT(self, "Drained"); | |
| self->draining = FALSE; | |
| g_cond_signal(&self->drain_cond); | |
| } | |
| GST_DEBUG_OBJECT(self, "Waiting for playing or shutdown"); | |
| while ((!self->playing && !self->shutdown) || | |
| (self->playing && | |
| gst_queue_array_get_length(self->queue) < self->queue_size / 2 && | |
| !self->eos)) | |
| 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 playing"); | |
| g_mutex_unlock(&self->queue_lock); | |
| { | |
| // Make sure to globally lock here as the routing settings and others are | |
| // global shared state | |
| GstAjaNtv2DeviceLocker locker(self->device); | |
| self->device->device->AutoCirculateStop(self->channel); | |
| if (!self->device->device->EnableChannel(self->channel)) { | |
| GST_ERROR_OBJECT(self, "Failed to enable channel"); | |
| g_mutex_lock(&self->queue_lock); | |
| GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL), | |
| ("Failed to configure device")); | |
| goto out; | |
| } | |
| if (self->quad_mode) { | |
| for (int i = 1; i < 4; i++) { | |
| if (!self->device->device->EnableChannel( | |
| (NTV2Channel)(self->channel + i))) { | |
| GST_ERROR_OBJECT(self, "Failed to enable channel"); | |
| g_mutex_lock(&self->queue_lock); | |
| GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL), | |
| ("Failed to configure device")); | |
| goto out; | |
| } | |
| } | |
| } | |
| self->device->device->EnableOutputInterrupt(self->channel); | |
| self->device->device->SubscribeOutputVerticalEvent(self->channel); | |
| guint16 start_frame = self->start_frame; | |
| guint16 end_frame = self->end_frame; | |
| // If both are the same, try to find queue_size/2 unallocated frames and | |
| // use those. | |
| if (start_frame == end_frame) { | |
| guint16 num_frames = self->queue_size / 2; | |
| gint assigned_start_frame = gst_aja_ntv2_device_find_unallocated_frames( | |
| self->device, self->channel, num_frames); | |
| if (assigned_start_frame == -1) { | |
| GST_ELEMENT_ERROR(self, STREAM, FAILED, (NULL), | |
| ("Failed to allocate %u frames", num_frames)); | |
| goto out; | |
| } | |
| start_frame = assigned_start_frame; | |
| end_frame = start_frame + num_frames - 1; | |
| } | |
| GST_DEBUG_OBJECT( | |
| self, "Configuring channel %u with start frame %u and end frame %u", | |
| self->channel, start_frame, end_frame); | |
| // Configure render delay based on the framerate and queue size | |
| gst_base_sink_set_render_delay( | |
| GST_BASE_SINK(self), | |
| gst_util_uint64_scale(end_frame - start_frame + 1, | |
| self->configured_info.fps_d * GST_SECOND, | |
| self->configured_info.fps_n)); | |
| if (!self->device->device->AutoCirculateInitForOutput( | |
| self->channel, 0, self->audio_system, | |
| (self->rp188 ? AUTOCIRCULATE_WITH_RP188 : 0) | | |
| (self->vanc_mode == ::NTV2_VANCMODE_OFF ? AUTOCIRCULATE_WITH_ANC | |
| : 0), | |
| 1, start_frame, end_frame)) { | |
| 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_rendered_start = G_MAXUINT64; | |
| frames_rendered_start_time = GST_CLOCK_TIME_NONE; | |
| frames_dropped_last = G_MAXUINT64; | |
| g_mutex_lock(&self->queue_lock); | |
| while (self->playing && !self->shutdown && | |
| !(self->draining && gst_queue_array_get_length(self->queue) == 0)) { | |
| 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); | |
| // Detect if we were too slow with providing frames and report if that was | |
| // the case together with the amount of frames dropped | |
| 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.GetNumAvailableOutputFrames() > 1) { | |
| QueueItem item, *item_p; | |
| while ((item_p = (QueueItem *)gst_queue_array_pop_head_struct( | |
| self->queue)) == NULL && | |
| self->playing && !self->shutdown && !self->draining) { | |
| GST_DEBUG_OBJECT( | |
| self, | |
| "Element queue underrun, waiting for more frames or shutdown"); | |
| g_cond_wait(&self->queue_cond, &self->queue_lock); | |
| } | |
| if (!self->playing || self->shutdown || (!item_p && self->draining)) { | |
| if (item_p && item_p->type == QUEUE_ITEM_TYPE_FRAME) { | |
| gst_buffer_unmap(item_p->video_buffer, &item_p->video_map); | |
| gst_buffer_unref(item_p->video_buffer); | |
| if (item_p->audio_buffer) { | |
| gst_buffer_unmap(item_p->audio_buffer, &item_p->audio_map); | |
| gst_buffer_unref(item_p->audio_buffer); | |
| } | |
| if (item_p->anc_buffer) { | |
| gst_buffer_unmap(item_p->anc_buffer, &item_p->anc_map); | |
| gst_buffer_unref(item_p->anc_buffer); | |
| } | |
| if (item_p->anc_buffer2) { | |
| gst_buffer_unmap(item_p->anc_buffer2, &item_p->anc_map2); | |
| gst_buffer_unref(item_p->anc_buffer2); | |
| } | |
| } | |
| break; | |
| } | |
| if (item_p && item_p->type != QUEUE_ITEM_TYPE_FRAME) { | |
| continue; | |
| } | |
| GST_TRACE_OBJECT(self, "%u frames queued", | |
| gst_queue_array_get_length(self->queue)); | |
| item = *item_p; | |
| g_mutex_unlock(&self->queue_lock); | |
| GST_TRACE_OBJECT(self, | |
| "Transferring frame: " | |
| "Video %p %" G_GSIZE_FORMAT | |
| " " | |
| "Audio %p %" G_GSIZE_FORMAT, | |
| item.video_map.data, item.video_map.size, | |
| item.audio_buffer ? item.audio_map.data : NULL, | |
| item.audio_buffer ? item.audio_map.size : 0); | |
| // Set timecodes if provided by upstream | |
| if (item.tc.IsValid() && item.tc.fDBB != 0xffffffff && self->tc_indexes) { | |
| NTV2TimeCodes timecodes; | |
| for (const auto &tc_index : *self->tc_indexes) { | |
| timecodes[tc_index] = item.tc; | |
| } | |
| transfer.SetOutputTimeCodes(timecodes); | |
| } | |
| transfer.SetVideoBuffer((ULWord *)item.video_map.data, | |
| item.video_map.size); | |
| transfer.SetAudioBuffer((ULWord *)item.audio_map.data, | |
| item.audio_map.size); | |
| transfer.SetAncBuffers((ULWord *)item.anc_map.data, item.anc_map.size, | |
| (ULWord *)item.anc_map2.data, item.anc_map2.size); | |
| if (!self->device->device->AutoCirculateTransfer(self->channel, | |
| transfer)) { | |
| GST_WARNING_OBJECT(self, "Failed to transfer frame"); | |
| } | |
| gst_buffer_unmap(item.video_buffer, &item.video_map); | |
| gst_buffer_unref(item.video_buffer); | |
| if (item.audio_buffer) { | |
| gst_buffer_unmap(item.audio_buffer, &item.audio_map); | |
| gst_buffer_unref(item.audio_buffer); | |
| } | |
| if (item.anc_buffer) { | |
| gst_buffer_unmap(item.anc_buffer, &item.anc_map); | |
| gst_buffer_unref(item.anc_buffer); | |
| } | |
| if (item.anc_buffer2) { | |
| gst_buffer_unmap(item.anc_buffer2, &item.anc_map2); | |
| gst_buffer_unref(item.anc_buffer2); | |
| } | |
| GST_TRACE_OBJECT( | |
| self, | |
| "Transferred frame. " | |
| "frame time %" GST_TIME_FORMAT | |
| " " | |
| "current frame %u " | |
| "current frame time %" GST_TIME_FORMAT | |
| " " | |
| "frames processed %u " | |
| "frames dropped %u " | |
| "buffer level %u", | |
| GST_TIME_ARGS(transfer.acTransferStatus.acFrameStamp.acFrameTime * | |
| 100), | |
| transfer.acTransferStatus.acFrameStamp.acCurrentFrame, | |
| GST_TIME_ARGS( | |
| transfer.acTransferStatus.acFrameStamp.acCurrentFrameTime * 100), | |
| transfer.acTransferStatus.acFramesProcessed, | |
| transfer.acTransferStatus.acFramesDropped, | |
| transfer.acTransferStatus.acBufferLevel); | |
| // Trivial drift calculation | |
| // | |
| // TODO: Should probably take averages over a timespan (say 1 minute) | |
| // into a ringbuffer and calculate a linear regression over them | |
| // FIXME: Add some compensation by dropping/duplicating frames as needed | |
| // but make this configurable | |
| if (frames_rendered_start_time == GST_CLOCK_TIME_NONE && | |
| transfer.acTransferStatus.acFrameStamp.acCurrentFrameTime != 0 && | |
| transfer.acTransferStatus.acFramesProcessed + | |
| transfer.acTransferStatus.acFramesDropped > | |
| self->queue_size && | |
| clock) { | |
| frames_rendered_start = transfer.acTransferStatus.acFramesProcessed + | |
| transfer.acTransferStatus.acFramesDropped; | |
| GstClockTime now_gst = gst_clock_get_time(clock); | |
| GstClockTime now_sys = g_get_real_time() * 1000; | |
| GstClockTime render_time = | |
| transfer.acTransferStatus.acFrameStamp.acCurrentFrameTime * 100; | |
| if (render_time < now_sys) { | |
| frames_rendered_start_time = now_gst - (now_sys - render_time); | |
| } | |
| } | |
| if (clock && frames_rendered_start_time != GST_CLOCK_TIME_NONE) { | |
| GstClockTime now_gst = gst_clock_get_time(clock); | |
| GstClockTime now_sys = g_get_real_time() * 1000; | |
| GstClockTime render_time = | |
| transfer.acTransferStatus.acFrameStamp.acCurrentFrameTime * 100; | |
| GstClockTime sys_diff; | |
| if (now_sys > render_time) { | |
| sys_diff = now_sys - render_time; | |
| } else { | |
| sys_diff = 0; | |
| } | |
| GstClockTime diff = now_gst - frames_rendered_start_time; | |
| if (sys_diff < diff) diff -= sys_diff; | |
| guint64 frames_rendered = (transfer.acTransferStatus.acFramesProcessed + | |
| transfer.acTransferStatus.acFramesDropped) - | |
| frames_rendered_start; | |
| guint64 frames_produced = | |
| gst_util_uint64_scale(diff, self->configured_info.fps_n, | |
| self->configured_info.fps_d * GST_SECOND); | |
| gdouble fps_rendered = ((gdouble)frames_rendered * GST_SECOND) / diff; | |
| GST_TRACE_OBJECT(self, | |
| "Frames rendered %" G_GUINT64_FORMAT | |
| ", frames produced %" G_GUINT64_FORMAT | |
| ", FPS rendered %lf", | |
| frames_rendered, frames_produced, fps_rendered); | |
| } | |
| g_mutex_lock(&self->queue_lock); | |
| } else { | |
| g_mutex_unlock(&self->queue_lock); | |
| self->device->device->WaitForOutputVerticalInterrupt(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 | |
| GstAjaNtv2DeviceLocker locker(self->device); | |
| self->device->device->AutoCirculateStop(self->channel); | |
| self->device->device->UnsubscribeOutputVerticalEvent(self->channel); | |
| self->device->device->DisableOutputInterrupt(self->channel); | |
| self->device->device->DisableChannel(self->channel); | |
| if (self->quad_mode) { | |
| for (int i = 1; i < 4; i++) { | |
| self->device->device->DisableChannel((NTV2Channel)(self->channel + i)); | |
| } | |
| } | |
| } | |
| if ((!self->playing || self->draining) && !self->shutdown) goto restart; | |
| g_mutex_unlock(&self->queue_lock); | |
| gst_clear_object(&clock); | |
| GST_DEBUG_OBJECT(self, "Stopped"); | |
| } |