mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 03:31:05 +00:00
d86a6715e1
The global semaphore was never closed/unlinked, causing permission denied issue if the device is later used by another user. Properly removing the semaphore when stopping the pipeline would still leave it open in case of a crash. With a GStreamer specific name, it was also not preventing other apps to access the device concurrently. Finally, if the system has multiple cards, the lock should be per card and not global (to be confirmed). Fixes: #3283. Sponsored-by: Netflix Inc. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6117>
2261 lines
83 KiB
C++
2261 lines
83 KiB
C++
/* GStreamer
|
|
* Copyright (C) 2021 Sebastian Dröge <sebastian@centricular.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
|
* Boston, MA 02110-1335, USA.
|
|
*/
|
|
|
|
/**
|
|
* 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");
|
|
}
|