mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 11:41:09 +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>
2811 lines
104 KiB
C++
2811 lines
104 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) capture cards.
|
|
*
|
|
* ## Example usage
|
|
*
|
|
* Capture 1080p30 audio/video and display it locally
|
|
*
|
|
* ```sh
|
|
* gst-launch-1.0 ajasrc video-format=1080p-3000 ! ajasrcdemux name=d \
|
|
* d.video ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=1000000000 ! videoconvert ! autovideosink \
|
|
* d.audio ! queue max-size-bytes=0 max-size-buffers=0 max-size-time=1000000000 ! audioconvert ! audioresample ! autoaudiosink
|
|
* ```
|
|
*
|
|
* 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 <ajantv2/includes/ntv2vpid.h>
|
|
|
|
#include "gstajacommon.h"
|
|
#include "gstajasrc.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC(gst_aja_src_debug);
|
|
#define GST_CAT_DEFAULT gst_aja_src_debug
|
|
|
|
#define DEFAULT_DEVICE_IDENTIFIER ("0")
|
|
#define DEFAULT_CHANNEL (::NTV2_CHANNEL1)
|
|
#define DEFAULT_VIDEO_FORMAT (GST_AJA_VIDEO_FORMAT_AUTO)
|
|
#define DEFAULT_AUDIO_SYSTEM (GST_AJA_AUDIO_SYSTEM_AUTO)
|
|
#define DEFAULT_INPUT_SOURCE (GST_AJA_INPUT_SOURCE_AUTO)
|
|
#define DEFAULT_SDI_MODE (GST_AJA_SDI_MODE_SINGLE_LINK)
|
|
#define DEFAULT_AUDIO_SOURCE (GST_AJA_AUDIO_SOURCE_EMBEDDED)
|
|
#define DEFAULT_EMBEDDED_AUDIO_INPUT (GST_AJA_EMBEDDED_AUDIO_INPUT_AUTO)
|
|
#define DEFAULT_TIMECODE_INDEX (GST_AJA_TIMECODE_INDEX_VITC)
|
|
#define DEFAULT_RP188 (TRUE)
|
|
#define DEFAULT_REFERENCE_SOURCE (GST_AJA_REFERENCE_SOURCE_FREERUN)
|
|
#define DEFAULT_CLOSED_CAPTION_CAPTURE_MODE \
|
|
(GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA708_AND_CEA608)
|
|
#define DEFAULT_QUEUE_SIZE (16)
|
|
#define DEFAULT_START_FRAME (8)
|
|
#define DEFAULT_END_FRAME (8)
|
|
#define DEFAULT_CAPTURE_CPU_CORE (G_MAXUINT)
|
|
#define DEFAULT_ATTACH_ANCILLARY_META (FALSE)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_DEVICE_IDENTIFIER,
|
|
PROP_CHANNEL,
|
|
PROP_VIDEO_FORMAT,
|
|
PROP_AUDIO_SYSTEM,
|
|
PROP_INPUT_SOURCE,
|
|
PROP_SDI_MODE,
|
|
PROP_AUDIO_SOURCE,
|
|
PROP_EMBEDDED_AUDIO_INPUT,
|
|
PROP_TIMECODE_INDEX,
|
|
PROP_RP188,
|
|
PROP_REFERENCE_SOURCE,
|
|
PROP_CLOSED_CAPTION_CAPTURE_MODE,
|
|
PROP_START_FRAME,
|
|
PROP_END_FRAME,
|
|
PROP_QUEUE_SIZE,
|
|
PROP_CAPTURE_CPU_CORE,
|
|
PROP_SIGNAL,
|
|
PROP_ATTACH_ANCILLARY_META,
|
|
};
|
|
|
|
// Make these plain C structs for usage in GstQueueArray
|
|
G_BEGIN_DECLS
|
|
|
|
typedef enum {
|
|
QUEUE_ITEM_TYPE_DUMMY,
|
|
QUEUE_ITEM_TYPE_FRAME,
|
|
QUEUE_ITEM_TYPE_SIGNAL_CHANGE,
|
|
QUEUE_ITEM_TYPE_ERROR,
|
|
QUEUE_ITEM_TYPE_FRAMES_DROPPED,
|
|
} QueueItemType;
|
|
|
|
typedef struct {
|
|
QueueItemType type;
|
|
|
|
union {
|
|
// For DUMMY
|
|
struct {
|
|
gchar dummy;
|
|
} dummy;
|
|
// For FRAME
|
|
struct {
|
|
GstClockTime capture_time;
|
|
GstBuffer *video_buffer;
|
|
GstBuffer *audio_buffer;
|
|
GstBuffer *anc_buffer, *anc_buffer2;
|
|
NTV2_RP188 tc;
|
|
|
|
NTV2VideoFormat detected_format;
|
|
guint32 vpid;
|
|
} frame;
|
|
// For SIGNAL_CHANGE
|
|
struct {
|
|
gboolean have_signal;
|
|
NTV2VideoFormat detected_format;
|
|
guint32 vpid;
|
|
} signal_change;
|
|
// For ERROR
|
|
struct {
|
|
GstMessage *msg;
|
|
} error;
|
|
// For FRAMES_DROPPED
|
|
struct {
|
|
gboolean driver_side;
|
|
GstClockTime timestamp_start, timestamp_end;
|
|
} frames_dropped;
|
|
};
|
|
} QueueItem;
|
|
|
|
G_END_DECLS
|
|
|
|
static void queue_item_clear(QueueItem *item) {
|
|
switch (item->type) {
|
|
case QUEUE_ITEM_TYPE_DUMMY:
|
|
break;
|
|
case QUEUE_ITEM_TYPE_FRAME:
|
|
gst_clear_buffer(&item->frame.video_buffer);
|
|
gst_clear_buffer(&item->frame.audio_buffer);
|
|
gst_clear_buffer(&item->frame.anc_buffer);
|
|
gst_clear_buffer(&item->frame.anc_buffer2);
|
|
item->frame.tc.~NTV2_RP188();
|
|
break;
|
|
case QUEUE_ITEM_TYPE_SIGNAL_CHANGE:
|
|
break;
|
|
case QUEUE_ITEM_TYPE_ERROR:
|
|
gst_clear_message(&item->error.msg);
|
|
break;
|
|
case QUEUE_ITEM_TYPE_FRAMES_DROPPED:
|
|
break;
|
|
}
|
|
|
|
item->type = QUEUE_ITEM_TYPE_DUMMY;
|
|
}
|
|
|
|
static void gst_aja_src_set_property(GObject *object, guint property_id,
|
|
const GValue *value, GParamSpec *pspec);
|
|
static void gst_aja_src_get_property(GObject *object, guint property_id,
|
|
GValue *value, GParamSpec *pspec);
|
|
static void gst_aja_src_finalize(GObject *object);
|
|
|
|
static GstCaps *gst_aja_src_get_caps(GstBaseSrc *bsrc, GstCaps *filter);
|
|
static gboolean gst_aja_src_query(GstBaseSrc *bsrc, GstQuery *query);
|
|
static gboolean gst_aja_src_unlock(GstBaseSrc *bsrc);
|
|
static gboolean gst_aja_src_unlock_stop(GstBaseSrc *bsrc);
|
|
|
|
static GstFlowReturn gst_aja_src_create(GstPushSrc *psrc, GstBuffer **buffer);
|
|
|
|
static gboolean gst_aja_src_open(GstAjaSrc *src);
|
|
static gboolean gst_aja_src_close(GstAjaSrc *src);
|
|
static gboolean gst_aja_src_stop(GstAjaSrc *src);
|
|
|
|
static GstStateChangeReturn gst_aja_src_change_state(GstElement *element,
|
|
GstStateChange transition);
|
|
|
|
static void capture_thread_func(AJAThread *thread, void *data);
|
|
|
|
#define parent_class gst_aja_src_parent_class
|
|
G_DEFINE_TYPE(GstAjaSrc, gst_aja_src, GST_TYPE_PUSH_SRC);
|
|
|
|
static void gst_aja_src_class_init(GstAjaSrcClass *klass) {
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
|
GstBaseSrcClass *basesrc_class = GST_BASE_SRC_CLASS(klass);
|
|
GstPushSrcClass *pushsrc_class = GST_PUSH_SRC_CLASS(klass);
|
|
GstCaps *templ_caps;
|
|
|
|
gobject_class->set_property = gst_aja_src_set_property;
|
|
gobject_class->get_property = gst_aja_src_get_property;
|
|
gobject_class->finalize = gst_aja_src_finalize;
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_DEVICE_IDENTIFIER,
|
|
g_param_spec_string(
|
|
"device-identifier", "Device identifier",
|
|
"Input device instance to use", DEFAULT_DEVICE_IDENTIFIER,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_CHANNEL,
|
|
g_param_spec_uint(
|
|
"channel", "Channel", "Channel to use", 0, NTV2_MAX_NUM_CHANNELS - 1,
|
|
DEFAULT_CHANNEL,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_VIDEO_FORMAT,
|
|
g_param_spec_enum(
|
|
"video-format", "Video Format", "Video format to use",
|
|
GST_TYPE_AJA_VIDEO_FORMAT, DEFAULT_VIDEO_FORMAT,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_QUEUE_SIZE,
|
|
g_param_spec_uint(
|
|
"queue-size", "Queue Size",
|
|
"Size of internal queue in number of video frames. "
|
|
"Half of this is allocated as device buffers and equal to the "
|
|
"latency.",
|
|
1, G_MAXINT, DEFAULT_QUEUE_SIZE,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_START_FRAME,
|
|
g_param_spec_uint(
|
|
"start-frame", "Start Frame",
|
|
"Start frame buffer to be used for capturing (automatically assign "
|
|
"that many frames 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 capturing (automatically assign "
|
|
"that many frames 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_INPUT_SOURCE,
|
|
g_param_spec_enum(
|
|
"input-source", "Input Source", "Input source to use",
|
|
GST_TYPE_AJA_INPUT_SOURCE, DEFAULT_INPUT_SOURCE,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_SDI_MODE,
|
|
g_param_spec_enum(
|
|
"sdi-input-mode", "SDI Input Mode", "SDI input mode to use",
|
|
GST_TYPE_AJA_SDI_MODE, DEFAULT_SDI_MODE,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_AUDIO_SOURCE,
|
|
g_param_spec_enum(
|
|
"audio-source", "Audio Source", "Audio source to use",
|
|
GST_TYPE_AJA_AUDIO_SOURCE, DEFAULT_AUDIO_SOURCE,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_EMBEDDED_AUDIO_INPUT,
|
|
g_param_spec_enum(
|
|
"embedded-audio-input", "Embedded Audio Input",
|
|
"Embedded Audio Input to use", GST_TYPE_AJA_EMBEDDED_AUDIO_INPUT,
|
|
DEFAULT_EMBEDDED_AUDIO_INPUT,
|
|
(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 retrieval", 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_CLOSED_CAPTION_CAPTURE_MODE,
|
|
g_param_spec_enum(
|
|
"closed-caption-capture-mode", "Closed Caption Capture Mode",
|
|
"Closed Caption Capture Mode",
|
|
GST_TYPE_AJA_CLOSED_CAPTION_CAPTURE_MODE,
|
|
DEFAULT_CLOSED_CAPTION_CAPTURE_MODE,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_CAPTURE_CPU_CORE,
|
|
g_param_spec_uint(
|
|
"capture-cpu-core", "Capture CPU Core",
|
|
"Sets the affinity of the capture thread to this CPU core "
|
|
"(-1=disabled)",
|
|
0, G_MAXUINT, DEFAULT_CAPTURE_CPU_CORE,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_SIGNAL,
|
|
g_param_spec_boolean(
|
|
"signal", "Input signal available",
|
|
"True if there is a valid input signal available", FALSE,
|
|
(GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
|
|
|
|
/**
|
|
* GstAjaSrc:attach-ancillary-meta:
|
|
*
|
|
* If set to %TRUE attach any ancillary data as #GstAncillaryMeta on buffers
|
|
*
|
|
* Since: 1.24
|
|
*/
|
|
g_object_class_install_property(
|
|
gobject_class, PROP_ATTACH_ANCILLARY_META,
|
|
g_param_spec_boolean(
|
|
"attach-ancillary-meta", "Attach Ancillary Meta",
|
|
"Attach ancillary meta to video frames",
|
|
DEFAULT_ATTACH_ANCILLARY_META,
|
|
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
G_PARAM_CONSTRUCT)));
|
|
|
|
element_class->change_state = GST_DEBUG_FUNCPTR(gst_aja_src_change_state);
|
|
|
|
basesrc_class->get_caps = GST_DEBUG_FUNCPTR(gst_aja_src_get_caps);
|
|
basesrc_class->negotiate = NULL;
|
|
basesrc_class->query = GST_DEBUG_FUNCPTR(gst_aja_src_query);
|
|
basesrc_class->unlock = GST_DEBUG_FUNCPTR(gst_aja_src_unlock);
|
|
basesrc_class->unlock_stop = GST_DEBUG_FUNCPTR(gst_aja_src_unlock_stop);
|
|
|
|
pushsrc_class->create = GST_DEBUG_FUNCPTR(gst_aja_src_create);
|
|
|
|
templ_caps = gst_ntv2_supported_caps(DEVICE_ID_INVALID);
|
|
gst_element_class_add_pad_template(
|
|
element_class,
|
|
gst_pad_template_new("src", GST_PAD_SRC, GST_PAD_ALWAYS, templ_caps));
|
|
gst_caps_unref(templ_caps);
|
|
|
|
gst_element_class_set_static_metadata(
|
|
element_class, "AJA audio/video src", "Audio/Video/Source",
|
|
"Captures audio/video frames with AJA devices",
|
|
"Sebastian Dröge <sebastian@centricular.com>");
|
|
|
|
GST_DEBUG_CATEGORY_INIT(gst_aja_src_debug, "ajasrc", 0, "AJA src");
|
|
}
|
|
|
|
static void gst_aja_src_init(GstAjaSrc *self) {
|
|
g_mutex_init(&self->queue_lock);
|
|
g_cond_init(&self->queue_cond);
|
|
|
|
self->device_identifier = g_strdup(DEFAULT_DEVICE_IDENTIFIER);
|
|
self->channel = DEFAULT_CHANNEL;
|
|
self->queue_size = DEFAULT_QUEUE_SIZE;
|
|
self->start_frame = DEFAULT_START_FRAME;
|
|
self->end_frame = DEFAULT_END_FRAME;
|
|
self->video_format_setting = DEFAULT_VIDEO_FORMAT;
|
|
self->audio_system_setting = DEFAULT_AUDIO_SYSTEM;
|
|
self->input_source = DEFAULT_INPUT_SOURCE;
|
|
self->audio_source = DEFAULT_AUDIO_SOURCE;
|
|
self->embedded_audio_input = DEFAULT_EMBEDDED_AUDIO_INPUT;
|
|
self->timecode_index = DEFAULT_TIMECODE_INDEX;
|
|
self->reference_source = DEFAULT_REFERENCE_SOURCE;
|
|
self->closed_caption_capture_mode = DEFAULT_CLOSED_CAPTION_CAPTURE_MODE;
|
|
self->capture_cpu_core = DEFAULT_CAPTURE_CPU_CORE;
|
|
self->attach_ancillary_meta = DEFAULT_ATTACH_ANCILLARY_META;
|
|
|
|
self->queue =
|
|
gst_queue_array_new_for_struct(sizeof(QueueItem), self->queue_size);
|
|
gst_base_src_set_live(GST_BASE_SRC_CAST(self), TRUE);
|
|
gst_base_src_set_format(GST_BASE_SRC_CAST(self), GST_FORMAT_TIME);
|
|
|
|
self->video_format = NTV2_FORMAT_UNKNOWN;
|
|
}
|
|
|
|
void gst_aja_src_set_property(GObject *object, guint property_id,
|
|
const GValue *value, GParamSpec *pspec) {
|
|
GstAjaSrc *self = GST_AJA_SRC(object);
|
|
|
|
switch (property_id) {
|
|
case PROP_DEVICE_IDENTIFIER:
|
|
g_free(self->device_identifier);
|
|
self->device_identifier = g_value_dup_string(value);
|
|
break;
|
|
case PROP_CHANNEL:
|
|
self->channel = (NTV2Channel)g_value_get_uint(value);
|
|
break;
|
|
case PROP_QUEUE_SIZE:
|
|
self->queue_size = g_value_get_uint(value);
|
|
break;
|
|
case PROP_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_VIDEO_FORMAT:
|
|
self->video_format_setting = (GstAjaVideoFormat)g_value_get_enum(value);
|
|
break;
|
|
case PROP_AUDIO_SYSTEM:
|
|
self->audio_system_setting = (GstAjaAudioSystem)g_value_get_enum(value);
|
|
break;
|
|
case PROP_INPUT_SOURCE:
|
|
self->input_source = (GstAjaInputSource)g_value_get_enum(value);
|
|
break;
|
|
case PROP_SDI_MODE:
|
|
self->sdi_mode = (GstAjaSdiMode)g_value_get_enum(value);
|
|
break;
|
|
case PROP_AUDIO_SOURCE:
|
|
self->audio_source = (GstAjaAudioSource)g_value_get_enum(value);
|
|
break;
|
|
case PROP_EMBEDDED_AUDIO_INPUT:
|
|
self->embedded_audio_input =
|
|
(GstAjaEmbeddedAudioInput)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_CLOSED_CAPTION_CAPTURE_MODE:
|
|
self->closed_caption_capture_mode =
|
|
(GstAjaClosedCaptionCaptureMode)g_value_get_enum(value);
|
|
break;
|
|
case PROP_CAPTURE_CPU_CORE:
|
|
self->capture_cpu_core = g_value_get_uint(value);
|
|
break;
|
|
case PROP_ATTACH_ANCILLARY_META:
|
|
self->attach_ancillary_meta = g_value_get_boolean(value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void gst_aja_src_get_property(GObject *object, guint property_id, GValue *value,
|
|
GParamSpec *pspec) {
|
|
GstAjaSrc *self = GST_AJA_SRC(object);
|
|
|
|
switch (property_id) {
|
|
case PROP_DEVICE_IDENTIFIER:
|
|
g_value_set_string(value, self->device_identifier);
|
|
break;
|
|
case PROP_CHANNEL:
|
|
g_value_set_uint(value, self->channel);
|
|
break;
|
|
case PROP_QUEUE_SIZE:
|
|
g_value_set_uint(value, self->queue_size);
|
|
break;
|
|
case PROP_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_VIDEO_FORMAT:
|
|
g_value_set_enum(value, self->video_format_setting);
|
|
break;
|
|
case PROP_AUDIO_SYSTEM:
|
|
g_value_set_enum(value, self->audio_system_setting);
|
|
break;
|
|
case PROP_INPUT_SOURCE:
|
|
g_value_set_enum(value, self->input_source);
|
|
break;
|
|
case PROP_SDI_MODE:
|
|
g_value_set_enum(value, self->sdi_mode);
|
|
break;
|
|
case PROP_AUDIO_SOURCE:
|
|
g_value_set_enum(value, self->audio_source);
|
|
break;
|
|
case PROP_EMBEDDED_AUDIO_INPUT:
|
|
g_value_set_enum(value, self->embedded_audio_input);
|
|
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_CLOSED_CAPTION_CAPTURE_MODE:
|
|
g_value_set_enum(value, self->closed_caption_capture_mode);
|
|
break;
|
|
case PROP_CAPTURE_CPU_CORE:
|
|
g_value_set_uint(value, self->capture_cpu_core);
|
|
break;
|
|
case PROP_SIGNAL:
|
|
g_value_set_boolean(value, self->signal);
|
|
break;
|
|
case PROP_ATTACH_ANCILLARY_META:
|
|
g_value_set_boolean(value, self->attach_ancillary_meta);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void gst_aja_src_finalize(GObject *object) {
|
|
GstAjaSrc *self = GST_AJA_SRC(object);
|
|
|
|
g_assert(self->device == NULL);
|
|
g_assert(gst_queue_array_get_length(self->queue) == 0);
|
|
g_clear_pointer(&self->queue, gst_queue_array_free);
|
|
|
|
g_mutex_clear(&self->queue_lock);
|
|
g_cond_clear(&self->queue_cond);
|
|
|
|
G_OBJECT_CLASS(parent_class)->finalize(object);
|
|
}
|
|
|
|
static gboolean gst_aja_src_open(GstAjaSrc *self) {
|
|
GST_DEBUG_OBJECT(self, "Opening device");
|
|
|
|
g_assert(self->device == NULL);
|
|
|
|
self->device = gst_aja_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_src_close(GstAjaSrc *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;
|
|
}
|
|
|
|
// Must be called with GstAjaNtv2DeviceLocker
|
|
static gboolean gst_aja_src_configure(GstAjaSrc *self) {
|
|
GST_DEBUG_OBJECT(self, "Starting");
|
|
|
|
#define NEEDS_QUAD_MODE(self) \
|
|
(self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_SQD || \
|
|
self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_TSI || \
|
|
(self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 && \
|
|
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4))
|
|
|
|
self->quad_mode = NEEDS_QUAD_MODE(self);
|
|
|
|
#undef NEEDS_QUAD_MODE
|
|
|
|
if (self->quad_mode) {
|
|
if (self->input_source != GST_AJA_INPUT_SOURCE_AUTO &&
|
|
!(self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
|
|
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4)) {
|
|
GST_ERROR_OBJECT(
|
|
self,
|
|
"Quad modes require usage of the channel's default input source");
|
|
return FALSE;
|
|
}
|
|
|
|
if (self->channel != ::NTV2_CHANNEL1 && self->channel != ::NTV2_CHANNEL5) {
|
|
GST_ERROR_OBJECT(self, "Quad modes require channels 1 or 5");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
bool had_quad_enabled = false, had_quad_quad_enabled = false;
|
|
|
|
// HDMI can also be internally quad mode but it runs on a single channel.
|
|
if (!(self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
|
|
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4)) {
|
|
if (self->channel < ::NTV2_CHANNEL5) {
|
|
self->device->device->GetQuadFrameEnable(had_quad_enabled,
|
|
::NTV2_CHANNEL1);
|
|
|
|
// 12G UHD is also internally considered quad modes but they run on a
|
|
// single channel.
|
|
if (had_quad_enabled && ::NTV2DeviceCanDo12gRouting(self->device_id)) {
|
|
NTV2VideoFormat fmt =
|
|
self->device->device->GetInputVideoFormat(::NTV2_INPUTSOURCE_SDI1);
|
|
if (fmt >= NTV2_FORMAT_FIRST_UHD_TSI_DEF_FORMAT &&
|
|
fmt < NTV2_FORMAT_END_4K_TSI_DEF_FORMATS)
|
|
had_quad_enabled = false;
|
|
}
|
|
|
|
self->device->device->GetQuadQuadFrameEnable(had_quad_quad_enabled,
|
|
::NTV2_CHANNEL1);
|
|
} else {
|
|
self->device->device->GetQuadFrameEnable(had_quad_enabled,
|
|
::NTV2_CHANNEL5);
|
|
|
|
// 12G UHD is also internally considered quad modes but they run on a
|
|
// single channel.
|
|
if (had_quad_enabled && ::NTV2DeviceCanDo12gRouting(self->device_id)) {
|
|
NTV2VideoFormat fmt =
|
|
self->device->device->GetInputVideoFormat(::NTV2_INPUTSOURCE_SDI5);
|
|
if (fmt >= NTV2_FORMAT_FIRST_UHD_TSI_DEF_FORMAT &&
|
|
fmt < NTV2_FORMAT_END_4K_TSI_DEF_FORMATS)
|
|
had_quad_enabled = false;
|
|
}
|
|
|
|
self->device->device->GetQuadQuadFrameEnable(had_quad_quad_enabled,
|
|
::NTV2_CHANNEL5);
|
|
}
|
|
}
|
|
|
|
// Stop any previously running quad mode, or other configurations on the
|
|
// quad channels
|
|
self->device->device->AutoCirculateStop(self->channel);
|
|
if (self->quad_mode || had_quad_enabled || had_quad_enabled) {
|
|
NTV2Channel quad_channel;
|
|
|
|
if (self->channel < ::NTV2_CHANNEL5)
|
|
quad_channel = ::NTV2_CHANNEL1;
|
|
else
|
|
quad_channel = ::NTV2_CHANNEL5;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
self->device->device->AutoCirculateStop((NTV2Channel)(quad_channel + i));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
NTV2VANCMode vanc_mode;
|
|
NTV2InputSource input_source;
|
|
NTV2OutputCrosspointID input_source_id;
|
|
switch (self->input_source) {
|
|
case GST_AJA_INPUT_SOURCE_AUTO:
|
|
input_source = ::NTV2ChannelToInputSource(self->channel);
|
|
input_source_id = ::GetSDIInputOutputXptFromChannel(self->channel, false);
|
|
vanc_mode = ::NTV2DeviceCanDoCustomAnc(self->device_id)
|
|
? ::NTV2_VANCMODE_OFF
|
|
: ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_ANALOG1:
|
|
input_source = ::NTV2_INPUTSOURCE_ANALOG1;
|
|
input_source_id = ::NTV2_XptAnalogIn;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_HDMI1:
|
|
input_source = ::NTV2_INPUTSOURCE_HDMI1;
|
|
input_source_id = ::NTV2_XptHDMIIn1;
|
|
vanc_mode = ::NTV2_VANCMODE_OFF;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_HDMI2:
|
|
input_source = ::NTV2_INPUTSOURCE_HDMI2;
|
|
input_source_id = ::NTV2_XptHDMIIn2;
|
|
vanc_mode = ::NTV2_VANCMODE_OFF;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_HDMI3:
|
|
input_source = ::NTV2_INPUTSOURCE_HDMI3;
|
|
input_source_id = ::NTV2_XptHDMIIn3;
|
|
vanc_mode = ::NTV2_VANCMODE_OFF;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_HDMI4:
|
|
input_source = ::NTV2_INPUTSOURCE_HDMI4;
|
|
input_source_id = ::NTV2_XptHDMIIn4;
|
|
vanc_mode = ::NTV2_VANCMODE_OFF;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI1:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI1;
|
|
input_source_id = ::NTV2_XptSDIIn1;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI2:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI2;
|
|
input_source_id = ::NTV2_XptSDIIn2;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI3:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI3;
|
|
input_source_id = ::NTV2_XptSDIIn3;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI4:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI4;
|
|
input_source_id = ::NTV2_XptSDIIn4;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI5:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI5;
|
|
input_source_id = ::NTV2_XptSDIIn5;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI6:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI6;
|
|
input_source_id = ::NTV2_XptSDIIn6;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI7:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI7;
|
|
input_source_id = ::NTV2_XptSDIIn7;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
case GST_AJA_INPUT_SOURCE_SDI8:
|
|
input_source = ::NTV2_INPUTSOURCE_SDI8;
|
|
input_source_id = ::NTV2_XptSDIIn8;
|
|
vanc_mode = ::NTV2_VANCMODE_TALL;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
self->configured_input_source = input_source;
|
|
|
|
self->vanc_mode = vanc_mode;
|
|
|
|
if (!self->device->device->EnableChannel(self->channel)) {
|
|
GST_ERROR_OBJECT(self, "Failed to enable channel");
|
|
return FALSE;
|
|
}
|
|
|
|
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");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
self->device->device->EnableInputInterrupt(self->channel);
|
|
self->device->device->SubscribeInputVerticalEvent(self->channel);
|
|
|
|
if (self->video_format_setting == GST_AJA_VIDEO_FORMAT_AUTO) {
|
|
self->device->device->WaitForInputVerticalInterrupt(self->channel, 10);
|
|
self->video_format = self->device->device->GetInputVideoFormat(
|
|
self->configured_input_source);
|
|
if (self->video_format == NTV2_FORMAT_UNKNOWN) {
|
|
GST_ERROR_OBJECT(self, "Input video format not detected");
|
|
return TRUE;
|
|
}
|
|
std::string configured_string = NTV2VideoFormatToString(self->video_format);
|
|
GST_DEBUG_OBJECT(self, "Detected input video format %s (%d)",
|
|
configured_string.c_str(), (int)self->video_format);
|
|
} else {
|
|
self->video_format = gst_ntv2_video_format_from_aja_format(
|
|
self->video_format_setting, self->quad_mode);
|
|
}
|
|
|
|
if (self->video_format == NTV2_FORMAT_UNKNOWN) {
|
|
GST_ERROR_OBJECT(self, "Unsupported mode");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!::NTV2DeviceCanDoVideoFormat(self->device_id, self->video_format)) {
|
|
GST_ERROR_OBJECT(self, "Device does not support mode %d",
|
|
(int)self->video_format);
|
|
return FALSE;
|
|
}
|
|
|
|
gst_video_info_from_ntv2_video_format(&self->configured_info,
|
|
self->video_format);
|
|
|
|
if (self->quad_mode) {
|
|
if (self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
|
|
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
|
|
self->device->device->SetQuadQuadFrameEnable(false, self->channel);
|
|
self->device->device->SetQuadQuadSquaresEnable(false, self->channel);
|
|
self->device->device->Set4kSquaresEnable(true, self->channel);
|
|
self->device->device->SetTsiFrameEnable(true, self->channel);
|
|
} else {
|
|
switch (self->sdi_mode) {
|
|
case GST_AJA_SDI_MODE_SINGLE_LINK:
|
|
g_assert_not_reached();
|
|
break;
|
|
case GST_AJA_SDI_MODE_QUAD_LINK_SQD:
|
|
if (self->configured_info.height > 2160) {
|
|
self->device->device->Set4kSquaresEnable(false, self->channel);
|
|
self->device->device->SetTsiFrameEnable(false, self->channel);
|
|
self->device->device->SetQuadQuadFrameEnable(true, self->channel);
|
|
self->device->device->SetQuadQuadSquaresEnable(true, self->channel);
|
|
} else {
|
|
self->device->device->SetQuadQuadFrameEnable(false, self->channel);
|
|
self->device->device->SetQuadQuadSquaresEnable(false,
|
|
self->channel);
|
|
self->device->device->Set4kSquaresEnable(true, self->channel);
|
|
self->device->device->SetTsiFrameEnable(false, self->channel);
|
|
}
|
|
break;
|
|
case GST_AJA_SDI_MODE_QUAD_LINK_TSI:
|
|
if (self->configured_info.height > 2160) {
|
|
self->device->device->Set4kSquaresEnable(false, self->channel);
|
|
self->device->device->SetTsiFrameEnable(false, self->channel);
|
|
self->device->device->SetQuadQuadFrameEnable(true, self->channel);
|
|
self->device->device->SetQuadQuadSquaresEnable(false,
|
|
self->channel);
|
|
} else {
|
|
self->device->device->SetQuadQuadFrameEnable(false, self->channel);
|
|
self->device->device->SetQuadQuadSquaresEnable(false,
|
|
self->channel);
|
|
self->device->device->Set4kSquaresEnable(false, self->channel);
|
|
self->device->device->SetTsiFrameEnable(true, self->channel);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else if (had_quad_enabled || had_quad_quad_enabled) {
|
|
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);
|
|
}
|
|
|
|
self->device->device->SetMode(self->channel, NTV2_MODE_CAPTURE, false);
|
|
if (self->quad_mode) {
|
|
for (int i = 1; i < 4; i++)
|
|
self->device->device->SetMode((NTV2Channel)(self->channel + i),
|
|
NTV2_MODE_CAPTURE, false);
|
|
}
|
|
|
|
std::string configured_string = NTV2VideoFormatToString(self->video_format);
|
|
GST_DEBUG_OBJECT(self, "Configuring video format %s (%d) on channel %d",
|
|
configured_string.c_str(), (int)self->video_format,
|
|
(int)self->channel);
|
|
if (!self->device->device->SetVideoFormat(self->video_format, false, false,
|
|
self->channel)) {
|
|
GST_DEBUG_OBJECT(
|
|
self, "Failed configuring video format %s (%d) on channel %d",
|
|
configured_string.c_str(), (int)self->video_format, (int)self->channel);
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!self->device->device->SetFrameBufferFormat(self->channel,
|
|
::NTV2_FBF_10BIT_YCBCR)) {
|
|
GST_ERROR_OBJECT(self, "Failed configuring frame buffer format %d",
|
|
(int)::NTV2_FBF_10BIT_YCBCR);
|
|
return FALSE;
|
|
}
|
|
|
|
// FIXME: Workaround for sometimes setting the video format not actually
|
|
// changing the register values. Let's just try again.
|
|
{
|
|
NTV2VideoFormat fmt;
|
|
self->device->device->GetVideoFormat(fmt, self->channel);
|
|
|
|
if (fmt != self->video_format) {
|
|
std::string actual_string = NTV2VideoFormatToString(fmt);
|
|
|
|
GST_ERROR_OBJECT(self,
|
|
"Configured video format %s (%d) on channel %d but %s "
|
|
"(%d) is configured instead, trying again",
|
|
configured_string.c_str(), (int)self->video_format,
|
|
(int)self->channel, actual_string.c_str(), (int)fmt);
|
|
self->video_format = ::NTV2_FORMAT_UNKNOWN;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (self->quad_mode) {
|
|
for (int i = 1; i < 4; i++)
|
|
self->device->device->SetFrameBufferFormat(
|
|
(NTV2Channel)(self->channel + i), ::NTV2_FBF_10BIT_YCBCR);
|
|
}
|
|
|
|
self->device->device->DMABufferAutoLock(false, true, 0);
|
|
|
|
if (::NTV2DeviceHasBiDirectionalSDI(self->device_id)) {
|
|
self->device->device->SetSDITransmitEnable(self->channel, false);
|
|
if (self->quad_mode) {
|
|
for (int i = 1; i < 4; i++)
|
|
self->device->device->SetSDITransmitEnable(
|
|
(NTV2Channel)(self->channel + i), false);
|
|
}
|
|
}
|
|
|
|
// Always use the framebuffer associated with the channel
|
|
NTV2InputCrosspointID framebuffer_id =
|
|
::GetFrameBufferInputXptFromChannel(self->channel, false);
|
|
|
|
const NTV2Standard standard(
|
|
::GetNTV2StandardFromVideoFormat(self->video_format));
|
|
self->device->device->SetStandard(standard, self->channel);
|
|
if (self->quad_mode) {
|
|
for (int i = 1; i < 4; i++)
|
|
self->device->device->SetStandard(standard,
|
|
(NTV2Channel)(self->channel + i));
|
|
}
|
|
|
|
const NTV2FrameGeometry geometry =
|
|
::GetNTV2FrameGeometryFromVideoFormat(self->video_format);
|
|
|
|
self->vanc_mode =
|
|
::HasVANCGeometries(geometry) ? vanc_mode : ::NTV2_VANCMODE_OFF;
|
|
if (self->vanc_mode == ::NTV2_VANCMODE_OFF) {
|
|
self->device->device->SetFrameGeometry(geometry, false, self->channel);
|
|
|
|
if (self->quad_mode) {
|
|
for (int i = 1; i < 4; i++) {
|
|
self->device->device->SetFrameGeometry(
|
|
geometry, false, (NTV2Channel)(self->channel + i));
|
|
}
|
|
}
|
|
} else {
|
|
const NTV2FrameGeometry vanc_geometry =
|
|
::GetVANCFrameGeometry(geometry, self->vanc_mode);
|
|
|
|
self->device->device->SetFrameGeometry(vanc_geometry, false, self->channel);
|
|
|
|
if (self->quad_mode) {
|
|
for (int i = 1; i < 4; i++) {
|
|
self->device->device->SetFrameGeometry(
|
|
vanc_geometry, false, (NTV2Channel)(self->channel + i));
|
|
}
|
|
}
|
|
}
|
|
|
|
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->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
|
|
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
|
|
// Need to disconnect the 4 inputs corresponding to this channel from
|
|
// their framebuffers/muxers, and muxers from their framebuffers
|
|
for (auto iter = connections.begin(); iter != connections.end(); iter++) {
|
|
if (iter->first == NTV2_XptFrameBuffer1Input ||
|
|
iter->first == NTV2_XptFrameBuffer1BInput ||
|
|
iter->first == NTV2_XptFrameBuffer2Input ||
|
|
iter->first == NTV2_XptFrameBuffer2BInput ||
|
|
iter->second == NTV2_Xpt425Mux1AYUV ||
|
|
iter->second == NTV2_Xpt425Mux1BYUV ||
|
|
iter->second == NTV2_Xpt425Mux2AYUV ||
|
|
iter->second == NTV2_Xpt425Mux2BYUV ||
|
|
iter->first == NTV2_Xpt425Mux1AInput ||
|
|
iter->first == NTV2_Xpt425Mux1BInput ||
|
|
iter->first == NTV2_Xpt425Mux2AInput ||
|
|
iter->first == NTV2_Xpt425Mux2BInput ||
|
|
iter->second == NTV2_XptHDMIIn1 ||
|
|
iter->second == NTV2_XptHDMIIn1Q2 ||
|
|
iter->second == NTV2_XptHDMIIn1Q3 ||
|
|
iter->second == NTV2_XptHDMIIn1Q4)
|
|
router.RemoveConnection(iter->first, iter->second);
|
|
}
|
|
} else if (self->channel == NTV2_CHANNEL1) {
|
|
for (auto iter = connections.begin(); iter != connections.end(); iter++) {
|
|
if (iter->first == NTV2_XptFrameBuffer1Input ||
|
|
iter->first == NTV2_XptFrameBuffer1BInput ||
|
|
iter->first == NTV2_XptFrameBuffer1DS2Input ||
|
|
iter->first == NTV2_XptFrameBuffer2Input ||
|
|
iter->first == NTV2_XptFrameBuffer2BInput ||
|
|
iter->first == NTV2_XptFrameBuffer2DS2Input ||
|
|
iter->second == NTV2_Xpt425Mux1AYUV ||
|
|
iter->second == NTV2_Xpt425Mux1BYUV ||
|
|
iter->second == NTV2_Xpt425Mux2AYUV ||
|
|
iter->second == NTV2_Xpt425Mux2BYUV ||
|
|
iter->first == NTV2_Xpt425Mux1AInput ||
|
|
iter->first == NTV2_Xpt425Mux1BInput ||
|
|
iter->first == NTV2_Xpt425Mux2AInput ||
|
|
iter->first == NTV2_Xpt425Mux2BInput ||
|
|
iter->second == NTV2_XptSDIIn1 || iter->second == NTV2_XptSDIIn2 ||
|
|
iter->second == NTV2_XptSDIIn3 || iter->second == NTV2_XptSDIIn4 ||
|
|
iter->second == NTV2_XptSDIIn1DS2 ||
|
|
iter->second == NTV2_XptSDIIn2DS2 ||
|
|
iter->first == NTV2_XptFrameBuffer1Input ||
|
|
iter->first == NTV2_XptFrameBuffer2Input ||
|
|
iter->first == NTV2_XptFrameBuffer3Input ||
|
|
iter->first == NTV2_XptFrameBuffer4Input)
|
|
router.RemoveConnection(iter->first, iter->second);
|
|
}
|
|
} else if (self->channel == NTV2_CHANNEL5) {
|
|
for (auto iter = connections.begin(); iter != connections.end(); iter++) {
|
|
if (iter->first == NTV2_XptFrameBuffer5Input ||
|
|
iter->first == NTV2_XptFrameBuffer5BInput ||
|
|
iter->first == NTV2_XptFrameBuffer5DS2Input ||
|
|
iter->first == NTV2_XptFrameBuffer6Input ||
|
|
iter->first == NTV2_XptFrameBuffer6BInput ||
|
|
iter->first == NTV2_XptFrameBuffer6DS2Input ||
|
|
iter->second == NTV2_Xpt425Mux3AYUV ||
|
|
iter->second == NTV2_Xpt425Mux3BYUV ||
|
|
iter->second == NTV2_Xpt425Mux4AYUV ||
|
|
iter->second == NTV2_Xpt425Mux4BYUV ||
|
|
iter->first == NTV2_Xpt425Mux3AInput ||
|
|
iter->first == NTV2_Xpt425Mux3BInput ||
|
|
iter->first == NTV2_Xpt425Mux4AInput ||
|
|
iter->first == NTV2_Xpt425Mux4BInput ||
|
|
iter->second == NTV2_XptSDIIn5 || iter->second == NTV2_XptSDIIn6 ||
|
|
iter->second == NTV2_XptSDIIn7 || iter->second == NTV2_XptSDIIn8 ||
|
|
iter->second == NTV2_XptSDIIn5DS2 ||
|
|
iter->second == NTV2_XptSDIIn6DS2 ||
|
|
iter->first == NTV2_XptFrameBuffer5Input ||
|
|
iter->first == NTV2_XptFrameBuffer6Input ||
|
|
iter->first == NTV2_XptFrameBuffer7Input ||
|
|
iter->first == NTV2_XptFrameBuffer8Input)
|
|
router.RemoveConnection(iter->first, iter->second);
|
|
}
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
} else {
|
|
// This also removes all connections for any previous quad mode on the
|
|
// corresponding channels.
|
|
|
|
NTV2OutputCrosspointID quad_input_source_ids[10];
|
|
|
|
if (input_source_id == NTV2_XptSDIIn1 ||
|
|
input_source_id == NTV2_XptSDIIn2 ||
|
|
input_source_id == NTV2_XptSDIIn3 ||
|
|
input_source_id == NTV2_XptSDIIn4) {
|
|
if (had_quad_enabled || had_quad_quad_enabled) {
|
|
quad_input_source_ids[0] = NTV2_XptSDIIn1;
|
|
quad_input_source_ids[1] = NTV2_XptSDIIn2;
|
|
quad_input_source_ids[2] = NTV2_XptSDIIn3;
|
|
quad_input_source_ids[3] = NTV2_XptSDIIn4;
|
|
quad_input_source_ids[4] = NTV2_XptSDIIn1DS2;
|
|
quad_input_source_ids[5] = NTV2_XptSDIIn2DS2;
|
|
quad_input_source_ids[6] = NTV2_Xpt425Mux1AYUV;
|
|
quad_input_source_ids[7] = NTV2_Xpt425Mux1BYUV;
|
|
quad_input_source_ids[8] = NTV2_Xpt425Mux2AYUV;
|
|
quad_input_source_ids[9] = NTV2_Xpt425Mux2BYUV;
|
|
}
|
|
} else if (input_source_id == NTV2_XptSDIIn5 ||
|
|
input_source_id == NTV2_XptSDIIn6 ||
|
|
input_source_id == NTV2_XptSDIIn7 ||
|
|
input_source_id == NTV2_XptSDIIn8) {
|
|
if (had_quad_enabled || had_quad_quad_enabled) {
|
|
quad_input_source_ids[0] = NTV2_XptSDIIn5;
|
|
quad_input_source_ids[1] = NTV2_XptSDIIn6;
|
|
quad_input_source_ids[2] = NTV2_XptSDIIn7;
|
|
quad_input_source_ids[3] = NTV2_XptSDIIn8;
|
|
quad_input_source_ids[4] = NTV2_XptSDIIn5DS2;
|
|
quad_input_source_ids[5] = NTV2_XptSDIIn6DS2;
|
|
quad_input_source_ids[6] = NTV2_Xpt425Mux3AYUV;
|
|
quad_input_source_ids[7] = NTV2_Xpt425Mux3BYUV;
|
|
quad_input_source_ids[8] = NTV2_Xpt425Mux4AYUV;
|
|
quad_input_source_ids[9] = NTV2_Xpt425Mux4BYUV;
|
|
}
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
for (auto iter = connections.begin(); iter != connections.end(); iter++) {
|
|
if (had_quad_enabled || had_quad_quad_enabled) {
|
|
for (auto quad_input_source_id : quad_input_source_ids) {
|
|
if (iter->second == quad_input_source_id)
|
|
router.RemoveConnection(iter->first, iter->second);
|
|
}
|
|
} else {
|
|
if (iter->first == framebuffer_id || iter->second == input_source_id)
|
|
router.RemoveConnection(iter->first, iter->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->quad_mode) {
|
|
if (self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
|
|
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
|
|
input_source_id = NTV2_Xpt425Mux1AYUV;
|
|
} else if (self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_TSI &&
|
|
!NTV2_IS_QUAD_QUAD_HFR_VIDEO_FORMAT(self->video_format) &&
|
|
!NTV2_IS_QUAD_QUAD_FORMAT(self->video_format)) {
|
|
if (self->channel == NTV2_CHANNEL1)
|
|
input_source_id = NTV2_Xpt425Mux1AYUV;
|
|
else if (self->channel == NTV2_CHANNEL5)
|
|
input_source_id = NTV2_Xpt425Mux3AYUV;
|
|
else
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT(self, "Creating connection %d - %d", framebuffer_id,
|
|
input_source_id);
|
|
router.AddConnection(framebuffer_id, input_source_id);
|
|
|
|
if (self->quad_mode) {
|
|
if (self->input_source >= GST_AJA_INPUT_SOURCE_HDMI1 &&
|
|
self->input_source <= GST_AJA_INPUT_SOURCE_HDMI4) {
|
|
router.AddConnection(NTV2_XptFrameBuffer1BInput, NTV2_Xpt425Mux1BYUV);
|
|
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_Xpt425Mux2AYUV);
|
|
router.AddConnection(NTV2_XptFrameBuffer2BInput, NTV2_Xpt425Mux2BYUV);
|
|
|
|
router.AddConnection(NTV2_Xpt425Mux1AInput, NTV2_XptHDMIIn1);
|
|
router.AddConnection(NTV2_Xpt425Mux1BInput, NTV2_XptHDMIIn1Q2);
|
|
router.AddConnection(NTV2_Xpt425Mux2AInput, NTV2_XptHDMIIn1Q3);
|
|
router.AddConnection(NTV2_Xpt425Mux2BInput, NTV2_XptHDMIIn1Q4);
|
|
} else {
|
|
if (self->sdi_mode == GST_AJA_SDI_MODE_QUAD_LINK_TSI) {
|
|
if (NTV2_IS_QUAD_QUAD_HFR_VIDEO_FORMAT(self->video_format)) {
|
|
if (self->channel == NTV2_CHANNEL1) {
|
|
router.AddConnection(NTV2_XptFrameBuffer1DS2Input, NTV2_XptSDIIn2);
|
|
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_XptSDIIn3);
|
|
router.AddConnection(NTV2_XptFrameBuffer2DS2Input, NTV2_XptSDIIn4);
|
|
} else if (self->channel == NTV2_CHANNEL5) {
|
|
router.AddConnection(NTV2_XptFrameBuffer5DS2Input, NTV2_XptSDIIn6);
|
|
router.AddConnection(NTV2_XptFrameBuffer5Input, NTV2_XptSDIIn7);
|
|
router.AddConnection(NTV2_XptFrameBuffer6DS2Input, NTV2_XptSDIIn8);
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
} else if (NTV2_IS_QUAD_QUAD_FORMAT(self->video_format)) {
|
|
if (self->channel == NTV2_CHANNEL1) {
|
|
router.AddConnection(NTV2_XptFrameBuffer1DS2Input,
|
|
NTV2_XptSDIIn1DS2);
|
|
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_XptSDIIn2);
|
|
router.AddConnection(NTV2_XptFrameBuffer2DS2Input,
|
|
NTV2_XptSDIIn2DS2);
|
|
} else if (self->channel == NTV2_CHANNEL5) {
|
|
router.AddConnection(NTV2_XptFrameBuffer5DS2Input,
|
|
NTV2_XptSDIIn5DS2);
|
|
router.AddConnection(NTV2_XptFrameBuffer5Input, NTV2_XptSDIIn6);
|
|
router.AddConnection(NTV2_XptFrameBuffer6DS2Input,
|
|
NTV2_XptSDIIn6DS2);
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
// FIXME: Need special handling of NTV2_IS_4K_HFR_VIDEO_FORMAT for
|
|
// TSI?
|
|
} else {
|
|
if (self->channel == NTV2_CHANNEL1) {
|
|
router.AddConnection(NTV2_XptFrameBuffer1BInput,
|
|
NTV2_Xpt425Mux1BYUV);
|
|
router.AddConnection(NTV2_XptFrameBuffer2Input,
|
|
NTV2_Xpt425Mux2AYUV);
|
|
router.AddConnection(NTV2_XptFrameBuffer2BInput,
|
|
NTV2_Xpt425Mux2BYUV);
|
|
|
|
router.AddConnection(NTV2_Xpt425Mux1AInput, NTV2_XptSDIIn1);
|
|
router.AddConnection(NTV2_Xpt425Mux1BInput, NTV2_XptSDIIn2);
|
|
router.AddConnection(NTV2_Xpt425Mux2AInput, NTV2_XptSDIIn3);
|
|
router.AddConnection(NTV2_Xpt425Mux2BInput, NTV2_XptSDIIn4);
|
|
} else if (self->channel == NTV2_CHANNEL5) {
|
|
router.AddConnection(NTV2_XptFrameBuffer5BInput,
|
|
NTV2_Xpt425Mux3BYUV);
|
|
router.AddConnection(NTV2_XptFrameBuffer6Input,
|
|
NTV2_Xpt425Mux4AYUV);
|
|
router.AddConnection(NTV2_XptFrameBuffer6BInput,
|
|
NTV2_Xpt425Mux4BYUV);
|
|
|
|
router.AddConnection(NTV2_Xpt425Mux3AInput, NTV2_XptSDIIn5);
|
|
router.AddConnection(NTV2_Xpt425Mux3BInput, NTV2_XptSDIIn6);
|
|
router.AddConnection(NTV2_Xpt425Mux4AInput, NTV2_XptSDIIn7);
|
|
router.AddConnection(NTV2_Xpt425Mux4BInput, NTV2_XptSDIIn8);
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
} else {
|
|
if (self->channel == NTV2_CHANNEL1) {
|
|
router.AddConnection(NTV2_XptFrameBuffer2Input, NTV2_XptSDIIn2);
|
|
router.AddConnection(NTV2_XptFrameBuffer3Input, NTV2_XptSDIIn3);
|
|
router.AddConnection(NTV2_XptFrameBuffer4Input, NTV2_XptSDIIn4);
|
|
} else if (self->channel == NTV2_CHANNEL5) {
|
|
router.AddConnection(NTV2_XptFrameBuffer6Input, NTV2_XptSDIIn6);
|
|
router.AddConnection(NTV2_XptFrameBuffer7Input, NTV2_XptSDIIn7);
|
|
router.AddConnection(NTV2_XptFrameBuffer8Input, NTV2_XptSDIIn8);
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
std::stringstream os;
|
|
CNTV2SignalRouter oldRouter;
|
|
self->device->device->GetRouting(oldRouter);
|
|
oldRouter.Print(os);
|
|
GST_DEBUG_OBJECT(self, "Previous routing:\n%s", os.str().c_str());
|
|
}
|
|
self->device->device->ApplySignalRoute(router, true);
|
|
{
|
|
std::stringstream os;
|
|
CNTV2SignalRouter currentRouter;
|
|
self->device->device->GetRouting(currentRouter);
|
|
currentRouter.Print(os);
|
|
GST_DEBUG_OBJECT(self, "New routing:\n%s", os.str().c_str());
|
|
}
|
|
|
|
switch (self->audio_system_setting) {
|
|
case GST_AJA_AUDIO_SYSTEM_1:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_1;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_2:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_2;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_3:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_3;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_4:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_4;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_5:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_5;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_6:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_6;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_7:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_7;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_8:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_8;
|
|
break;
|
|
case GST_AJA_AUDIO_SYSTEM_AUTO:
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_1;
|
|
if (::NTV2DeviceGetNumAudioSystems(self->device_id) > 1)
|
|
self->audio_system = ::NTV2ChannelToAudioSystem(self->channel);
|
|
if (!::NTV2DeviceCanDoFrameStore1Display(self->device_id))
|
|
self->audio_system = ::NTV2_AUDIOSYSTEM_1;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT(self, "Using audio system %d", self->audio_system);
|
|
|
|
NTV2AudioSource audio_source;
|
|
switch (self->audio_source) {
|
|
case GST_AJA_AUDIO_SOURCE_EMBEDDED:
|
|
audio_source = ::NTV2_AUDIO_EMBEDDED;
|
|
break;
|
|
case GST_AJA_AUDIO_SOURCE_AES:
|
|
audio_source = ::NTV2_AUDIO_AES;
|
|
break;
|
|
case GST_AJA_AUDIO_SOURCE_ANALOG:
|
|
audio_source = ::NTV2_AUDIO_ANALOG;
|
|
break;
|
|
case GST_AJA_AUDIO_SOURCE_HDMI:
|
|
audio_source = ::NTV2_AUDIO_HDMI;
|
|
break;
|
|
case GST_AJA_AUDIO_SOURCE_MIC:
|
|
audio_source = ::NTV2_AUDIO_MIC;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
NTV2EmbeddedAudioInput embedded_audio_input;
|
|
switch (self->embedded_audio_input) {
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_AUTO:
|
|
embedded_audio_input =
|
|
::NTV2InputSourceToEmbeddedAudioInput(input_source);
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO1:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_1;
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO2:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_2;
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO3:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_3;
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO4:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_4;
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO5:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_5;
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO6:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_6;
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO7:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_7;
|
|
break;
|
|
case GST_AJA_EMBEDDED_AUDIO_INPUT_VIDEO8:
|
|
embedded_audio_input = ::NTV2_EMBEDDED_AUDIO_INPUT_VIDEO_8;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
self->device->device->SetAudioSystemInputSource(
|
|
self->audio_system, audio_source, embedded_audio_input);
|
|
self->configured_audio_channels =
|
|
::NTV2DeviceGetMaxAudioChannels(self->device_id);
|
|
self->device->device->SetNumberAudioChannels(self->configured_audio_channels,
|
|
self->audio_system);
|
|
self->device->device->SetAudioRate(::NTV2_AUDIO_48K, self->audio_system);
|
|
self->device->device->SetAudioBufferSize(::NTV2_AUDIO_BUFFER_BIG,
|
|
self->audio_system);
|
|
self->device->device->SetAudioLoopBack(::NTV2_AUDIO_LOOPBACK_OFF,
|
|
self->audio_system);
|
|
self->device->device->SetEmbeddedAudioClock(
|
|
::NTV2_EMBEDDED_AUDIO_CLOCK_VIDEO_INPUT, self->audio_system);
|
|
|
|
NTV2ReferenceSource reference_source;
|
|
switch (self->reference_source) {
|
|
case GST_AJA_REFERENCE_SOURCE_AUTO:
|
|
reference_source = ::NTV2InputSourceToReferenceSource(input_source);
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_EXTERNAL:
|
|
reference_source = ::NTV2_REFERENCE_EXTERNAL;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_FREERUN:
|
|
reference_source = ::NTV2_REFERENCE_FREERUN;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_1:
|
|
reference_source = ::NTV2_REFERENCE_INPUT1;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_2:
|
|
reference_source = ::NTV2_REFERENCE_INPUT2;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_3:
|
|
reference_source = ::NTV2_REFERENCE_INPUT3;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_4:
|
|
reference_source = ::NTV2_REFERENCE_INPUT4;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_5:
|
|
reference_source = ::NTV2_REFERENCE_INPUT5;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_6:
|
|
reference_source = ::NTV2_REFERENCE_INPUT6;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_7:
|
|
reference_source = ::NTV2_REFERENCE_INPUT7;
|
|
break;
|
|
case GST_AJA_REFERENCE_SOURCE_INPUT_8:
|
|
reference_source = ::NTV2_REFERENCE_INPUT8;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
GST_DEBUG_OBJECT(self, "Configuring reference source %d",
|
|
(int)reference_source);
|
|
|
|
self->device->device->SetReference(reference_source);
|
|
self->device->device->SetLTCInputEnable(true);
|
|
self->device->device->SetRP188SourceFilter(self->channel, 0xff);
|
|
|
|
guint video_buffer_size = ::GetVideoActiveSize(
|
|
self->video_format, ::NTV2_FBF_10BIT_YCBCR, self->vanc_mode);
|
|
|
|
self->buffer_pool = gst_buffer_pool_new();
|
|
GstStructure *config = gst_buffer_pool_get_config(self->buffer_pool);
|
|
gst_buffer_pool_config_set_params(config, NULL, video_buffer_size,
|
|
2 * self->queue_size, 0);
|
|
gst_buffer_pool_config_set_allocator(config, self->allocator, NULL);
|
|
gst_buffer_pool_set_config(self->buffer_pool, config);
|
|
gst_buffer_pool_set_active(self->buffer_pool, TRUE);
|
|
|
|
guint audio_buffer_size = 401 * 1024;
|
|
|
|
self->audio_buffer_pool = gst_buffer_pool_new();
|
|
config = gst_buffer_pool_get_config(self->audio_buffer_pool);
|
|
gst_buffer_pool_config_set_params(config, NULL, audio_buffer_size,
|
|
2 * self->queue_size, 0);
|
|
gst_buffer_pool_config_set_allocator(config, self->allocator, NULL);
|
|
gst_buffer_pool_set_config(self->audio_buffer_pool, config);
|
|
gst_buffer_pool_set_active(self->audio_buffer_pool, TRUE);
|
|
|
|
guint anc_buffer_size = 8 * 1024;
|
|
|
|
if (self->vanc_mode == ::NTV2_VANCMODE_OFF &&
|
|
::NTV2DeviceCanDoCustomAnc(self->device_id)) {
|
|
self->anc_buffer_pool = gst_buffer_pool_new();
|
|
config = gst_buffer_pool_get_config(self->anc_buffer_pool);
|
|
gst_buffer_pool_config_set_params(
|
|
config, NULL, anc_buffer_size,
|
|
(self->configured_info.interlace_mode ==
|
|
GST_VIDEO_INTERLACE_MODE_PROGRESSIVE
|
|
? 1
|
|
: 2) *
|
|
self->queue_size,
|
|
0);
|
|
gst_buffer_pool_config_set_allocator(config, self->allocator, NULL);
|
|
gst_buffer_pool_set_config(self->anc_buffer_pool, config);
|
|
gst_buffer_pool_set_active(self->anc_buffer_pool, TRUE);
|
|
}
|
|
|
|
gst_element_post_message(GST_ELEMENT_CAST(self),
|
|
gst_message_new_latency(GST_OBJECT_CAST(self)));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean gst_aja_src_start(GstAjaSrc *self) {
|
|
GST_DEBUG_OBJECT(self, "Starting");
|
|
|
|
self->video_format = NTV2_FORMAT_UNKNOWN;
|
|
self->signal = FALSE;
|
|
|
|
self->capture_thread = new AJAThread();
|
|
self->capture_thread->Attach(capture_thread_func, self);
|
|
self->capture_thread->SetPriority(AJA_ThreadPriority_High);
|
|
self->capture_thread->Start();
|
|
g_mutex_lock(&self->queue_lock);
|
|
self->shutdown = FALSE;
|
|
self->playing = FALSE;
|
|
self->flushing = FALSE;
|
|
g_cond_signal(&self->queue_cond);
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean gst_aja_src_stop(GstAjaSrc *self) {
|
|
QueueItem *item;
|
|
|
|
GST_DEBUG_OBJECT(self, "Stopping");
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
self->shutdown = TRUE;
|
|
self->flushing = TRUE;
|
|
self->playing = FALSE;
|
|
g_cond_signal(&self->queue_cond);
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
if (self->capture_thread) {
|
|
self->capture_thread->Stop();
|
|
delete self->capture_thread;
|
|
self->capture_thread = NULL;
|
|
}
|
|
|
|
GST_OBJECT_LOCK(self);
|
|
memset(&self->current_info, 0, sizeof(self->current_info));
|
|
memset(&self->configured_info, 0, sizeof(self->configured_info));
|
|
self->configured_audio_channels = 0;
|
|
GST_OBJECT_UNLOCK(self);
|
|
|
|
while ((item = (QueueItem *)gst_queue_array_pop_head_struct(self->queue))) {
|
|
queue_item_clear(item);
|
|
}
|
|
self->queue_num_frames = 0;
|
|
|
|
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);
|
|
}
|
|
|
|
self->video_format = NTV2_FORMAT_UNKNOWN;
|
|
|
|
if (self->signal) {
|
|
self->signal = FALSE;
|
|
g_object_notify(G_OBJECT(self), "signal");
|
|
}
|
|
|
|
GST_DEBUG_OBJECT(self, "Stopped");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstStateChangeReturn gst_aja_src_change_state(
|
|
GstElement *element, GstStateChange transition) {
|
|
GstAjaSrc *self = GST_AJA_SRC(element);
|
|
GstStateChangeReturn ret;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
if (!gst_aja_src_open(self)) return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
if (!gst_aja_src_start(self)) return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE) return ret;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
g_mutex_lock(&self->queue_lock);
|
|
self->playing = FALSE;
|
|
g_cond_signal(&self->queue_cond);
|
|
g_mutex_unlock(&self->queue_lock);
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
g_mutex_lock(&self->queue_lock);
|
|
self->playing = TRUE;
|
|
g_cond_signal(&self->queue_cond);
|
|
g_mutex_unlock(&self->queue_lock);
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
if (!gst_aja_src_stop(self)) return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
if (!gst_aja_src_close(self)) return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstCaps *gst_aja_src_get_caps(GstBaseSrc *bsrc, GstCaps *filter) {
|
|
GstAjaSrc *self = GST_AJA_SRC(bsrc);
|
|
GstCaps *caps;
|
|
|
|
if (self->device) {
|
|
caps = gst_ntv2_supported_caps(self->device_id);
|
|
} else {
|
|
caps = gst_pad_get_pad_template_caps(GST_BASE_SRC_PAD(self));
|
|
}
|
|
|
|
// Intersect with the configured video format if any to constrain the caps
|
|
// further.
|
|
if (self->video_format_setting != GST_AJA_VIDEO_FORMAT_AUTO) {
|
|
GstCaps *configured_caps =
|
|
gst_aja_video_format_to_caps(self->video_format_setting);
|
|
|
|
if (configured_caps) {
|
|
GstCaps *tmp;
|
|
|
|
// Remove pixel-aspect-ratio from the configured caps to allow for both
|
|
// widescreen and non-widescreen PAL/NTSC. It's added back by the
|
|
// template caps above when intersecting.
|
|
guint n = gst_caps_get_size(configured_caps);
|
|
for (guint i = 0; i < n; i++) {
|
|
GstStructure *s = gst_caps_get_structure(configured_caps, i);
|
|
|
|
gst_structure_remove_fields(s, "pixel-aspect-ratio", NULL);
|
|
}
|
|
|
|
tmp = gst_caps_intersect(caps, configured_caps);
|
|
gst_caps_unref(caps);
|
|
gst_caps_unref(configured_caps);
|
|
caps = tmp;
|
|
}
|
|
}
|
|
|
|
if (filter) {
|
|
GstCaps *tmp =
|
|
gst_caps_intersect_full(filter, caps, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref(caps);
|
|
caps = tmp;
|
|
}
|
|
|
|
return caps;
|
|
}
|
|
|
|
static gboolean gst_aja_src_query(GstBaseSrc *bsrc, GstQuery *query) {
|
|
GstAjaSrc *self = GST_AJA_SRC(bsrc);
|
|
gboolean ret = TRUE;
|
|
|
|
switch (GST_QUERY_TYPE(query)) {
|
|
case GST_QUERY_LATENCY: {
|
|
if (self->current_info.finfo &&
|
|
self->current_info.finfo->format != GST_VIDEO_FORMAT_UNKNOWN) {
|
|
GstClockTime min, max;
|
|
|
|
min = gst_util_uint64_scale_ceil(
|
|
GST_SECOND, 3 * self->current_info.fps_d, self->current_info.fps_n);
|
|
max = self->queue_size * min;
|
|
|
|
gst_query_set_latency(query, TRUE, min, max);
|
|
ret = TRUE;
|
|
} else {
|
|
ret = FALSE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
default:
|
|
return GST_BASE_SRC_CLASS(parent_class)->query(bsrc, query);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean gst_aja_src_unlock(GstBaseSrc *bsrc) {
|
|
GstAjaSrc *self = GST_AJA_SRC(bsrc);
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
self->flushing = TRUE;
|
|
g_cond_signal(&self->queue_cond);
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean gst_aja_src_unlock_stop(GstBaseSrc *bsrc) {
|
|
GstAjaSrc *self = GST_AJA_SRC(bsrc);
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
self->flushing = FALSE;
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn gst_aja_src_create(GstPushSrc *psrc, GstBuffer **buffer) {
|
|
GstAjaSrc *self = GST_AJA_SRC(psrc);
|
|
GstFlowReturn flow_ret = GST_FLOW_OK;
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_DUMMY,
|
|
};
|
|
|
|
next_item:
|
|
item.type = QUEUE_ITEM_TYPE_DUMMY;
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
while (gst_queue_array_is_empty(self->queue) && !self->flushing) {
|
|
g_cond_wait(&self->queue_cond, &self->queue_lock);
|
|
}
|
|
|
|
if (self->flushing) {
|
|
g_mutex_unlock(&self->queue_lock);
|
|
GST_DEBUG_OBJECT(self, "Flushing");
|
|
return GST_FLOW_FLUSHING;
|
|
}
|
|
|
|
item = *(QueueItem *)gst_queue_array_pop_head_struct(self->queue);
|
|
if (item.type == QUEUE_ITEM_TYPE_FRAME) {
|
|
self->queue_num_frames -= 1;
|
|
}
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
switch (item.type) {
|
|
case QUEUE_ITEM_TYPE_DUMMY:
|
|
queue_item_clear(&item);
|
|
goto next_item;
|
|
case QUEUE_ITEM_TYPE_SIGNAL_CHANGE:
|
|
// These are already only produced when signal status is changing
|
|
if (item.signal_change.have_signal) {
|
|
GST_ELEMENT_INFO(GST_ELEMENT(self), RESOURCE, READ,
|
|
("Signal recovered"), ("Input source detected"));
|
|
self->signal = TRUE;
|
|
g_object_notify(G_OBJECT(self), "signal");
|
|
} else if (!item.signal_change.have_signal) {
|
|
if (item.signal_change.detected_format != ::NTV2_FORMAT_UNKNOWN) {
|
|
std::string format_string =
|
|
NTV2VideoFormatToString(item.signal_change.detected_format);
|
|
|
|
GST_ELEMENT_WARNING_WITH_DETAILS(
|
|
GST_ELEMENT(self), RESOURCE, READ, ("Signal lost"),
|
|
("Input source with different mode %s was detected",
|
|
format_string.c_str()),
|
|
("detected-format", G_TYPE_STRING, format_string.c_str(), "vpid",
|
|
G_TYPE_UINT, item.signal_change.vpid, NULL));
|
|
} else {
|
|
GST_ELEMENT_WARNING(GST_ELEMENT(self), RESOURCE, READ,
|
|
("Signal lost"),
|
|
("No input source was detected"));
|
|
}
|
|
self->signal = FALSE;
|
|
g_object_notify(G_OBJECT(self), "signal");
|
|
}
|
|
queue_item_clear(&item);
|
|
goto next_item;
|
|
case QUEUE_ITEM_TYPE_ERROR:
|
|
GST_ERROR_OBJECT(self, "Stopping because of error on capture thread");
|
|
gst_element_post_message(GST_ELEMENT(self),
|
|
(GstMessage *)g_steal_pointer(&item.error.msg));
|
|
queue_item_clear(&item);
|
|
return GST_FLOW_ERROR;
|
|
case QUEUE_ITEM_TYPE_FRAMES_DROPPED:
|
|
GST_WARNING_OBJECT(
|
|
self, "Dropped frames from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS(item.frames_dropped.timestamp_start),
|
|
GST_TIME_ARGS(item.frames_dropped.timestamp_end));
|
|
gst_element_post_message(
|
|
GST_ELEMENT(self),
|
|
gst_message_new_qos(GST_OBJECT_CAST(self), TRUE, GST_CLOCK_TIME_NONE,
|
|
GST_CLOCK_TIME_NONE,
|
|
item.frames_dropped.timestamp_start,
|
|
item.frames_dropped.timestamp_end -
|
|
item.frames_dropped.timestamp_start));
|
|
queue_item_clear(&item);
|
|
goto next_item;
|
|
case QUEUE_ITEM_TYPE_FRAME:
|
|
// fall through below
|
|
break;
|
|
}
|
|
|
|
g_assert(item.type == QUEUE_ITEM_TYPE_FRAME);
|
|
|
|
if (!self->signal) {
|
|
self->signal = TRUE;
|
|
g_object_notify(G_OBJECT(self), "signal");
|
|
}
|
|
|
|
*buffer = (GstBuffer *)g_steal_pointer(&item.frame.video_buffer);
|
|
gst_buffer_add_aja_audio_meta(*buffer, item.frame.audio_buffer);
|
|
gst_clear_buffer(&item.frame.audio_buffer);
|
|
|
|
if (item.frame.tc.IsValid()) {
|
|
TimecodeFormat tc_format = ::kTCFormatUnknown;
|
|
GstVideoTimeCodeFlags flags = GST_VIDEO_TIME_CODE_FLAGS_NONE;
|
|
|
|
if (self->configured_info.fps_n == 24 && self->configured_info.fps_d == 1) {
|
|
tc_format = kTCFormat24fps;
|
|
} else if (self->configured_info.fps_n == 25 &&
|
|
self->configured_info.fps_d == 1) {
|
|
tc_format = kTCFormat25fps;
|
|
} else if (self->configured_info.fps_n == 30 &&
|
|
self->configured_info.fps_d == 1) {
|
|
tc_format = kTCFormat30fps;
|
|
} else if (self->configured_info.fps_n == 30000 &&
|
|
self->configured_info.fps_d == 1001) {
|
|
tc_format = kTCFormat30fpsDF;
|
|
flags =
|
|
(GstVideoTimeCodeFlags)(flags | GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME);
|
|
} else if (self->configured_info.fps_n == 48 &&
|
|
self->configured_info.fps_d == 1) {
|
|
tc_format = kTCFormat48fps;
|
|
} else if (self->configured_info.fps_n == 50 &&
|
|
self->configured_info.fps_d == 1) {
|
|
tc_format = kTCFormat50fps;
|
|
} else if (self->configured_info.fps_n == 60 &&
|
|
self->configured_info.fps_d == 1) {
|
|
tc_format = kTCFormat60fps;
|
|
} else if (self->configured_info.fps_n == 60000 &&
|
|
self->configured_info.fps_d == 1001) {
|
|
tc_format = kTCFormat60fpsDF;
|
|
flags =
|
|
(GstVideoTimeCodeFlags)(flags | GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME);
|
|
}
|
|
|
|
if (self->configured_info.interlace_mode !=
|
|
GST_VIDEO_INTERLACE_MODE_PROGRESSIVE)
|
|
flags =
|
|
(GstVideoTimeCodeFlags)(flags | GST_VIDEO_TIME_CODE_FLAGS_INTERLACED);
|
|
|
|
CRP188 rp188(item.frame.tc, tc_format);
|
|
|
|
{
|
|
std::stringstream os;
|
|
os << rp188;
|
|
GST_TRACE_OBJECT(self, "Adding timecode %s", os.str().c_str());
|
|
}
|
|
|
|
guint hours, minutes, seconds, frames;
|
|
rp188.GetRP188Hrs(hours);
|
|
rp188.GetRP188Mins(minutes);
|
|
rp188.GetRP188Secs(seconds);
|
|
rp188.GetRP188Frms(frames);
|
|
|
|
GstVideoTimeCode tc;
|
|
gst_video_time_code_init(&tc, self->configured_info.fps_n,
|
|
self->configured_info.fps_d, NULL, flags, hours,
|
|
minutes, seconds, frames, 0);
|
|
gst_buffer_add_video_time_code_meta(*buffer, &tc);
|
|
}
|
|
|
|
AJAAncillaryList anc_packets;
|
|
|
|
if (item.frame.anc_buffer) {
|
|
GstMapInfo map = GST_MAP_INFO_INIT;
|
|
GstMapInfo map2 = GST_MAP_INFO_INIT;
|
|
|
|
gst_buffer_map(item.frame.anc_buffer, &map, GST_MAP_READ);
|
|
if (item.frame.anc_buffer2)
|
|
gst_buffer_map(item.frame.anc_buffer2, &map2, GST_MAP_READ);
|
|
|
|
NTV2_POINTER ptr1(map.data, map.size);
|
|
NTV2_POINTER ptr2(map2.data, map2.size);
|
|
|
|
AJAAncillaryList::SetFromDeviceAncBuffers(ptr1, ptr2, anc_packets);
|
|
|
|
if (item.frame.anc_buffer2) gst_buffer_unmap(item.frame.anc_buffer2, &map2);
|
|
gst_buffer_unmap(item.frame.anc_buffer, &map);
|
|
} else if (self->vanc_mode != ::NTV2_VANCMODE_OFF) {
|
|
GstMapInfo map;
|
|
|
|
NTV2FormatDescriptor format_desc(self->video_format, ::NTV2_FBF_10BIT_YCBCR,
|
|
self->vanc_mode);
|
|
|
|
gst_buffer_map(*buffer, &map, GST_MAP_READ);
|
|
NTV2_POINTER ptr(map.data, map.size);
|
|
AJAAncillaryList::SetFromVANCData(ptr, format_desc, anc_packets);
|
|
gst_buffer_unmap(*buffer, &map);
|
|
|
|
guint offset =
|
|
format_desc.RasterLineToByteOffset(format_desc.GetFirstActiveLine());
|
|
guint size = format_desc.GetVisibleRasterBytes();
|
|
|
|
gst_buffer_resize(*buffer, offset, size);
|
|
}
|
|
|
|
gst_clear_buffer(&item.frame.anc_buffer);
|
|
gst_clear_buffer(&item.frame.anc_buffer2);
|
|
|
|
// Not using CountAncillaryDataWithType(AJAAncillaryDataType_Cea708) etc
|
|
// here because for SD it doesn't recognize the packets. It assumes they
|
|
// would only be received on AJAAncillaryDataChannel_Y but for SD it is
|
|
// actually AJAAncillaryDataChannel_Both.
|
|
//
|
|
// See AJA SDK support ticket #4844.
|
|
guint32 n_vanc_packets = anc_packets.CountAncillaryData();
|
|
|
|
// Check if we have either CEA608 or CEA708 packets, or both.
|
|
bool have_cea608 = false;
|
|
bool have_cea708 = false;
|
|
for (guint32 i = 0; i < n_vanc_packets; i++) {
|
|
AJAAncillaryData *packet = anc_packets.GetAncillaryDataAtIndex(i);
|
|
|
|
if (packet->GetDID() == AJAAncillaryData_Cea608_Vanc_DID &&
|
|
packet->GetSID() == AJAAncillaryData_Cea608_Vanc_SID &&
|
|
packet->GetPayloadData() && packet->GetPayloadByteCount() &&
|
|
AJA_SUCCESS(packet->ParsePayloadData())) {
|
|
GST_TRACE_OBJECT(
|
|
self, "Found CEA608 VANC of %" G_GSIZE_FORMAT " bytes at line %u",
|
|
packet->GetPayloadByteCount(), packet->GetLocationLineNumber());
|
|
have_cea608 = true;
|
|
} else if (packet->GetDID() == AJAAncillaryData_CEA708_DID &&
|
|
packet->GetSID() == AJAAncillaryData_CEA708_SID &&
|
|
packet->GetPayloadData() && packet->GetPayloadByteCount() &&
|
|
AJA_SUCCESS(packet->ParsePayloadData())) {
|
|
GST_TRACE_OBJECT(
|
|
self, "Found CEA708 CDP VANC of %" G_GSIZE_FORMAT " bytes at line %u",
|
|
packet->GetPayloadByteCount(), packet->GetLocationLineNumber());
|
|
have_cea708 = true;
|
|
}
|
|
}
|
|
|
|
// Decide based on the closed-caption-capture-mode property and closed
|
|
// caption availability which ones to add as metadata to the output buffer.
|
|
bool want_cea608 =
|
|
have_cea608 &&
|
|
(self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA708_AND_CEA608 ||
|
|
self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA608_OR_CEA708 ||
|
|
self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA608_ONLY ||
|
|
(!have_cea708 &&
|
|
self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA708_OR_CEA608));
|
|
|
|
bool want_cea708 =
|
|
have_cea708 &&
|
|
(self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA708_AND_CEA608 ||
|
|
self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA708_OR_CEA608 ||
|
|
self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA708_ONLY ||
|
|
(!have_cea608 &&
|
|
self->closed_caption_capture_mode ==
|
|
GST_AJA_CLOSED_CAPTION_CAPTURE_MODE_CEA608_OR_CEA708));
|
|
|
|
bool aspect_ratio_flag = false;
|
|
bool have_afd_bar = false;
|
|
for (guint32 i = 0; i < n_vanc_packets; i++) {
|
|
AJAAncillaryData *packet = anc_packets.GetAncillaryDataAtIndex(i);
|
|
|
|
if (want_cea608 && packet->GetDID() == AJAAncillaryData_Cea608_Vanc_DID &&
|
|
packet->GetSID() == AJAAncillaryData_Cea608_Vanc_SID &&
|
|
packet->GetPayloadData() && packet->GetPayloadByteCount() &&
|
|
AJA_SUCCESS(packet->ParsePayloadData())) {
|
|
GST_TRACE_OBJECT(
|
|
self, "Adding CEA608 VANC of %" G_GSIZE_FORMAT " bytes at line %u",
|
|
packet->GetPayloadByteCount(), packet->GetLocationLineNumber());
|
|
gst_buffer_add_video_caption_meta(
|
|
*buffer, GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A,
|
|
packet->GetPayloadData(), packet->GetPayloadByteCount());
|
|
} else if (want_cea708 && packet->GetDID() == AJAAncillaryData_CEA708_DID &&
|
|
packet->GetSID() == AJAAncillaryData_CEA708_SID &&
|
|
packet->GetPayloadData() && packet->GetPayloadByteCount() &&
|
|
AJA_SUCCESS(packet->ParsePayloadData())) {
|
|
GST_TRACE_OBJECT(
|
|
self,
|
|
"Adding CEA708 CDP VANC of %" G_GSIZE_FORMAT " bytes at line %u",
|
|
packet->GetPayloadByteCount(), packet->GetLocationLineNumber());
|
|
gst_buffer_add_video_caption_meta(
|
|
*buffer, GST_VIDEO_CAPTION_TYPE_CEA708_CDP, packet->GetPayloadData(),
|
|
packet->GetPayloadByteCount());
|
|
} else if (packet->GetDID() == 0x41 && packet->GetSID() == 0x05 &&
|
|
packet->GetPayloadData() && packet->GetPayloadByteCount() == 8) {
|
|
const guint8 *data = packet->GetPayloadData();
|
|
|
|
have_afd_bar = true;
|
|
aspect_ratio_flag = (data[0] >> 2) & 0x1;
|
|
|
|
GstVideoAFDValue afd = (GstVideoAFDValue)((data[0] >> 3) & 0xf);
|
|
gboolean is_letterbox = ((data[3] >> 4) & 0x3) == 0;
|
|
guint16 bar1 = GST_READ_UINT16_BE(&data[4]);
|
|
guint16 bar2 = GST_READ_UINT16_BE(&data[6]);
|
|
|
|
GST_TRACE_OBJECT(self,
|
|
"Found AFD/Bar VANC at line %u: AR %u, AFD %u, "
|
|
"letterbox %u, bar1 %u, bar2 %u",
|
|
packet->GetLocationLineNumber(), aspect_ratio_flag, afd,
|
|
is_letterbox, bar1, bar2);
|
|
|
|
const NTV2Standard standard(
|
|
::GetNTV2StandardFromVideoFormat(item.frame.detected_format));
|
|
const NTV2SmpteLineNumber smpte_line_num_info =
|
|
::GetSmpteLineNumber(standard);
|
|
bool field2 =
|
|
packet->GetLocationLineNumber() >
|
|
smpte_line_num_info.GetLastLine(
|
|
smpte_line_num_info.firstFieldTop ? NTV2_FIELD0 : NTV2_FIELD1);
|
|
|
|
gst_buffer_add_video_afd_meta(*buffer, field2 ? 1 : 0,
|
|
GST_VIDEO_AFD_SPEC_SMPTE_ST2016_1, afd);
|
|
gst_buffer_add_video_bar_meta(*buffer, field2 ? 1 : 0, is_letterbox, bar1,
|
|
bar2);
|
|
}
|
|
|
|
// Don't attach other ANC as ancillary meta if not requested to do so.
|
|
if (!self->attach_ancillary_meta) continue;
|
|
|
|
// Skip non-SMPTE 291M ancillary data
|
|
if (packet->GetDataCoding() != AJAAncillaryDataCoding_Digital) continue;
|
|
|
|
const guint8 *in_data = packet->GetPayloadData();
|
|
guint data_count = packet->GetDC();
|
|
|
|
guint16 line_number = packet->GetLocationLineNumber();
|
|
guint16 horiz_offset = packet->GetLocationHorizOffset();
|
|
|
|
if (!in_data || data_count == 0) {
|
|
GST_TRACE_OBJECT(self, "ANC %s (%04x) at (%u,%u) has no payload data",
|
|
packet->IDAsString().c_str(), packet->GetDIDSID(),
|
|
line_number, horiz_offset);
|
|
continue;
|
|
}
|
|
|
|
GST_TRACE_OBJECT(self,
|
|
"Adding ANC meta for %s (%04x) at (%u,%u) of size %u",
|
|
packet->IDAsString().c_str(), packet->GetDIDSID(),
|
|
line_number, horiz_offset, data_count);
|
|
GstAncillaryMeta *anc_meta = gst_buffer_add_ancillary_meta(*buffer);
|
|
|
|
anc_meta->c_not_y_channel = packet->IsChromaChannel();
|
|
anc_meta->line = line_number;
|
|
anc_meta->offset = horiz_offset;
|
|
|
|
packet->GeneratePayloadData();
|
|
anc_meta->DID = AJAAncillaryData::AddEvenParity(packet->GetDID());
|
|
anc_meta->SDID_block_number =
|
|
AJAAncillaryData::AddEvenParity(packet->GetSID());
|
|
|
|
anc_meta->data_count = AJAAncillaryData::AddEvenParity(data_count);
|
|
|
|
guint16 *data = g_new(guint16, packet->GetDC());
|
|
for (guint i = 0; i < data_count; i++) {
|
|
data[i] = AJAAncillaryData::AddEvenParity(in_data[i]);
|
|
}
|
|
anc_meta->data = data;
|
|
|
|
anc_meta->checksum = packet->Calculate9BitChecksum();
|
|
}
|
|
|
|
bool caps_changed = false;
|
|
|
|
CNTV2VPID vpid(item.frame.vpid);
|
|
if (vpid.IsValid()) {
|
|
GstVideoInfo info;
|
|
|
|
{
|
|
std::stringstream os;
|
|
vpid.Print(os);
|
|
GST_TRACE_OBJECT(self, "Got valid VPID %s", os.str().c_str());
|
|
}
|
|
|
|
if (gst_video_info_from_ntv2_video_format(&info,
|
|
item.frame.detected_format)) {
|
|
switch (vpid.GetTransferCharacteristics()) {
|
|
default:
|
|
case NTV2_VPID_TC_SDR_TV:
|
|
if (info.height < 720) {
|
|
info.colorimetry.transfer = GST_VIDEO_TRANSFER_BT601;
|
|
} else {
|
|
info.colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
|
|
}
|
|
break;
|
|
case NTV2_VPID_TC_HLG:
|
|
info.colorimetry.transfer = GST_VIDEO_TRANSFER_ARIB_STD_B67;
|
|
break;
|
|
case NTV2_VPID_TC_PQ:
|
|
info.colorimetry.transfer = GST_VIDEO_TRANSFER_SMPTE2084;
|
|
break;
|
|
}
|
|
|
|
switch (vpid.GetColorimetry()) {
|
|
case NTV2_VPID_Color_Rec709:
|
|
info.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
|
|
info.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
|
|
break;
|
|
case NTV2_VPID_Color_UHDTV:
|
|
info.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
|
|
info.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
|
|
break;
|
|
default:
|
|
// Default handling
|
|
break;
|
|
}
|
|
|
|
switch (vpid.GetRGBRange()) {
|
|
case NTV2_VPID_Range_Full:
|
|
info.colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
|
|
break;
|
|
case NTV2_VPID_Range_Narrow:
|
|
info.colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
|
|
break;
|
|
}
|
|
|
|
if (!have_afd_bar && vpid.GetImageAspect16x9()) aspect_ratio_flag = true;
|
|
|
|
// Widescreen PAL/NTSC
|
|
if (aspect_ratio_flag && info.height == 486) {
|
|
info.par_n = 40;
|
|
info.par_d = 33;
|
|
} else if (aspect_ratio_flag && info.height == 576) {
|
|
info.par_n = 16;
|
|
info.par_d = 11;
|
|
}
|
|
|
|
if (!gst_pad_has_current_caps(GST_BASE_SRC_PAD(self)) ||
|
|
!gst_video_info_is_equal(&info, &self->current_info)) {
|
|
self->current_info = info;
|
|
caps_changed = true;
|
|
}
|
|
}
|
|
} else {
|
|
GstVideoInfo info;
|
|
|
|
if (gst_video_info_from_ntv2_video_format(&info,
|
|
item.frame.detected_format)) {
|
|
// Widescreen PAL/NTSC
|
|
if (aspect_ratio_flag && info.height == 486) {
|
|
info.par_n = 40;
|
|
info.par_d = 33;
|
|
} else if (aspect_ratio_flag && info.height == 576) {
|
|
info.par_n = 16;
|
|
info.par_d = 11;
|
|
}
|
|
|
|
if (!gst_pad_has_current_caps(GST_BASE_SRC_PAD(self)) ||
|
|
!gst_video_info_is_equal(&info, &self->current_info)) {
|
|
self->current_info = info;
|
|
caps_changed = true;
|
|
}
|
|
} else if (!gst_pad_has_current_caps(GST_BASE_SRC_PAD(self))) {
|
|
self->current_info = self->configured_info;
|
|
|
|
// Widescreen PAL/NTSC
|
|
if (aspect_ratio_flag && self->current_info.height == 486) {
|
|
self->current_info.par_n = 40;
|
|
self->current_info.par_d = 33;
|
|
} else if (aspect_ratio_flag && self->current_info.height == 576) {
|
|
self->current_info.par_n = 16;
|
|
self->current_info.par_d = 11;
|
|
}
|
|
|
|
caps_changed = true;
|
|
}
|
|
}
|
|
|
|
if (caps_changed) {
|
|
GstCaps *caps = gst_video_info_to_caps(&self->current_info);
|
|
gst_caps_set_simple(caps, "audio-channels", G_TYPE_INT,
|
|
self->configured_audio_channels, NULL);
|
|
GST_DEBUG_OBJECT(self, "Configuring caps %" GST_PTR_FORMAT, caps);
|
|
gst_base_src_set_caps(GST_BASE_SRC_CAST(self), caps);
|
|
gst_caps_unref(caps);
|
|
}
|
|
|
|
if (self->configured_info.interlace_mode !=
|
|
GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) {
|
|
GST_BUFFER_FLAG_SET(*buffer, GST_VIDEO_BUFFER_FLAG_INTERLACED);
|
|
switch (GST_VIDEO_INFO_FIELD_ORDER(&self->configured_info)) {
|
|
case GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST:
|
|
GST_BUFFER_FLAG_SET(*buffer, GST_VIDEO_BUFFER_FLAG_TFF);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
queue_item_clear(&item);
|
|
|
|
GST_TRACE_OBJECT(self, "Outputting buffer %" GST_PTR_FORMAT, *buffer);
|
|
|
|
return flow_ret;
|
|
}
|
|
|
|
#define AJA_SRC_ERROR(el, domain, code, text, debug) \
|
|
G_STMT_START { \
|
|
gchar *__txt = _gst_element_error_printf text; \
|
|
gchar *__dbg = _gst_element_error_printf debug; \
|
|
GstMessage *__msg; \
|
|
GError *__err; \
|
|
gchar *__name, *__fmt_dbg; \
|
|
if (__txt) GST_WARNING_OBJECT(el, "error: %s", __txt); \
|
|
if (__dbg) GST_WARNING_OBJECT(el, "error: %s", __dbg); \
|
|
if (!__txt) \
|
|
__txt = gst_error_get_message(GST_##domain##_ERROR, \
|
|
GST_##domain##_ERROR_##code); \
|
|
__err = g_error_new_literal(GST_##domain##_ERROR, \
|
|
GST_##domain##_ERROR_##code, __txt); \
|
|
__name = gst_object_get_path_string(GST_OBJECT_CAST(el)); \
|
|
if (__dbg) \
|
|
__fmt_dbg = g_strdup_printf("%s(%d): %s (): %s:\n%s", __FILE__, \
|
|
__LINE__, GST_FUNCTION, __name, __dbg); \
|
|
else \
|
|
__fmt_dbg = g_strdup_printf("%s(%d): %s (): %s", __FILE__, __LINE__, \
|
|
GST_FUNCTION, __name); \
|
|
g_free(__name); \
|
|
g_free(__dbg); \
|
|
__msg = gst_message_new_error(GST_OBJECT(el), __err, __fmt_dbg); \
|
|
QueueItem item = {.type = QUEUE_ITEM_TYPE_ERROR, .error{.msg = __msg}}; \
|
|
gst_queue_array_push_tail_struct(el->queue, &item); \
|
|
g_cond_signal(&el->queue_cond); \
|
|
} \
|
|
G_STMT_END;
|
|
|
|
static void capture_thread_func(AJAThread *thread, void *data) {
|
|
GstAjaSrc *self = GST_AJA_SRC(data);
|
|
GstClock *clock = NULL;
|
|
AUTOCIRCULATE_TRANSFER transfer;
|
|
guint64 frames_dropped_last = G_MAXUINT64;
|
|
gboolean have_signal = TRUE, discont = TRUE;
|
|
guint iterations_without_frame = 0;
|
|
NTV2VideoFormat last_detected_video_format = ::NTV2_FORMAT_UNKNOWN;
|
|
|
|
if (self->capture_cpu_core != G_MAXUINT) {
|
|
cpu_set_t mask;
|
|
pthread_t current_thread = pthread_self();
|
|
|
|
CPU_ZERO(&mask);
|
|
CPU_SET(self->capture_cpu_core, &mask);
|
|
|
|
if (pthread_setaffinity_np(current_thread, sizeof(mask), &mask) != 0) {
|
|
GST_ERROR_OBJECT(self,
|
|
"Failed to set affinity for current thread to core %u",
|
|
self->capture_cpu_core);
|
|
}
|
|
}
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
restart:
|
|
GST_DEBUG_OBJECT(self, "Waiting for playing or shutdown");
|
|
while (!self->playing && !self->shutdown)
|
|
g_cond_wait(&self->queue_cond, &self->queue_lock);
|
|
if (self->shutdown) {
|
|
GST_DEBUG_OBJECT(self, "Shutting down");
|
|
g_mutex_unlock(&self->queue_lock);
|
|
return;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT(self, "Starting capture");
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
gst_clear_object(&clock);
|
|
clock = gst_element_get_clock(GST_ELEMENT_CAST(self));
|
|
|
|
frames_dropped_last = G_MAXUINT64;
|
|
have_signal = TRUE;
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
while (self->playing && !self->shutdown) {
|
|
// If we don't have a video format configured, configure the device now
|
|
// and potentially auto-detect the video format
|
|
if (self->video_format == NTV2_FORMAT_UNKNOWN) {
|
|
// Don't keep queue locked while configuring as this might take a while
|
|
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);
|
|
|
|
if (!gst_aja_src_configure(self)) {
|
|
g_mutex_lock(&self->queue_lock);
|
|
AJA_SRC_ERROR(self, STREAM, FAILED, (NULL),
|
|
("Failed to configure device"));
|
|
goto out;
|
|
}
|
|
g_mutex_lock(&self->queue_lock);
|
|
|
|
if (!self->playing || self->shutdown) goto restart;
|
|
|
|
if (self->video_format == ::NTV2_FORMAT_UNKNOWN) {
|
|
GST_DEBUG_OBJECT(self, "No signal, waiting");
|
|
frames_dropped_last = G_MAXUINT64;
|
|
if (have_signal) {
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_SIGNAL_CHANGE,
|
|
.signal_change = {.have_signal = FALSE,
|
|
.detected_format = ::NTV2_FORMAT_UNKNOWN,
|
|
.vpid = 0}};
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
g_cond_signal(&self->queue_cond);
|
|
have_signal = FALSE;
|
|
discont = TRUE;
|
|
}
|
|
self->device->device->WaitForInputVerticalInterrupt(self->channel);
|
|
continue;
|
|
}
|
|
|
|
guint16 start_frame = self->start_frame;
|
|
guint16 end_frame = self->end_frame;
|
|
|
|
// If both are set to the same value, try to find that many unallocated
|
|
// frames and use those.
|
|
if (start_frame == end_frame) {
|
|
gint assigned_start_frame = gst_aja_ntv2_device_find_unallocated_frames(
|
|
self->device, self->channel, self->start_frame);
|
|
|
|
if (assigned_start_frame == -1) {
|
|
AJA_SRC_ERROR(self, STREAM, FAILED, (NULL),
|
|
("Failed to allocate %u frames", start_frame));
|
|
goto out;
|
|
}
|
|
|
|
start_frame = assigned_start_frame;
|
|
end_frame = start_frame + self->start_frame - 1;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT(
|
|
self, "Configuring channel %u with start frame %u and end frame %u",
|
|
self->channel, start_frame, end_frame);
|
|
|
|
if (!self->device->device->AutoCirculateInitForInput(
|
|
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)) {
|
|
AJA_SRC_ERROR(self, STREAM, FAILED, (NULL),
|
|
("Failed to initialize autocirculate"));
|
|
goto out;
|
|
}
|
|
|
|
self->device->device->AutoCirculateStart(self->channel);
|
|
}
|
|
|
|
// Check for valid signal first
|
|
NTV2VideoFormat current_video_format =
|
|
self->device->device->GetInputVideoFormat(
|
|
self->configured_input_source);
|
|
|
|
bool all_quads_equal = true;
|
|
if (self->quad_mode) {
|
|
for (int i = 1; i < 4; i++) {
|
|
NTV2VideoFormat other_video_format =
|
|
self->device->device->GetInputVideoFormat(
|
|
(NTV2InputSource)(self->configured_input_source + i));
|
|
if (other_video_format != current_video_format) {
|
|
std::string current_string =
|
|
NTV2VideoFormatToString(current_video_format);
|
|
std::string other_string =
|
|
NTV2VideoFormatToString(other_video_format);
|
|
GST_DEBUG_OBJECT(
|
|
self,
|
|
"Not all quadrants had the same format in "
|
|
"quad-link-mode: %s (%d) on input 1 vs. %s (%d) on input %d",
|
|
current_string.c_str(), current_video_format,
|
|
other_string.c_str(), other_video_format, i + 1);
|
|
all_quads_equal = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ULWord vpid_a = 0;
|
|
ULWord vpid_b = 0;
|
|
self->device->device->ReadSDIInVPID(self->channel, vpid_a, vpid_b);
|
|
|
|
{
|
|
std::string current_string =
|
|
NTV2VideoFormatToString(current_video_format);
|
|
GST_TRACE_OBJECT(
|
|
self, "Detected input video format %s (%d) with VPID %08x / %08x",
|
|
current_string.c_str(), (int)current_video_format, vpid_a, vpid_b);
|
|
}
|
|
|
|
NTV2VideoFormat effective_video_format = self->video_format;
|
|
// Can't call this unconditionally as it also maps e.g. 3840x2160p to 1080p
|
|
if (self->quad_mode) {
|
|
effective_video_format =
|
|
::GetQuarterSizedVideoFormat(effective_video_format);
|
|
}
|
|
switch (self->video_format) {
|
|
case NTV2_FORMAT_1080psf_2500_2:
|
|
if (current_video_format == NTV2_FORMAT_1080i_5000)
|
|
current_video_format = NTV2_FORMAT_1080psf_2500_2;
|
|
break;
|
|
case NTV2_FORMAT_1080psf_2997_2:
|
|
if (current_video_format == NTV2_FORMAT_1080i_5994)
|
|
current_video_format = NTV2_FORMAT_1080psf_2997_2;
|
|
break;
|
|
case NTV2_FORMAT_1080psf_3000_2:
|
|
if (current_video_format == NTV2_FORMAT_1080i_6000)
|
|
current_video_format = NTV2_FORMAT_1080psf_3000_2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (current_video_format == ::NTV2_FORMAT_UNKNOWN || !all_quads_equal) {
|
|
if (self->video_format_setting == GST_AJA_VIDEO_FORMAT_AUTO)
|
|
self->video_format = NTV2_FORMAT_UNKNOWN;
|
|
|
|
GST_DEBUG_OBJECT(self, "No signal, waiting");
|
|
g_mutex_unlock(&self->queue_lock);
|
|
frames_dropped_last = G_MAXUINT64;
|
|
if (have_signal) {
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_SIGNAL_CHANGE,
|
|
.signal_change = {.have_signal = FALSE,
|
|
.detected_format = ::NTV2_FORMAT_UNKNOWN,
|
|
.vpid = 0}};
|
|
last_detected_video_format = ::NTV2_FORMAT_UNKNOWN;
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
g_cond_signal(&self->queue_cond);
|
|
have_signal = FALSE;
|
|
discont = TRUE;
|
|
}
|
|
self->device->device->WaitForInputVerticalInterrupt(self->channel);
|
|
g_mutex_lock(&self->queue_lock);
|
|
continue;
|
|
} else if (current_video_format != effective_video_format &&
|
|
current_video_format != self->video_format) {
|
|
// Try reconfiguring with the newly detected video format
|
|
if (self->video_format_setting == GST_AJA_VIDEO_FORMAT_AUTO) {
|
|
self->video_format = NTV2_FORMAT_UNKNOWN;
|
|
continue;
|
|
}
|
|
|
|
std::string current_string =
|
|
NTV2VideoFormatToString(current_video_format);
|
|
std::string configured_string =
|
|
NTV2VideoFormatToString(self->video_format);
|
|
std::string effective_string =
|
|
NTV2VideoFormatToString(effective_video_format);
|
|
|
|
GST_DEBUG_OBJECT(self,
|
|
"Different input format %s than configured %s "
|
|
"(effective %s), waiting",
|
|
current_string.c_str(), configured_string.c_str(),
|
|
effective_string.c_str());
|
|
g_mutex_unlock(&self->queue_lock);
|
|
frames_dropped_last = G_MAXUINT64;
|
|
if (have_signal || current_video_format != last_detected_video_format) {
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_SIGNAL_CHANGE,
|
|
.signal_change = {.have_signal = FALSE,
|
|
.detected_format = current_video_format,
|
|
.vpid = vpid_a}};
|
|
last_detected_video_format = current_video_format;
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
g_cond_signal(&self->queue_cond);
|
|
have_signal = FALSE;
|
|
discont = TRUE;
|
|
}
|
|
self->device->device->WaitForInputVerticalInterrupt(self->channel);
|
|
g_mutex_lock(&self->queue_lock);
|
|
continue;
|
|
}
|
|
|
|
AUTOCIRCULATE_STATUS status;
|
|
|
|
self->device->device->AutoCirculateGetStatus(self->channel, status);
|
|
|
|
GST_TRACE_OBJECT(self,
|
|
"Start frame %d "
|
|
"end frame %d "
|
|
"active frame %d "
|
|
"start time %" G_GUINT64_FORMAT
|
|
" "
|
|
"current time %" G_GUINT64_FORMAT
|
|
" "
|
|
"frames processed %u "
|
|
"frames dropped %u "
|
|
"buffer level %u",
|
|
status.acStartFrame, status.acEndFrame,
|
|
status.acActiveFrame, status.acRDTSCStartTime,
|
|
status.acRDTSCCurrentTime, status.acFramesProcessed,
|
|
status.acFramesDropped, status.acBufferLevel);
|
|
|
|
if (frames_dropped_last == G_MAXUINT64) {
|
|
frames_dropped_last = status.acFramesDropped;
|
|
} else if (frames_dropped_last < status.acFramesDropped) {
|
|
GST_WARNING_OBJECT(self, "Dropped %" G_GUINT64_FORMAT " frames",
|
|
status.acFramesDropped - frames_dropped_last);
|
|
|
|
GstClockTime timestamp =
|
|
gst_util_uint64_scale(status.acFramesProcessed + frames_dropped_last,
|
|
self->configured_info.fps_n,
|
|
self->configured_info.fps_d * GST_SECOND);
|
|
GstClockTime timestamp_end = gst_util_uint64_scale(
|
|
status.acFramesProcessed + status.acFramesDropped,
|
|
self->configured_info.fps_n,
|
|
self->configured_info.fps_d * GST_SECOND);
|
|
|
|
QueueItem item = {.type = QUEUE_ITEM_TYPE_FRAMES_DROPPED,
|
|
.frames_dropped = {.driver_side = TRUE,
|
|
.timestamp_start = timestamp,
|
|
.timestamp_end = timestamp_end}};
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
g_cond_signal(&self->queue_cond);
|
|
|
|
frames_dropped_last = status.acFramesDropped;
|
|
discont = TRUE;
|
|
}
|
|
|
|
if (status.IsRunning() && status.acBufferLevel > 1) {
|
|
GstBuffer *video_buffer = NULL;
|
|
GstBuffer *audio_buffer = NULL;
|
|
GstBuffer *anc_buffer = NULL, *anc_buffer2 = NULL;
|
|
GstMapInfo video_map = GST_MAP_INFO_INIT;
|
|
GstMapInfo audio_map = GST_MAP_INFO_INIT;
|
|
GstMapInfo anc_map = GST_MAP_INFO_INIT;
|
|
GstMapInfo anc_map2 = GST_MAP_INFO_INIT;
|
|
AUTOCIRCULATE_TRANSFER transfer;
|
|
|
|
if (!have_signal) {
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_SIGNAL_CHANGE,
|
|
.signal_change = {.have_signal = TRUE,
|
|
.detected_format = current_video_format,
|
|
.vpid = vpid_a}};
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
g_cond_signal(&self->queue_cond);
|
|
have_signal = TRUE;
|
|
}
|
|
|
|
iterations_without_frame = 0;
|
|
|
|
if (gst_buffer_pool_acquire_buffer(self->buffer_pool, &video_buffer,
|
|
NULL) != GST_FLOW_OK) {
|
|
AJA_SRC_ERROR(self, STREAM, FAILED, (NULL),
|
|
("Failed to acquire video buffer"));
|
|
break;
|
|
}
|
|
|
|
if (gst_buffer_pool_acquire_buffer(self->audio_buffer_pool, &audio_buffer,
|
|
NULL) != GST_FLOW_OK) {
|
|
gst_buffer_unref(video_buffer);
|
|
AJA_SRC_ERROR(self, STREAM, FAILED, (NULL),
|
|
("Failed to acquire audio buffer"));
|
|
break;
|
|
}
|
|
|
|
if (self->vanc_mode == ::NTV2_VANCMODE_OFF &&
|
|
::NTV2DeviceCanDoCustomAnc(self->device_id)) {
|
|
if (gst_buffer_pool_acquire_buffer(self->anc_buffer_pool, &anc_buffer,
|
|
NULL) != GST_FLOW_OK) {
|
|
gst_buffer_unref(audio_buffer);
|
|
gst_buffer_unref(video_buffer);
|
|
AJA_SRC_ERROR(self, STREAM, FAILED, (NULL),
|
|
("Failed to acquire anc buffer"));
|
|
break;
|
|
}
|
|
|
|
if (self->configured_info.interlace_mode !=
|
|
GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) {
|
|
if (gst_buffer_pool_acquire_buffer(
|
|
self->anc_buffer_pool, &anc_buffer2, NULL) != GST_FLOW_OK) {
|
|
gst_buffer_unref(anc_buffer);
|
|
gst_buffer_unref(audio_buffer);
|
|
gst_buffer_unref(video_buffer);
|
|
AJA_SRC_ERROR(self, STREAM, FAILED, (NULL),
|
|
("Failed to acquire anc buffer"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gst_buffer_map(video_buffer, &video_map, GST_MAP_READWRITE);
|
|
gst_buffer_map(audio_buffer, &audio_map, GST_MAP_READWRITE);
|
|
if (anc_buffer) gst_buffer_map(anc_buffer, &anc_map, GST_MAP_READWRITE);
|
|
if (anc_buffer2)
|
|
gst_buffer_map(anc_buffer2, &anc_map2, GST_MAP_READWRITE);
|
|
|
|
transfer.acFrameBufferFormat = ::NTV2_FBF_10BIT_YCBCR;
|
|
|
|
transfer.SetVideoBuffer((ULWord *)video_map.data, video_map.size);
|
|
transfer.SetAudioBuffer((ULWord *)audio_map.data, audio_map.size);
|
|
transfer.SetAncBuffers((ULWord *)anc_map.data, anc_map.size,
|
|
(ULWord *)anc_map2.data, anc_map2.size);
|
|
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
bool transfered = true;
|
|
if (!self->device->device->AutoCirculateTransfer(self->channel,
|
|
transfer)) {
|
|
GST_WARNING_OBJECT(self, "Failed to transfer frame");
|
|
transfered = false;
|
|
}
|
|
|
|
if (anc_buffer2) gst_buffer_unmap(anc_buffer2, &anc_map2);
|
|
if (anc_buffer) gst_buffer_unmap(anc_buffer, &anc_map);
|
|
gst_buffer_unmap(audio_buffer, &audio_map);
|
|
gst_buffer_unmap(video_buffer, &video_map);
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
|
|
if (!transfered) {
|
|
gst_clear_buffer(&anc_buffer2);
|
|
gst_clear_buffer(&anc_buffer);
|
|
gst_clear_buffer(&audio_buffer);
|
|
gst_clear_buffer(&video_buffer);
|
|
continue;
|
|
}
|
|
|
|
gst_buffer_set_size(audio_buffer, transfer.GetCapturedAudioByteCount());
|
|
if (anc_buffer)
|
|
gst_buffer_set_size(anc_buffer,
|
|
transfer.GetCapturedAncByteCount(false));
|
|
if (anc_buffer2)
|
|
gst_buffer_set_size(anc_buffer2,
|
|
transfer.GetCapturedAncByteCount(true));
|
|
|
|
NTV2TCIndex tc_index;
|
|
switch (self->timecode_index) {
|
|
case GST_AJA_TIMECODE_INDEX_VITC:
|
|
tc_index = ::NTV2InputSourceToTimecodeIndex(
|
|
self->configured_input_source, true);
|
|
break;
|
|
case GST_AJA_TIMECODE_INDEX_ATC_LTC:
|
|
tc_index = ::NTV2InputSourceToTimecodeIndex(
|
|
self->configured_input_source, false);
|
|
break;
|
|
case GST_AJA_TIMECODE_INDEX_LTC1:
|
|
tc_index = ::NTV2_TCINDEX_LTC1;
|
|
break;
|
|
case GST_AJA_TIMECODE_INDEX_LTC2:
|
|
tc_index = ::NTV2_TCINDEX_LTC2;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
break;
|
|
}
|
|
|
|
NTV2_RP188 time_code;
|
|
transfer.acTransferStatus.acFrameStamp.GetInputTimeCode(time_code,
|
|
tc_index);
|
|
|
|
gint64 frame_time = transfer.acTransferStatus.acFrameStamp.acFrameTime;
|
|
gint64 now_sys = g_get_real_time();
|
|
GstClockTime now_gst = gst_clock_get_time(clock);
|
|
if (now_sys * 10 > frame_time) {
|
|
GstClockTime diff = now_sys * 1000 - frame_time * 100;
|
|
if (now_gst > diff)
|
|
now_gst -= diff;
|
|
else
|
|
now_gst = 0;
|
|
}
|
|
|
|
GstClockTime base_time =
|
|
gst_element_get_base_time(GST_ELEMENT_CAST(self));
|
|
if (now_gst > base_time)
|
|
now_gst -= base_time;
|
|
else
|
|
now_gst = 0;
|
|
|
|
// TODO: Drift detection and compensation
|
|
GST_BUFFER_PTS(video_buffer) = now_gst;
|
|
GST_BUFFER_DURATION(video_buffer) = gst_util_uint64_scale(
|
|
GST_SECOND, self->configured_info.fps_d, self->configured_info.fps_n);
|
|
GST_BUFFER_PTS(audio_buffer) = now_gst;
|
|
GST_BUFFER_DURATION(audio_buffer) = gst_util_uint64_scale(
|
|
GST_SECOND, self->configured_info.fps_d, self->configured_info.fps_n);
|
|
|
|
while (self->queue_num_frames >= self->queue_size) {
|
|
guint n = gst_queue_array_get_length(self->queue);
|
|
|
|
for (guint i = 0; i < n; i++) {
|
|
QueueItem *tmp =
|
|
(QueueItem *)gst_queue_array_peek_nth_struct(self->queue, i);
|
|
if (tmp->type == QUEUE_ITEM_TYPE_FRAME) {
|
|
GST_WARNING_OBJECT(self,
|
|
"Element queue overrun, dropping old frame");
|
|
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_FRAMES_DROPPED,
|
|
.frames_dropped = {
|
|
.driver_side = FALSE,
|
|
.timestamp_start = tmp->frame.capture_time,
|
|
.timestamp_end =
|
|
tmp->frame.capture_time +
|
|
gst_util_uint64_scale(GST_SECOND,
|
|
self->configured_info.fps_d,
|
|
self->configured_info.fps_n)}};
|
|
queue_item_clear(tmp);
|
|
gst_queue_array_drop_struct(self->queue, i, NULL);
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
self->queue_num_frames -= 1;
|
|
discont = TRUE;
|
|
g_cond_signal(&self->queue_cond);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (discont) {
|
|
GST_BUFFER_FLAG_SET(video_buffer, GST_BUFFER_FLAG_DISCONT);
|
|
GST_BUFFER_FLAG_SET(audio_buffer, GST_BUFFER_FLAG_DISCONT);
|
|
discont = FALSE;
|
|
}
|
|
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_FRAME,
|
|
.frame = {.capture_time = now_gst,
|
|
.video_buffer = video_buffer,
|
|
.audio_buffer = audio_buffer,
|
|
.anc_buffer = anc_buffer,
|
|
.anc_buffer2 = anc_buffer2,
|
|
.tc = time_code,
|
|
.detected_format =
|
|
(self->quad_mode
|
|
? ::GetQuadSizedVideoFormat(current_video_format)
|
|
: current_video_format),
|
|
.vpid = vpid_a}};
|
|
|
|
GST_TRACE_OBJECT(self, "Queuing frame %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS(now_gst));
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
self->queue_num_frames += 1;
|
|
GST_TRACE_OBJECT(self, "%u frames queued", self->queue_num_frames);
|
|
g_cond_signal(&self->queue_cond);
|
|
} else {
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
// If we don't have a frame for 32 iterations (512ms) then consider
|
|
// this as signal loss too even if the driver still reports the
|
|
// expected mode above
|
|
if (have_signal && iterations_without_frame < 32) {
|
|
iterations_without_frame++;
|
|
} else {
|
|
frames_dropped_last = G_MAXUINT64;
|
|
if (have_signal || last_detected_video_format != current_video_format) {
|
|
QueueItem item = {
|
|
.type = QUEUE_ITEM_TYPE_SIGNAL_CHANGE,
|
|
.signal_change = {.have_signal = TRUE,
|
|
.detected_format = current_video_format,
|
|
.vpid = vpid_a}};
|
|
last_detected_video_format = current_video_format;
|
|
gst_queue_array_push_tail_struct(self->queue, &item);
|
|
g_cond_signal(&self->queue_cond);
|
|
have_signal = FALSE;
|
|
discont = TRUE;
|
|
}
|
|
}
|
|
|
|
self->device->device->WaitForInputVerticalInterrupt(self->channel);
|
|
|
|
g_mutex_lock(&self->queue_lock);
|
|
}
|
|
}
|
|
|
|
out: {
|
|
// Make sure to globally lock here as the routing settings and others are
|
|
// global shared state
|
|
GstAjaNtv2DeviceLocker locker(self->device);
|
|
|
|
self->device->device->AutoCirculateStop(self->channel);
|
|
self->device->device->UnsubscribeInputVerticalEvent(self->channel);
|
|
self->device->device->DisableInputInterrupt(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->shutdown) goto restart;
|
|
g_mutex_unlock(&self->queue_lock);
|
|
|
|
gst_clear_object(&clock);
|
|
|
|
GST_DEBUG_OBJECT(self, "Stopped");
|
|
}
|