aja: Capture and Output plugin for AJA Video Systems IO devices

* aja: Initial commit of AJA capture/output plugin

* aja: Fix clang-format on aja-output-ui code

* aja: Remove script used during dev/testing

* aja: Address pull request feedback from @RytoEX

* aja: Remove the SDK sources and update CMakeLists to point to new headers-only/static libs dependency distribution.

* aja: Only build AJA plugin on x64 on macOS for now

* aja: Remove the non-English placeholder locale files. The english strings/files will be produced via crowdin, according to @ddrboxman.

* aja: Add FindLibAJANTV2.cmake script to locate the ajantv2 headers and static libs in the OBS external deps package(s). Tested on Windows x64. macOS and Linux x64 TBD.

* aja: Add ajantv2/includes to FindLibAJANTV2 include search paths

* aja: Remove commented code from aja CMakeLists

* aja: Remove debug code and comments that are no longer needed.

* aja: Fix indentation

* aja: Remove disablement of clang-format in routing table and SDIWireFormat map

* aja: Use spaces for all indentation in widget crosspoint arrays where we disable clang-format

* aja: Address code style comments made by @RytoEX

* aja: Fix uneven indentation

* aja: More fixes to if/else placement and remove superfluous comments.

* aja: Rename 'dwns' to 'deactivateWhileNotShowing' for clarity. The DeckLink plugin still uses the variable name 'dwns' and should be changed, if desired, in a separate PR.

* aja: Remove X11Extras dependency from AJA Output frontend plugin

* aja: Add patch from Jim to find AJA release/debug libs

* aja: Improve AV sync of queued video/audio sent to the AJA card in the AJA Output plugin.
master
Paul Hindt 2021-11-23 18:31:11 -08:00 committed by GitHub
parent b387047632
commit ce3ae8e423
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 9746 additions and 0 deletions

View File

@ -329,6 +329,8 @@ bundle_dylibs() {
./OBS.app/Contents/PlugIns/obs-x264.so
./OBS.app/Contents/PlugIns/text-freetype2.so
./OBS.app/Contents/PlugIns/obs-outputs.so
./OBS.app/Contents/PlugIns/aja.so
./OBS.app/Contents/PlugIns/aja-output-ui.so
)
if ! [ "${MACOS_CEF_BUILD_VERSION:-${CI_MACOS_CEF_VERSION}}" -le 3770 ]; then
"${CI_SCRIPTS}/app/dylibbundler" -cd -of -a ./OBS.app -q -f \

View File

@ -1,3 +1,4 @@
add_subdirectory(decklink-output-ui)
add_subdirectory(frontend-tools)
add_subdirectory(decklink-captions)
add_subdirectory(aja-output-ui)

View File

@ -0,0 +1,169 @@
#include "AJAOutputUI.h"
#include "aja-ui-main.h"
#include "../../../plugins/aja/aja-ui-props.hpp"
#include "../../../plugins/aja/aja-enums.hpp"
#include <ajantv2/includes/ntv2enums.h>
#include <obs-module.h>
#include <util/platform.h>
#include <util/util.hpp>
AJAOutputUI::AJAOutputUI(QWidget *parent) : QDialog(parent), ui(new Ui_Output)
{
ui->setupUi(this);
setSizeGripEnabled(true);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
propertiesView = nullptr;
previewPropertiesView = nullptr;
}
void AJAOutputUI::ShowHideDialog()
{
SetupPropertiesView();
SetupPreviewPropertiesView();
setVisible(!isVisible());
}
void AJAOutputUI::SetupPropertiesView()
{
if (propertiesView)
delete propertiesView;
obs_data_t *settings = obs_data_create();
OBSData data = load_settings(kProgramPropsFilename);
if (data) {
obs_data_apply(settings, data);
} else {
// apply default settings
obs_data_set_int(settings, kUIPropOutput.id,
static_cast<long long>(IOSelection::Invalid));
obs_data_set_int(settings, kUIPropVideoFormatSelect.id,
static_cast<long long>(NTV2_FORMAT_720p_5994));
obs_data_set_int(settings, kUIPropPixelFormatSelect.id,
static_cast<long long>(NTV2_FBF_8BIT_YCBCR));
obs_data_set_int(settings, kUIPropSDI4KTransport.id,
static_cast<long long>(
SDI4KTransport::TwoSampleInterleave));
}
// Assign an ID to the program output plugin instance for channel usage tracking
obs_data_set_string(settings, kUIPropAJAOutputID.id, kProgramOutputID);
propertiesView = new OBSPropertiesView(
settings, "aja_output",
(PropertiesReloadCallback)obs_get_output_properties, 170);
ui->propertiesLayout->addWidget(propertiesView);
obs_data_release(settings);
connect(propertiesView, SIGNAL(Changed()), this,
SLOT(PropertiesChanged()));
}
void AJAOutputUI::SaveSettings(const char *filename, obs_data_t *settings)
{
BPtr<char> modulePath =
obs_module_get_config_path(obs_current_module(), "");
os_mkdirs(modulePath);
BPtr<char> path =
obs_module_get_config_path(obs_current_module(), filename);
if (settings)
obs_data_save_json_safe(settings, path, "tmp", "bak");
}
void AJAOutputUI::SetupPreviewPropertiesView()
{
if (previewPropertiesView)
delete previewPropertiesView;
obs_data_t *settings = obs_data_create();
OBSData data = load_settings(kPreviewPropsFilename);
if (data) {
obs_data_apply(settings, data);
} else {
// apply default settings
obs_data_set_int(settings, kUIPropOutput.id,
static_cast<long long>(IOSelection::Invalid));
obs_data_set_int(settings, kUIPropVideoFormatSelect.id,
static_cast<long long>(NTV2_FORMAT_720p_5994));
obs_data_set_int(settings, kUIPropPixelFormatSelect.id,
static_cast<long long>(NTV2_FBF_8BIT_YCBCR));
obs_data_set_int(settings, kUIPropSDI4KTransport.id,
static_cast<long long>(
SDI4KTransport::TwoSampleInterleave));
}
// Assign an ID to the program output plugin instance for channel usage tracking
obs_data_set_string(settings, kUIPropAJAOutputID.id, kPreviewOutputID);
previewPropertiesView = new OBSPropertiesView(
settings, "aja_output",
(PropertiesReloadCallback)obs_get_output_properties, 170);
ui->previewPropertiesLayout->addWidget(previewPropertiesView);
obs_data_release(settings);
connect(previewPropertiesView, SIGNAL(Changed()), this,
SLOT(PreviewPropertiesChanged()));
}
void AJAOutputUI::on_outputButton_clicked()
{
SaveSettings(kProgramPropsFilename, propertiesView->GetSettings());
output_toggle();
}
void AJAOutputUI::PropertiesChanged()
{
SaveSettings(kProgramPropsFilename, propertiesView->GetSettings());
}
void AJAOutputUI::OutputStateChanged(bool active)
{
QString text;
if (active) {
text = QString(obs_module_text("Stop"));
} else {
text = QString(obs_module_text("Start"));
}
ui->outputButton->setChecked(active);
ui->outputButton->setText(text);
}
void AJAOutputUI::on_previewOutputButton_clicked()
{
SaveSettings(kPreviewPropsFilename,
previewPropertiesView->GetSettings());
preview_output_toggle();
}
void AJAOutputUI::PreviewPropertiesChanged()
{
SaveSettings(kPreviewPropsFilename,
previewPropertiesView->GetSettings());
}
void AJAOutputUI::PreviewOutputStateChanged(bool active)
{
QString text;
if (active) {
text = QString(obs_module_text("Stop"));
} else {
text = QString(obs_module_text("Start"));
}
ui->previewOutputButton->setChecked(active);
ui->previewOutputButton->setText(text);
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <QDialog>
#include "ui_output.h"
#include "../../UI/properties-view.hpp"
class AJAOutputUI : public QDialog {
Q_OBJECT
private:
OBSPropertiesView *propertiesView;
OBSPropertiesView *previewPropertiesView;
public slots:
void on_outputButton_clicked();
void PropertiesChanged();
void OutputStateChanged(bool);
void on_previewOutputButton_clicked();
void PreviewPropertiesChanged();
void PreviewOutputStateChanged(bool);
public:
std::unique_ptr<Ui_Output> ui;
AJAOutputUI(QWidget *parent);
void ShowHideDialog();
void SaveSettings(const char *filename, obs_data_t *settings);
void SetupPropertiesView();
void SetupPreviewPropertiesView();
};

View File

@ -0,0 +1,113 @@
project(aja-output-ui)
if(DISABLE_AJA)
message(STATUS "aja-output-ui plugin disabled")
return()
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
find_package(LibAJANTV2)
if (NOT LIBAJANTV2_FOUND)
message(STATUS "aja-output-ui plugin disabled (deps not found)")
return()
else()
message("aja-output-ui includes: ${LIBAJANTV2_INCLUDE_DIRS}")
message("aja-output-ui libs: ${LIBAJANTV2_LIBRARIES}")
endif()
else()
message(STATUS "aja-output-ui disabled (32-bit not supported)")
return()
endif()
if(APPLE)
find_library(COCOA Cocoa)
include_directories(${COCOA})
endif()
if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED)
link_libraries(${X11_LIBRARIES})
include_directories(${X11_INCLUDE_DIR})
find_package(Qt5X11Extras REQUIRED)
endif()
set(aja-output-ui_HEADERS
${aja-output-ui_HEADERS}
../../qt-wrappers.hpp
../../properties-view.hpp
../../properties-view.moc.hpp
../../vertical-scroll-area.hpp
../../double-slider.hpp
../../slider-ignorewheel.hpp
../../combobox-ignorewheel.hpp
../../spinbox-ignorewheel.hpp
AJAOutputUI.h
aja-ui-main.h
../../../plugins/aja/aja-ui-props.hpp
../../../plugins/aja/aja-enums.hpp)
set(aja-output-ui_SOURCES
${aja-output-ui_SOURCES}
../../qt-wrappers.cpp
../../properties-view.cpp
../../vertical-scroll-area.cpp
../../double-slider.cpp
../../slider-ignorewheel.cpp
../../combobox-ignorewheel.cpp
../../spinbox-ignorewheel.cpp
AJAOutputUI.cpp
aja-ui-main.cpp)
set(aja-output-ui_UI
${aja-output-ui_UI}
forms/output.ui)
if(WIN32)
set(MODULE_DESCRIPTION "OBS AJA Output UI")
configure_file(${CMAKE_SOURCE_DIR}/cmake/winrc/obs-module.rc.in aja-output-ui.rc)
list(APPEND aja-output-ui_SOURCES
aja-output-ui.rc)
endif()
if(APPLE)
set(aja-output-ui_PLATFORM_LIBS
${COCOA})
endif()
qt5_wrap_ui(aja-output-ui_UI_HEADERS
${aja-output-ui_UI})
add_library(aja-output-ui MODULE
${aja-output-ui_HEADERS}
${aja-output-ui_SOURCES}
${aja-output-ui_UI_HEADERS})
if (APPLE)
target_compile_definitions(aja-output-ui PUBLIC
AJAMac
AJA_MAC)
elseif(WIN32)
target_compile_definitions(aja-output-ui PUBLIC
AJA_WINDOWS
_WINDOWS
WIN32
MSWindows)
elseif(UNIX AND NOT APPLE)
target_compile_definitions(aja-output-ui PUBLIC
AJA_LINUX
AJALinux)
endif()
target_include_directories(aja-output-ui PUBLIC
${LIBAJANTV2_INCLUDE_DIRS})
target_link_libraries(aja-output-ui
${frontend-tools_PLATFORM_LIBS}
obs-frontend-api
Qt5::Widgets
libobs)
set_target_properties(aja-output-ui PROPERTIES FOLDER "frontend")
install_obs_plugin_with_data(aja-output-ui data)

View File

@ -0,0 +1,318 @@
#include "aja-ui-main.h"
#include "AJAOutputUI.h"
#include "../../../plugins/aja/aja-ui-props.hpp"
#include <obs-module.h>
#include <obs-frontend-api.h>
#include <QMainWindow>
#include <QAction>
#include <util/util.hpp>
#include <util/platform.h>
#include <media-io/video-io.h>
#include <media-io/video-frame.h>
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("aja-output-ui", "en-US")
AJAOutputUI *doUI;
bool main_output_running = false;
bool preview_output_running = false;
obs_output_t *output;
struct preview_output {
bool enabled;
obs_source_t *current_source;
obs_output_t *output;
video_t *video_queue;
gs_texrender_t *texrender;
gs_stagesurf_t *stagesurface;
uint8_t *video_data;
uint32_t video_linesize;
obs_video_info ovi;
};
static struct preview_output context = {0};
OBSData load_settings(const char *filename)
{
BPtr<char> path =
obs_module_get_config_path(obs_current_module(), filename);
BPtr<char> jsonData = os_quick_read_utf8_file(path);
if (!!jsonData) {
obs_data_t *data = obs_data_create_from_json(jsonData);
OBSData dataRet(data);
obs_data_release(data);
return dataRet;
}
return nullptr;
}
void output_stop()
{
obs_output_stop(output);
obs_output_release(output);
main_output_running = false;
doUI->OutputStateChanged(false);
}
void output_start()
{
OBSData settings = load_settings(kProgramPropsFilename);
if (settings != nullptr) {
output = obs_output_create("aja_output", kProgramOutputID,
settings, NULL);
bool started = obs_output_start(output);
obs_data_release(settings);
main_output_running = started;
doUI->OutputStateChanged(started);
if (!started)
output_stop();
}
}
void output_toggle()
{
if (main_output_running)
output_stop();
else
output_start();
}
void on_preview_scene_changed(enum obs_frontend_event event, void *param);
void render_preview_source(void *param, uint32_t cx, uint32_t cy);
void preview_output_stop()
{
obs_output_stop(context.output);
obs_output_release(context.output);
video_output_stop(context.video_queue);
obs_remove_main_render_callback(render_preview_source, &context);
obs_frontend_remove_event_callback(on_preview_scene_changed, &context);
obs_source_release(context.current_source);
obs_enter_graphics();
gs_stagesurface_destroy(context.stagesurface);
gs_texrender_destroy(context.texrender);
obs_leave_graphics();
video_output_close(context.video_queue);
preview_output_running = false;
doUI->PreviewOutputStateChanged(false);
}
void preview_output_start()
{
OBSData settings = load_settings(kPreviewPropsFilename);
if (settings != nullptr) {
context.output = obs_output_create(
"aja_output", kPreviewOutputID, settings, NULL);
obs_get_video_info(&context.ovi);
uint32_t width = context.ovi.base_width;
uint32_t height = context.ovi.base_height;
obs_enter_graphics();
context.texrender = gs_texrender_create(GS_BGRA, GS_ZS_NONE);
context.stagesurface =
gs_stagesurface_create(width, height, GS_BGRA);
obs_leave_graphics();
const video_output_info *mainVOI =
video_output_get_info(obs_get_video());
video_output_info vi = {0};
vi.format = VIDEO_FORMAT_BGRA;
vi.width = width;
vi.height = height;
vi.fps_den = context.ovi.fps_den;
vi.fps_num = context.ovi.fps_num;
vi.cache_size = 16;
vi.colorspace = mainVOI->colorspace;
vi.range = mainVOI->range;
vi.name = kPreviewOutputID;
video_output_open(&context.video_queue, &vi);
obs_frontend_add_event_callback(on_preview_scene_changed,
&context);
if (obs_frontend_preview_program_mode_active()) {
context.current_source =
obs_frontend_get_current_preview_scene();
} else {
context.current_source =
obs_frontend_get_current_scene();
}
obs_add_main_render_callback(render_preview_source, &context);
obs_output_set_media(context.output, context.video_queue,
obs_get_audio());
bool started = obs_output_start(context.output);
obs_data_release(settings);
preview_output_running = started;
doUI->PreviewOutputStateChanged(started);
if (!started)
preview_output_stop();
}
}
void preview_output_toggle()
{
if (preview_output_running)
preview_output_stop();
else
preview_output_start();
}
void on_preview_scene_changed(enum obs_frontend_event event, void *param)
{
auto ctx = (struct preview_output *)param;
switch (event) {
case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED:
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
obs_source_release(ctx->current_source);
ctx->current_source = obs_frontend_get_current_preview_scene();
break;
case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED:
obs_source_release(ctx->current_source);
ctx->current_source = obs_frontend_get_current_scene();
break;
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
if (!obs_frontend_preview_program_mode_active()) {
obs_source_release(ctx->current_source);
ctx->current_source = obs_frontend_get_current_scene();
}
break;
default:
break;
}
}
void render_preview_source(void *param, uint32_t cx, uint32_t cy)
{
UNUSED_PARAMETER(cx);
UNUSED_PARAMETER(cy);
auto ctx = (struct preview_output *)param;
if (!ctx->current_source)
return;
uint32_t width = obs_source_get_base_width(ctx->current_source);
uint32_t height = obs_source_get_base_height(ctx->current_source);
gs_texrender_reset(ctx->texrender);
if (gs_texrender_begin(ctx->texrender, width, height)) {
struct vec4 background;
vec4_zero(&background);
gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
gs_ortho(0.0f, (float)width, 0.0f, (float)height, -100.0f,
100.0f);
gs_blend_state_push();
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
obs_source_video_render(ctx->current_source);
gs_blend_state_pop();
gs_texrender_end(ctx->texrender);
struct video_frame output_frame;
if (video_output_lock_frame(ctx->video_queue, &output_frame, 1,
os_gettime_ns())) {
gs_stage_texture(
ctx->stagesurface,
gs_texrender_get_texture(ctx->texrender));
if (gs_stagesurface_map(ctx->stagesurface,
&ctx->video_data,
&ctx->video_linesize)) {
uint32_t linesize = output_frame.linesize[0];
for (uint32_t i = 0; i < ctx->ovi.base_height;
i++) {
uint32_t dst_offset = linesize * i;
uint32_t src_offset =
ctx->video_linesize * i;
memcpy(output_frame.data[0] +
dst_offset,
ctx->video_data + src_offset,
linesize);
}
gs_stagesurface_unmap(ctx->stagesurface);
ctx->video_data = nullptr;
}
video_output_unlock_frame(ctx->video_queue);
}
}
}
void addOutputUI(void)
{
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
"AJA I/O Device Output");
QMainWindow *window = (QMainWindow *)obs_frontend_get_main_window();
obs_frontend_push_ui_translation(obs_module_get_string);
doUI = new AJAOutputUI(window);
obs_frontend_pop_ui_translation();
auto cb = []() { doUI->ShowHideDialog(); };
action->connect(action, &QAction::triggered, cb);
}
static void OBSEvent(enum obs_frontend_event event, void *)
{
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
OBSData settings = load_settings(kProgramPropsFilename);
if (settings &&
obs_data_get_bool(settings, kUIPropAutoStartOutput.id))
output_start();
OBSData previewSettings = load_settings(kPreviewPropsFilename);
if (previewSettings &&
obs_data_get_bool(previewSettings,
kUIPropAutoStartOutput.id))
preview_output_start();
} else if (event == OBS_FRONTEND_EVENT_EXIT) {
if (main_output_running)
output_stop();
if (preview_output_running)
preview_output_stop();
}
}
bool obs_module_load(void)
{
addOutputUI();
obs_frontend_add_event_callback(OBSEvent, nullptr);
return true;
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <obs.hpp>
static const char *kProgramPropsFilename = "ajaOutputProps.json";
static const char *kPreviewPropsFilename = "ajaPreviewOutputProps.json";
OBSData load_settings(const char *filename);
void output_toggle();
void preview_output_toggle();

View File

@ -0,0 +1,3 @@
AJAOutput.Device="AJA I/O Device Output"
AJAOutput.ProgramOutput="Program Output"
AJAOutput.PreviewOutput="Preview Output"

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Output</class>
<widget class="QDialog" name="Output">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>540</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>AJAOutput.Device</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<property name="modal">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>AJAOutput.ProgramOutput</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="propertiesLayout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="outputButton">
<property name="text">
<string>Start</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>AJAOutput.PreviewOutput</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="previewPropertiesLayout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="previewOutputButton">
<property name="text">
<string>Start</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,113 @@
# Once done these will be defined:
#
# LIBAJANTV2_FOUND
# LIBAJANTV2_INCLUDE_DIRS
# LIBAJANTV2_LIBRARIES
#
find_package(PkgConfig QUIET)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_AJA QUIET ajantv2)
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_lib_suffix 64)
else()
set(_lib_suffix 32)
endif()
find_path(AJA_LIBRARIES_INCLUDE_DIR
NAMES ajalibraries
HINTS
ENV AJASDKPath${_lib_suffix}
ENV AJASDKPath
ENV DepsPath${_lib_suffix}
ENV DepsPath
${AJASDKPath${_lib_suffix}}
${AJASDKPath}
${DepsPath${_lib_suffix}}
${DepsPath}
${_AJA_NTV2_INCLUDE_DIRS}
PATHS
/usr/include /usr/local/include /opt/local/include /sw/include
PATH_SUFFIXES
include)
find_library(AJA_NTV2_LIB
NAMES ${_AJA_NTV2_LIBRARIES} ajantv2 libajantv2
HINTS
ENV AJASDKPath${_lib_suffix}
ENV AJASDKPath
ENV DepsPath${_lib_suffix}
ENV DepsPath
${AJASDKPath${_lib_suffix}}
${AJASDKPath}
${DepsPath${_lib_suffix}}
${DepsPath}
${_AJA_NTV2_LIBRARY_DIRS}
PATHS
/usr/lib /usr/local/lib /opt/local/lib /sw/lib
PATH_SUFFIXES
lib${_lib_suffix} lib
libs${_lib_suffix} libs
bin${_lib_suffix} bin
../lib${_lib_suffix} ../lib
../libs${_lib_suffix} ../libs
../bin${_lib_suffix} ../bin)
find_library(AJA_NTV2_DEBUG_LIB
NAMES ajantv2d libajantv2d
HINTS
ENV AJASDKPath${_lib_suffix}
ENV AJASDKPath
ENV DepsPath${_lib_suffix}
ENV DepsPath
${AJASDKPath${_lib_suffix}}
${AJASDKPath}
${DepsPath${_lib_suffix}}
${DepsPath}
${_AJA_NTV2_LIBRARY_DIRS}
PATHS
/usr/lib /usr/local/lib /opt/local/lib /sw/lib
PATH_SUFFIXES
lib${_lib_suffix} lib
libs${_lib_suffix} libs
bin${_lib_suffix} bin
../lib${_lib_suffix} ../lib
../libs${_lib_suffix} ../libs
../bin${_lib_suffix} ../bin)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibAJANTV2 DEFAULT_MSG AJA_LIBRARIES_INCLUDE_DIR AJA_NTV2_LIB)
mark_as_advanced(AJA_LIBRARIES_INCLUDE_DIR AJA_NTV2_LIB)
if(LIBAJANTV2_FOUND)
set(AJA_LIBRARIES_INCLUDE_DIR
${AJA_LIBRARIES_INCLUDE_DIR}/ajalibraries)
set(AJA_LIBRARIES_INCLUDE_DIRS
${AJA_LIBRARIES_INCLUDE_DIR}
${AJA_LIBRARIES_INCLUDE_DIR}/ajaanc
${AJA_LIBRARIES_INCLUDE_DIR}/ajabase
${AJA_LIBRARIES_INCLUDE_DIR}/ajantv2
${AJA_LIBRARIES_INCLUDE_DIR}/ajantv2/includes
${AJA_LIBRARIES_INCLUDE_DIR}/ajantv2/src)
if (WIN32)
set(AJA_LIBRARIES_INCLUDE_DIRS
${AJA_LIBRARIES_INCLUDE_DIRS}
${AJA_LIBRARIES_INCLUDE_DIR}/ajantv2/src/win)
elseif (APPLE)
set(AJA_LIBRARIES_INCLUDE_DIRS
${AJA_LIBRARIES_INCLUDE_DIRS}
${AJA_LIBRARIES_INCLUDE_DIR}/ajantv2/src/mac)
elseif(UNIX AND NOT APPLE)
set(AJA_LIBRARIES_INCLUDE_DIRS
${AJA_LIBRARIES_INCLUDE_DIRS}
${AJA_LIBRARIES_INCLUDE_DIR}/ajantv2/src/lin)
endif()
set(LIBAJANTV2_LIBRARIES ${AJA_NTV2_LIB})
if(AJA_NTV2_DEBUG_LIB STREQUAL "AJA_NTV2_DEBUG_LIB-NOTFOUND")
set(AJA_NTV2_DEBUG_LIB ${AJA_NTV2_LIB})
endif()
set(LIBAJANTV2_DEBUG_LIBRARIES ${AJA_NTV2_DEBUG_LIB})
set(LIBAJANTV2_INCLUDE_DIRS ${AJA_LIBRARIES_INCLUDE_DIRS})
endif()

View File

@ -38,5 +38,6 @@ find . -type d \( -path ./deps \
-o -path ./plugins/mac-syphon/syphon-framework \
-o -path ./plugins/obs-outputs/ftl-sdk \
-o -path ./plugins/obs-vst \
-o -path ./plugins/aja/sdk \
-o -path ./build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \
| xargs -L100 -P${NPROC} ${CLANG_FORMAT} -i -style=file -fallback-style=none

View File

@ -91,3 +91,4 @@ add_subdirectory(obs-transitions)
add_subdirectory(obs-text)
add_subdirectory(rtmp-services)
add_subdirectory(text-freetype2)
add_subdirectory(aja)

102
plugins/aja/CMakeLists.txt Normal file
View File

@ -0,0 +1,102 @@
project(aja)
if(DISABLE_AJA)
message(STATUS "aja plugin disabled")
return()
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
find_package(LibAJANTV2)
if (NOT LIBAJANTV2_FOUND)
message(STATUS "aja plugin disabled (deps not found)")
return()
else()
message("aja plugin includes: ${LIBAJANTV2_INCLUDE_DIRS}")
message("aja plugin libs: ${LIBAJANTV2_LIBRARIES}")
message("aja plugin debug libs: ${LIBAJANTV2_DEBUG_LIBRARIES}")
endif()
else()
message(STATUS "aja plugin disabled (32-bit not supported)")
return()
endif()
set(aja_INCLUDE_DIRS
${LIBAJANTV2_INCLUDE_DIRS})
set(aja_LIBRARIES
$<IF:$<CONFIG:Debug>,${LIBAJANTV2_DEBUG_LIBRARIES},${LIBAJANTV2_LIBRARIES}>
libobs)
set(aja_SOURCES
main.cpp
aja-card-manager.cpp
aja-common.cpp
aja-output.cpp
aja-props.cpp
aja-routing.cpp
aja-source.cpp
aja-widget-io.cpp)
set(aja_HEADERS
aja-card-manager.hpp
aja-common.hpp
aja-enums.hpp
aja-ui-props.hpp
aja-output.hpp
aja-props.hpp
aja-routing.hpp
aja-source.hpp
aja-widget-io.hpp)
# macOS
if(APPLE)
set(aja_COMPILE_DEFS
AJAMac
AJA_MAC)
find_library(IOKIT_FRAMEWORK Iokit)
find_library(COREFOUNDATION_LIBRARY CoreFoundation)
find_library(APPKIT_FRAMEWORK AppKit)
list(APPEND aja_LIBRARIES
${IOKIT_FRAMEWORK}
${COREFOUNDATION_LIBRARY}
${APPKIT_FRAMEWORK})
# Windows
elseif(WIN32)
set(aja_COMPILE_DEFS
AJA_WINDOWS
_WINDOWS
WIN32
MSWindows)
if(CMAKE_BUILD_TYPE STREQUAL Debug)
list(APPEND aja_COMPILE_DEFS
_DEBUG)
else()
list(APPEND aja_COMPILE_DEFS
NDEBUG)
endif()
list(APPEND aja_LIBRARIES
ws2_32.lib
setupapi.lib
Winmm.lib
netapi32.lib
Shlwapi.lib)
# Linux
elseif(UNIX AND NOT APPLE)
set(aja_COMPILE_DEFS
AJA_LINUX
AJALinux)
endif()
add_library(aja MODULE ${aja_SOURCES} ${aja_HEADERS})
target_include_directories(aja PUBLIC ${aja_INCLUDE_DIRS})
target_link_libraries(aja PUBLIC ${aja_LIBRARIES})
target_compile_definitions(aja PUBLIC ${aja_COMPILE_DEFS})
set_target_properties(aja PROPERTIES FOLDER
"plugins")
install_obs_plugin_with_data(aja data)

78
plugins/aja/README.md Normal file
View File

@ -0,0 +1,78 @@
# AJA I/O Device Capture and Output Plugin for OBS
A capture and outptu plugin for OBS for use with AJA NTV2 I/O devices. The plugin communicates with the AJA device driver via the open-source AJA NTV2 SDK (MIT license). See instructions below for installing the AJA device driver.
---
## AJA Device Driver Installation
Usage of this plugin with an AJA I/O device requires installation of the AJA Device Driver.
Follow one of the methods below to install the AJA device driver for your platform.
### Method 1 - AJA Software Installer (Windows/macOS/Ubuntu Linux)
Pre-built driver packages for Windows, macOS and Ubuntu Linux are provided as a component of the AJA Software Installer. Navigate to the AJA Support page and download the latest AJA Software Installer for your platform:
https://www.aja.com/support
### Method 2 - Build From Sources (Linux)
The Linux verison of the AJA device driver can be built from the MIT-licensed AJA NTV2 SDK sources found here:
https://github.com/aja-video/ntv2
- Clone the ntv2 repository.
- `cd` into the `ntv2/ajadriver/linux` directory
- Run `make`
- `cd` into the `ntv2/bin` directory and run `sudo sh load_ajantv2` to install the AJA kernel module.
NOTE: To uniunstall the Linux kernel module, `cd` into the `ntv2/bin` directory and run `sudo sh unload_ajantv2`.
---
## How to use the plugin
- Capture plugin instances can be added from `Sources -> Add -> AJA I/O Device Capture`.
- The Output plugin is accessible from `Tools -> AJA I/O Device Output`.
## Updating the plugin AJA NTV2 SDK code
The AJA NTV2 SDK is open-source and licensed under the MIT license. A copy of the SDK is included in the source tree of the OBS AJA plugin directory.
To update the plugin's working copy of the AJA NTV2 SDK:
1. Clone the AJA NTV2 SDK from https://github.com/aja-video/ntv2
1. Place the contents of the ntv2 directory into `plugins/aja/sdk` within the cloned OBS repository.
You should end up with a directory structure like:
```
plugins/aja/sdk
/ajadriver
/ajalibraries
/cmake
/CMakeLists.txt
/LICENSE
/README.md
```
## Known Issues/TODOS
1. Fix AV sync in the AJA I/O Output plugin with certain OBS/device frame rate combinations.
1. Improve frame tearing in the AJA I/O Device Capture plugin with certain OBS/device frame rate combinations.
1. Capture plugins not always auto-detecting signal properly after disconnecting and reconnecting the physical SDI/HDMI cable from the device.
1. Improve handling of certain invalid video/pixel/SDI 4K transport setting combinations.
1. Optimize framebuffer memory usage on the device.
1. Add support for Analog I/O capture and output on supported devices.
1. Luminance is too bright/washed out in output plugin video.
1. Certain combinations of video format with RGB pixel format needs additional testing.
1. 4K video formats and SDI 4K transports need more testing in general.
1. 8K video not yet implemented.
1. Add support for selecting an alternate audio input source on supported devices (i.e. Microphone Input on AJA io4K+, analog audio, AES audio, etc). Capture and output currently default to using up to 8 channels of embedded SDI/HDMI audio.
1. All translation strings are placeholders and default to English. Translation strings needed for additional language support.
---
_Development Credits_
- ddrboxman
- paulh-aja

View File

@ -0,0 +1,610 @@
#include "aja-card-manager.hpp"
#include "aja-common.hpp"
#include "aja-output.hpp"
#include "aja-source.hpp"
#include "obs-properties.h"
#include <util/base.h>
#include <ajantv2/includes/ntv2card.h>
#include <ajantv2/includes/ntv2devicescanner.h>
#include <ajantv2/includes/ntv2devicefeatures.h>
#include <ajantv2/includes/ntv2utils.h>
#include <ajabase/system/process.h>
#include <ajabase/system/systemtime.h>
static const uint32_t kStreamingAppID = NTV2_FOURCC('O', 'B', 'S', ' ');
namespace aja {
CardManager &CardManager::Instance()
{
static CardManager instance;
return instance;
}
CardEntry::CardEntry(uint32_t cardIndex, const std::string &cardID)
: mCardIndex{cardIndex},
mCardID{cardID},
mCard{std::make_unique<CNTV2Card>((UWord)cardIndex)},
mChannelPwnz{},
mMutex{}
{
}
CardEntry::~CardEntry()
{
if (mCard) {
mCard->Close();
mCard.reset();
}
}
CNTV2Card *CardEntry::GetCard()
{
return mCard.get();
}
bool CardEntry::Initialize()
{
if (!mCard) {
blog(LOG_ERROR, "Invalid card instance %s!", mCardID.c_str());
return false;
}
const NTV2DeviceID deviceID = mCard->GetDeviceID();
// Briefly enter Standard Tasks mode to reset card via AJA Daemon/Agent.
auto taskMode = NTV2_STANDARD_TASKS;
mCard->GetEveryFrameServices(taskMode);
if (taskMode != NTV2_STANDARD_TASKS) {
mCard->SetEveryFrameServices(NTV2_STANDARD_TASKS);
AJATime::Sleep(100);
}
mCard->SetEveryFrameServices(NTV2_OEM_TASKS);
const int32_t obsPid = (int32_t)AJAProcess::GetPid();
mCard->AcquireStreamForApplicationWithReference((ULWord)kStreamingAppID,
obsPid);
mCard->SetSuspendHostAudio(true);
mCard->ClearRouting();
if (NTV2DeviceCanDoMultiFormat(deviceID)) {
mCard->SetMultiFormatMode(true);
}
mCard->SetReference(NTV2_REFERENCE_FREERUN);
for (UWord i = 0; i < aja::CardNumAudioSystems(deviceID); i++) {
mCard->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF,
static_cast<NTV2AudioSystem>(i));
}
auto numFramestores = aja::CardNumFramestores(deviceID);
for (UWord i = 0; i < NTV2DeviceGetNumVideoInputs(deviceID); i++) {
mCard->SetInputFrame(static_cast<NTV2Channel>(i), 0xff);
// Disable 3G Level B converter by default
if (NTV2DeviceCanDo3GLevelConversion(deviceID)) {
mCard->SetSDIInLevelBtoLevelAConversion(
static_cast<NTV2Channel>(i), false);
}
}
// SDI Outputs Default State
for (UWord i = 0; i < NTV2DeviceGetNumVideoOutputs(deviceID); i++) {
auto channel = GetNTV2ChannelForIndex(i);
if (NTV2DeviceCanDo3GOut(deviceID, i)) {
mCard->SetSDIOut3GEnable(channel, true);
mCard->SetSDIOut3GbEnable(channel, false);
}
if (NTV2DeviceCanDo12GOut(deviceID, i)) {
mCard->SetSDIOut6GEnable(channel, false);
mCard->SetSDIOut12GEnable(channel, false);
}
if (NTV2DeviceCanDo3GLevelConversion(deviceID)) {
mCard->SetSDIOutLevelAtoLevelBConversion(i, false);
mCard->SetSDIOutRGBLevelAConversion(i, false);
}
}
for (UWord i = 0; i < numFramestores; i++) {
auto channel = GetNTV2ChannelForIndex(i);
if (isAutoCirculateRunning(channel)) {
mCard->AutoCirculateStop(channel, true);
}
mCard->SetVideoFormat(NTV2_FORMAT_1080p_5994_A, false, false,
channel);
mCard->SetFrameBufferFormat(channel, NTV2_FBF_8BIT_YCBCR);
mCard->DisableChannel(channel);
}
blog(LOG_DEBUG, "NTV2 Card Initialized: %s", mCardID.c_str());
return true;
}
uint32_t CardEntry::GetCardIndex() const
{
return mCardIndex;
}
std::string CardEntry::GetCardID() const
{
return mCardID;
}
std::string CardEntry::GetDisplayName() const
{
if (mCard) {
std::ostringstream oss;
oss << mCard->GetIndexNumber() << " - "
<< mCard->GetModelName();
const std::string &serial = GetSerial();
if (!serial.empty())
oss << " (" << serial << ")";
return oss.str();
}
// very bad if we get here...
return "Unknown";
}
std::string CardEntry::GetSerial() const
{
std::string serial;
if (mCard)
mCard->GetSerialNumberString(serial);
return serial;
}
NTV2DeviceID CardEntry::GetDeviceID() const
{
NTV2DeviceID id = DEVICE_ID_NOTFOUND;
if (mCard)
id = mCard->GetDeviceID();
return id;
}
bool CardEntry::ChannelReady(NTV2Channel chan, const std::string &owner) const
{
const std::lock_guard<std::mutex> lock(mMutex);
bool found = false;
for (const auto &pwn : mChannelPwnz) {
if (pwn.second & (1 << static_cast<int32_t>(chan))) {
return pwn.first == owner;
}
}
return !found;
}
bool CardEntry::AcquireChannel(NTV2Channel chan, NTV2Mode mode,
const std::string &owner)
{
bool acquired = false;
if (ChannelReady(chan, owner)) {
const std::lock_guard<std::mutex> lock(mMutex);
if (mChannelPwnz.find(owner) != mChannelPwnz.end()) {
if (mChannelPwnz[owner] &
(1 << static_cast<int32_t>(chan))) {
acquired = true;
} else {
mChannelPwnz[owner] |=
(1 << static_cast<int32_t>(chan));
acquired = true;
}
} else {
mChannelPwnz[owner] |=
(1 << static_cast<int32_t>(chan));
acquired = true;
}
// Acquire interrupt handles
if (acquired && mCard) {
if (mode == NTV2_MODE_CAPTURE) {
mCard->EnableInputInterrupt(chan);
mCard->SubscribeInputVerticalEvent(chan);
} else if (mode == NTV2_MODE_DISPLAY) {
mCard->EnableOutputInterrupt(chan);
mCard->SubscribeOutputVerticalEvent(chan);
}
}
}
return acquired;
}
bool CardEntry::ReleaseChannel(NTV2Channel chan, NTV2Mode mode,
const std::string &owner)
{
const std::lock_guard<std::mutex> lock(mMutex);
for (const auto &pwn : mChannelPwnz) {
if (pwn.first == owner) {
if (mChannelPwnz[owner] &
(1 << static_cast<int32_t>(chan))) {
mChannelPwnz[owner] ^=
(1 << static_cast<int32_t>(chan));
// Release interrupt handles
if (mCard) {
if (mode == NTV2_MODE_CAPTURE) {
mCard->DisableInputInterrupt(
chan);
mCard->UnsubscribeInputVerticalEvent(
chan);
} else if (mode == NTV2_MODE_DISPLAY) {
mCard->DisableOutputInterrupt(
chan);
mCard->UnsubscribeOutputVerticalEvent(
chan);
}
}
return true;
}
}
}
return false;
}
bool CardEntry::InputSelectionReady(IOSelection io, NTV2DeviceID id,
const std::string &owner) const
{
UNUSED_PARAMETER(id);
NTV2InputSourceSet inputSources;
aja::IOSelectionToInputSources(io, inputSources);
if (inputSources.size() > 0) {
size_t channelsReady = 0;
for (auto &&src : inputSources) {
auto channel = NTV2InputSourceToChannel(src);
if (ChannelReady(channel, owner))
channelsReady++;
}
if (channelsReady == inputSources.size())
return true;
}
return false;
}
bool CardEntry::OutputSelectionReady(IOSelection io, NTV2DeviceID id,
const std::string &owner) const
{
/* Handle checking special case outputs before all other outputs.
* 1. HDMI Monitor uses framestore 4
* 2. SDI Monitor on Io 4K/Io 4K Plus, etc. uses framestore 4.
* 3. Everything else...
*/
if (aja::CardCanDoHDMIMonitorOutput(id) &&
io == IOSelection::HDMIMonitorOut) {
NTV2Channel hdmiMonChannel = NTV2_CHANNEL4;
return ChannelReady(hdmiMonChannel, owner);
} else if (aja::CardCanDoSDIMonitorOutput(id) &&
io == IOSelection::SDI5) {
NTV2Channel sdiMonChannel = NTV2_CHANNEL4;
return ChannelReady(sdiMonChannel, owner);
} else {
NTV2OutputDestinations outputDests;
aja::IOSelectionToOutputDests(io, outputDests);
if (outputDests.size() > 0) {
size_t channelsReady = 0;
for (auto &&dst : outputDests) {
auto channel =
NTV2OutputDestinationToChannel(dst);
if (ChannelReady(channel, owner))
channelsReady++;
}
if (channelsReady == outputDests.size())
return true;
}
}
return false;
}
bool CardEntry::AcquireInputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner)
{
UNUSED_PARAMETER(id);
NTV2InputSourceSet inputSources;
aja::IOSelectionToInputSources(io, inputSources);
std::vector<NTV2Channel> acquiredChannels;
for (auto &&src : inputSources) {
auto acqChan = NTV2InputSourceToChannel(src);
if (AcquireChannel(acqChan, NTV2_MODE_CAPTURE, owner)) {
blog(LOG_DEBUG, "Source %s acquired channel %s",
owner.c_str(),
NTV2ChannelToString(acqChan).c_str());
acquiredChannels.push_back(acqChan);
} else {
blog(LOG_DEBUG,
"Source %s could not acquire channel %s",
owner.c_str(),
NTV2ChannelToString(acqChan).c_str());
}
}
// Release channels if we couldn't acquire all required channels.
if (acquiredChannels.size() != inputSources.size()) {
for (auto &&ac : acquiredChannels) {
ReleaseChannel(ac, NTV2_MODE_CAPTURE, owner);
}
}
return acquiredChannels.size() == inputSources.size();
}
bool CardEntry::ReleaseInputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner)
{
UNUSED_PARAMETER(id);
NTV2InputSourceSet currentInputSources;
aja::IOSelectionToInputSources(io, currentInputSources);
uint32_t releasedCount = 0;
for (auto &&src : currentInputSources) {
auto relChan = NTV2InputSourceToChannel(src);
if (ReleaseChannel(relChan, NTV2_MODE_CAPTURE, owner)) {
blog(LOG_DEBUG, "Released Channel %s",
NTV2ChannelToString(relChan).c_str());
releasedCount++;
}
}
return releasedCount == currentInputSources.size();
}
bool CardEntry::AcquireOutputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner)
{
std::vector<NTV2Channel> acquiredChannels;
NTV2OutputDestinations outputDests;
aja::IOSelectionToOutputDests(io, outputDests);
// Handle acquiring special case outputs --
// HDMI Monitor uses framestore 4
if (aja::CardCanDoHDMIMonitorOutput(id) &&
io == IOSelection::HDMIMonitorOut) {
NTV2Channel hdmiMonChannel = NTV2_CHANNEL4;
if (AcquireChannel(hdmiMonChannel, NTV2_MODE_DISPLAY, owner)) {
blog(LOG_DEBUG, "Output %s acquired channel %s",
owner.c_str(),
NTV2ChannelToString(hdmiMonChannel).c_str());
acquiredChannels.push_back(hdmiMonChannel);
} else {
blog(LOG_DEBUG,
"Output %s could not acquire channel %s",
owner.c_str(),
NTV2ChannelToString(hdmiMonChannel).c_str());
}
} else if (aja::CardCanDoSDIMonitorOutput(id) &&
io == IOSelection::SDI5) {
// SDI Monitor on io4K/io4K+/etc. uses framestore 4
NTV2Channel sdiMonChannel = NTV2_CHANNEL4;
if (AcquireChannel(sdiMonChannel, NTV2_MODE_DISPLAY, owner)) {
blog(LOG_DEBUG, "Output %s acquired channel %s",
owner.c_str(),
NTV2ChannelToString(sdiMonChannel).c_str());
acquiredChannels.push_back(sdiMonChannel);
} else {
blog(LOG_DEBUG,
"Output %s could not acquire channel %s",
owner.c_str(),
NTV2ChannelToString(sdiMonChannel).c_str());
}
} else {
// Handle acquiring all other channels
for (auto &&dst : outputDests) {
auto acqChan = NTV2OutputDestinationToChannel(dst);
if (AcquireChannel(acqChan, NTV2_MODE_DISPLAY, owner)) {
acquiredChannels.push_back(acqChan);
blog(LOG_DEBUG, "Output %s acquired channel %s",
owner.c_str(),
NTV2ChannelToString(acqChan).c_str());
} else {
blog(LOG_DEBUG,
"Output %s could not acquire channel %s",
owner.c_str(),
NTV2ChannelToString(acqChan).c_str());
}
}
// Release channels if we couldn't acquire all required channels.
if (acquiredChannels.size() != outputDests.size()) {
for (auto &&ac : acquiredChannels) {
ReleaseChannel(ac, NTV2_MODE_DISPLAY, owner);
}
}
}
return acquiredChannels.size() == outputDests.size();
}
bool CardEntry::ReleaseOutputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner)
{
NTV2OutputDestinations currentOutputDests;
aja::IOSelectionToOutputDests(io, currentOutputDests);
uint32_t releasedCount = 0;
// Handle releasing special case outputs --
// HDMI Monitor uses framestore 4
if (aja::CardCanDoHDMIMonitorOutput(id) &&
io == IOSelection::HDMIMonitorOut) {
NTV2Channel hdmiMonChannel = NTV2_CHANNEL4;
if (ReleaseChannel(hdmiMonChannel, NTV2_MODE_DISPLAY, owner)) {
blog(LOG_DEBUG, "Released Channel %s",
NTV2ChannelToString(hdmiMonChannel).c_str());
releasedCount++;
}
} else if (aja::CardCanDoSDIMonitorOutput(id) &&
io == IOSelection::SDI5) {
// SDI Monitor on io4K/io4K+/etc. uses framestore 4
NTV2Channel sdiMonChannel = NTV2_CHANNEL4;
if (ReleaseChannel(sdiMonChannel, NTV2_MODE_DISPLAY, owner)) {
blog(LOG_DEBUG, "Released Channel %s",
NTV2ChannelToString(sdiMonChannel).c_str());
releasedCount++;
}
} else {
// Release all other channels
for (auto &&dst : currentOutputDests) {
auto relChan = NTV2OutputDestinationToChannel(dst);
if (ReleaseChannel(relChan, NTV2_MODE_DISPLAY, owner)) {
blog(LOG_DEBUG, "Released Channel %s",
NTV2ChannelToString(relChan).c_str());
releasedCount++;
}
}
}
return releasedCount == currentOutputDests.size();
}
bool CardEntry::UpdateChannelOwnerName(const std::string &oldName,
const std::string &newName)
{
const std::lock_guard<std::mutex> lock(mMutex);
for (const auto &pwn : mChannelPwnz) {
if (pwn.first == oldName) {
mChannelPwnz.insert(std::pair<std::string, int32_t>{
newName, pwn.second});
mChannelPwnz.erase(oldName);
return true;
}
}
return false;
}
bool CardEntry::isAutoCirculateRunning(NTV2Channel chan)
{
if (!mCard)
return false;
AUTOCIRCULATE_STATUS acStatus;
if (mCard->AutoCirculateGetStatus(chan, acStatus)) {
if (acStatus.acState != NTV2_AUTOCIRCULATE_RUNNING &&
acStatus.acState != NTV2_AUTOCIRCULATE_STARTING &&
acStatus.acState != NTV2_AUTOCIRCULATE_PAUSED &&
acStatus.acState != NTV2_AUTOCIRCULATE_INIT) {
return false;
}
}
return true;
}
void CardManager::ClearCardEntries()
{
const std::lock_guard<std::mutex> lock(mMutex);
for (auto &&entry : mCardEntries) {
CNTV2Card *card = entry.second->GetCard();
if (card) {
card->SetSuspendHostAudio(false);
card->SetEveryFrameServices(NTV2_STANDARD_TASKS);
// Workaround for AJA internal bug #11378
// Set HDMI output back to Audio System 1 on card release
if (NTV2DeviceGetNumHDMIVideoOutputs(
card->GetDeviceID()) > 0) {
card->SetHDMIOutAudioSource8Channel(
NTV2_AudioChannel1_8,
NTV2_AUDIOSYSTEM_1);
}
int32_t pid = (int32_t)AJAProcess::GetPid();
card->ReleaseStreamForApplicationWithReference(
(ULWord)kStreamingAppID, pid);
}
}
mCardEntries.clear();
}
void CardManager::EnumerateCards()
{
const std::lock_guard<std::mutex> lock(mMutex);
CNTV2DeviceScanner scanner;
for (const auto &iter : scanner.GetDeviceInfoList()) {
CNTV2Card card((UWord)iter.deviceIndex);
const std::string &cardID = aja::MakeCardID(card);
// New Card Entry
if (mCardEntries.find(cardID) == mCardEntries.end()) {
CardEntryPtr cardEntry = std::make_shared<CardEntry>(
iter.deviceIndex, cardID);
if (cardEntry && cardEntry->Initialize())
mCardEntries.emplace(cardID, cardEntry);
} else {
// Card fell off of the bus and came back with a new physical index?
auto currEntry = mCardEntries[cardID];
if (currEntry) {
if (currEntry->GetCardIndex() !=
iter.deviceIndex) {
mCardEntries.erase(cardID);
CardEntryPtr cardEntry =
std::make_shared<CardEntry>(
iter.deviceIndex,
cardID);
if (cardEntry &&
cardEntry->Initialize())
mCardEntries.emplace(cardID,
cardEntry);
}
}
}
}
}
size_t CardManager::NumCardEntries() const
{
const std::lock_guard<std::mutex> lock(mMutex);
return mCardEntries.size();
}
const CardEntryPtr CardManager::GetCardEntry(const std::string &cardID) const
{
const std::lock_guard<std::mutex> lock(mMutex);
for (const auto &entry : mCardEntries) {
if (entry.second && entry.second->GetCardID() == cardID)
return entry.second;
}
return nullptr;
}
const CardEntries &CardManager::GetCardEntries() const
{
const std::lock_guard<std::mutex> lock(mMutex);
return mCardEntries;
}
} // aja

View File

@ -0,0 +1,103 @@
#pragma once
#include "aja-props.hpp"
#include <obs-module.h>
#include <ajantv2/includes/ntv2enums.h>
#include <ajantv2/includes/ntv2publicinterface.h>
#include <memory>
#include <map>
#include <mutex>
#include <vector>
class CNTV2Card;
class AJAOutput;
class AJASource;
namespace aja {
using ChannelPwnz = std::map<std::string, int32_t>;
/* A CardEntry for each physical AJA card is added to a map retained by the CardManager.
* Each CardEntry itself maintains a map representing the AJA card "Channels" the are
* owned by a particular capture or output plugin instance. The Channel ownership map is
* then used to determine which "IOSelection" (i.e. SDI1, SDI3+4, HDMI Monitor Output, etc.)
* drop-down menu items are either accessible or grayed out in the capture and output plugin UIs.
*/
class CardEntry {
public:
CardEntry(uint32_t cardIndex, const std::string &cardID);
virtual ~CardEntry();
CNTV2Card *GetCard();
virtual bool Initialize();
virtual uint32_t GetCardIndex() const;
virtual std::string GetCardID() const;
virtual std::string GetDisplayName() const;
virtual std::string GetSerial() const;
virtual NTV2DeviceID GetDeviceID() const;
virtual bool ChannelReady(NTV2Channel chan,
const std::string &owner) const;
virtual bool AcquireChannel(NTV2Channel chan, NTV2Mode mode,
const std::string &owner);
virtual bool ReleaseChannel(NTV2Channel chan, NTV2Mode mode,
const std::string &owner);
virtual bool InputSelectionReady(IOSelection io, NTV2DeviceID id,
const std::string &owner) const;
virtual bool OutputSelectionReady(IOSelection io, NTV2DeviceID id,
const std::string &owner) const;
virtual bool AcquireInputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner);
virtual bool ReleaseInputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner);
virtual bool AcquireOutputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner);
virtual bool ReleaseOutputSelection(IOSelection io, NTV2DeviceID id,
const std::string &owner);
virtual bool UpdateChannelOwnerName(const std::string &oldName,
const std::string &newName);
private:
virtual bool isAutoCirculateRunning(NTV2Channel);
protected:
uint32_t mCardIndex;
std::string mCardID;
std::unique_ptr<CNTV2Card> mCard;
ChannelPwnz mChannelPwnz;
mutable std::mutex mMutex;
};
using CardEntryPtr = std::shared_ptr<CardEntry>;
using CardEntries = std::map<std::string, CardEntryPtr>;
/* The CardManager enumerates the physical AJA cards in the system, reverts them to a default
* state on exit, and maintains a map of CardEntry objects corresponding to each physical card.
* Each CardEntry object holds a pointer to the CNTV2Card instance and a map of NTV2Channels
* that are "owned" by each plugin instance. NTV2Channels are essentially treated as indices
* for various firwmare Widgets and sub-systems throughout the AJA NTV2 SDK.
*/
class CardManager {
public:
static CardManager &Instance();
void ClearCardEntries();
void EnumerateCards();
size_t NumCardEntries() const;
const CardEntryPtr GetCardEntry(const std::string &cardID) const;
const CardEntries &GetCardEntries() const;
private:
CardManager() = default;
~CardManager() = default;
CardManager(const CardManager &) = delete;
CardManager(const CardManager &&) = delete;
CardManager &operator=(const CardManager &) = delete;
CardManager &operator=(const CardManager &&) = delete;
CardEntries mCardEntries;
mutable std::mutex mMutex;
};
} // aja

892
plugins/aja/aja-common.cpp Normal file
View File

@ -0,0 +1,892 @@
#include "aja-card-manager.hpp"
#include "aja-common.hpp"
#include "aja-ui-props.hpp"
#include "aja-props.hpp"
#include <ajantv2/includes/ntv2devicescanner.h>
#include <ajantv2/includes/ntv2devicefeatures.h>
#include <ajantv2/includes/ntv2utils.h>
void filter_io_selection_input_list(const std::string &cardID,
const std::string &channelOwner,
obs_property_t *list)
{
auto &cardManager = aja::CardManager::Instance();
auto cardEntry = cardManager.GetCardEntry(cardID);
if (!cardEntry) {
blog(LOG_DEBUG,
"filter_io_selection_input_list: Card Entry not found for %s",
cardID.c_str());
return;
}
NTV2DeviceID deviceID = DEVICE_ID_NOTFOUND;
CNTV2Card *card = cardEntry->GetCard();
if (card)
deviceID = card->GetDeviceID();
// Gray out the IOSelection list items that are in use by other plugin instances
for (size_t idx = 0; idx < obs_property_list_item_count(list); idx++) {
auto io_select = static_cast<IOSelection>(
obs_property_list_item_int(list, idx));
if (io_select == IOSelection::Invalid) {
obs_property_list_item_disable(list, idx, false);
continue;
}
bool enabled = cardEntry->InputSelectionReady(
io_select, deviceID, channelOwner);
obs_property_list_item_disable(list, idx, !enabled);
blog(LOG_DEBUG, "IOSelection %s = %s",
aja::IOSelectionToString(io_select).c_str(),
enabled ? "enabled" : "disabled");
}
}
void filter_io_selection_output_list(const std::string &cardID,
const std::string &channelOwner,
obs_property_t *list)
{
auto &cardManager = aja::CardManager::Instance();
auto cardEntry = cardManager.GetCardEntry(cardID);
if (!cardEntry) {
blog(LOG_DEBUG,
"filter_io_selection_output_list: Card Entry not found for %s",
cardID.c_str());
return;
}
NTV2DeviceID deviceID = DEVICE_ID_NOTFOUND;
CNTV2Card *card = cardEntry->GetCard();
if (card)
deviceID = card->GetDeviceID();
// Gray out the IOSelection list items that are in use by other plugin instances
for (size_t idx = 0; idx < obs_property_list_item_count(list); idx++) {
auto io_select = static_cast<IOSelection>(
obs_property_list_item_int(list, idx));
if (io_select == IOSelection::Invalid) {
obs_property_list_item_disable(list, idx, false);
continue;
}
bool enabled = cardEntry->OutputSelectionReady(
io_select, deviceID, channelOwner);
obs_property_list_item_disable(list, idx, !enabled);
blog(LOG_DEBUG, "IOSelection %s = %s",
aja::IOSelectionToString(io_select).c_str(),
enabled ? "enabled" : "disabled");
}
}
void populate_io_selection_input_list(const std::string &cardID,
const std::string &channelOwner,
NTV2DeviceID deviceID,
obs_property_t *list)
{
obs_property_list_clear(list);
obs_property_list_add_int(list, obs_module_text(kUIPropIOSelect.text),
static_cast<long long>(IOSelection::Invalid));
for (auto i = 0; i < static_cast<int32_t>(IOSelection::NumIOSelections);
i++) {
auto ioSelect = static_cast<IOSelection>(i);
if (ioSelect == IOSelection::SDI1_2_Squares ||
ioSelect == IOSelection::SDI3_4_Squares)
continue;
if (aja::DeviceCanDoIOSelectionIn(deviceID, ioSelect)) {
obs_property_list_add_int(
list,
aja::IOSelectionToString(ioSelect).c_str(),
static_cast<long long>(ioSelect));
}
}
filter_io_selection_input_list(cardID, channelOwner, list);
}
void populate_io_selection_output_list(const std::string &cardID,
const std::string &channelOwner,
NTV2DeviceID deviceID,
obs_property_t *list)
{
obs_property_list_clear(list);
obs_property_list_add_int(list, obs_module_text(kUIPropIOSelect.text),
static_cast<long long>(IOSelection::Invalid));
if (deviceID == DEVICE_ID_TTAP_PRO) {
obs_property_list_add_int(
list, "SDI & HDMI",
static_cast<long long>(IOSelection::HDMIMonitorOut));
} else {
for (auto i = 0;
i < static_cast<int32_t>(IOSelection::NumIOSelections);
i++) {
auto ioSelect = static_cast<IOSelection>(i);
if (ioSelect == IOSelection::Invalid ||
ioSelect == IOSelection::SDI1_2_Squares ||
ioSelect == IOSelection::SDI3_4_Squares)
continue;
if (aja::DeviceCanDoIOSelectionOut(deviceID,
ioSelect)) {
obs_property_list_add_int(
list,
aja::IOSelectionToString(ioSelect)
.c_str(),
static_cast<long long>(ioSelect));
}
}
}
filter_io_selection_output_list(cardID, channelOwner, list);
}
void populate_video_format_list(NTV2DeviceID deviceID, obs_property_t *list,
NTV2VideoFormat genlockFormat)
{
VideoFormatList videoFormats = {};
VideoStandardList orderedStandards = {};
orderedStandards.push_back(NTV2_STANDARD_525);
orderedStandards.push_back(NTV2_STANDARD_625);
if (NTV2DeviceCanDoHDVideo(deviceID)) {
orderedStandards.push_back(NTV2_STANDARD_720);
orderedStandards.push_back(NTV2_STANDARD_1080);
orderedStandards.push_back(NTV2_STANDARD_1080p);
}
if (NTV2DeviceCanDo2KVideo(deviceID)) {
orderedStandards.push_back(NTV2_STANDARD_2K);
orderedStandards.push_back(NTV2_STANDARD_2Kx1080p);
orderedStandards.push_back(NTV2_STANDARD_2Kx1080i);
}
if (NTV2DeviceCanDo4KVideo(deviceID)) {
orderedStandards.push_back(NTV2_STANDARD_3840i);
orderedStandards.push_back(NTV2_STANDARD_3840x2160p);
orderedStandards.push_back(NTV2_STANDARD_3840HFR);
orderedStandards.push_back(NTV2_STANDARD_4096i);
orderedStandards.push_back(NTV2_STANDARD_4096x2160p);
orderedStandards.push_back(NTV2_STANDARD_4096HFR);
}
aja::GetSortedVideoFormats(deviceID, orderedStandards, videoFormats);
for (const auto &vf : videoFormats) {
bool addFormat = true;
// Filter formats by framerate family if specified
if (genlockFormat != NTV2_FORMAT_UNKNOWN)
addFormat = IsMultiFormatCompatible(genlockFormat, vf);
if (addFormat) {
std::string name = NTV2VideoFormatToString(vf, true);
obs_property_list_add_int(list, name.c_str(), (int)vf);
}
}
}
void populate_pixel_format_list(NTV2DeviceID deviceID, obs_property_t *list)
{
const NTV2PixelFormat supported_pix_fmts[] = {kDefaultAJAPixelFormat,
NTV2_FBF_24BIT_BGR};
for (auto &&pf : supported_pix_fmts) {
if (NTV2DeviceCanDoFrameBufferFormat(deviceID, pf)) {
obs_property_list_add_int(
list,
NTV2FrameBufferFormatToString(pf, true).c_str(),
static_cast<long long>(pf));
}
}
}
void populate_sdi_4k_transport_list(obs_property_t *list)
{
obs_property_list_add_int(
list,
aja::SDI4KTransportToString(SDI4KTransport::Squares).c_str(),
static_cast<long long>(SDI4KTransport::Squares));
obs_property_list_add_int(
list,
aja::SDI4KTransportToString(SDI4KTransport::TwoSampleInterleave)
.c_str(),
static_cast<long long>(SDI4KTransport::TwoSampleInterleave));
}
bool aja_video_format_changed(obs_properties_t *props, obs_property_t *list,
obs_data_t *settings)
{
UNUSED_PARAMETER(list);
auto vid_fmt = static_cast<NTV2VideoFormat>(
obs_data_get_int(settings, kUIPropVideoFormatSelect.id));
obs_property_t *sdi_4k_trx =
obs_properties_get(props, kUIPropSDI4KTransport.id);
obs_property_set_visible(sdi_4k_trx, NTV2_IS_4K_VIDEO_FORMAT(vid_fmt));
return true;
}
namespace aja {
video_format AJAPixelFormatToOBSVideoFormat(NTV2PixelFormat pf)
{
video_format obs_video_format = VIDEO_FORMAT_NONE;
switch (pf) {
case NTV2_FBF_8BIT_YCBCR:
obs_video_format = VIDEO_FORMAT_UYVY;
break;
case NTV2_FBF_24BIT_RGB:
case NTV2_FBF_24BIT_BGR:
obs_video_format = VIDEO_FORMAT_BGR3;
break;
case NTV2_FBF_ARGB:
case NTV2_FBF_ABGR:
case NTV2_FBF_RGBA:
obs_video_format = VIDEO_FORMAT_BGRA;
break;
case NTV2_FBF_10BIT_YCBCR:
case NTV2_FBF_10BIT_RGB:
case NTV2_FBF_8BIT_YCBCR_YUY2:
case NTV2_FBF_10BIT_DPX:
case NTV2_FBF_10BIT_YCBCR_DPX:
case NTV2_FBF_8BIT_DVCPRO:
case NTV2_FBF_8BIT_YCBCR_420PL3:
case NTV2_FBF_8BIT_HDV:
case NTV2_FBF_10BIT_YCBCRA:
case NTV2_FBF_10BIT_DPX_LE:
case NTV2_FBF_48BIT_RGB:
case NTV2_FBF_12BIT_RGB_PACKED:
case NTV2_FBF_PRORES_DVCPRO:
case NTV2_FBF_PRORES_HDV:
case NTV2_FBF_10BIT_RGB_PACKED:
case NTV2_FBF_10BIT_ARGB:
case NTV2_FBF_16BIT_ARGB:
case NTV2_FBF_8BIT_YCBCR_422PL3:
case NTV2_FBF_10BIT_RAW_RGB:
case NTV2_FBF_10BIT_RAW_YCBCR:
case NTV2_FBF_10BIT_YCBCR_420PL3_LE:
case NTV2_FBF_10BIT_YCBCR_422PL3_LE:
case NTV2_FBF_10BIT_YCBCR_420PL2:
case NTV2_FBF_10BIT_YCBCR_422PL2:
case NTV2_FBF_8BIT_YCBCR_420PL2:
case NTV2_FBF_8BIT_YCBCR_422PL2:
default:
obs_video_format = VIDEO_FORMAT_NONE;
break;
}
return obs_video_format;
}
void GetSortedVideoFormats(NTV2DeviceID id, const VideoStandardList &standards,
VideoFormatList &videoFormats)
{
if (standards.empty())
return;
VideoFormatMap videoFormatMap;
// Bin all the formats based on video standard
for (size_t i = (size_t)NTV2_FORMAT_UNKNOWN;
i < (size_t)NTV2_MAX_NUM_VIDEO_FORMATS; i++) {
NTV2VideoFormat fmt = (NTV2VideoFormat)i;
NTV2Standard standard = GetNTV2StandardFromVideoFormat(fmt);
bool addFmt = true;
if (id != DEVICE_ID_NOTFOUND) {
addFmt = NTV2DeviceCanDoVideoFormat(id, fmt);
}
if (addFmt) {
if (videoFormatMap.count(standard)) {
videoFormatMap.at(standard).push_back(fmt);
} else {
std::vector<NTV2VideoFormat> v;
v.push_back(fmt);
videoFormatMap.insert(
std::pair<NTV2Standard,
std::vector<NTV2VideoFormat>>(
standard, v));
}
}
}
for (size_t v = (size_t)NTV2_STANDARD_1080;
v < (size_t)NTV2_NUM_STANDARDS; v++) {
NTV2Standard standard = (NTV2Standard)v;
if (videoFormatMap.count(standard)) {
std::sort(videoFormatMap.at(standard).begin(),
videoFormatMap.at(standard).end(),
[&](const NTV2VideoFormat &d1,
const NTV2VideoFormat &d2) {
std::string d1Str, d2Str;
d1Str = NTV2VideoFormatToString(d1);
d2Str = NTV2VideoFormatToString(d2);
return d1Str < d2Str;
});
}
}
for (size_t v = 0; v < standards.size(); v++) {
NTV2Standard standard = standards.at(v);
if (videoFormatMap.count(standard)) {
for (size_t i = 0;
i < videoFormatMap.at(standard).size(); i++) {
NTV2VideoFormat vf =
videoFormatMap.at(standard).at(i);
videoFormats.push_back(vf);
}
}
}
}
uint32_t CardNumFramestores(NTV2DeviceID id)
{
auto numFramestores = NTV2DeviceGetNumFrameStores(id);
if (id == DEVICE_ID_CORVIDHBR) {
numFramestores = 1;
}
return numFramestores;
}
uint32_t CardNumAudioSystems(NTV2DeviceID id)
{
if (id == DEVICE_ID_KONALHI || id == DEVICE_ID_KONALHEPLUS)
return 2;
return NTV2DeviceGetNumAudioSystems(id);
}
// IO4K and IO4K+ perform SDI Monitor Output on "SDI5" and "Framestore 4".
bool CardCanDoSDIMonitorOutput(NTV2DeviceID id)
{
return (id == DEVICE_ID_IO4K || id == DEVICE_ID_IO4KPLUS);
}
// Cards with a dedicated HDMI Monitor Output tie it to "Framestore 4".
bool CardCanDoHDMIMonitorOutput(NTV2DeviceID id)
{
return (id == DEVICE_ID_IO4K || id == DEVICE_ID_IO4KPLUS ||
id == DEVICE_ID_IOXT || id == DEVICE_ID_IOX3 ||
id == DEVICE_ID_KONA4 || id == DEVICE_ID_KONA5 ||
id == DEVICE_ID_KONA5_8K || id == DEVICE_ID_KONA5_2X4K ||
id == DEVICE_ID_KONA5_8KMK);
}
// Cards capable of 1x SDI at 6G/12G.
bool CardCanDo1xSDI12G(NTV2DeviceID id)
{
return (id == DEVICE_ID_KONA5_8K || id == DEVICE_ID_KONA5_8KMK ||
id == DEVICE_ID_KONA5 || id == DEVICE_ID_KONA5_2X4K ||
id == DEVICE_ID_IO4KPLUS || id == DEVICE_ID_CORVID44_12G);
}
// Check for 3G level-B SDI on the wire.
bool Is3GLevelB(CNTV2Card *card, NTV2Channel channel)
{
if (!card)
return false;
bool levelB = false;
auto deviceID = card->GetDeviceID();
UWord channelIndex = static_cast<UWord>(channel);
if (NTV2DeviceCanDo3GIn(deviceID, channelIndex) ||
NTV2DeviceCanDo12GIn(deviceID, channelIndex)) {
if (!card->GetSDIInput3GbPresent(levelB, channel))
return false;
}
return levelB;
}
// Get the 3G Level-A enum for a 3G Level-B format enum.
NTV2VideoFormat GetLevelAFormatForLevelBFormat(NTV2VideoFormat vf)
{
NTV2VideoFormat result = vf;
switch (vf) {
default:
break;
case NTV2_FORMAT_1080p_5000_B:
result = NTV2_FORMAT_1080p_5000_A;
break;
case NTV2_FORMAT_1080p_5994_B:
result = NTV2_FORMAT_1080p_5994_A;
break;
case NTV2_FORMAT_1080p_6000_B:
result = NTV2_FORMAT_1080p_6000_A;
break;
case NTV2_FORMAT_1080p_2K_4795_B:
result = NTV2_FORMAT_1080p_2K_4795_A;
break;
case NTV2_FORMAT_1080p_2K_4800_B:
result = NTV2_FORMAT_1080p_2K_4800_A;
break;
case NTV2_FORMAT_1080p_2K_5000_B:
result = NTV2_FORMAT_1080p_2K_5000_A;
break;
case NTV2_FORMAT_1080p_2K_5994_B:
result = NTV2_FORMAT_1080p_2K_5994_A;
break;
case NTV2_FORMAT_1080p_2K_6000_B:
result = NTV2_FORMAT_1080p_2K_6000_A;
break;
}
return result;
}
NTV2VideoFormat InterlacedFormatForPsfFormat(NTV2VideoFormat vf)
{
NTV2VideoFormat result = vf;
switch (vf) {
default:
break;
case NTV2_FORMAT_1080psf_2500_2:
result = NTV2_FORMAT_1080i_5000;
break;
case NTV2_FORMAT_1080psf_2997_2:
result = NTV2_FORMAT_1080i_5994;
break;
}
return result;
}
// Certain cards only have 1 SDI spigot.
bool IsSingleSDIDevice(NTV2DeviceID id)
{
return (id == DEVICE_ID_TTAP_PRO || id == DEVICE_ID_KONA1);
}
bool IsIODevice(NTV2DeviceID id)
{
return (id == DEVICE_ID_IOXT || id == DEVICE_ID_IOX3 ||
id == DEVICE_ID_IO4K || id == DEVICE_ID_IO4KPLUS ||
id == DEVICE_ID_IOIP_2022 || id == DEVICE_ID_IOIP_2110);
}
bool IsRetailSDI12G(NTV2DeviceID id)
{
return (id == DEVICE_ID_KONA5 || id == DEVICE_ID_IO4KPLUS);
}
bool IsOutputOnlyDevice(NTV2DeviceID id)
{
return id == DEVICE_ID_TTAP_PRO;
}
std::string SDI4KTransportToString(SDI4KTransport mode)
{
std::string str = "";
switch (mode) {
case SDI4KTransport::Squares:
str = "Squares";
break;
case SDI4KTransport::TwoSampleInterleave:
str = "2SI";
break;
default:
case SDI4KTransport::Unknown:
str = "Unknown";
break;
}
return str;
}
std::string IOSelectionToString(IOSelection io)
{
std::string str;
switch (io) {
case IOSelection::SDI1:
str = "SDI 1";
break;
case IOSelection::SDI2:
str = "SDI 2";
break;
case IOSelection::SDI3:
str = "SDI 3";
break;
case IOSelection::SDI4:
str = "SDI 4";
break;
case IOSelection::SDI5:
str = "SDI 5";
break;
case IOSelection::SDI6:
str = "SDI 6";
break;
case IOSelection::SDI7:
str = "SDI 7";
break;
case IOSelection::SDI8:
str = "SDI 8";
break;
case IOSelection::SDI1_2:
str = "SDI 1 & 2";
break;
case IOSelection::SDI1_2_Squares:
str = "SDI 1 & 2 (4K Squares)";
break;
case IOSelection::SDI3_4:
str = "SDI 3 & 4";
break;
case IOSelection::SDI3_4_Squares:
str = "SDI 3 & 4 (4K Squares)";
break;
case IOSelection::SDI5_6:
str = "SDI 5 & 6";
break;
case IOSelection::SDI7_8:
str = "SDI 7 & 8";
break;
case IOSelection::SDI1__4:
str = "SDI 1-4";
break;
case IOSelection::SDI5__8:
str = "SDI 5-8";
break;
case IOSelection::HDMI1:
str = "HDMI 1";
break;
case IOSelection::HDMI2:
str = "HDMI 2";
break;
case IOSelection::HDMI3:
str = "HDMI 3";
break;
case IOSelection::HDMI4:
str = "HDMI 4";
break;
case IOSelection::HDMIMonitorIn:
str = "HDMI Monitor In";
break;
case IOSelection::HDMIMonitorOut:
str = "HDMI Monitor Out";
break;
case IOSelection::AnalogIn:
str = "Analog In";
break;
case IOSelection::AnalogOut:
str = "Analog Out";
break;
case IOSelection::Invalid:
str = "Invalid";
break;
}
return str;
}
void IOSelectionToInputSources(IOSelection io, NTV2InputSourceSet &inputSources)
{
switch (io) {
case IOSelection::SDI1:
inputSources.insert(NTV2_INPUTSOURCE_SDI1);
break;
case IOSelection::SDI2:
inputSources.insert(NTV2_INPUTSOURCE_SDI2);
break;
case IOSelection::SDI3:
inputSources.insert(NTV2_INPUTSOURCE_SDI3);
break;
case IOSelection::SDI4:
inputSources.insert(NTV2_INPUTSOURCE_SDI4);
break;
case IOSelection::SDI5:
inputSources.insert(NTV2_INPUTSOURCE_SDI5);
break;
case IOSelection::SDI6:
inputSources.insert(NTV2_INPUTSOURCE_SDI6);
break;
case IOSelection::SDI7:
inputSources.insert(NTV2_INPUTSOURCE_SDI7);
break;
case IOSelection::SDI8:
inputSources.insert(NTV2_INPUTSOURCE_SDI8);
break;
case IOSelection::SDI1_2:
inputSources.insert(NTV2_INPUTSOURCE_SDI1);
inputSources.insert(NTV2_INPUTSOURCE_SDI2);
break;
case IOSelection::SDI1_2_Squares:
inputSources.insert(NTV2_INPUTSOURCE_SDI1);
inputSources.insert(NTV2_INPUTSOURCE_SDI2);
inputSources.insert(NTV2_INPUTSOURCE_SDI3);
inputSources.insert(NTV2_INPUTSOURCE_SDI4);
break;
case IOSelection::SDI3_4:
inputSources.insert(NTV2_INPUTSOURCE_SDI3);
inputSources.insert(NTV2_INPUTSOURCE_SDI4);
break;
case IOSelection::SDI3_4_Squares:
inputSources.insert(NTV2_INPUTSOURCE_SDI1);
inputSources.insert(NTV2_INPUTSOURCE_SDI2);
inputSources.insert(NTV2_INPUTSOURCE_SDI3);
inputSources.insert(NTV2_INPUTSOURCE_SDI4);
break;
case IOSelection::SDI5_6:
inputSources.insert(NTV2_INPUTSOURCE_SDI5);
inputSources.insert(NTV2_INPUTSOURCE_SDI6);
break;
case IOSelection::SDI7_8:
inputSources.insert(NTV2_INPUTSOURCE_SDI7);
inputSources.insert(NTV2_INPUTSOURCE_SDI8);
break;
case IOSelection::SDI1__4:
inputSources.insert(NTV2_INPUTSOURCE_SDI1);
inputSources.insert(NTV2_INPUTSOURCE_SDI2);
inputSources.insert(NTV2_INPUTSOURCE_SDI3);
inputSources.insert(NTV2_INPUTSOURCE_SDI4);
break;
case IOSelection::SDI5__8:
inputSources.insert(NTV2_INPUTSOURCE_SDI5);
inputSources.insert(NTV2_INPUTSOURCE_SDI6);
inputSources.insert(NTV2_INPUTSOURCE_SDI7);
inputSources.insert(NTV2_INPUTSOURCE_SDI8);
break;
case IOSelection::HDMI1:
inputSources.insert(NTV2_INPUTSOURCE_HDMI1);
break;
case IOSelection::HDMI2:
inputSources.insert(NTV2_INPUTSOURCE_HDMI2);
break;
case IOSelection::HDMI3:
inputSources.insert(NTV2_INPUTSOURCE_HDMI3);
break;
case IOSelection::HDMI4:
inputSources.insert(NTV2_INPUTSOURCE_HDMI4);
break;
case IOSelection::HDMIMonitorIn:
inputSources.insert(NTV2_INPUTSOURCE_HDMI1);
break;
case IOSelection::AnalogIn:
inputSources.insert(NTV2_INPUTSOURCE_ANALOG1);
break;
default:
case IOSelection::HDMIMonitorOut:
case IOSelection::AnalogOut:
case IOSelection::Invalid:
break;
}
}
void IOSelectionToOutputDests(IOSelection io,
NTV2OutputDestinations &outputDests)
{
switch (io) {
case IOSelection::SDI1:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI1);
break;
case IOSelection::SDI2:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI2);
break;
case IOSelection::SDI3:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI3);
break;
case IOSelection::SDI4:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI4);
break;
case IOSelection::SDI5:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI5);
break;
case IOSelection::SDI6:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI6);
break;
case IOSelection::SDI7:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI7);
break;
case IOSelection::SDI8:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI8);
break;
case IOSelection::SDI1_2:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI1);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI2);
break;
// Requires 4x framestores and 2x SDI spigots
case IOSelection::SDI1_2_Squares:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI1);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI2);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI3);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI4);
break;
case IOSelection::SDI3_4:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI3);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI4);
break;
// Requires 4x framestores and 2x SDI spigots
case IOSelection::SDI3_4_Squares:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI1);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI2);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI3);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI4);
break;
case IOSelection::SDI5_6:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI5);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI6);
break;
case IOSelection::SDI7_8:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI7);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI8);
break;
case IOSelection::SDI1__4:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI1);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI2);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI3);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI4);
break;
case IOSelection::SDI5__8:
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI5);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI6);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI7);
outputDests.insert(NTV2_OUTPUTDESTINATION_SDI8);
break;
case IOSelection::HDMIMonitorOut:
outputDests.insert(NTV2_OUTPUTDESTINATION_HDMI);
break;
case IOSelection::AnalogOut:
outputDests.insert(NTV2_OUTPUTDESTINATION_ANALOG);
break;
default:
case IOSelection::HDMI1:
case IOSelection::HDMI2:
case IOSelection::HDMI3:
case IOSelection::HDMI4:
case IOSelection::HDMIMonitorIn:
case IOSelection::AnalogIn:
case IOSelection::Invalid:
break;
}
}
bool DeviceCanDoIOSelectionIn(NTV2DeviceID id, IOSelection io)
{
NTV2InputSourceSet inputSources;
if (io != IOSelection::Invalid) {
IOSelectionToInputSources(io, inputSources);
size_t numSrcs = inputSources.size();
size_t canDo = 0;
if (numSrcs > 0) {
for (auto &&inp : inputSources) {
if (NTV2DeviceCanDoInputSource(id, inp))
canDo++;
}
if (canDo == numSrcs)
return true;
}
}
return false;
}
bool DeviceCanDoIOSelectionOut(NTV2DeviceID id, IOSelection io)
{
NTV2OutputDestinations outputDests;
if (io != IOSelection::Invalid) {
IOSelectionToOutputDests(io, outputDests);
size_t numOuts = outputDests.size();
size_t canDo = 0;
if (numOuts > 0) {
for (auto &&out : outputDests) {
if (NTV2DeviceCanDoOutputDestination(id, out))
canDo++;
}
if (canDo == numOuts)
return true;
}
}
return false;
}
bool IsSDIOneWireIOSelection(IOSelection io)
{
bool result = false;
switch (io) {
case IOSelection::SDI1:
case IOSelection::SDI2:
case IOSelection::SDI3:
case IOSelection::SDI4:
case IOSelection::SDI5:
case IOSelection::SDI6:
case IOSelection::SDI7:
case IOSelection::SDI8:
result = true;
break;
default:
result = false;
}
return result;
}
bool IsSDITwoWireIOSelection(IOSelection io)
{
bool result = false;
switch (io) {
case IOSelection::SDI1_2:
case IOSelection::SDI1_2_Squares:
case IOSelection::SDI3_4:
case IOSelection::SDI3_4_Squares:
case IOSelection::SDI5_6:
case IOSelection::SDI7_8:
result = true;
break;
default:
result = false;
}
return result;
}
bool IsSDIFourWireIOSelection(IOSelection io)
{
bool result = false;
switch (io) {
case IOSelection::SDI1__4:
case IOSelection::SDI5__8:
result = true;
break;
default:
result = false;
}
return result;
}
bool IsMonitorOutputSelection(NTV2DeviceID id, IOSelection io)
{
if (CardCanDoSDIMonitorOutput(id) && io == IOSelection::SDI5)
return true;
if (CardCanDoHDMIMonitorOutput(id) && io == IOSelection::HDMIMonitorOut)
return true;
return false;
}
std::string MakeCardID(CNTV2Card &card)
{
std::string cardID;
if (card.GetSerialNumberString(cardID)) {
// Try to construct CardID from device ID and serial number...
cardID = NTV2DeviceIDToString(card.GetDeviceID(), false) + "_" +
cardID;
} else {
// ...otherwise fall back to the CNTV2DeviceScanner method.
cardID = CNTV2DeviceScanner::GetDeviceRefName(card);
}
return cardID;
}
} // aja

View File

@ -0,0 +1,92 @@
#include "aja-enums.hpp"
#include <obs-module.h>
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include <ajantv2/includes/ntv2enums.h>
#include <ajantv2/includes/ntv2card.h>
using VideoFormatMap = std::map<NTV2Standard, std::vector<NTV2VideoFormat>>;
using VideoFormatList = std::vector<NTV2VideoFormat>;
using VideoStandardList = std::vector<NTV2Standard>;
static const uint32_t kDefaultAudioChannels = 8;
static const uint32_t kDefaultAudioSampleRate = 48000;
static const uint32_t kDefaultAudioSampleSize = 4;
static const int kVideoFormatAuto = -1;
static const int kPixelFormatAuto = -1;
static const NTV2PixelFormat kDefaultAJAPixelFormat = NTV2_FBF_8BIT_YCBCR;
// Common OBS property helpers used by both the capture and output plugins
extern void filter_io_selection_input_list(const std::string &cardID,
const std::string &channelOwner,
obs_property_t *list);
extern void filter_io_selection_output_list(const std::string &cardID,
const std::string &channelOwner,
obs_property_t *list);
extern void populate_io_selection_input_list(const std::string &cardID,
const std::string &channelOwner,
NTV2DeviceID deviceID,
obs_property_t *list);
extern void populate_io_selection_output_list(const std::string &cardID,
const std::string &channelOwner,
NTV2DeviceID deviceID,
obs_property_t *list);
extern void
populate_video_format_list(NTV2DeviceID deviceID, obs_property_t *list,
NTV2VideoFormat genlockFormat = NTV2_FORMAT_UNKNOWN);
extern void populate_pixel_format_list(NTV2DeviceID deviceID,
obs_property_t *list);
extern void populate_sdi_4k_transport_list(obs_property_t *list);
extern bool aja_video_format_changed(obs_properties_t *props,
obs_property_t *list,
obs_data_t *settings);
// Additional helpers for AJA channel and signal routing configuration not found in the NTV2 SDK
namespace aja {
template<typename T> bool vec_contains(const std::vector<T> &vec, const T &elem)
{
return std::find(vec.begin(), vec.end(), elem) != vec.end();
}
extern video_format AJAPixelFormatToOBSVideoFormat(NTV2PixelFormat pf);
extern void GetSortedVideoFormats(NTV2DeviceID id,
const VideoStandardList &standards,
VideoFormatList &videoFormats);
extern uint32_t CardNumFramestores(NTV2DeviceID id);
extern uint32_t CardNumAudioSystems(NTV2DeviceID id);
extern bool CardCanDoSDIMonitorOutput(NTV2DeviceID id);
extern bool CardCanDoHDMIMonitorOutput(NTV2DeviceID id);
extern bool CardCanDo1xSDI12G(NTV2DeviceID id);
extern bool Is3GLevelB(CNTV2Card *card, NTV2Channel channel);
extern NTV2VideoFormat GetLevelAFormatForLevelBFormat(NTV2VideoFormat vf);
extern NTV2VideoFormat InterlacedFormatForPsfFormat(NTV2VideoFormat vf);
extern bool IsSingleSDIDevice(NTV2DeviceID id);
extern bool IsIODevice(NTV2DeviceID id);
extern bool IsRetailSDI12G(NTV2DeviceID id);
extern bool IsOutputOnlyDevice(NTV2DeviceID id);
extern std::string SDI4KTransportToString(SDI4KTransport mode);
extern std::string IOSelectionToString(IOSelection io);
extern void IOSelectionToInputSources(IOSelection io,
NTV2InputSourceSet &inputSources);
extern void IOSelectionToOutputDests(IOSelection io,
NTV2OutputDestinations &outputDests);
extern bool DeviceCanDoIOSelectionIn(NTV2DeviceID id, IOSelection io);
extern bool DeviceCanDoIOSelectionOut(NTV2DeviceID id, IOSelection io);
extern bool IsSDIOneWireIOSelection(IOSelection io);
extern bool IsSDITwoWireIOSelection(IOSelection io);
extern bool IsSDIFourWireIOSelection(IOSelection io);
extern bool IsMonitorOutputSelection(NTV2DeviceID id, IOSelection io);
extern std::string MakeCardID(CNTV2Card &card);
} // aja

88
plugins/aja/aja-enums.hpp Normal file
View File

@ -0,0 +1,88 @@
#pragma once
// Additional enums used throughout the AJA plugins for signal routing configuration.
enum class IOSelection {
SDI1 = 0,
SDI2 = 1,
SDI3 = 2,
SDI4 = 3,
SDI5 = 4,
SDI6 = 5,
SDI7 = 6,
SDI8 = 7,
SDI1_2 = 8,
// special case for 2xSDI 4K Squares (requires 4x framestores)
SDI1_2_Squares = 9,
SDI3_4 = 10,
// special case for 2xSDI 4K Squares (requires 4x framestores)
SDI3_4_Squares = 11,
SDI5_6 = 12,
SDI7_8 = 13,
SDI1__4 = 14,
SDI5__8 = 15,
HDMI1 = 16,
HDMI2 = 17,
HDMI3 = 18,
HDMI4 = 19,
HDMIMonitorIn = 20,
HDMIMonitorOut = 21,
AnalogIn = 22,
AnalogOut = 23,
Invalid = 24,
NumIOSelections = Invalid
};
enum class SDI4KTransport { Squares = 0, TwoSampleInterleave = 1, Unknown = 2 };
enum class RasterDefinition {
SD = 0,
HD = 1,
UHD_4K = 2,
UHD_4K_Retail_12G = 3,
UHD2_8K = 4,
Unknown = 5
};
enum class SDIWireFormat {
SD_ST352 = 0,
HD_720p_ST292 = 1,
HD_1080_ST292 = 2,
HD_1080_ST372_Dual = 3,
HD_720p_ST425_3Ga = 4,
HD_1080p_ST425_3Ga = 5,
HD_1080p_ST425_3Gb_DL = 6,
HD_720p_ST425_3Gb = 7,
HD_1080p_ST425_3Gb = 8,
HD_1080p_ST425_Dual_3Ga = 9,
HD_1080p_ST425_Dual_3Gb = 10,
UHD4K_ST292_Dual_1_5_Squares = 11,
UHD4K_ST292_Quad_1_5_Squares = 12,
UHD4K_ST425_Quad_3Ga_Squares = 13,
UHD4K_ST425_Quad_3Gb_Squares = 14,
UHD4K_ST425_Dual_3Gb_2SI = 15,
UHD4K_ST425_Quad_3Ga_2SI = 16,
UHD4K_ST425_Quad_3Gb_2SI = 17,
UHD4K_ST2018_6G_Squares_2SI = 18,
UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus = 19,
UHD4K_ST2018_12G_Squares_2SI = 20,
UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus = 21,
UHD28K_ST2082_Dual_12G = 22,
UHD28K_ST2082_RGB_Dual_12G = 23,
UHD28K_ST2082_Quad_12G = 24,
Unknown = 25,
};
enum class HDMIWireFormat {
SD_HDMI = 0,
HD_YCBCR_LFR = 1,
HD_YCBCR_HFR = 2,
HD_RGB_LFR = 3,
HD_RGB_HFR = 4,
UHD_4K_YCBCR_LFR = 5,
UHD_4K_YCBCR_HFR = 6,
UHD_4K_RGB_LFR = 7,
UHD_4K_RGB_HFR = 8,
TTAP_PRO = 9,
Unknown = 10
};

1267
plugins/aja/aja-output.cpp Normal file

File diff suppressed because it is too large Load Diff

160
plugins/aja/aja-output.hpp Normal file
View File

@ -0,0 +1,160 @@
#pragma once
#include "aja-props.hpp"
#include <ajantv2/includes/ntv2testpatterngen.h>
#include <ajabase/common/types.h>
#include <ajabase/system/thread.h>
// #define AJA_WRITE_DEBUG_WAV
#ifdef AJA_WRITE_DEBUG_WAV
#include <ajabase/common/wavewriter.h>
#endif
#include <deque>
#include <memory>
#include <mutex>
struct VideoFrame {
struct video_data frame;
int64_t frameNum;
size_t size;
};
struct AudioFrames {
struct audio_data frames;
size_t offset;
size_t size;
};
//TODO(paulh): Refactor me into OutputProps
struct FrameTimes {
double obsFps;
uint64_t obsFrameTime;
double cardFps;
uint64_t cardFrameTime;
};
using VideoQueue = std::deque<VideoFrame>;
using AudioQueue = std::deque<AudioFrames>;
class CNTV2Card; // forward decl
class AJAOutput {
public:
enum {
// min queue sizes computed in AJAOutput
kVideoQueueMaxSize = 15,
kAudioQueueMaxSize =
96, // ~(48000 / 1024 samples per audio_frame) * 2sec
};
AJAOutput(CNTV2Card *card, const std::string &cardID,
const std::string &outputID, UWord deviceIndex,
const NTV2DeviceID deviceID);
~AJAOutput();
CNTV2Card *GetCard();
void Initialize(const OutputProps &props);
void SetOBSOutput(obs_output_t *output);
obs_output_t *GetOBSOutput();
void SetOutputProps(const OutputProps &props);
OutputProps GetOutputProps() const;
void GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
NTV2TestPatternSelect pattern);
void QueueVideoFrame(struct video_data *frame, size_t size);
void QueueAudioFrames(struct audio_data *frames, size_t size);
void ClearVideoQueue();
void ClearAudioQueue();
size_t VideoQueueSize();
size_t AudioQueueSize();
bool HaveEnoughAudio(size_t needAudioSize);
void DMAAudioFromQueue(NTV2AudioSystem audioSys);
void DMAVideoFromQueue();
void CreateThread(bool enable = false);
void StopThread();
bool ThreadRunning();
static void OutputThread(AJAThread *thread, void *ctx);
std::string mCardID;
std::string mOutputID;
UWord mDeviceIndex;
NTV2DeviceID mDeviceID;
FrameTimes mFrameTimes;
uint32_t mAudioPlayCursor;
uint32_t mAudioWriteCursor;
uint32_t mAudioWrapAddress;
uint32_t mAudioRate;
uint64_t mAudioQueueSamples;
uint64_t mAudioWriteSamples;
uint64_t mAudioPlaySamples;
uint32_t mNumCardFrames;
uint32_t mFirstCardFrame;
uint32_t mLastCardFrame;
uint32_t mWriteCardFrame;
uint32_t mPlayCardFrame;
uint32_t mPlayCardNext;
uint32_t mFrameRateNum;
uint32_t mFrameRateDen;
uint64_t mVideoQueueFrames;
uint64_t mVideoWriteFrames;
uint64_t mVideoPlayFrames;
uint64_t mFirstVideoTS;
uint64_t mFirstAudioTS;
uint64_t mLastVideoTS;
uint64_t mLastAudioTS;
int64_t mVideoDelay;
int64_t mAudioDelay;
int64_t mAudioVideoSync;
int64_t mAudioAdjust;
int64_t mLastStatTime;
#ifdef AJA_WRITE_DEBUG_WAV
AJAWavWriter *mWaveWriter;
#endif
private:
void calculate_card_frame_indices(uint32_t numFrames, NTV2DeviceID id,
NTV2Channel channel,
NTV2VideoFormat vf,
NTV2PixelFormat pf);
uint32_t get_frame_count();
void dma_audio_samples(NTV2AudioSystem audioSys, uint32_t *data,
size_t size);
CNTV2Card *mCard;
OutputProps mOutputProps;
NTV2TestPatternBuffer mTestPattern;
bool mIsRunning;
bool mAudioStarted;
AJAThread mRunThread;
mutable std::mutex mVideoLock;
mutable std::mutex mAudioLock;
mutable std::mutex mRunThreadLock;
std::unique_ptr<VideoQueue> mVideoQueue;
std::unique_ptr<AudioQueue> mAudioQueue;
obs_output_t *mOBSOutput;
};

436
plugins/aja/aja-props.cpp Normal file
View File

@ -0,0 +1,436 @@
#include "aja-props.hpp"
#include <ajantv2/includes/ntv2devicefeatures.h>
#include <ajantv2/includes/ntv2utils.h>
#include <ajantv2/includes/ntv2vpid.h>
VPIDData::VPIDData()
: mVpidA{0},
mVpidB{0},
mStandardA{VPIDStandard_Unknown},
mStandardB{VPIDStandard_Unknown},
mSamplingA{VPIDSampling_XYZ_444},
mSamplingB{VPIDSampling_XYZ_444}
{
}
VPIDData::VPIDData(ULWord vpidA, ULWord vpidB)
: mVpidA{vpidA},
mVpidB{vpidB},
mStandardA{VPIDStandard_Unknown},
mStandardB{VPIDStandard_Unknown},
mSamplingA{VPIDSampling_XYZ_444},
mSamplingB{VPIDSampling_XYZ_444}
{
Parse();
}
VPIDData::VPIDData(const VPIDData &other)
: mVpidA{other.mVpidA},
mVpidB{other.mVpidB},
mStandardA{VPIDStandard_Unknown},
mStandardB{VPIDStandard_Unknown},
mSamplingA{VPIDSampling_XYZ_444},
mSamplingB{VPIDSampling_XYZ_444}
{
Parse();
}
VPIDData::VPIDData(VPIDData &&other)
: mVpidA{other.mVpidA},
mVpidB{other.mVpidB},
mStandardA{VPIDStandard_Unknown},
mStandardB{VPIDStandard_Unknown},
mSamplingA{VPIDSampling_XYZ_444},
mSamplingB{VPIDSampling_XYZ_444}
{
Parse();
}
VPIDData &VPIDData::operator=(const VPIDData &other)
{
mVpidA = other.mVpidA;
mVpidB = other.mVpidB;
return *this;
}
VPIDData &VPIDData::operator=(VPIDData &&other)
{
mVpidA = other.mVpidA;
mVpidB = other.mVpidB;
return *this;
}
bool VPIDData::operator==(const VPIDData &rhs) const
{
return (mVpidA == rhs.mVpidA && mVpidB == rhs.mVpidB);
}
bool VPIDData::operator!=(const VPIDData &rhs) const
{
return !operator==(rhs);
}
void VPIDData::SetA(ULWord vpidA)
{
mVpidA = vpidA;
}
void VPIDData::SetB(ULWord vpidB)
{
mVpidB = vpidB;
}
void VPIDData::Parse()
{
CNTV2VPID parserA;
parserA.SetVPID(mVpidA);
mStandardA = parserA.GetStandard();
mSamplingA = parserA.GetSampling();
CNTV2VPID parserB;
parserB.SetVPID(mVpidB);
mStandardB = parserB.GetStandard();
mSamplingB = parserB.GetSampling();
}
bool VPIDData::IsRGB() const
{
switch (mSamplingA) {
default:
break;
case VPIDSampling_GBR_444:
case VPIDSampling_GBRA_4444:
case VPIDSampling_GBRD_4444:
return true;
}
return false;
}
VPIDStandard VPIDData::Standard() const
{
return mStandardA;
}
VPIDSampling VPIDData::Sampling() const
{
return mSamplingA;
}
// AJASource Properties
SourceProps::SourceProps()
: deviceID{DEVICE_ID_NOTFOUND},
ioSelect{IOSelection::Invalid},
inputSource{NTV2_INPUTSOURCE_INVALID},
videoFormat{NTV2_FORMAT_UNKNOWN},
pixelFormat{NTV2_FBF_INVALID},
sdi4kTransport{SDI4KTransport::TwoSampleInterleave},
audioNumChannels{8},
audioSampleSize{4},
audioSampleRate{48000},
vpids{},
autoDetect{false},
deactivateWhileNotShowing{false}
{
}
SourceProps::SourceProps(NTV2DeviceID devID)
: deviceID{devID},
ioSelect{IOSelection::Invalid},
inputSource{NTV2_INPUTSOURCE_INVALID},
videoFormat{NTV2_FORMAT_UNKNOWN},
pixelFormat{NTV2_FBF_INVALID},
sdi4kTransport{SDI4KTransport::TwoSampleInterleave},
audioNumChannels{8},
audioSampleSize{4},
audioSampleRate{48000},
vpids{},
autoDetect{false},
deactivateWhileNotShowing{false}
{
}
SourceProps::SourceProps(const SourceProps &props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
inputSource = props.inputSource;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
vpids = props.vpids;
autoDetect = props.autoDetect;
deactivateWhileNotShowing = props.deactivateWhileNotShowing;
}
SourceProps::SourceProps(SourceProps &&props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
inputSource = props.inputSource;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
vpids = props.vpids;
autoDetect = props.autoDetect;
deactivateWhileNotShowing = props.deactivateWhileNotShowing;
}
void SourceProps::operator=(const SourceProps &props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
inputSource = props.inputSource;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
vpids = props.vpids;
autoDetect = props.autoDetect;
deactivateWhileNotShowing = props.deactivateWhileNotShowing;
}
void SourceProps::operator=(SourceProps &&props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
inputSource = props.inputSource;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
vpids = props.vpids;
autoDetect = props.autoDetect;
deactivateWhileNotShowing = props.deactivateWhileNotShowing;
}
bool SourceProps::operator==(const SourceProps &props)
{
return (deviceID == props.deviceID && ioSelect == props.ioSelect &&
// inputSource == props.inputSource &&
videoFormat == props.videoFormat &&
pixelFormat == props.pixelFormat &&
// vpid == props.vpid &&
autoDetect == props.autoDetect &&
sdi4kTransport == props.sdi4kTransport &&
audioNumChannels == props.audioNumChannels &&
audioSampleSize == props.audioSampleSize &&
audioSampleRate == props.audioSampleRate &&
deactivateWhileNotShowing == props.deactivateWhileNotShowing);
}
bool SourceProps::operator!=(const SourceProps &props)
{
return !operator==(props);
}
NTV2Channel SourceProps::Channel() const
{
return NTV2InputSourceToChannel(inputSource);
}
NTV2AudioSystem SourceProps::AudioSystem() const
{
return NTV2ChannelToAudioSystem(Channel());
}
NTV2AudioRate SourceProps::AudioRate() const
{
NTV2AudioRate rate = NTV2_AUDIO_48K;
switch (audioSampleRate) {
default:
case 48000:
rate = NTV2_AUDIO_48K;
break;
case 96000:
rate = NTV2_AUDIO_96K;
break;
case 192000:
rate = NTV2_AUDIO_192K;
break;
}
return rate;
}
// Size in bytes of N channels of audio
size_t SourceProps::AudioSize() const
{
return audioNumChannels * audioSampleSize;
}
audio_format SourceProps::AudioFormat() const
{
// NTV2 is always 32-bit PCM
return AUDIO_FORMAT_32BIT;
}
speaker_layout SourceProps::SpeakerLayout() const
{
if (audioNumChannels == 2)
return SPEAKERS_STEREO;
// NTV2 is always at least 8ch on modern boards
return SPEAKERS_7POINT1;
}
//
// AJAOutput Properties
//
OutputProps::OutputProps(NTV2DeviceID devID)
: deviceID{devID},
ioSelect{IOSelection::Invalid},
outputDest{NTV2_OUTPUTDESTINATION_ANALOG},
videoFormat{NTV2_FORMAT_UNKNOWN},
pixelFormat{NTV2_FBF_INVALID},
sdi4kTransport{SDI4KTransport::TwoSampleInterleave},
audioNumChannels{8},
audioSampleSize{4},
audioSampleRate{48000}
{
}
OutputProps::OutputProps(OutputProps &&props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
outputDest = props.outputDest;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
}
OutputProps::OutputProps(const OutputProps &props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
outputDest = props.outputDest;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
}
void OutputProps::operator=(const OutputProps &props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
outputDest = props.outputDest;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
}
void OutputProps::operator=(OutputProps &&props)
{
deviceID = props.deviceID;
ioSelect = props.ioSelect;
outputDest = props.outputDest;
videoFormat = props.videoFormat;
pixelFormat = props.pixelFormat;
sdi4kTransport = props.sdi4kTransport;
audioNumChannels = props.audioNumChannels;
audioSampleSize = props.audioSampleSize;
audioSampleRate = props.audioSampleRate;
}
bool OutputProps::operator==(const OutputProps &props)
{
return (deviceID == props.deviceID && ioSelect == props.ioSelect &&
// outputDest == props.outputDest &&
videoFormat == props.videoFormat &&
pixelFormat == props.pixelFormat &&
audioNumChannels == props.audioNumChannels &&
audioSampleSize == props.audioSampleSize &&
audioSampleRate == props.audioSampleRate);
}
bool OutputProps::operator!=(const OutputProps &props)
{
return !operator==(props);
}
NTV2FormatDesc OutputProps::FormatDesc()
{
return NTV2FormatDesc(videoFormat, pixelFormat, NTV2_VANCMODE_OFF);
}
NTV2Channel OutputProps::Channel() const
{
// Output Channel Special Cases
// KONA1 -- Has 2 framestores but only 1 bi-directional SDI widget
if (deviceID == DEVICE_ID_KONA1) {
return NTV2_CHANNEL2;
} else if ((deviceID == DEVICE_ID_IO4K ||
deviceID == DEVICE_ID_IO4KPLUS) &&
outputDest == NTV2_OUTPUTDESTINATION_SDI5) {
// IO4K/IO4K+ SDI Monitor - Use framestore 4 but SDI5
return NTV2_CHANNEL4;
}
if (NTV2_OUTPUT_DEST_IS_HDMI(outputDest))
return static_cast<NTV2Channel>(
NTV2DeviceGetNumFrameStores(deviceID) - 1);
return NTV2OutputDestinationToChannel(outputDest);
}
NTV2AudioSystem OutputProps::AudioSystem() const
{
return NTV2ChannelToAudioSystem(Channel());
}
NTV2AudioRate OutputProps::AudioRate() const
{
NTV2AudioRate rate = NTV2_AUDIO_48K;
switch (audioSampleRate) {
default:
case 48000:
rate = NTV2_AUDIO_48K;
break;
case 96000:
rate = NTV2_AUDIO_96K;
break;
case 192000:
rate = NTV2_AUDIO_192K;
break;
}
return rate;
}
// Size in bytes of N channels of audio
size_t OutputProps::AudioSize() const
{
return audioNumChannels * audioSampleSize;
}
audio_format OutputProps::AudioFormat() const
{
// NTV2 is always 32-bit PCM
return AUDIO_FORMAT_32BIT;
}
speaker_layout OutputProps::SpeakerLayout() const
{
if (audioNumChannels == 2)
return SPEAKERS_STEREO;
// NTV2 is always at least 8ch on modern boards
return SPEAKERS_7POINT1;
}

108
plugins/aja/aja-props.hpp Normal file
View File

@ -0,0 +1,108 @@
#pragma once
#include "aja-enums.hpp"
#include <media-io/audio-io.h>
#include <ajantv2/includes/ntv2enums.h>
#include <ajantv2/includes/ntv2formatdescriptor.h>
#include <map>
#include <string>
#include <vector>
class VPIDData {
public:
VPIDData();
VPIDData(ULWord vpidA, ULWord vpidB);
VPIDData(const VPIDData &other);
VPIDData(VPIDData &&other);
~VPIDData() = default;
VPIDData &operator=(const VPIDData &other);
VPIDData &operator=(VPIDData &&other);
bool operator==(const VPIDData &rhs) const;
bool operator!=(const VPIDData &rhs) const;
void SetA(ULWord vpidA);
void SetB(ULWord vpidB);
void Parse();
bool IsRGB() const;
VPIDStandard Standard() const;
VPIDSampling Sampling() const;
private:
ULWord mVpidA;
ULWord mVpidB;
VPIDStandard mStandardA;
VPIDSampling mSamplingA;
VPIDStandard mStandardB;
VPIDSampling mSamplingB;
};
using VPIDDataList = std::vector<VPIDData>;
//TODO(paulh): Consolidate the two Props classes
class SourceProps {
public:
explicit SourceProps();
explicit SourceProps(NTV2DeviceID devID);
~SourceProps() = default;
SourceProps(const SourceProps &props);
SourceProps(SourceProps &&props);
void operator=(const SourceProps &props);
void operator=(SourceProps &&props);
bool operator==(const SourceProps &props);
bool operator!=(const SourceProps &props);
NTV2Channel Channel() const;
NTV2AudioSystem AudioSystem() const;
NTV2AudioRate AudioRate() const;
size_t AudioSize() const;
audio_format AudioFormat() const;
speaker_layout SpeakerLayout() const;
NTV2DeviceID deviceID;
IOSelection ioSelect;
NTV2InputSource inputSource;
NTV2VideoFormat videoFormat;
NTV2PixelFormat pixelFormat;
SDI4KTransport sdi4kTransport;
VPIDDataList vpids;
uint32_t audioNumChannels;
uint32_t audioSampleSize;
uint32_t audioSampleRate;
bool autoDetect;
bool deactivateWhileNotShowing;
};
class OutputProps {
public:
explicit OutputProps(NTV2DeviceID devID);
~OutputProps() = default;
OutputProps(const OutputProps &props);
OutputProps(OutputProps &&props);
void operator=(const OutputProps &props);
void operator=(OutputProps &&props);
bool operator==(const OutputProps &props);
bool operator!=(const OutputProps &props);
NTV2FormatDesc FormatDesc();
NTV2Channel Channel() const;
NTV2AudioSystem AudioSystem() const;
NTV2AudioRate AudioRate() const;
size_t AudioSize() const;
audio_format AudioFormat() const;
speaker_layout SpeakerLayout() const;
NTV2DeviceID deviceID;
IOSelection ioSelect;
NTV2OutputDestination outputDest;
NTV2VideoFormat videoFormat;
NTV2PixelFormat pixelFormat;
SDI4KTransport sdi4kTransport;
uint32_t audioNumChannels;
uint32_t audioSampleSize;
uint32_t audioSampleRate;
};

823
plugins/aja/aja-routing.cpp Normal file
View File

@ -0,0 +1,823 @@
#include "aja-card-manager.hpp"
#include "aja-common.hpp"
#include "aja-routing.hpp"
#include "aja-widget-io.hpp"
// Signal routing crosspoint and register setting tables for SDI/HDMI/etc.
#include "routing/hdmi_rgb_capture.h"
#include "routing/hdmi_rgb_display.h"
#include "routing/hdmi_ycbcr_capture.h"
#include "routing/hdmi_ycbcr_display.h"
#include "routing/sdi_ycbcr_capture.h"
#include "routing/sdi_ycbcr_display.h"
#include "routing/sdi_rgb_capture.h"
#include "routing/sdi_rgb_display.h"
#include <ajabase/common/common.h>
#include <ajantv2/includes/ntv2card.h>
#include <ajantv2/includes/ntv2devicefeatures.h>
#include <obs-module.h>
RasterDefinition GetRasterDefinition(IOSelection io, NTV2VideoFormat vf,
NTV2DeviceID deviceID)
{
RasterDefinition def = RasterDefinition::Unknown;
if (NTV2_IS_SD_VIDEO_FORMAT(vf)) {
def = RasterDefinition::SD;
} else if (NTV2_IS_HD_VIDEO_FORMAT(vf)) {
def = RasterDefinition::HD;
} else if (NTV2_IS_QUAD_FRAME_FORMAT(vf)) {
def = RasterDefinition::UHD_4K;
/* NOTE(paulh): Special enum for Kona5 Retail & IO4K+ firmwares which route UHD/4K formats
* over 1x 6G/12G SDI using an undocumented crosspoint config.
*/
if (aja::IsSDIOneWireIOSelection(io) &&
aja::IsRetailSDI12G(deviceID))
def = RasterDefinition::UHD_4K_Retail_12G;
} else if (NTV2_IS_QUAD_QUAD_FORMAT(vf)) {
def = RasterDefinition::UHD2_8K;
} else {
def = RasterDefinition::Unknown;
}
return def;
}
#define NTV2UTILS_ENUM_CASE_RETURN_STR(enum_name) \
case (enum_name): \
return #enum_name
std::string RasterDefinitionToString(RasterDefinition rd)
{
std::string str = "";
switch (rd) {
NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::SD);
NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::HD);
NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::UHD_4K);
NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::UHD2_8K);
NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::Unknown);
}
return str;
}
/*
* Parse the widget routing shorthand string into a map of input and output NTV2CrosspointIDs.
* For example "sdi[0][0]->fb[0][0]" is shorthand for connecting the output crosspoint for
* SDI1/Datastream1 (NTV2_XptSDIIn1) to the input crosspoint for Framestore1/Datastream1 (NTV2_XptFrameBuffer1Input).
* These routing shorthand strings are found within the RoutingConfig structs in the "routing" sub-directory of the plugin.
*/
bool Routing::ParseRouteString(const std::string &route,
NTV2XptConnections &cnx)
{
blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Input string: %s",
route.c_str());
std::string route_lower(route);
route_lower = aja::lower(route_lower);
const std::string &route_strip = aja::replace(route_lower, " ", "");
if (route_strip.empty()) {
blog(LOG_DEBUG,
"Routing::ParseRouteString: input string is empty!");
return false;
}
/* TODO(paulh): Tally up the lines and tokens and check that they are all parsed OK.
* Right now we just return true if ANY tokens were parsed. This is OK _for now_ because
* the route strings currently only come from a known set.
*/
NTV2StringList lines;
NTV2StringList tokens;
lines = aja::split(route_strip, ';');
if (lines.empty())
lines.push_back(route_strip);
int32_t parse_ok = 0;
for (const auto &l : lines) {
if (l.empty()) {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: Empty line!");
continue;
}
blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Line: %s",
l.c_str());
NTV2StringList tokens = aja::split(l, "->");
if (tokens.empty() || tokens.size() != 2) {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: Invalid token count!");
continue;
}
const std::string &left = tokens[0]; // output crosspoint
const std::string &right = tokens[1]; // input crosspoint
if (left.empty() || left.length() > 64) {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: Invalid Left token!");
continue;
}
if (right.empty() || right.length() > 64) {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: Invalid right token!");
continue;
}
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: Left Token: %s -> Right Token: %s",
left.c_str(), right.c_str());
// Parse Output Crosspoint from left token
int32_t out_chan = 0;
int32_t out_ds = 0;
std::string out_name(64, ' ');
if (std::sscanf(left.c_str(), "%[A-Za-z_0-9][%d][%d]",
&out_name[0], &out_chan, &out_ds)) {
out_name = aja::rstrip(out_name).substr(
0, out_name.find_first_of('\0'));
WidgetOutputSocket widget_out;
if (WidgetOutputSocket::Find(out_name,
(NTV2Channel)out_chan,
out_ds, widget_out)) {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: Found NTV2OutputCrosspointID %s",
NTV2OutputCrosspointIDToString(
widget_out.id)
.c_str());
// Parse Input Crosspoint from right token
int32_t inp_chan = 0;
int32_t inp_ds = 0;
std::string inp_name(64, ' ');
if (std::sscanf(right.c_str(),
"%[A-Za-z_0-9][%d][%d]",
&inp_name[0], &inp_chan,
&inp_ds)) {
inp_name = aja::rstrip(inp_name).substr(
0,
inp_name.find_first_of('\0'));
WidgetInputSocket widget_inp;
if (WidgetInputSocket::Find(
inp_name,
(NTV2Channel)inp_chan,
inp_ds, widget_inp)) {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: Found NTV2InputCrosspointID %s",
NTV2InputCrosspointIDToString(
widget_inp.id)
.c_str());
cnx[widget_inp.id] =
widget_out.id;
parse_ok++;
} else {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: NTV2InputCrosspointID not found!");
}
}
} else {
blog(LOG_DEBUG,
"aja::Routing::ParseRouteString: NTV2OutputCrosspointID not found!");
}
}
}
return parse_ok > 0;
}
// Determine the appropriate SDIWireFormat based on the specified device ID and VPID specification.
bool Routing::DetermineSDIWireFormat(NTV2DeviceID deviceID, VPIDSpec spec,
SDIWireFormat &swf)
{
if (deviceID == DEVICE_ID_KONA5 || deviceID == DEVICE_ID_IO4KPLUS) {
static const std::vector<VPIDStandard> kRetail6GVpidStandards = {
VPIDStandard_2160_Single_6Gb,
VPIDStandard_1080_Single_6Gb,
VPIDStandard_1080_AFR_Single_6Gb,
};
static const std::vector<VPIDStandard> kRetail12GVpidStandards =
{VPIDStandard_2160_Single_12Gb,
VPIDStandard_1080_10_12_AFR_Single_12Gb};
if (spec.first == RasterDefinition::UHD_4K &&
aja::vec_contains<VPIDStandard>(kRetail6GVpidStandards,
spec.second)) {
swf = SDIWireFormat::
UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus;
return true;
} else if (spec.first == RasterDefinition::UHD_4K &&
aja::vec_contains<VPIDStandard>(
kRetail12GVpidStandards, spec.second)) {
swf = SDIWireFormat::
UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus;
return true;
} else {
if (kSDIWireFormats.find(spec) !=
kSDIWireFormats.end()) {
swf = kSDIWireFormats.at(spec);
return true;
}
}
} else {
if (kSDIWireFormats.find(spec) != kSDIWireFormats.end()) {
swf = kSDIWireFormats.at(spec);
return true;
}
}
return false;
}
// Lookup configuration for HDMI input/output in the routing table.
bool Routing::FindRoutingConfigHDMI(HDMIWireFormat hwf, NTV2Mode mode,
bool isRGB, NTV2DeviceID deviceID,
RoutingConfig &routing)
{
if (isRGB) {
if (mode == NTV2_MODE_CAPTURE) {
if (kHDMIRGBCaptureConfigs.find(hwf) !=
kHDMIRGBCaptureConfigs.end()) {
routing = kHDMIRGBCaptureConfigs.at(hwf);
return true;
}
} else {
if (deviceID == DEVICE_ID_TTAP_PRO) {
routing = kHDMIRGBDisplayConfigs.at(
HDMIWireFormat::TTAP_PRO);
return true;
}
if (kHDMIRGBDisplayConfigs.find(hwf) !=
kHDMIRGBDisplayConfigs.end()) {
routing = kHDMIRGBDisplayConfigs.at(hwf);
return true;
}
}
} else {
if (mode == NTV2_MODE_CAPTURE) {
if (kHDMIYCbCrCaptureConfigs.find(hwf) !=
kHDMIYCbCrCaptureConfigs.end()) {
routing = kHDMIYCbCrCaptureConfigs.at(hwf);
return true;
}
} else {
if (kHDMIYCbCrDisplayConfigs.find(hwf) !=
kHDMIYCbCrDisplayConfigs.end()) {
routing = kHDMIYCbCrDisplayConfigs.at(hwf);
return true;
}
}
}
return false;
}
// Lookup configuration for SDI input/output in the routing table.
bool Routing::FindRoutingConfigSDI(SDIWireFormat swf, NTV2Mode mode, bool isRGB,
NTV2DeviceID deviceID,
RoutingConfig &routing)
{
UNUSED_PARAMETER(deviceID);
if (isRGB) {
if (mode == NTV2_MODE_CAPTURE) {
if (kSDIRGBCaptureConfigs.find(swf) !=
kSDIRGBCaptureConfigs.end()) {
routing = kSDIRGBCaptureConfigs.at(swf);
return true;
}
} else if (mode == NTV2_MODE_DISPLAY) {
if (kSDIRGBDisplayConfigs.find(swf) !=
kSDIRGBDisplayConfigs.end()) {
routing = kSDIRGBDisplayConfigs.at(swf);
return true;
}
}
} else {
if (mode == NTV2_MODE_CAPTURE) {
if (kSDIYCbCrCaptureConfigs.find(swf) !=
kSDIYCbCrCaptureConfigs.end()) {
routing = kSDIYCbCrCaptureConfigs.at(swf);
return true;
}
} else if (mode == NTV2_MODE_DISPLAY) {
if (kSDIYCbCrDisplayConfigs.find(swf) !=
kSDIYCbCrDisplayConfigs.end()) {
routing = kSDIYCbCrDisplayConfigs.at(swf);
return true;
}
}
}
return false;
}
void Routing::StartSourceAudio(const SourceProps &props, CNTV2Card *card)
{
if (!card)
return;
auto inputSrc = props.inputSource;
auto channel = props.Channel();
auto audioSys = props.AudioSystem();
card->WriteAudioSource(0, channel);
card->SetAudioSystemInputSource(
audioSys, NTV2InputSourceToAudioSource(inputSrc),
NTV2InputSourceToEmbeddedAudioInput(inputSrc));
card->SetNumberAudioChannels(props.audioNumChannels, audioSys);
card->SetAudioRate(props.AudioRate(), audioSys);
card->SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, audioSys);
// Fix for AJA NTV2 internal bug #11467
ULWord magicAudioBits = 0;
if (NTV2_INPUT_SOURCE_IS_HDMI(inputSrc)) {
magicAudioBits = 0x00100000;
switch (inputSrc) {
default:
case NTV2_INPUTSOURCE_HDMI1:
magicAudioBits = 0x00100000;
break;
case NTV2_INPUTSOURCE_HDMI2:
magicAudioBits = 0x00110000;
break;
case NTV2_INPUTSOURCE_HDMI3:
magicAudioBits = 0x00900000;
break;
case NTV2_INPUTSOURCE_HDMI4:
magicAudioBits = 0x00910000;
break;
}
} else if (NTV2_INPUT_SOURCE_IS_ANALOG(inputSrc)) {
magicAudioBits = 0x00000990;
} else { // SDI
magicAudioBits = 0x00000320;
}
// TODO(paulh): Ask aja-seanl about these deprecated calls and if they are still needed
ULWord oldValue = 0;
if (card->ReadAudioSource(oldValue, channel)) {
card->WriteAudioSource(oldValue | magicAudioBits, channel);
}
for (int a = 0; a < NTV2DeviceGetNumAudioSystems(card->GetDeviceID());
a++) {
card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF,
NTV2AudioSystem(a));
}
card->StartAudioInput(audioSys);
card->SetAudioCaptureEnable(audioSys, true);
}
void Routing::StopSourceAudio(const SourceProps &props, CNTV2Card *card)
{
if (card) {
auto audioSys = props.AudioSystem();
card->SetAudioCaptureEnable(audioSys, false);
card->StopAudioInput(audioSys);
}
}
// Guess an SDIWireFormat based on specified Video Format, IOSelection, 4K Transport and device ID.
SDIWireFormat GuessSDIWireFormat(NTV2VideoFormat vf, IOSelection io,
SDI4KTransport t4k,
NTV2DeviceID device_id = DEVICE_ID_NOTFOUND)
{
auto rd = GetRasterDefinition(io, vf, device_id);
auto fg = GetNTV2FrameGeometryFromVideoFormat(vf);
SDIWireFormat swf = SDIWireFormat::Unknown;
if (rd == RasterDefinition::SD) {
swf = SDIWireFormat::SD_ST352;
} else if (rd == RasterDefinition::HD) {
if (fg == NTV2_FG_1280x720) {
swf = SDIWireFormat::HD_720p_ST292;
} else if (fg == NTV2_FG_1920x1080 || fg == NTV2_FG_2048x1080) {
swf = SDIWireFormat::HD_1080_ST292;
}
} else if (rd == RasterDefinition::UHD_4K) {
if (t4k == SDI4KTransport::Squares) {
if (aja::IsSDIFourWireIOSelection(io)) {
swf = SDIWireFormat::UHD4K_ST292_Quad_1_5_Squares;
} else if (aja::IsSDITwoWireIOSelection(io)) {
if (t4k == SDI4KTransport::Squares) {
swf = SDIWireFormat::
UHD4K_ST292_Dual_1_5_Squares;
} else {
swf = SDIWireFormat::
UHD4K_ST425_Dual_3Gb_2SI;
}
}
} else if (t4k == SDI4KTransport::TwoSampleInterleave) {
if (aja::IsSDIOneWireIOSelection(io)) {
if (NTV2_IS_4K_HFR_VIDEO_FORMAT(vf)) {
if (aja::IsRetailSDI12G(device_id)) {
swf = SDIWireFormat::
UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus;
} else {
swf = SDIWireFormat::
UHD4K_ST2018_12G_Squares_2SI;
}
} else {
if (aja::IsRetailSDI12G(device_id)) {
swf = SDIWireFormat::
UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus;
} else {
swf = SDIWireFormat::
UHD4K_ST2018_6G_Squares_2SI;
}
}
} else if (aja::IsSDITwoWireIOSelection(io)) {
swf = SDIWireFormat::UHD4K_ST425_Dual_3Gb_2SI;
} else if (aja::IsSDIFourWireIOSelection(io)) {
swf = SDIWireFormat::UHD4K_ST425_Quad_3Gb_2SI;
}
}
}
return swf;
}
bool Routing::ConfigureSourceRoute(const SourceProps &props, NTV2Mode mode,
CNTV2Card *card)
{
if (!card)
return false;
auto deviceID = props.deviceID;
NTV2VideoFormat vf = props.videoFormat;
if (NTV2_VIDEO_FORMAT_IS_B(props.videoFormat)) {
vf = aja::GetLevelAFormatForLevelBFormat(props.videoFormat);
}
NTV2InputSourceSet inputSources;
aja::IOSelectionToInputSources(props.ioSelect, inputSources);
if (inputSources.empty()) {
blog(LOG_DEBUG,
"No Input Sources specified to configure routing!");
return false;
}
auto init_src = *inputSources.begin();
auto init_channel = NTV2InputSourceToChannel(init_src);
RoutingConfig rc;
if (NTV2_INPUT_SOURCE_IS_SDI(init_src)) {
SDIWireFormat swf = SDIWireFormat::Unknown;
auto standard = VPIDStandard_Unknown;
auto vpidList = props.vpids;
if (vpidList.size() > 0)
standard = vpidList.at(0).Standard();
if (standard != VPIDStandard_Unknown) {
// Determine SDI format based on raster "definition" and VPID byte 1 value (AKA SMPTE standard)
auto rasterDef = GetRasterDefinition(props.ioSelect, vf,
props.deviceID);
VPIDSpec vpidSpec = std::make_pair(rasterDef, standard);
DetermineSDIWireFormat(deviceID, vpidSpec, swf);
} else {
// Best guess SDI format from incoming video format if no VPIDs detected
swf = GuessSDIWireFormat(vf, props.ioSelect,
props.sdi4kTransport,
props.deviceID);
}
if (swf == SDIWireFormat::Unknown) {
blog(LOG_DEBUG, "Could not determine SDI Wire Format!");
return false;
}
if (!FindRoutingConfigSDI(swf, mode,
NTV2_IS_FBF_RGB(props.pixelFormat),
props.deviceID, rc)) {
blog(LOG_DEBUG,
"Could not find RoutingConfig for SDI Wire Format!");
return false;
}
} else if (NTV2_INPUT_SOURCE_IS_HDMI(init_src)) {
HDMIWireFormat hwf = HDMIWireFormat::Unknown;
if (NTV2_IS_FBF_RGB(props.pixelFormat)) {
if (NTV2_IS_HD_VIDEO_FORMAT(vf))
hwf = HDMIWireFormat::HD_RGB_LFR;
} else {
if (NTV2_IS_HD_VIDEO_FORMAT(vf))
hwf = HDMIWireFormat::HD_YCBCR_LFR;
else if (NTV2_IS_4K_VIDEO_FORMAT(vf))
hwf = HDMIWireFormat::UHD_4K_YCBCR_LFR;
}
if (!FindRoutingConfigHDMI(hwf, mode,
NTV2_IS_FBF_RGB(props.pixelFormat),
props.deviceID, rc)) {
blog(LOG_DEBUG,
"Could not find RoutingConfig for HDMI Wire Format!");
return false;
}
}
// Substitute channel placeholders for actual indices
std::string route_string = rc.route_string;
ULWord start_channel_index = GetIndexForNTV2Channel(init_channel);
for (ULWord c = 0; c < 8; c++) {
std::string channel_placeholder =
std::string("{ch" + aja::to_string(c + 1) + "}");
route_string =
aja::replace(route_string, channel_placeholder,
aja::to_string(start_channel_index++));
}
NTV2XptConnections cnx;
ParseRouteString(route_string, cnx);
card->ApplySignalRoute(cnx, false);
// Apply SDI widget settings
start_channel_index = GetIndexForNTV2Channel(init_channel);
for (uint32_t i = (uint32_t)start_channel_index;
i < (start_channel_index + rc.num_wires); i++) {
NTV2Channel channel = GetNTV2ChannelForIndex(i);
if (::NTV2DeviceHasBiDirectionalSDI(deviceID)) {
card->SetSDITransmitEnable(channel,
mode == NTV2_MODE_DISPLAY);
}
card->SetSDIOut3GEnable(channel, rc.enable_3g_out);
card->SetSDIOut3GbEnable(channel, rc.enable_3gb_out);
card->SetSDIOut6GEnable(channel, rc.enable_6g_out);
card->SetSDIOut12GEnable(channel, rc.enable_12g_out);
card->SetSDIInLevelBtoLevelAConversion((UWord)i,
rc.convert_3g_in);
card->SetSDIOutLevelAtoLevelBConversion((UWord)i,
rc.convert_3g_out);
card->SetSDIOutRGBLevelAConversion((UWord)i,
rc.enable_rgb_3ga_convert);
}
// Apply Framestore settings
for (uint32_t i = (uint32_t)start_channel_index;
i < (start_channel_index + rc.num_framestores); i++) {
NTV2Channel channel = GetNTV2ChannelForIndex(i);
card->EnableChannel(channel);
card->SetMode(channel, mode);
card->SetVANCMode(NTV2_VANCMODE_OFF, channel);
card->SetVideoFormat(vf, false, false, channel);
card->SetFrameBufferFormat(channel, props.pixelFormat);
card->SetTsiFrameEnable(rc.enable_tsi, channel);
card->Set4kSquaresEnable(rc.enable_4k_squares, channel);
card->SetQuadQuadSquaresEnable(rc.enable_8k_squares, channel);
}
return true;
}
bool Routing::ConfigureOutputRoute(const OutputProps &props, NTV2Mode mode,
CNTV2Card *card)
{
if (!card)
return false;
auto deviceID = props.deviceID;
NTV2OutputDestinations outputDests;
aja::IOSelectionToOutputDests(props.ioSelect, outputDests);
if (outputDests.empty()) {
blog(LOG_DEBUG,
"No Output Destinations specified to configure routing!");
return false;
}
auto init_dest = *outputDests.begin();
auto init_channel = NTV2OutputDestinationToChannel(init_dest);
RoutingConfig rc;
if (NTV2_OUTPUT_DEST_IS_SDI(init_dest)) {
SDIWireFormat swf = GuessSDIWireFormat(props.videoFormat,
props.ioSelect,
props.sdi4kTransport,
props.deviceID);
if (swf == SDIWireFormat::Unknown) {
blog(LOG_DEBUG, "Could not determine SDI Wire Format!");
return false;
}
if (!FindRoutingConfigSDI(swf, mode,
NTV2_IS_FBF_RGB(props.pixelFormat),
props.deviceID, rc)) {
blog(LOG_DEBUG,
"Could not find RoutingConfig for SDI Wire Format!");
return false;
}
} else if (NTV2_OUTPUT_DEST_IS_HDMI(init_dest)) {
HDMIWireFormat hwf = HDMIWireFormat::Unknown;
// special case devices...
if (props.deviceID == DEVICE_ID_TTAP_PRO) {
hwf = HDMIWireFormat::TTAP_PRO;
} else {
// ...all other devices.
if (NTV2_IS_FBF_RGB(props.pixelFormat)) {
if (NTV2_IS_HD_VIDEO_FORMAT(props.videoFormat))
hwf = HDMIWireFormat::HD_RGB_LFR;
} else {
if (NTV2_IS_HD_VIDEO_FORMAT(
props.videoFormat)) {
hwf = HDMIWireFormat::HD_YCBCR_LFR;
} else if (NTV2_IS_4K_VIDEO_FORMAT(
props.videoFormat)) {
hwf = HDMIWireFormat::UHD_4K_YCBCR_LFR;
}
}
}
if (!FindRoutingConfigHDMI(hwf, mode,
NTV2_IS_FBF_RGB(props.pixelFormat),
props.deviceID, rc)) {
blog(LOG_DEBUG,
"Could not find RoutingConfig for HDMI Wire Format!");
return false;
}
}
std::string route_string = rc.route_string;
// Replace framestore channel placeholders
ULWord start_framestore_index = initial_framestore_output_index(
deviceID, props.ioSelect, init_channel);
for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
std::string fs_channel_placeholder =
std::string("fb[{ch" + aja::to_string(c + 1) + "}]");
route_string = aja::replace(
route_string, fs_channel_placeholder,
"fb[" + aja::to_string(start_framestore_index++) + "]");
}
// Replace other channel placeholders
ULWord start_channel_index = GetIndexForNTV2Channel(init_channel);
for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
std::string channel_placeholder =
std::string("{ch" + aja::to_string(c + 1) + "}");
route_string =
aja::replace(route_string, channel_placeholder,
aja::to_string(start_channel_index++));
}
NTV2XptConnections cnx;
ParseRouteString(route_string, cnx);
card->ApplySignalRoute(cnx, false);
// Apply SDI widget settings
if (props.ioSelect != IOSelection::HDMIMonitorOut) {
start_channel_index = GetIndexForNTV2Channel(init_channel);
for (uint32_t i = (uint32_t)start_channel_index;
i < (start_channel_index + rc.num_wires); i++) {
NTV2Channel channel = GetNTV2ChannelForIndex(i);
if (::NTV2DeviceHasBiDirectionalSDI(deviceID)) {
card->SetSDITransmitEnable(
channel, mode == NTV2_MODE_DISPLAY);
}
card->SetSDIOut3GEnable(channel, rc.enable_3g_out);
card->SetSDIOut3GbEnable(channel, rc.enable_3gb_out);
card->SetSDIOut6GEnable(channel, rc.enable_6g_out);
card->SetSDIOut12GEnable(channel, rc.enable_12g_out);
card->SetSDIInLevelBtoLevelAConversion(
(UWord)i, rc.convert_3g_in);
card->SetSDIOutLevelAtoLevelBConversion(
(UWord)i, rc.convert_3g_out);
card->SetSDIOutRGBLevelAConversion(
(UWord)i, rc.enable_rgb_3ga_convert);
}
}
// Apply Framestore settings
start_framestore_index = initial_framestore_output_index(
deviceID, props.ioSelect, init_channel);
for (uint32_t i = (uint32_t)start_framestore_index;
i < (start_framestore_index + rc.num_framestores); i++) {
NTV2Channel channel = GetNTV2ChannelForIndex(i);
card->EnableChannel(channel);
card->SetMode(channel, mode);
card->SetVANCMode(NTV2_VANCMODE_OFF, channel);
card->SetVideoFormat(props.videoFormat, false, false, channel);
card->SetFrameBufferFormat(channel, props.pixelFormat);
card->SetTsiFrameEnable(rc.enable_tsi, channel);
card->Set4kSquaresEnable(rc.enable_4k_squares, channel);
card->SetQuadQuadSquaresEnable(rc.enable_8k_squares, channel);
}
return true;
}
ULWord Routing::initial_framestore_output_index(NTV2DeviceID deviceID,
IOSelection io,
NTV2Channel init_channel)
{
if (deviceID == DEVICE_ID_TTAP_PRO) {
return 0;
} else if (deviceID == DEVICE_ID_KONA1) {
return 1;
} else if (deviceID == DEVICE_ID_IO4K ||
deviceID == DEVICE_ID_IO4KPLUS) {
// SDI Monitor output uses framestore 4
if (io == IOSelection::SDI5)
return 3;
}
// HDMI Monitor output uses framestore 4
if (io == IOSelection::HDMIMonitorOut) {
return 3;
}
return GetIndexForNTV2Channel(init_channel);
}
// Output Routing
void Routing::ConfigureOutputAudio(const OutputProps &props, CNTV2Card *card)
{
if (!card)
return;
auto deviceID = card->GetDeviceID();
auto audioSys = props.AudioSystem();
auto channel = props.Channel();
card->SetNumberAudioChannels(props.audioNumChannels, audioSys);
card->SetAudioRate(props.AudioRate(), audioSys);
card->SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, audioSys);
card->SetAudioOutputDelay(audioSys, 0);
card->SetSDIOutputAudioSystem(channel, audioSys);
card->SetSDIOutputDS2AudioSystem(channel, audioSys);
/* NOTE(paulh):
* The SDK has a specifies an SDI audio system by Channel rather than by SDI output
* and certain devices require setting the SDI audio system to NTV2_CHANNEL1.
* i.e.
* SDI 1 = NTV2_CHANNEL1
* SDI 2 = NTV2_CHANNEL2
* ...
* SDI 5/Monitor = NTV2_CHANNEL5
* etc...
*
* This fixes AJA internal bugs: 10730, 10986, 16274
*/
if (deviceID == DEVICE_ID_IOXT || deviceID == DEVICE_ID_IO4KUFC ||
deviceID == DEVICE_ID_IO4KPLUS || deviceID == DEVICE_ID_KONA1 ||
deviceID == DEVICE_ID_KONA3G || deviceID == DEVICE_ID_KONA4UFC ||
deviceID == DEVICE_ID_KONA5 || deviceID == DEVICE_ID_KONA5_2X4K) {
// Make sure SDI out 1 (aka Channel 1) is set to the correct sub-system
card->SetSDIOutputAudioSystem(NTV2_CHANNEL1, audioSys);
card->SetSDIOutputDS2AudioSystem(NTV2_CHANNEL1, audioSys);
}
// make sure that audio is setup for the SDI monitor output on devices that support it
if (NTV2DeviceCanDoWidget(deviceID, NTV2_WgtSDIMonOut1)) {
card->SetSDIOutputAudioSystem(NTV2_CHANNEL5, audioSys);
card->SetSDIOutputDS2AudioSystem(NTV2_CHANNEL5, audioSys);
}
card->SetHDMIOutAudioRate(props.AudioRate());
card->SetHDMIOutAudioFormat(NTV2_AUDIO_FORMAT_LPCM);
card->SetAudioOutputMonitorSource(NTV2_AudioChannel1_2, channel);
card->SetAESOutputSource(NTV2_AudioChannel1_4, audioSys,
NTV2_AudioChannel1_4);
card->SetAESOutputSource(NTV2_AudioChannel5_8, audioSys,
NTV2_AudioChannel5_8);
card->SetAESOutputSource(NTV2_AudioChannel9_12, audioSys,
NTV2_AudioChannel9_12);
card->SetAESOutputSource(NTV2_AudioChannel13_16, audioSys,
NTV2_AudioChannel13_16);
// make sure that audio is setup for HDMI output on devices that support it
if (NTV2DeviceGetNumHDMIVideoOutputs(deviceID) > 0) {
if (NTV2DeviceCanDoAudioMixer(deviceID)) {
card->SetAudioMixerInputAudioSystem(
NTV2_AudioMixerInputMain, audioSys);
card->SetAudioMixerInputChannelSelect(
NTV2_AudioMixerInputMain, NTV2_AudioChannel1_2);
card->SetAudioMixerInputChannelsMute(
NTV2_AudioMixerInputAux1,
NTV2AudioChannelsMuteAll);
card->SetAudioMixerInputChannelsMute(
NTV2_AudioMixerInputAux2,
NTV2AudioChannelsMuteAll);
}
card->SetHDMIOutAudioChannels(NTV2_HDMIAudio8Channels);
card->SetHDMIOutAudioSource2Channel(NTV2_AudioChannel1_2,
audioSys);
card->SetHDMIOutAudioSource8Channel(NTV2_AudioChannel1_8,
audioSys);
}
card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF, audioSys);
card->StopAudioOutput(audioSys);
}

139
plugins/aja/aja-routing.hpp Normal file
View File

@ -0,0 +1,139 @@
#pragma once
#include "aja-props.hpp"
#include <ajantv2/includes/ntv2enums.h>
#include <iostream>
#include <string>
#include <map>
#include <vector>
class CNTV2Card;
/* The AJA hardware and NTV2 SDK uses a concept called "Signal Routing" to connect high-level firmware
* blocks known as "Widgets" to one another via "crosspoint" connections. This facilitates streaming
* data from one Widget to another to achieve specific functionality.
* Such functionality may include SDI/HDMI capture/output, colorspace conversion, hardware LUTs, etc.
*
* This code references a table of RoutingConfig entries, where each entry contains the settings required
* to configure an AJA device for a particular capture or output task. These settings include the number of
* physical IO Widgets (SDI or HDMI) required, number of framestore Widgets required, register settings
* that must be enabled or disabled, and a special short-hand "route string".
* Of special note is the route string, which is parsed into a map of NTV2XptConnections. These connections
* are then applied as the "signal route", connecting the Widget's crosspoints together.
*/
struct RoutingConfig {
NTV2Mode mode; // capture or playout?
uint32_t num_wires; // number of physical connections
uint32_t num_framestores; // number of framestores used
bool enable_3g_out; // enable register for 3G SDI Output?
bool enable_6g_out; // enable register for 6G SDI Output?
bool enable_12g_out; // enable register for 12G SDI Output?
bool convert_3g_in; // enable register for 3G level-B -> level-A SDI input conversion?
bool convert_3g_out; // enable register for 3G level-A -> level-B SDI output conversion?
bool enable_rgb_3ga_convert; // enable register for RGB 3G level-B -> level-A SDI output conversion?
bool enable_3gb_out; // enable register for 3G level-B SDI output?
bool enable_4k_squares; // enable register for 4K square division?
bool enable_8k_squares; // enable register for 8K square division?
bool enable_tsi; // enable register for two-sample interleave (UHD/4K/8K)
std::string
route_string; // signal routing shorthand string to parse into crosspoint connections
};
/* This table is used to correlate a particular "raster definition" (i.e. SD/HD/4K/etc.)
* and SMPTE VPID transport byte (VPIDStandard) to an SDIWireFormat enum.
* This allows mapping SDI video signals to the correct format, particularly in the case
* where multiple SDI formats share the same VPID transport value.
* For example: VPIDStandard_1080 (0x85) is used on the wire for both single-link (1x SDI wire)
* 1080-line HD SDI video AND quad-link (4x SDI wires) UHD/4K "square-division" video.
*/
using VPIDSpec = std::pair<RasterDefinition, VPIDStandard>;
static const std::map<VPIDSpec, SDIWireFormat> kSDIWireFormats = {
{{RasterDefinition::SD, VPIDStandard_483_576}, SDIWireFormat::SD_ST352},
{{RasterDefinition::HD, VPIDStandard_720},
SDIWireFormat::HD_720p_ST292},
{{RasterDefinition::HD, VPIDStandard_1080},
SDIWireFormat::HD_1080_ST292},
{{RasterDefinition::HD, VPIDStandard_1080_DualLink},
SDIWireFormat::HD_1080_ST372_Dual},
{{RasterDefinition::HD, VPIDStandard_720_3Ga},
SDIWireFormat::HD_720p_ST425_3Ga},
{{RasterDefinition::HD, VPIDStandard_1080_3Ga},
SDIWireFormat::HD_1080p_ST425_3Ga},
{{RasterDefinition::HD, VPIDStandard_1080_DualLink_3Gb},
SDIWireFormat::HD_1080p_ST425_3Gb_DL},
{{RasterDefinition::HD, VPIDStandard_720_3Gb},
SDIWireFormat::HD_720p_ST425_3Gb},
{{RasterDefinition::HD, VPIDStandard_1080_3Gb},
SDIWireFormat::HD_1080p_ST425_3Gb},
{{RasterDefinition::HD, VPIDStandard_1080_Dual_3Ga},
SDIWireFormat::HD_1080p_ST425_Dual_3Ga},
{{RasterDefinition::HD, VPIDStandard_1080_Dual_3Gb},
SDIWireFormat::HD_1080p_ST425_Dual_3Gb},
{{RasterDefinition::UHD_4K, VPIDStandard_1080_3Gb},
SDIWireFormat::UHD4K_ST292_Dual_1_5_Squares},
{{RasterDefinition::UHD_4K, VPIDStandard_1080},
SDIWireFormat::UHD4K_ST292_Quad_1_5_Squares},
{{RasterDefinition::UHD_4K, VPIDStandard_1080_3Ga},
SDIWireFormat::UHD4K_ST425_Quad_3Ga_Squares},
{{RasterDefinition::UHD_4K, VPIDStandard_1080_DualLink_3Gb},
SDIWireFormat::UHD4K_ST425_Quad_3Gb_Squares},
{{RasterDefinition::UHD_4K, VPIDStandard_2160_DualLink},
SDIWireFormat::UHD4K_ST425_Dual_3Gb_2SI},
{{RasterDefinition::UHD_4K, VPIDStandard_2160_QuadLink_3Ga},
SDIWireFormat::UHD4K_ST425_Quad_3Ga_2SI},
{{RasterDefinition::UHD_4K, VPIDStandard_2160_QuadDualLink_3Gb},
SDIWireFormat::UHD4K_ST425_Quad_3Gb_2SI},
{{RasterDefinition::UHD_4K, VPIDStandard_2160_Single_6Gb},
SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI},
{{RasterDefinition::UHD_4K_Retail_12G, VPIDStandard_2160_Single_6Gb},
SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus},
{{RasterDefinition::UHD_4K, VPIDStandard_2160_Single_12Gb},
SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI},
{{RasterDefinition::UHD_4K_Retail_12G, VPIDStandard_2160_Single_12Gb},
SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus},
{{RasterDefinition::UHD2_8K, VPIDStandard_4320_DualLink_12Gb},
SDIWireFormat::UHD28K_ST2082_Dual_12G},
{{RasterDefinition::UHD2_8K, VPIDStandard_2160_DualLink_12Gb},
SDIWireFormat::UHD28K_ST2082_RGB_Dual_12G},
{{RasterDefinition::UHD2_8K, VPIDStandard_4320_QuadLink_12Gb},
SDIWireFormat::UHD28K_ST2082_Quad_12G},
};
extern RasterDefinition
GetRasterDefinition(IOSelection io, NTV2VideoFormat vf,
NTV2DeviceID deviceID = DEVICE_ID_NOTFOUND);
extern std::string RasterDefinitionToString(RasterDefinition rd);
// Applies RoutingConfig settings to the card to configure a specific SDI/HDMI capture/output mode.
class Routing {
public:
static bool ParseRouteString(const std::string &route,
NTV2XptConnections &cnx);
static bool DetermineSDIWireFormat(NTV2DeviceID deviceID, VPIDSpec spec,
SDIWireFormat &swf);
static bool FindRoutingConfigHDMI(HDMIWireFormat hwf, NTV2Mode mode,
bool isRGB, NTV2DeviceID deviceID,
RoutingConfig &routing);
static bool FindRoutingConfigSDI(SDIWireFormat swf, NTV2Mode mode,
bool isRGB, NTV2DeviceID deviceID,
RoutingConfig &routing);
static void StartSourceAudio(const SourceProps &props, CNTV2Card *card);
static void StopSourceAudio(const SourceProps &props, CNTV2Card *card);
static bool ConfigureSourceRoute(const SourceProps &props,
NTV2Mode mode, CNTV2Card *card);
static bool ConfigureOutputRoute(const OutputProps &props,
NTV2Mode mode, CNTV2Card *card);
static ULWord initial_framestore_output_index(NTV2DeviceID deviceID,
IOSelection io,
NTV2Channel init_channel);
static void ConfigureOutputAudio(const OutputProps &props,
CNTV2Card *card);
};

1184
plugins/aja/aja-source.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
#pragma once
#include "aja-props.hpp"
#include <obs-module.h>
#include <ajantv2/includes/ajatypes.h>
#include <ajantv2/includes/ntv2testpatterngen.h>
#include <ajabase/common/types.h>
#include <ajabase/system/thread.h>
#include <mutex>
class CNTV2Card;
class AJASource {
public:
explicit AJASource(obs_source_t *source);
~AJASource();
void SetCard(CNTV2Card *card);
CNTV2Card *GetCard();
void SetOBSSource(obs_source_t *source);
obs_source_t *GetOBSSource(void) const;
void SetName(const std::string &name);
std::string GetName() const;
void GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
NTV2TestPatternSelect ps);
// Capture Thread stuff
static void CaptureThread(AJAThread *thread, void *data);
void Activate(bool enable = false);
void Deactivate();
bool IsCapturing() const;
void SetCapturing(bool capturing);
// CardEntry/Device stuff
std::string CardID() const;
void SetCardID(const std::string &cardID);
uint32_t DeviceIndex() const;
void SetDeviceIndex(uint32_t index);
// Source Props
void SetSourceProps(const SourceProps &props);
SourceProps GetSourceProps() const;
bool ReadChannelVPIDs(NTV2Channel channel, VPIDData &vpids);
bool ReadWireFormats(NTV2DeviceID device_id,
const NTV2InputSourceSet &srcs,
NTV2VideoFormat &vf, NTV2PixelFormat &pf,
VPIDDataList &vpids);
void ResetVideoBuffer(NTV2VideoFormat vf, NTV2PixelFormat pf);
void ResetAudioBuffer(size_t size);
NTV2_POINTER mVideoBuffer;
NTV2_POINTER mAudioBuffer;
private:
CNTV2Card *mCard;
std::string mSourceName;
std::string mCardID;
UWord mDeviceIndex;
bool mBuffering;
bool mIsCapturing;
SourceProps mSourceProps;
NTV2TestPatternBuffer mTestPattern;
AJAThread *mCaptureThread;
std::mutex mMutex;
obs_source_t *mSource;
};

View File

@ -0,0 +1,91 @@
#pragma once
#include <obs-module.h>
static const char *kProgramOutputID = "aja_output";
static const char *kPreviewOutputID = "aja_preview_output";
struct UIProperty {
const char *id;
const char *text;
const char *tooltip;
};
static const UIProperty kUIPropCaptureModule = {
"aja_source",
obs_module_text("AJACapture.Device"),
"",
};
static const UIProperty kUIPropOutputModule = {
"aja_output",
obs_module_text("AJAOutput.Device"),
"",
};
// This is used as an "invisible" property to give the program and preview
// plugin instances an identifier before the output has been created/started.
// This ID is then used by the CardManager class for tracking device channel
// usage across the capture and output plugin instances.
static const UIProperty kUIPropAJAOutputID = {
"aja_output_id",
"",
"",
};
static const UIProperty kUIPropDevice = {
"ui_prop_device",
obs_module_text("Device"),
"",
};
static const UIProperty kUIPropOutput = {
"ui_prop_output",
obs_module_text("Output"),
"",
};
static const UIProperty kUIPropInput = {
"ui_prop_input",
obs_module_text("Input"),
"",
};
static const UIProperty kUIPropIOSelect = {"ui_prop_select_input",
obs_module_text("IOSelect"), ""};
static const UIProperty kUIPropSDI4KTransport = {
"ui_prop_sdi_transport",
obs_module_text("SDI4KTransport"),
"",
};
static const UIProperty kUIPropVideoFormatSelect = {
"ui_prop_vid_fmt",
obs_module_text("VideoFormat"),
"",
};
static const UIProperty kUIPropPixelFormatSelect = {
"ui_prop_pix_fmt",
obs_module_text("PixelFormat"),
"",
};
static const UIProperty kUIPropAutoStartOutput = {
"ui_prop_auto_start_output",
obs_module_text("AutoStart"),
"",
};
static const UIProperty kUIPropDeactivateWhenNotShowing = {
"ui_prop_deactivate_when_not_showing",
obs_module_text("DeactivateWhenNotShowing"),
"",
};
static const UIProperty kUIPropBuffering = {
"ui_prop_buffering",
obs_module_text("Buffering"),
"",
};

View File

@ -0,0 +1,475 @@
#include "aja-widget-io.hpp"
#include <ajantv2/includes/ntv2utils.h>
#include <iostream>
// firmware widget nicknames used by signal routing syntax parser
static const char *kFramebufferNickname = "fb";
static const char *kCSCNickname = "csc";
static const char *kDualLinkInNickname = "dli";
static const char *kDualLinkOutNickname = "dlo";
static const char *kLUTNickname = "lut";
static const char *kSDINickname = "sdi";
static const char *kMultiLinkNickname = "ml";
static const char *kMixerNickname = "mix";
static const char *kHDMINickname = "hdmi";
static const char *kLUT3DNickname = "lut3d";
static const char *k4KDownConvertNickname = "4kdc";
static const char *kAnalogNickname = "analog";
static const char *kTSIMuxNickname = "tsi";
static const char *kUpDownConvertNickname = "udc";
static const char *kCompositeNickname = "composite";
static const char *kStereoCompNickname = "stereo";
static const char *kWatermarkNickname = "watermark";
static const char *kBlackNickname = "black";
static const char *kCompressionNickname = "comp";
static const char *kFrameSyncNickname = "fsync";
static const char *kTestPatternNickname = "pat";
static const char *kOENickname = "oe";
// Table of firmware widget's input crosspoint/id/channel/name/datastream index
// clang-format off
static const WidgetInputSocket kWidgetInputSockets[] = {
//NTV2InputCrosspointID | NTV2WidgetID | NTV2Channel | Name | DatastreamIndex
{ NTV2_INPUT_CROSSPOINT_INVALID, NTV2_WIDGET_INVALID, NTV2_CHANNEL_INVALID, "", -1},
{ NTV2_XptFrameBuffer1Input, NTV2_WgtFrameBuffer1, NTV2_CHANNEL1, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer1BInput, NTV2_WgtFrameBuffer1, NTV2_CHANNEL1, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer2Input, NTV2_WgtFrameBuffer2, NTV2_CHANNEL2, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer2BInput, NTV2_WgtFrameBuffer2, NTV2_CHANNEL2, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer3Input, NTV2_WgtFrameBuffer3, NTV2_CHANNEL3, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer3BInput, NTV2_WgtFrameBuffer3, NTV2_CHANNEL3, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer4Input, NTV2_WgtFrameBuffer4, NTV2_CHANNEL4, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer4BInput, NTV2_WgtFrameBuffer4, NTV2_CHANNEL4, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer5Input, NTV2_WgtFrameBuffer5, NTV2_CHANNEL5, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer5BInput, NTV2_WgtFrameBuffer5, NTV2_CHANNEL5, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer6Input, NTV2_WgtFrameBuffer6, NTV2_CHANNEL6, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer6BInput, NTV2_WgtFrameBuffer6, NTV2_CHANNEL6, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer7Input, NTV2_WgtFrameBuffer7, NTV2_CHANNEL7, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer7BInput, NTV2_WgtFrameBuffer7, NTV2_CHANNEL7, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer8Input, NTV2_WgtFrameBuffer8, NTV2_CHANNEL8, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer8BInput, NTV2_WgtFrameBuffer8, NTV2_CHANNEL8, kFramebufferNickname, 1},
{ NTV2_XptCSC1VidInput, NTV2_WgtCSC1, NTV2_CHANNEL1, kCSCNickname, 0},
{ NTV2_XptCSC1KeyInput, NTV2_WgtCSC1, NTV2_CHANNEL1, kCSCNickname, 1},
{ NTV2_XptCSC2VidInput, NTV2_WgtCSC2, NTV2_CHANNEL2, kCSCNickname, 0},
{ NTV2_XptCSC2KeyInput, NTV2_WgtCSC2, NTV2_CHANNEL2, kCSCNickname, 1},
{ NTV2_XptCSC3VidInput, NTV2_WgtCSC3, NTV2_CHANNEL3, kCSCNickname, 0},
{ NTV2_XptCSC3KeyInput, NTV2_WgtCSC3, NTV2_CHANNEL3, kCSCNickname, 1},
{ NTV2_XptCSC4VidInput, NTV2_WgtCSC4, NTV2_CHANNEL4, kCSCNickname, 0},
{ NTV2_XptCSC4KeyInput, NTV2_WgtCSC4, NTV2_CHANNEL4, kCSCNickname, 1},
{ NTV2_XptCSC5VidInput, NTV2_WgtCSC5, NTV2_CHANNEL5, kCSCNickname, 0},
{ NTV2_XptCSC5KeyInput, NTV2_WgtCSC5, NTV2_CHANNEL5, kCSCNickname, 1},
{ NTV2_XptCSC6VidInput, NTV2_WgtCSC6, NTV2_CHANNEL6, kCSCNickname, 0},
{ NTV2_XptCSC6KeyInput, NTV2_WgtCSC6, NTV2_CHANNEL6, kCSCNickname, 1},
{ NTV2_XptCSC7VidInput, NTV2_WgtCSC7, NTV2_CHANNEL7, kCSCNickname, 0},
{ NTV2_XptCSC7KeyInput, NTV2_WgtCSC7, NTV2_CHANNEL7, kCSCNickname, 1},
{ NTV2_XptCSC8VidInput, NTV2_WgtCSC8, NTV2_CHANNEL8, kCSCNickname, 0},
{ NTV2_XptCSC8KeyInput, NTV2_WgtCSC8, NTV2_CHANNEL8, kCSCNickname, 1},
{ NTV2_XptLUT1Input, NTV2_WgtLUT1, NTV2_CHANNEL1, kLUTNickname, 0},
{ NTV2_XptLUT2Input, NTV2_WgtLUT2, NTV2_CHANNEL2, kLUTNickname, 0},
{ NTV2_XptLUT3Input, NTV2_WgtLUT3, NTV2_CHANNEL3, kLUTNickname, 0},
{ NTV2_XptLUT4Input, NTV2_WgtLUT4, NTV2_CHANNEL4, kLUTNickname, 0},
{ NTV2_XptLUT5Input, NTV2_WgtLUT5, NTV2_CHANNEL5, kLUTNickname, 0},
{ NTV2_XptLUT6Input, NTV2_WgtLUT6, NTV2_CHANNEL6, kLUTNickname, 0},
{ NTV2_XptLUT7Input, NTV2_WgtLUT7, NTV2_CHANNEL7, kLUTNickname, 0},
{ NTV2_XptLUT8Input, NTV2_WgtLUT8, NTV2_CHANNEL8, kLUTNickname, 0},
{ NTV2_XptMultiLinkOut1Input, NTV2_WgtMultiLinkOut1, NTV2_CHANNEL1, kMultiLinkNickname, 0},
{ NTV2_XptMultiLinkOut1InputDS2, NTV2_WgtMultiLinkOut1, NTV2_CHANNEL1, kMultiLinkNickname, 0},
{ NTV2_XptMultiLinkOut2Input, NTV2_WgtMultiLinkOut2, NTV2_CHANNEL1, kMultiLinkNickname, 0},
{ NTV2_XptMultiLinkOut2InputDS2, NTV2_WgtMultiLinkOut2, NTV2_CHANNEL1, kMultiLinkNickname, 0},
{ NTV2_XptSDIOut1Input, NTV2_WgtSDIOut1, NTV2_CHANNEL1, kSDINickname, 0},
{ NTV2_XptSDIOut1InputDS2, NTV2_Wgt3GSDIOut1, NTV2_CHANNEL1, kSDINickname, 1},
{ NTV2_XptSDIOut2Input, NTV2_WgtSDIOut2, NTV2_CHANNEL2, kSDINickname, 0},
{ NTV2_XptSDIOut2InputDS2, NTV2_Wgt3GSDIOut2, NTV2_CHANNEL2, kSDINickname, 1},
{ NTV2_XptSDIOut3Input, NTV2_WgtSDIOut3, NTV2_CHANNEL3, kSDINickname, 0},
{ NTV2_XptSDIOut3InputDS2, NTV2_Wgt3GSDIOut3, NTV2_CHANNEL3, kSDINickname, 1},
{ NTV2_XptSDIOut4Input, NTV2_WgtSDIOut4, NTV2_CHANNEL4, kSDINickname, 0},
{ NTV2_XptSDIOut4InputDS2, NTV2_Wgt3GSDIOut4, NTV2_CHANNEL4, kSDINickname, 1},
{ NTV2_XptSDIOut5Input, NTV2_WgtSDIMonOut1, NTV2_CHANNEL5, kSDINickname, 0},
{ NTV2_XptSDIOut5InputDS2, NTV2_WgtSDIMonOut1, NTV2_CHANNEL5, kSDINickname, 1},
{ NTV2_XptSDIOut6Input, NTV2_Wgt3GSDIOut6, NTV2_CHANNEL6, kSDINickname, 0},
{ NTV2_XptSDIOut6InputDS2, NTV2_Wgt3GSDIOut6, NTV2_CHANNEL6, kSDINickname, 1},
{ NTV2_XptSDIOut7Input, NTV2_Wgt3GSDIOut7, NTV2_CHANNEL7, kSDINickname, 0},
{ NTV2_XptSDIOut7InputDS2, NTV2_Wgt3GSDIOut7, NTV2_CHANNEL7, kSDINickname, 1},
{ NTV2_XptSDIOut8Input, NTV2_Wgt3GSDIOut8, NTV2_CHANNEL8, kSDINickname, 0},
{ NTV2_XptSDIOut8InputDS2, NTV2_Wgt3GSDIOut8, NTV2_CHANNEL8, kSDINickname, 1},
{ NTV2_XptDualLinkIn1Input, NTV2_WgtDualLinkV2In1, NTV2_CHANNEL1, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn1DSInput, NTV2_WgtDualLinkV2In1, NTV2_CHANNEL1, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkIn2Input, NTV2_WgtDualLinkV2In2, NTV2_CHANNEL2, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn2DSInput, NTV2_WgtDualLinkV2In2, NTV2_CHANNEL2, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkIn3Input, NTV2_WgtDualLinkV2In3, NTV2_CHANNEL3, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn3DSInput, NTV2_WgtDualLinkV2In3, NTV2_CHANNEL3, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkIn4Input, NTV2_WgtDualLinkV2In4, NTV2_CHANNEL4, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn4DSInput, NTV2_WgtDualLinkV2In4, NTV2_CHANNEL4, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkIn5Input, NTV2_WgtDualLinkV2In5, NTV2_CHANNEL5, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn5DSInput, NTV2_WgtDualLinkV2In5, NTV2_CHANNEL5, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkIn6Input, NTV2_WgtDualLinkV2In6, NTV2_CHANNEL6, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn6DSInput, NTV2_WgtDualLinkV2In6, NTV2_CHANNEL6, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkIn7Input, NTV2_WgtDualLinkV2In7, NTV2_CHANNEL7, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn7DSInput, NTV2_WgtDualLinkV2In7, NTV2_CHANNEL7, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkIn8Input, NTV2_WgtDualLinkV2In8, NTV2_CHANNEL8, kDualLinkInNickname, 0},
{ NTV2_XptDualLinkIn8DSInput, NTV2_WgtDualLinkV2In8, NTV2_CHANNEL8, kDualLinkInNickname, 1},
{ NTV2_XptDualLinkOut1Input, NTV2_WgtDualLinkV2Out1, NTV2_CHANNEL1, kDualLinkOutNickname, 0},
{ NTV2_XptDualLinkOut2Input, NTV2_WgtDualLinkV2Out2, NTV2_CHANNEL2, kDualLinkOutNickname, 0},
{ NTV2_XptDualLinkOut3Input, NTV2_WgtDualLinkV2Out3, NTV2_CHANNEL3, kDualLinkOutNickname, 0},
{ NTV2_XptDualLinkOut4Input, NTV2_WgtDualLinkV2Out4, NTV2_CHANNEL4, kDualLinkOutNickname, 0},
{ NTV2_XptDualLinkOut5Input, NTV2_WgtDualLinkV2Out5, NTV2_CHANNEL5, kDualLinkOutNickname, 0},
{ NTV2_XptDualLinkOut6Input, NTV2_WgtDualLinkV2Out6, NTV2_CHANNEL6, kDualLinkOutNickname, 0},
{ NTV2_XptDualLinkOut7Input, NTV2_WgtDualLinkV2Out7, NTV2_CHANNEL7, kDualLinkOutNickname, 0},
{ NTV2_XptDualLinkOut8Input, NTV2_WgtDualLinkV2Out8, NTV2_CHANNEL8, kDualLinkOutNickname, 0},
{ NTV2_XptMixer1BGKeyInput, NTV2_WgtMixer1, NTV2_CHANNEL1, kMixerNickname, 3},
{ NTV2_XptMixer1BGVidInput, NTV2_WgtMixer1, NTV2_CHANNEL1, kMixerNickname, 2},
{ NTV2_XptMixer1FGKeyInput, NTV2_WgtMixer1, NTV2_CHANNEL1, kMixerNickname, 1},
{ NTV2_XptMixer1FGVidInput, NTV2_WgtMixer1, NTV2_CHANNEL1, kMixerNickname, 0},
{ NTV2_XptMixer2BGKeyInput, NTV2_WgtMixer2, NTV2_CHANNEL2, kMixerNickname, 3},
{ NTV2_XptMixer2BGVidInput, NTV2_WgtMixer2, NTV2_CHANNEL2, kMixerNickname, 2},
{ NTV2_XptMixer2FGKeyInput, NTV2_WgtMixer2, NTV2_CHANNEL2, kMixerNickname, 1},
{ NTV2_XptMixer2FGVidInput, NTV2_WgtMixer2, NTV2_CHANNEL2, kMixerNickname, 0},
{ NTV2_XptMixer3BGKeyInput, NTV2_WgtMixer3, NTV2_CHANNEL3, kMixerNickname, 3},
{ NTV2_XptMixer3BGVidInput, NTV2_WgtMixer3, NTV2_CHANNEL3, kMixerNickname, 2},
{ NTV2_XptMixer3FGKeyInput, NTV2_WgtMixer3, NTV2_CHANNEL3, kMixerNickname, 1},
{ NTV2_XptMixer3FGVidInput, NTV2_WgtMixer3, NTV2_CHANNEL3, kMixerNickname, 0},
{ NTV2_XptMixer4BGKeyInput, NTV2_WgtMixer4, NTV2_CHANNEL4, kMixerNickname, 3},
{ NTV2_XptMixer4BGVidInput, NTV2_WgtMixer4, NTV2_CHANNEL4, kMixerNickname, 2},
{ NTV2_XptMixer4FGKeyInput, NTV2_WgtMixer4, NTV2_CHANNEL4, kMixerNickname, 1},
{ NTV2_XptMixer4FGVidInput, NTV2_WgtMixer4, NTV2_CHANNEL4, kMixerNickname, 0},
{ NTV2_XptHDMIOutInput, NTV2_WgtHDMIOut1, NTV2_CHANNEL1, kHDMINickname, 0},
{ NTV2_XptHDMIOutQ2Input, NTV2_WgtHDMIOut1v2, NTV2_CHANNEL1, kHDMINickname, 1},
{ NTV2_XptHDMIOutQ3Input, NTV2_WgtHDMIOut1v2, NTV2_CHANNEL1, kHDMINickname, 2},
{ NTV2_XptHDMIOutQ4Input, NTV2_WgtHDMIOut1v2, NTV2_CHANNEL1, kHDMINickname, 3},
{ NTV2_Xpt4KDCQ1Input, NTV2_Wgt4KDownConverter, NTV2_CHANNEL1, k4KDownConvertNickname, 0},
{ NTV2_Xpt4KDCQ2Input, NTV2_Wgt4KDownConverter, NTV2_CHANNEL2, k4KDownConvertNickname, 0},
{ NTV2_Xpt4KDCQ3Input, NTV2_Wgt4KDownConverter, NTV2_CHANNEL3, k4KDownConvertNickname, 0},
{ NTV2_Xpt4KDCQ4Input, NTV2_Wgt4KDownConverter, NTV2_CHANNEL4, k4KDownConvertNickname, 0},
{ NTV2_Xpt425Mux1AInput, NTV2_Wgt425Mux1, NTV2_CHANNEL1, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux1BInput, NTV2_Wgt425Mux1, NTV2_CHANNEL1, kTSIMuxNickname, 1},
{ NTV2_Xpt425Mux2AInput, NTV2_Wgt425Mux2, NTV2_CHANNEL2, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux2BInput, NTV2_Wgt425Mux2, NTV2_CHANNEL2, kTSIMuxNickname, 1},
{ NTV2_Xpt425Mux3AInput, NTV2_Wgt425Mux3, NTV2_CHANNEL3, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux3BInput, NTV2_Wgt425Mux3, NTV2_CHANNEL3, kTSIMuxNickname, 1},
{ NTV2_Xpt425Mux4AInput, NTV2_Wgt425Mux4, NTV2_CHANNEL4, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux4BInput, NTV2_Wgt425Mux4, NTV2_CHANNEL4, kTSIMuxNickname, 1},
{ NTV2_XptAnalogOutInput, NTV2_WgtAnalogOut1, NTV2_CHANNEL1, kAnalogNickname, 0},
{ NTV2_Xpt3DLUT1Input, NTV2_Wgt3DLUT1, NTV2_CHANNEL1, kLUT3DNickname, 0},
{ NTV2_XptAnalogOutCompositeOut, NTV2_WgtAnalogCompositeOut1, NTV2_CHANNEL1, kCompositeNickname, 0},
{ NTV2_XptStereoLeftInput, NTV2_WgtStereoCompressor, NTV2_CHANNEL1, kStereoCompNickname, 0},
{ NTV2_XptStereoRightInput, NTV2_WgtStereoCompressor, NTV2_CHANNEL1, kStereoCompNickname, 0},
{ NTV2_XptWaterMarker1Input, NTV2_WgtWaterMarker1, NTV2_CHANNEL1, kWatermarkNickname, 0},
{ NTV2_XptWaterMarker2Input, NTV2_WgtWaterMarker2, NTV2_CHANNEL2, kWatermarkNickname, 0},
{ NTV2_XptConversionMod2Input, NTV2_WgtUpDownConverter2, NTV2_CHANNEL2, kUpDownConvertNickname, 0},
{ NTV2_XptCompressionModInput, NTV2_WgtCompression1, NTV2_CHANNEL1, kCompressionNickname, 0},
{ NTV2_XptConversionModInput, NTV2_WgtUpDownConverter1, NTV2_CHANNEL1, kUpDownConvertNickname, 0},
{ NTV2_XptFrameSync2Input, NTV2_WgtFrameSync2, NTV2_CHANNEL2, kFrameSyncNickname, 0},
};
// Table of firmware widget's output crosspoint/id/channel/name/datastream index
static WidgetOutputSocket kWidgetOutputSockets[] = {
{ NTV2_OUTPUT_CROSSPOINT_INVALID, NTV2_WIDGET_INVALID, NTV2_CHANNEL_INVALID, "", -1},
{ NTV2_XptBlack, NTV2_WgtUndefined, NTV2_CHANNEL1, kBlackNickname, 0},
{ NTV2_XptSDIIn1, NTV2_WgtSDIIn1, NTV2_CHANNEL1, kSDINickname, 0},
{ NTV2_XptSDIIn2, NTV2_WgtSDIIn2, NTV2_CHANNEL2, kSDINickname, 0},
{ NTV2_XptLUT1YUV, NTV2_WgtLUT1, NTV2_CHANNEL1, kSDINickname, 0},
{ NTV2_XptCSC1VidYUV, NTV2_WgtCSC1, NTV2_CHANNEL1, kCSCNickname, 0},
{ NTV2_XptConversionModule, NTV2_WgtUpDownConverter1, NTV2_CHANNEL1, kUpDownConvertNickname, 0},
{ NTV2_XptCompressionModule, NTV2_WgtCompression1, NTV2_CHANNEL1, kCompressionNickname, 0},
{ NTV2_XptFrameBuffer1YUV, NTV2_WgtFrameBuffer1, NTV2_CHANNEL1, kFramebufferNickname, 0},
{ NTV2_XptFrameSync1YUV, NTV2_WgtFrameSync1, NTV2_CHANNEL1, kFrameSyncNickname, 0},
{ NTV2_XptFrameSync2YUV, NTV2_WgtFrameSync2, NTV2_CHANNEL2, kFrameSyncNickname, 0},
{ NTV2_XptDuallinkOut1, NTV2_WgtDualLinkV2Out1, NTV2_CHANNEL1, kDualLinkOutNickname, 0},
{ NTV2_XptCSC1KeyYUV, NTV2_WgtCSC1, NTV2_CHANNEL1, kCSCNickname, 2},
{ NTV2_XptFrameBuffer2YUV, NTV2_WgtFrameBuffer2, NTV2_CHANNEL2, kFramebufferNickname, 0},
{ NTV2_XptCSC2VidYUV, NTV2_WgtCSC2, NTV2_CHANNEL2, kCSCNickname, 0},
{ NTV2_XptCSC2KeyYUV, NTV2_WgtCSC2, NTV2_CHANNEL2, kCSCNickname, 2},
{ NTV2_XptMixer1VidYUV, NTV2_WgtMixer1, NTV2_CHANNEL1, kMixerNickname, 0},
{ NTV2_XptMixer1KeyYUV, NTV2_WgtMixer1, NTV2_CHANNEL1, kMixerNickname, 1},
{ NTV2_XptMultiLinkOut1DS1, NTV2_WgtMultiLinkOut1, NTV2_CHANNEL1, kMultiLinkNickname, 0},
{ NTV2_XptMultiLinkOut1DS2, NTV2_WgtMultiLinkOut1, NTV2_CHANNEL1, kMultiLinkNickname, 1},
{ NTV2_XptAnalogIn, NTV2_WgtAnalogIn1, NTV2_CHANNEL1, kAnalogNickname, 0},
{ NTV2_XptHDMIIn1, NTV2_WgtHDMIIn1, NTV2_CHANNEL1, kHDMINickname, 0},
{ NTV2_XptMultiLinkOut1DS3, NTV2_WgtMultiLinkOut1, NTV2_CHANNEL1, kMultiLinkNickname, 2},
{ NTV2_XptMultiLinkOut1DS4, NTV2_WgtMultiLinkOut1, NTV2_CHANNEL1, kMultiLinkNickname, 3},
{ NTV2_XptMultiLinkOut2DS1, NTV2_WgtMultiLinkOut2, NTV2_CHANNEL2, kMultiLinkNickname, 0},
{ NTV2_XptMultiLinkOut2DS2, NTV2_WgtMultiLinkOut2, NTV2_CHANNEL2, kMultiLinkNickname, 1},
{ NTV2_XptDuallinkOut2, NTV2_WgtDualLinkV2Out2, NTV2_CHANNEL2, kDualLinkOutNickname, 0},
{ NTV2_XptTestPatternYUV, NTV2_WgtTestPattern1, NTV2_CHANNEL1, kTestPatternNickname, 0},
{ NTV2_XptSDIIn1DS2, NTV2_Wgt3GSDIIn1, NTV2_CHANNEL1, kSDINickname, 1},
{ NTV2_XptSDIIn2DS2, NTV2_Wgt3GSDIIn2, NTV2_CHANNEL2, kSDINickname, 1},
{ NTV2_XptMixer2VidYUV, NTV2_WgtMixer2, NTV2_CHANNEL2, kMixerNickname, 0},
{ NTV2_XptMixer2KeyYUV, NTV2_WgtMixer2, NTV2_CHANNEL2, kMixerNickname, 1},
{ NTV2_XptStereoCompressorOut, NTV2_WgtStereoCompressor, NTV2_CHANNEL1, kStereoCompNickname, 0},
{ NTV2_XptFrameBuffer3YUV, NTV2_WgtFrameBuffer3, NTV2_CHANNEL3, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer4YUV, NTV2_WgtFrameBuffer4, NTV2_CHANNEL4, kFramebufferNickname, 0},
{ NTV2_XptDuallinkOut1DS2, NTV2_WgtDualLinkV2Out1, NTV2_CHANNEL1, kDualLinkOutNickname, 1},
{ NTV2_XptDuallinkOut2DS2, NTV2_WgtDualLinkV2Out2, NTV2_CHANNEL2, kDualLinkOutNickname, 1},
{ NTV2_XptCSC5VidYUV, NTV2_WgtCSC5, NTV2_CHANNEL5, kCSCNickname, 0},
{ NTV2_XptCSC5KeyYUV, NTV2_WgtCSC5, NTV2_CHANNEL5, kCSCNickname, 1},
{ NTV2_XptMultiLinkOut2DS3, NTV2_WgtMultiLinkOut2, NTV2_CHANNEL2, kMultiLinkNickname, 2},
{ NTV2_XptMultiLinkOut2DS4, NTV2_WgtMultiLinkOut2, NTV2_CHANNEL2, kMultiLinkNickname, 3},
{ NTV2_XptSDIIn3, NTV2_Wgt3GSDIIn3, NTV2_CHANNEL3, kSDINickname, 0},
{ NTV2_XptSDIIn4, NTV2_Wgt3GSDIIn4, NTV2_CHANNEL4, kSDINickname, 0},
{ NTV2_XptSDIIn3DS2, NTV2_Wgt3GSDIIn3, NTV2_CHANNEL3, kSDINickname, 1},
{ NTV2_XptSDIIn4DS2, NTV2_Wgt3GSDIIn4, NTV2_CHANNEL4, kSDINickname, 1},
{ NTV2_XptDuallinkOut3, NTV2_WgtDualLinkV2Out3, NTV2_CHANNEL3, kDualLinkOutNickname, 0},
{ NTV2_XptDuallinkOut3DS2, NTV2_WgtDualLinkV2Out3, NTV2_CHANNEL3, kDualLinkOutNickname, 1},
{ NTV2_XptDuallinkOut4, NTV2_WgtDualLinkV2Out4, NTV2_CHANNEL4, kDualLinkOutNickname, 0},
{ NTV2_XptDuallinkOut4DS2, NTV2_WgtDualLinkV2Out4, NTV2_CHANNEL4, kDualLinkOutNickname, 1},
{ NTV2_XptCSC3VidYUV, NTV2_WgtCSC3, NTV2_CHANNEL3, kCSCNickname, 0},
{ NTV2_XptCSC3KeyYUV, NTV2_WgtCSC3, NTV2_CHANNEL3, kCSCNickname, 2},
{ NTV2_XptCSC4VidYUV, NTV2_WgtCSC4, NTV2_CHANNEL4, kCSCNickname, 0},
{ NTV2_XptCSC4KeyYUV, NTV2_WgtCSC4, NTV2_CHANNEL4, kCSCNickname, 2},
{ NTV2_XptDuallinkOut5, NTV2_WgtDualLinkV2Out5, NTV2_CHANNEL5, kDualLinkOutNickname, 0},
{ NTV2_XptDuallinkOut5DS2, NTV2_WgtDualLinkV2Out5, NTV2_CHANNEL5, kDualLinkOutNickname, 1},
{ NTV2_Xpt3DLUT1YUV, NTV2_Wgt3DLUT1, NTV2_CHANNEL1, kLUT3DNickname, 0},
{ NTV2_XptHDMIIn1Q2, NTV2_WgtHDMIIn1v3, NTV2_CHANNEL1, kHDMINickname, 1},
{ NTV2_XptHDMIIn1Q3, NTV2_WgtHDMIIn1v3, NTV2_CHANNEL1, kHDMINickname, 2},
{ NTV2_XptHDMIIn1Q4, NTV2_WgtHDMIIn1v3, NTV2_CHANNEL1, kHDMINickname, 3},
{ NTV2_Xpt4KDownConverterOut, NTV2_Wgt4KDownConverter, NTV2_CHANNEL1, k4KDownConvertNickname, 0},
{ NTV2_XptSDIIn5, NTV2_Wgt3GSDIIn5, NTV2_CHANNEL5, kSDINickname, 0},
{ NTV2_XptSDIIn6, NTV2_Wgt3GSDIIn6, NTV2_CHANNEL6, kSDINickname, 0},
{ NTV2_XptSDIIn5DS2, NTV2_Wgt3GSDIIn5, NTV2_CHANNEL5, kSDINickname, 1},
{ NTV2_XptSDIIn6DS2, NTV2_Wgt3GSDIIn6, NTV2_CHANNEL6, kSDINickname, 1},
{ NTV2_XptSDIIn7, NTV2_Wgt3GSDIIn7, NTV2_CHANNEL7, kSDINickname, 0},
{ NTV2_XptSDIIn8, NTV2_Wgt3GSDIIn8, NTV2_CHANNEL8, kSDINickname, 0},
{ NTV2_XptSDIIn7DS2, NTV2_Wgt3GSDIIn7, NTV2_CHANNEL7, kSDINickname, 1},
{ NTV2_XptSDIIn8DS2, NTV2_Wgt3GSDIIn8, NTV2_CHANNEL8, kSDINickname, 1},
{ NTV2_XptFrameBuffer5YUV, NTV2_WgtFrameBuffer5, NTV2_CHANNEL5, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer6YUV, NTV2_WgtFrameBuffer6, NTV2_CHANNEL6, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer7YUV, NTV2_WgtFrameBuffer7, NTV2_CHANNEL7, kFramebufferNickname, 0},
{ NTV2_XptFrameBuffer8YUV, NTV2_WgtFrameBuffer8, NTV2_CHANNEL8, kFramebufferNickname, 0},
{ NTV2_XptMixer3VidYUV, NTV2_WgtMixer3, NTV2_CHANNEL3, kMixerNickname, 0},
{ NTV2_XptMixer3KeyYUV, NTV2_WgtMixer3, NTV2_CHANNEL3, kMixerNickname, 1},
{ NTV2_XptMixer4VidYUV, NTV2_WgtMixer4, NTV2_CHANNEL4, kMixerNickname, 0},
{ NTV2_XptMixer4KeyYUV, NTV2_WgtMixer4, NTV2_CHANNEL4, kMixerNickname, 1},
{ NTV2_XptCSC6VidYUV, NTV2_WgtCSC6, NTV2_CHANNEL6, kCSCNickname, 0},
{ NTV2_XptCSC6KeyYUV, NTV2_WgtCSC6, NTV2_CHANNEL6, kCSCNickname, 1},
{ NTV2_XptCSC7VidYUV, NTV2_WgtCSC7, NTV2_CHANNEL7, kCSCNickname, 0},
{ NTV2_XptCSC7KeyYUV, NTV2_WgtCSC7, NTV2_CHANNEL7, kCSCNickname, 1},
{ NTV2_XptCSC8VidYUV, NTV2_WgtCSC8, NTV2_CHANNEL8, kCSCNickname, 0},
{ NTV2_XptCSC8KeyYUV, NTV2_WgtCSC8, NTV2_CHANNEL8, kCSCNickname, 1},
{ NTV2_XptDuallinkOut6, NTV2_WgtDualLinkV2Out6, NTV2_CHANNEL6, kDualLinkOutNickname, 0},
{ NTV2_XptDuallinkOut6DS2, NTV2_WgtDualLinkV2Out6, NTV2_CHANNEL6, kDualLinkOutNickname, 1},
{ NTV2_XptDuallinkOut7, NTV2_WgtDualLinkV2Out7, NTV2_CHANNEL7, kDualLinkOutNickname, 0},
{ NTV2_XptDuallinkOut7DS2, NTV2_WgtDualLinkV2Out7, NTV2_CHANNEL7, kDualLinkOutNickname, 1},
{ NTV2_XptDuallinkOut8, NTV2_WgtDualLinkV2Out8, NTV2_CHANNEL8, kDualLinkOutNickname, 0},
{ NTV2_XptDuallinkOut8DS2, NTV2_WgtDualLinkV2Out8, NTV2_CHANNEL8, kDualLinkOutNickname, 1},
{ NTV2_Xpt425Mux1AYUV, NTV2_Wgt425Mux1, NTV2_CHANNEL1, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux1BYUV, NTV2_Wgt425Mux1, NTV2_CHANNEL1, kTSIMuxNickname, 1},
{ NTV2_Xpt425Mux2AYUV, NTV2_Wgt425Mux2, NTV2_CHANNEL2, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux2BYUV, NTV2_Wgt425Mux2, NTV2_CHANNEL2, kTSIMuxNickname, 1},
{ NTV2_Xpt425Mux3AYUV, NTV2_Wgt425Mux3, NTV2_CHANNEL3, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux3BYUV, NTV2_Wgt425Mux3, NTV2_CHANNEL3, kTSIMuxNickname, 1},
{ NTV2_Xpt425Mux4AYUV, NTV2_Wgt425Mux4, NTV2_CHANNEL4, kTSIMuxNickname, 0},
{ NTV2_Xpt425Mux4BYUV, NTV2_Wgt425Mux4, NTV2_CHANNEL4, kTSIMuxNickname, 1},
{ NTV2_XptFrameBuffer1_DS2YUV, NTV2_WgtFrameBuffer1, NTV2_CHANNEL1, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer2_DS2YUV, NTV2_WgtFrameBuffer2, NTV2_CHANNEL2, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer3_DS2YUV, NTV2_WgtFrameBuffer3, NTV2_CHANNEL3, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer4_DS2YUV, NTV2_WgtFrameBuffer4, NTV2_CHANNEL4, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer5_DS2YUV, NTV2_WgtFrameBuffer5, NTV2_CHANNEL5, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer6_DS2YUV, NTV2_WgtFrameBuffer6, NTV2_CHANNEL6, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer7_DS2YUV, NTV2_WgtFrameBuffer7, NTV2_CHANNEL7, kFramebufferNickname, 1},
{ NTV2_XptFrameBuffer8_DS2YUV, NTV2_WgtFrameBuffer8, NTV2_CHANNEL8, kFramebufferNickname, 1},
{ NTV2_XptHDMIIn2, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 0},
{ NTV2_XptHDMIIn2Q2, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 1},
{ NTV2_XptHDMIIn2Q3, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 2},
{ NTV2_XptHDMIIn2Q4, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 3},
{ NTV2_XptHDMIIn3, NTV2_WgtHDMIIn3v4, NTV2_CHANNEL3, kHDMINickname, 0},
{ NTV2_XptHDMIIn4, NTV2_WgtHDMIIn4v4, NTV2_CHANNEL4, kHDMINickname, 0},
{ NTV2_XptDuallinkIn1, NTV2_WgtDualLinkV2In1, NTV2_CHANNEL1, kDualLinkInNickname, 0},
{ NTV2_XptLUT1Out, NTV2_WgtLUT1, NTV2_CHANNEL1, kLUTNickname, 0},
{ NTV2_XptCSC1VidRGB, NTV2_WgtCSC1, NTV2_CHANNEL1, kCSCNickname, 1},
{ NTV2_XptFrameBuffer1RGB, NTV2_WgtFrameBuffer1, NTV2_CHANNEL1, kFramebufferNickname, 2},
{ NTV2_XptFrameSync1RGB, NTV2_WgtFrameSync1, NTV2_CHANNEL1, kFrameSyncNickname, 1},
{ NTV2_XptFrameSync2RGB, NTV2_WgtFrameSync2, NTV2_CHANNEL2, kFrameSyncNickname, 1},
{ NTV2_XptLUT2Out, NTV2_WgtLUT2, NTV2_CHANNEL2, kLUTNickname, 0},
{ NTV2_XptFrameBuffer2RGB, NTV2_WgtFrameBuffer2, NTV2_CHANNEL2, kFramebufferNickname, 2},
{ NTV2_XptCSC2VidRGB, NTV2_WgtCSC2, NTV2_CHANNEL2, kCSCNickname, 1},
{ NTV2_XptMixer1VidRGB, NTV2_WgtMixer1, NTV2_CHANNEL1, kMixerNickname, 1},
{ NTV2_XptHDMIIn1RGB, NTV2_WgtHDMIIn1v3, NTV2_CHANNEL1, kHDMINickname, 2},
{ NTV2_XptFrameBuffer3RGB, NTV2_WgtFrameBuffer3, NTV2_CHANNEL3, kFramebufferNickname, 2},
{ NTV2_XptFrameBuffer4RGB, NTV2_WgtFrameBuffer4, NTV2_CHANNEL4, kFramebufferNickname, 2},
{ NTV2_XptDuallinkIn2, NTV2_WgtDualLinkV2In2, NTV2_CHANNEL2, kDualLinkInNickname, 0},
{ NTV2_XptLUT3Out, NTV2_WgtLUT3, NTV2_CHANNEL3, kLUTNickname, 0},
{ NTV2_XptLUT4Out, NTV2_WgtLUT4, NTV2_CHANNEL4, kLUTNickname, 0},
{ NTV2_XptLUT5Out, NTV2_WgtLUT5, NTV2_CHANNEL5, kLUTNickname, 0},
{ NTV2_XptCSC5VidRGB, NTV2_WgtCSC5, NTV2_CHANNEL5, kCSCNickname, 2},
{ NTV2_XptDuallinkIn3, NTV2_WgtDualLinkV2In3, NTV2_CHANNEL3, kDualLinkInNickname, 0},
{ NTV2_XptDuallinkIn4, NTV2_WgtDualLinkV2In4, NTV2_CHANNEL4, kDualLinkInNickname, 0},
{ NTV2_XptCSC3VidRGB, NTV2_WgtCSC3, NTV2_CHANNEL3, kCSCNickname, 2},
{ NTV2_XptCSC4VidRGB, NTV2_WgtCSC4, NTV2_CHANNEL4, kCSCNickname, 2},
{ NTV2_Xpt3DLUT1RGB, NTV2_Wgt3DLUT1, NTV2_CHANNEL1, kLUT3DNickname, 1},
{ NTV2_XptHDMIIn1Q2RGB, NTV2_WgtHDMIIn1v3, NTV2_CHANNEL1, kHDMINickname, 1},
{ NTV2_XptHDMIIn1Q3RGB, NTV2_WgtHDMIIn1v3, NTV2_CHANNEL1, kHDMINickname, 2},
{ NTV2_XptHDMIIn1Q4RGB, NTV2_WgtHDMIIn1v3, NTV2_CHANNEL1, kHDMINickname, 3},
{ NTV2_Xpt4KDownConverterOutRGB, NTV2_Wgt4KDownConverter, NTV2_CHANNEL1, k4KDownConvertNickname, 1},
{ NTV2_XptDuallinkIn5, NTV2_WgtDualLinkV2In5, NTV2_CHANNEL5, kDualLinkInNickname, 0},
{ NTV2_XptDuallinkIn6, NTV2_WgtDualLinkV2In6, NTV2_CHANNEL6, kDualLinkInNickname, 0},
{ NTV2_XptDuallinkIn7, NTV2_WgtDualLinkV2In7, NTV2_CHANNEL7, kDualLinkInNickname, 0},
{ NTV2_XptDuallinkIn8, NTV2_WgtDualLinkV2In8, NTV2_CHANNEL8, kDualLinkInNickname, 0},
{ NTV2_XptFrameBuffer5RGB, NTV2_WgtFrameBuffer5, NTV2_CHANNEL5, kFramebufferNickname, 2},
{ NTV2_XptFrameBuffer6RGB, NTV2_WgtFrameBuffer6, NTV2_CHANNEL6, kFramebufferNickname, 2},
{ NTV2_XptFrameBuffer7RGB, NTV2_WgtFrameBuffer7, NTV2_CHANNEL7, kFramebufferNickname, 2},
{ NTV2_XptFrameBuffer8RGB, NTV2_WgtFrameBuffer8, NTV2_CHANNEL8, kFramebufferNickname, 2},
{ NTV2_XptCSC6VidRGB, NTV2_WgtCSC6, NTV2_CHANNEL6, kCSCNickname, 1},
{ NTV2_XptCSC7VidRGB, NTV2_WgtCSC7, NTV2_CHANNEL7, kCSCNickname, 1},
{ NTV2_XptCSC8VidRGB, NTV2_WgtCSC8, NTV2_CHANNEL8, kCSCNickname, 1},
{ NTV2_XptLUT6Out, NTV2_WgtLUT6, NTV2_CHANNEL6, kLUTNickname, 0},
{ NTV2_XptLUT7Out, NTV2_WgtLUT7, NTV2_CHANNEL7, kLUTNickname, 0},
{ NTV2_XptLUT8Out, NTV2_WgtLUT8, NTV2_CHANNEL8, kLUTNickname, 0},
{ NTV2_Xpt425Mux1ARGB, NTV2_Wgt425Mux1, NTV2_CHANNEL1, kTSIMuxNickname, 2},
{ NTV2_Xpt425Mux1BRGB, NTV2_Wgt425Mux1, NTV2_CHANNEL1, kTSIMuxNickname, 3},
{ NTV2_Xpt425Mux2ARGB, NTV2_Wgt425Mux2, NTV2_CHANNEL2, kTSIMuxNickname, 2},
{ NTV2_Xpt425Mux2BRGB, NTV2_Wgt425Mux2, NTV2_CHANNEL2, kTSIMuxNickname, 3},
{ NTV2_Xpt425Mux3ARGB, NTV2_Wgt425Mux3, NTV2_CHANNEL3, kTSIMuxNickname, 2},
{ NTV2_Xpt425Mux3BRGB, NTV2_Wgt425Mux3, NTV2_CHANNEL3, kTSIMuxNickname, 3},
{ NTV2_Xpt425Mux4ARGB, NTV2_Wgt425Mux4, NTV2_CHANNEL4, kTSIMuxNickname, 2},
{ NTV2_Xpt425Mux4BRGB, NTV2_Wgt425Mux4, NTV2_CHANNEL4, kTSIMuxNickname, 3},
{ NTV2_XptFrameBuffer1_DS2RGB, NTV2_WgtFrameBuffer1, NTV2_CHANNEL1, kFramebufferNickname, 3},
{ NTV2_XptFrameBuffer2_DS2RGB, NTV2_WgtFrameBuffer2, NTV2_CHANNEL2, kFramebufferNickname, 3},
{ NTV2_XptFrameBuffer3_DS2RGB, NTV2_WgtFrameBuffer3, NTV2_CHANNEL3, kFramebufferNickname, 3},
{ NTV2_XptFrameBuffer4_DS2RGB, NTV2_WgtFrameBuffer4, NTV2_CHANNEL4, kFramebufferNickname, 3},
{ NTV2_XptFrameBuffer5_DS2RGB, NTV2_WgtFrameBuffer5, NTV2_CHANNEL5, kFramebufferNickname, 3},
{ NTV2_XptFrameBuffer6_DS2RGB, NTV2_WgtFrameBuffer6, NTV2_CHANNEL6, kFramebufferNickname, 3},
{ NTV2_XptFrameBuffer7_DS2RGB, NTV2_WgtFrameBuffer7, NTV2_CHANNEL7, kFramebufferNickname, 3},
{ NTV2_XptFrameBuffer8_DS2RGB, NTV2_WgtFrameBuffer8, NTV2_CHANNEL8, kFramebufferNickname, 3},
{ NTV2_XptHDMIIn2RGB, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 0},
{ NTV2_XptHDMIIn2Q2RGB, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 1},
{ NTV2_XptHDMIIn2Q3RGB, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 2},
{ NTV2_XptHDMIIn2Q4RGB, NTV2_WgtHDMIIn2v4, NTV2_CHANNEL2, kHDMINickname, 3},
{ NTV2_XptHDMIIn3RGB, NTV2_WgtHDMIIn3v4, NTV2_CHANNEL3, kHDMINickname, 0},
{ NTV2_XptHDMIIn4RGB, NTV2_WgtHDMIIn4v4, NTV2_CHANNEL4, kHDMINickname, 0},
};
// clang-format on
static const size_t kNumWidgetInputSockets =
(sizeof(kWidgetInputSockets) / sizeof(WidgetInputSocket));
static const size_t kNumWidgetOutputSockets =
(sizeof(kWidgetOutputSockets) / sizeof(WidgetOutputSocket));
bool WidgetInputSocket::Find(const std::string &name, NTV2Channel channel,
int32_t datastream, WidgetInputSocket &inp)
{
for (const auto &in : kWidgetInputSockets) {
if (name == in.name && channel == in.channel &&
datastream == in.datastream_index) {
inp = in;
return true;
}
}
return false;
}
bool WidgetInputSocket::GetWidgetInputSocketByXpt(InputXpt id,
WidgetInputSocket &inp)
{
for (const auto &in : kWidgetInputSockets) {
if (in.id == id) {
inp = in;
return true;
}
}
return false;
}
int32_t WidgetInputSocket::InputXptDatastreamIndex(InputXpt xpt)
{
int32_t datastream = 0;
for (auto &x : kWidgetInputSockets) {
if (x.id == xpt) {
datastream = x.datastream_index;
break;
}
}
return datastream;
}
NTV2Channel WidgetInputSocket::InputXptChannel(InputXpt xpt)
{
NTV2Channel channel = NTV2_CHANNEL_INVALID;
for (auto &x : kWidgetInputSockets) {
if (x.id == xpt) {
channel = x.channel;
break;
}
}
return channel;
}
const char *WidgetInputSocket::InputXptName(InputXpt xpt)
{
const char *name = NULL;
for (auto &x : kWidgetInputSockets) {
if (x.id == xpt) {
name = x.name;
break;
}
}
return name;
}
bool WidgetOutputSocket::Find(const std::string &name, NTV2Channel channel,
int32_t datastream, WidgetOutputSocket &out)
{
// std::cout << "DEBUG -- WidgetOutputSocket::Find: name = " << name
// << ", chan = " << NTV2ChannelToString(channel)
// << ", datastream = " << datastream << std::endl;
for (const auto &wo : kWidgetOutputSockets) {
if (name == wo.name && channel == wo.channel &&
datastream == wo.datastream_index) {
out = wo;
return true;
}
}
return false;
}
bool WidgetOutputSocket::GetWidgetOutputSocketByXpt(OutputXpt id,
WidgetOutputSocket &out)
{
for (const auto &wo : kWidgetOutputSockets) {
if (wo.id == id) {
out = wo;
return true;
}
}
return false;
}
int32_t WidgetOutputSocket::OutputXptDatastreamIndex(OutputXpt xpt)
{
int32_t datastream = 0;
for (auto &x : kWidgetOutputSockets) {
if (x.id == xpt) {
datastream = x.datastream_index;
break;
}
}
return datastream;
}
NTV2Channel WidgetOutputSocket::OutputXptChannel(OutputXpt xpt)
{
NTV2Channel channel = NTV2_CHANNEL_INVALID;
for (auto &x : kWidgetOutputSockets) {
if (x.id == xpt) {
channel = x.channel;
break;
}
}
return channel;
}
const char *WidgetOutputSocket::OutputXptName(OutputXpt xpt)
{
const char *name = NULL;
for (auto &x : kWidgetOutputSockets) {
if (x.id == xpt) {
name = x.name;
break;
}
}
return name;
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <ajantv2/includes/ntv2enums.h>
#include <string>
using OutputXpt = NTV2OutputCrosspointID; // src
using InputXpt = NTV2InputCrosspointID; // dest
// Firmware widget input socket connector
struct WidgetInputSocket {
InputXpt id;
NTV2WidgetID widget_id;
NTV2Channel channel;
const char *name;
int32_t datastream_index;
static bool Find(const std::string &route, NTV2Channel channel,
int32_t datastream, WidgetInputSocket &inp);
static bool GetWidgetInputSocketByXpt(InputXpt id,
WidgetInputSocket &inp);
static int32_t InputXptDatastreamIndex(InputXpt xpt);
static NTV2Channel InputXptChannel(InputXpt xpt);
static const char *InputXptName(InputXpt xpt);
};
// Firmware widget output socket connector
struct WidgetOutputSocket {
OutputXpt id;
NTV2WidgetID widget_id;
NTV2Channel channel;
const char *name;
int32_t datastream_index;
static bool Find(const std::string &route, NTV2Channel channel,
int32_t datastream, WidgetOutputSocket &out);
static bool GetWidgetOutputSocketByXpt(OutputXpt id,
WidgetOutputSocket &out);
static int32_t OutputXptDatastreamIndex(OutputXpt xpt);
static NTV2Channel OutputXptChannel(OutputXpt xpt);
static const char *OutputXptName(OutputXpt xpt);
};

0
plugins/aja/data/.keepme Normal file
View File

View File

@ -0,0 +1,15 @@
AJACapture.Device="AJA I/O Device Capture"
AJAOutput.Device="AJA I/O Device Output"
Device="Device"
Output="Output"
Input="Input"
Mode="Mode"
VideoFormat="Video Format"
PixelFormat="Pixel Format"
AutoDetect="Auto Detect"
Interlaced="Interlaced"
AutoStart="Auto start on launch"
Buffering="Use Buffering"
DeactivateWhenNotShowing="Deactivate when not showing"
IOSelect="Select..."
SDI4KTransport="SDI 4K Transport"

34
plugins/aja/main.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "aja-card-manager.hpp"
#include <obs-module.h>
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("aja", "en-US")
MODULE_EXPORT const char *obs_module_description(void)
{
return "aja";
}
extern struct obs_source_info create_aja_source_info();
struct obs_source_info aja_source_info;
extern struct obs_output_info create_aja_output_info();
struct obs_output_info aja_output_info;
bool obs_module_load(void)
{
aja::CardManager::Instance().EnumerateCards();
aja_source_info = create_aja_source_info();
obs_register_source(&aja_source_info);
aja_output_info = create_aja_output_info();
obs_register_output(&aja_output_info);
return true;
}
void obs_module_unload(void)
{
aja::CardManager::Instance().ClearCardEntries();
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<HDMIWireFormat, RoutingConfig> kHDMIRGBCaptureConfigs = {
{HDMIWireFormat::HD_RGB_LFR,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"hdmi[{ch1}][0]->fb[{ch1}][0];",
}}};

View File

@ -0,0 +1,28 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<HDMIWireFormat, RoutingConfig> kHDMIRGBDisplayConfigs = {
{HDMIWireFormat::HD_RGB_LFR,
{NTV2_MODE_DISPLAY, 1, 1, true, false, false, false, false, false,
false, false, false, false, "fb[{ch1}][0]->hdmi[0][0];"}},
{HDMIWireFormat::TTAP_PRO,
{
NTV2_MODE_DISPLAY,
1,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];"
"fb[{ch1}][2]->hdmi[{ch1}][0];",
}}};

View File

@ -0,0 +1,47 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<HDMIWireFormat, RoutingConfig> kHDMIYCbCrCaptureConfigs = {
{HDMIWireFormat::HD_YCBCR_LFR,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"hdmi[{ch1}][0]->fb[{ch1}][0];",
}},
{HDMIWireFormat::UHD_4K_YCBCR_LFR,
{
NTV2_MODE_CAPTURE,
1,
2,
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
"hdmi[0][0]->tsi[{ch1}][0];"
"hdmi[0][1]->tsi[{ch1}][1];"
"hdmi[0][2]->tsi[{ch2}][0];"
"hdmi[0][3]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->fb[{ch1}][0];"
"tsi[{ch1}][1]->fb[{ch1}][1];"
"tsi[{ch2}][0]->fb[{ch2}][0];"
"tsi[{ch2}][1]->fb[{ch2}][1];",
}},
};

View File

@ -0,0 +1,64 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<HDMIWireFormat, RoutingConfig> kHDMIYCbCrDisplayConfigs = {
{HDMIWireFormat::HD_YCBCR_LFR,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->hdmi[0][0];",
}},
{HDMIWireFormat::UHD_4K_YCBCR_LFR,
{
NTV2_MODE_DISPLAY,
1,
2,
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
"fb[{ch1}][0]->tsi[{ch1}][0];"
"fb[{ch1}][1]->tsi[{ch1}][1];"
"fb[{ch2}][0]->tsi[{ch2}][0];"
"fb[{ch2}][1]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->hdmi[0][0];"
"tsi[{ch1}][1]->hdmi[0][1];"
"tsi[{ch2}][0]->hdmi[0][2];"
"tsi[{ch2}][1]->hdmi[0][3];",
}},
{HDMIWireFormat::TTAP_PRO,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch1}][0]->hdmi[{ch1}][0];",
}}};

View File

@ -0,0 +1,466 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<SDIWireFormat, RoutingConfig> kSDIRGBCaptureConfigs = {
{
SDIWireFormat::SD_ST352,
{
NTV2_MODE_CAPTURE, // i/o mode
1, // num wires
1, // num framestores
false, // enable 3G output?
false, // enable 6G output?
false, // enable 12G output?
false, // convert 3Gb -> 3Ga input?
false, // convert 3Ga -> 3Gb output?
false, // convert RGB 3Ga output?
false, // enable 3Gb output?
false, // enable 4K Square Division?
false, // enable 8K Square Division?
false, // enable two-sample-interleave?
"",
},
},
{SDIWireFormat::HD_720p_ST292,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_1080_ST292,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Capture
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_1080_ST372_Dual,
{
NTV2_MODE_CAPTURE,
2,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Capture
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch2}][0]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_720p_ST425_3Ga,
{
NTV2_MODE_CAPTURE,
1,
1,
true,
false,
false,
false,
false,
true,
false,
false,
false,
false,
// Capture
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_1080p_ST425_3Ga,
{
NTV2_MODE_CAPTURE,
1,
1,
true,
false,
false,
false,
false,
true,
false,
false,
false,
false,
// Capture
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb_DL,
{
NTV2_MODE_CAPTURE,
1,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Capture
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_720p_ST425_3Gb,
{
NTV2_MODE_CAPTURE,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Capture
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb,
{
NTV2_MODE_CAPTURE,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Capture
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"dli[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Ga,
{
NTV2_MODE_CAPTURE,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Gb,
{
NTV2_MODE_CAPTURE,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"",
}},
{SDIWireFormat::UHD4K_ST292_Quad_1_5_Squares,
{
NTV2_MODE_CAPTURE,
4,
4,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Capture
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch2}][0]->fb[{ch2}][0];"
"sdi[{ch3}][0]->fb[{ch3}][0];"
"sdi[{ch4}][0]->fb[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_Squares,
{
NTV2_MODE_CAPTURE,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Capture
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch2}][0]->fb[{ch2}][0];"
"sdi[{ch3}][0]->fb[{ch3}][0];"
"sdi[{ch4}][0]->fb[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_Squares,
{
NTV2_MODE_CAPTURE,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"sdi[{ch2}][0]->dli[{ch2}][0];"
"sdi[{ch2}][1]->dli[{ch2}][1];"
"sdi[{ch3}][0]->dli[{ch3}][0];"
"sdi[{ch3}][1]->dli[{ch3}][1];"
"sdi[{ch4}][0]->dli[{ch4}][0];"
"sdi[{ch4}][1]->dli[{ch4}][1];"
"dli[{ch1}][0]->fb[{ch1}][2];"
"dli[{ch2}][0]->fb[{ch2}][2];"
"dli[{ch3}][0]->fb[{ch3}][2];"
"dli[{ch4}][0]->fb[{ch4}][2];",
}},
{SDIWireFormat::UHD4K_ST425_Dual_3Gb_2SI,
{
NTV2_MODE_CAPTURE,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_2SI,
{
NTV2_MODE_CAPTURE,
4,
4,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
// SDI 1-4 -> Dual-Link 1-4
// -> TSI Mux 1-2 -> Framestore 1-2
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"sdi[{ch2}][0]->dli[{ch2}][0];"
"sdi[{ch2}][1]->dli[{ch2}][1];"
"sdi[{ch3}][0]->dli[{ch3}][0];"
"sdi[{ch3}][1]->dli[{ch3}][1];"
"sdi[{ch4}][0]->dli[{ch4}][0];"
"sdi[{ch4}][1]->dli[{ch4}][1];"
"dli[{ch1}][0]->tsi[{ch1}][0];"
"dli[{ch2}][0]->tsi[{ch1}][1];"
"dli[{ch3}][0]->tsi[{ch2}][0];"
"dli[{ch4}][0]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->fb[{ch1}][0];"
"tsi[{ch1}][1]->fb[{ch1}][1];"
"tsi[{ch2}][0]->fb[{ch2}][0];"
"tsi[{ch2}][1]->fb[{ch2}][1];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_2SI,
{
NTV2_MODE_CAPTURE,
4,
4,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
// SDI 1-4 -> Dual-Link 1-4
// -> TSI Mux 1-2 -> Framestore 1-2
"sdi[{ch1}][0]->dli[{ch1}][0];"
"sdi[{ch1}][1]->dli[{ch1}][1];"
"sdi[{ch2}][0]->dli[{ch2}][0];"
"sdi[{ch2}][1]->dli[{ch2}][1];"
"sdi[{ch3}][0]->dli[{ch3}][0];"
"sdi[{ch3}][1]->dli[{ch3}][1];"
"sdi[{ch4}][0]->dli[{ch4}][0];"
"sdi[{ch4}][1]->dli[{ch4}][1];"
"dli[{ch1}][0]->tsi[{ch1}][0];"
"dli[{ch2}][0]->tsi[{ch1}][1];"
"dli[{ch3}][0]->tsi[{ch2}][0];"
"dli[{ch4}][0]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->fb[{ch1}][0];"
"tsi[{ch1}][1]->fb[{ch1}][1];"
"tsi[{ch2}][0]->fb[{ch2}][0];"
"tsi[{ch2}][1]->fb[{ch2}][1];",
}},
{SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
true,
false,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
true,
false,
false,
true,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_Dual_12G,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_RGB_Dual_12G,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_Quad_12G,
{
NTV2_MODE_CAPTURE,
4,
4,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
};

View File

@ -0,0 +1,468 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<SDIWireFormat, RoutingConfig> kSDIRGBDisplayConfigs = {
{
SDIWireFormat::SD_ST352,
{
NTV2_MODE_DISPLAY,
1, // num wires
1, // num framestores
false, // enable 3G output?
false, // enable 6G output?
false, // enable 12G output?
false, // convert 3Gb -> 3Ga input?
false, // convert 3Ga -> 3Gb output?
false, // convert RGB 3Ga output?
false, // enable 3Gb output?
false, // enable 4K Square Division?
false, // enable 8K Square Division?
false, // enable two-sample-interleave?
"", // RGB Output Route
},
},
{SDIWireFormat::HD_720p_ST292,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}[0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080_ST292,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}[0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080_ST372_Dual,
{
NTV2_MODE_DISPLAY,
2,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Playout
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}[0];"
"dlo[{ch1}][1]->sdi[{ch2}][0];",
}},
{SDIWireFormat::HD_720p_ST425_3Ga,
{
NTV2_MODE_DISPLAY,
1,
1,
true,
false,
false,
false,
false,
true,
false,
false,
false,
false,
// Output
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080p_ST425_3Ga,
{
NTV2_MODE_DISPLAY,
1,
1,
true,
false,
false,
false,
false,
true,
false,
false,
false,
false,
// Output
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb_DL,
{
NTV2_MODE_DISPLAY,
1,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Output
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_720p_ST425_3Gb,
{
NTV2_MODE_DISPLAY,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Output
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb,
{
NTV2_MODE_DISPLAY,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
// Output
"fb[{ch1}][2]->dlo[{ch1}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Ga,
{
NTV2_MODE_DISPLAY,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Gb,
{
NTV2_MODE_DISPLAY,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"",
}},
{SDIWireFormat::UHD4K_ST292_Quad_1_5_Squares,
{
NTV2_MODE_DISPLAY,
4,
4,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Playout
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch2}][0]->sdi[{ch2}][0];"
"fb[{ch3}][0]->sdi[{ch3}][0];"
"fb[{ch4}][0]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_Squares,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Playout
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch2}][0]->sdi[{ch2}][0];"
"fb[{ch3}][0]->sdi[{ch3}][0];"
"fb[{ch4}][0]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_Squares,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Framestores 1-4 -> Dual-Link 1-4 -> SDI 1-4
"fb[{ch1}][2]->dlo[{ch1}][0];"
"fb[{ch2}][2]->dlo[{ch2}][0];"
"fb[{ch3}][2]->dlo[{ch3}][0];"
"fb[{ch4}][2]->dlo[{ch4}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];"
"dlo[{ch2}][0]->sdi[{ch2}][0];"
"dlo[{ch2}][1]->sdi[{ch2}][1];"
"dlo[{ch3}][0]->sdi[{ch3}][0];"
"dlo[{ch3}][1]->sdi[{ch3}][1];"
"dlo[{ch4}][0]->sdi[{ch4}][0];"
"dlo[{ch4}][1]->sdi[{ch4}][1];",
}},
{SDIWireFormat::UHD4K_ST425_Dual_3Gb_2SI,
{
NTV2_MODE_DISPLAY,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_2SI,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
// Framestores 1-2 -> TSI Mux 1-2
// -> Dual-Link 1-4 -> SDI 1-4
"fb[{ch1}][2]->tsi[{ch1}][0];"
"fb[{ch1}][3]->tsi[{ch1}][1];"
"fb[{ch2}][2]->tsi[{ch2}][0];"
"fb[{ch2}][3]->tsi[{ch2}][1];"
"tsi[{ch1}][2]->dlo[{ch1}][0];"
"tsi[{ch1}][3]->dlo[{ch2}][0];"
"tsi[{ch2}][2]->dlo[{ch3}][0];"
"tsi[{ch2}][3]->dlo[{ch4}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];"
"dlo[{ch2}][0]->sdi[{ch2}][0];"
"dlo[{ch2}][1]->sdi[{ch2}][1];"
"dlo[{ch3}][0]->sdi[{ch3}][0];"
"dlo[{ch3}][1]->sdi[{ch3}][1];"
"dlo[{ch4}][0]->sdi[{ch4}][0];"
"dlo[{ch4}][1]->sdi[{ch4}][1];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_2SI,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
// Framestores 1-2 -> TSI Mux 1-2
// -> Dual-Link 1-4 -> SDI 1-4
"fb[{ch1}][2]->tsi[{ch1}][0];"
"fb[{ch1}][3]->tsi[{ch1}][1];"
"fb[{ch2}][2]->tsi[{ch2}][0];"
"fb[{ch2}][3]->tsi[{ch2}][1];"
"tsi[{ch1}][2]->dlo[{ch1}][0];"
"tsi[{ch1}][3]->dlo[{ch2}][0];"
"tsi[{ch2}][2]->dlo[{ch3}][0];"
"tsi[{ch2}][3]->dlo[{ch4}][0];"
"dlo[{ch1}][0]->sdi[{ch1}][0];"
"dlo[{ch1}][1]->sdi[{ch1}][1];"
"dlo[{ch2}][0]->sdi[{ch2}][0];"
"dlo[{ch2}][1]->sdi[{ch2}][1];"
"dlo[{ch3}][0]->sdi[{ch3}][0];"
"dlo[{ch3}][1]->sdi[{ch3}][1];"
"dlo[{ch4}][0]->sdi[{ch4}][0];"
"dlo[{ch4}][1]->sdi[{ch4}][1];",
}},
{SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI,
{
NTV2_MODE_DISPLAY,
2,
2,
false,
true,
false,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
true,
false,
false,
true,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_Dual_12G,
{
NTV2_MODE_DISPLAY,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_RGB_Dual_12G,
{
NTV2_MODE_DISPLAY,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_Quad_12G,
{
NTV2_MODE_DISPLAY,
4,
4,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
};

View File

@ -0,0 +1,466 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<SDIWireFormat, RoutingConfig> kSDIYCbCrCaptureConfigs = {
{
SDIWireFormat::SD_ST352,
{
NTV2_MODE_CAPTURE,
1, // num wires
1, // num framestores
false, // enable 3G output?
false, // enable 6G output?
false, // enable 12G output?
false, // convert 3Gb -> 3Ga input?
false, // convert 3Ga -> 3Gb output?
false, // convert RGB 3Ga output?
false, // enable 3Gb output?
false, // enable 4K Square Division?
false, // enable 8K Square Division?
false, // enable two-sample-interleave?
"sdi[{ch1}][0]->fb[{ch1}][0]", // YCbCr Capture Route
},
},
{SDIWireFormat::HD_720p_ST292,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]",
}},
{SDIWireFormat::HD_1080_ST292,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]",
}},
{SDIWireFormat::HD_1080_ST372_Dual,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]; sdi[{ch2}][0]->fb[{ch2}][0]",
}},
{SDIWireFormat::HD_720p_ST425_3Ga,
{
NTV2_MODE_CAPTURE,
1,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]",
}},
{SDIWireFormat::HD_1080p_ST425_3Ga,
{
NTV2_MODE_CAPTURE,
1,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb_DL,
{
NTV2_MODE_CAPTURE,
1,
1,
true,
false,
false,
true,
true,
false,
true,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]",
}},
{SDIWireFormat::HD_720p_ST425_3Gb,
{
NTV2_MODE_CAPTURE,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]; sdi[{ch1}][1]->fb[{ch2}][0];",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb,
{
NTV2_MODE_CAPTURE,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0]; sdi[{ch1}][1]->fb[{ch2}][0];",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Ga,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch2}][0]->fb[{ch2}][0];",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Gb,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
false,
false,
true,
false,
false,
false,
false,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch2}][0]->fb[{ch2}][0];",
}},
{SDIWireFormat::UHD4K_ST292_Dual_1_5_Squares,
{
NTV2_MODE_CAPTURE,
2,
4,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch1}][1]->fb[{ch2}][0];"
"sdi[{ch2}][0]->fb[{ch3}][0];"
"sdi[{ch2}][1]->fb[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST292_Quad_1_5_Squares,
{
NTV2_MODE_CAPTURE,
4,
4,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Capture
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch2}][0]->fb[{ch2}][0];"
"sdi[{ch3}][0]->fb[{ch3}][0];"
"sdi[{ch4}][0]->fb[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_Squares,
{
NTV2_MODE_CAPTURE,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Capture
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch2}][0]->fb[{ch2}][0];"
"sdi[{ch3}][0]->fb[{ch3}][0];"
"sdi[{ch4}][0]->fb[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_Squares,
{
NTV2_MODE_CAPTURE,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Capture
"sdi[{ch1}][0]->fb[{ch1}][0];"
"sdi[{ch2}][0]->fb[{ch2}][0];"
"sdi[{ch3}][0]->fb[{ch3}][0];"
"sdi[{ch4}][0]->fb[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Dual_3Gb_2SI,
{NTV2_MODE_CAPTURE, 2, 2, true, false, false, false, false, false,
false, false, false, true,
"sdi[{ch1}][0]->tsi[{ch1}][0];"
"sdi[{ch1}][1]->tsi[{ch1}][1];"
"sdi[{ch2}][0]->tsi[{ch2}][0];"
"sdi[{ch2}][1]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->fb[{ch1}][0];"
"tsi[{ch1}][1]->fb[{ch1}][1];"
"tsi[{ch2}][0]->fb[{ch2}][0];"
"tsi[{ch2}][1]->fb[{ch2}][1];"}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_2SI,
{
NTV2_MODE_CAPTURE,
4,
4,
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
"sdi[{ch1}][0]->tsi[{ch1}][0];"
"sdi[{ch2}][0]->tsi[{ch1}][1];"
"sdi[{ch3}][0]->tsi[{ch2}][0];"
"sdi[{ch4}][0]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->fb[{ch1}][0];"
"tsi[{ch1}][1]->fb[{ch1}][1];"
"tsi[{ch2}][0]->fb[{ch2}][0];"
"tsi[{ch2}][1]->fb[{ch2}][1];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_2SI,
{
NTV2_MODE_CAPTURE,
4,
4,
false,
false,
false,
true,
false,
false,
false,
false,
false,
true,
"sdi[{ch1}][0]->tsi[{ch1}][0];"
"sdi[{ch2}][0]->tsi[{ch1}][1];"
"sdi[{ch3}][0]->tsi[{ch2}][0];"
"sdi[{ch4}][0]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->fb[{ch1}][0];"
"tsi[{ch1}][1]->fb[{ch1}][1];"
"tsi[{ch2}][0]->fb[{ch2}][0];"
"tsi[{ch2}][1]->fb[{ch2}][1];",
}},
{SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
true,
false,
false,
false,
false,
false,
false,
false,
true,
"sdi[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
true,
false,
false,
false,
false,
false,
false,
false,
true,
// Capture
"sdi[{ch1}][0]->tsi[{ch1}][0];"
"sdi[{ch2}][0]->tsi[{ch1}][1];"
"sdi[{ch3}][0]->tsi[{ch2}][0];"
"sdi[{ch4}][0]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->fb[{ch1}][0];"
"tsi[{ch1}][1]->fb[{ch1}][1];"
"tsi[{ch2}][0]->fb[{ch2}][0];"
"tsi[{ch2}][1]->fb[{ch2}][1];",
}},
{SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
true,
false,
false,
true,
false,
false,
false,
true,
"sdi[{ch1}][0]->fb[{ch1}][0];",
}},
{SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus,
{
NTV2_MODE_CAPTURE,
1,
1,
false,
false,
true,
false,
false,
true,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_Dual_12G,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_RGB_Dual_12G,
{
NTV2_MODE_CAPTURE,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_Quad_12G,
{
NTV2_MODE_CAPTURE,
4,
4,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
};

View File

@ -0,0 +1,486 @@
#pragma once
#include "../aja-routing.hpp"
static const std::map<SDIWireFormat, RoutingConfig> kSDIYCbCrDisplayConfigs = {
{
SDIWireFormat::SD_ST352,
{
NTV2_MODE_DISPLAY,
1, // num wires
1, // num framestores
false, // enable 3G output?
false, // enable 6G output?
false, // enable 12G output?
false, // convert 3Gb -> 3Ga input?
false, // convert 3Ga -> 3Gb output?
false, // convert RGB 3Ga output?
false, // enable 3Gb output?
false, // enable 4K Square Division?
false, // enable 8K Square Division?
false, // enable two-sample-interleave?
"fb[{ch1}][0]->sdi[{ch1}][0]", // YCbCr Output Route
},
},
{SDIWireFormat::HD_720p_ST292,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0]",
}},
{SDIWireFormat::HD_1080_ST292,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0]",
}},
{SDIWireFormat::HD_1080_ST372_Dual,
{
NTV2_MODE_DISPLAY,
2,
2,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch2}][0]->sdi[{ch2}][0]",
}},
{SDIWireFormat::HD_720p_ST425_3Ga,
{
NTV2_MODE_DISPLAY,
1,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0]",
}},
{SDIWireFormat::HD_1080p_ST425_3Ga,
{
NTV2_MODE_DISPLAY,
1,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0]",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb_DL,
{
NTV2_MODE_DISPLAY,
1,
1,
true,
false,
false,
true,
true,
false,
true,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0]",
}},
{SDIWireFormat::HD_720p_ST425_3Gb,
{
NTV2_MODE_DISPLAY,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0]; fb[{ch2}][0]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080p_ST425_3Gb,
{
NTV2_MODE_DISPLAY,
1,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0]; fb[{ch2}][0]->sdi[{ch1}][1];",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Ga,
{
NTV2_MODE_DISPLAY,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"",
}},
{SDIWireFormat::HD_1080p_ST425_Dual_3Gb,
{
NTV2_MODE_DISPLAY,
2,
2,
true,
false,
false,
false,
false,
false,
false,
false,
false,
false,
"",
}},
{SDIWireFormat::UHD4K_ST292_Dual_1_5_Squares,
{
NTV2_MODE_DISPLAY,
2,
4,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch2}][0]->sdi[{ch1}][1];"
"fb[{ch3}][0]->sdi[{ch2}][0];"
"fb[{ch4}][0]->sdi[{ch2}][1];",
}},
{SDIWireFormat::UHD4K_ST292_Quad_1_5_Squares,
{
NTV2_MODE_DISPLAY,
4,
4,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Playout
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch2}][0]->sdi[{ch2}][0];"
"fb[{ch3}][0]->sdi[{ch3}][0];"
"fb[{ch4}][0]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_Squares,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Playout
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch2}][0]->sdi[{ch2}][0];"
"fb[{ch3}][0]->sdi[{ch3}][0];"
"fb[{ch4}][0]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_Squares,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
false,
true,
false,
false,
// Playout
"fb[{ch1}][0]->sdi[{ch1}][0];"
"fb[{ch2}][0]->sdi[{ch2}][0];"
"fb[{ch3}][0]->sdi[{ch3}][0];"
"fb[{ch4}][0]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Dual_3Gb_2SI,
{
NTV2_MODE_DISPLAY,
2,
2,
true,
false,
false,
false,
false,
false,
true,
false,
false,
true,
"fb[{ch1}][0]->tsi[{ch1}][0];"
"fb[{ch1}][1]->tsi[{ch1}][1];"
"fb[{ch2}][0]->tsi[{ch2}][0];"
"fb[{ch2}][1]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->sdi[{ch1}][0];"
"tsi[{ch1}][1]->sdi[{ch1}][1];"
"tsi[{ch2}][0]->sdi[{ch2}][0];"
"tsi[{ch2}][1]->sdi[{ch2}][1];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Ga_2SI,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
false,
false,
false,
true,
"fb[{ch1}][0]->tsi[{ch1}][0];"
"fb[{ch1}][1]->tsi[{ch1}][1];"
"fb[{ch2}][0]->tsi[{ch2}][0];"
"fb[{ch2}][1]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->sdi[{ch1}][0];"
"tsi[{ch1}][1]->sdi[{ch2}][0];"
"tsi[{ch2}][0]->sdi[{ch3}][0];"
"tsi[{ch2}][1]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST425_Quad_3Gb_2SI,
{
NTV2_MODE_DISPLAY,
4,
4,
true,
false,
false,
false,
false,
false,
true,
false,
false,
true,
"fb[{ch1}][0]->tsi[{ch1}][0];"
"fb[{ch1}][1]->tsi[{ch1}][1];"
"fb[{ch2}][0]->tsi[{ch2}][0];"
"fb[{ch2}][1]->tsi[{ch2}][1];"
"tsi[{ch1}][0]->sdi[{ch1}][0];"
"tsi[{ch1}][1]->sdi[{ch2}][0];"
"tsi[{ch2}][0]->sdi[{ch3}][0];"
"tsi[{ch2}][1]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
true,
false,
false,
false,
false,
false,
false,
false,
true,
"fb[{ch1}][0]->sdi[{ch1}][0];",
}},
{SDIWireFormat::UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus,
{
NTV2_MODE_DISPLAY,
2,
2,
false,
true,
false,
false,
false,
false,
false,
false,
false,
true,
// Playout
"fb[{ch3}][0]->tsi[{ch3}][0];"
"fb[{ch3}][1]->tsi[{ch3}][1];"
"fb[{ch4}][0]->tsi[{ch4}][0];"
"fb[{ch4}][1]->tsi[{ch4}][1];"
"tsi[{ch3}][0]->sdi[{ch1}][0];"
"tsi[{ch3}][1]->sdi[{ch2}][0];"
"tsi[{ch4}][0]->sdi[{ch3}][0];"
"tsi[{ch4}][1]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
true,
false,
false,
true,
false,
false,
false,
true,
"fb[{ch1}][0]->sdi[{ch1}][0];",
}},
{SDIWireFormat::UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus,
{
NTV2_MODE_DISPLAY,
1,
1,
false,
false,
true,
false,
false,
true,
false,
false,
false,
true,
// Playout
"fb[{ch3}][0]->tsi[{ch3}][0];"
"fb[{ch3}][1]->tsi[{ch3}][1];"
"fb[{ch4}][0]->tsi[{ch4}][0];"
"fb[{ch4}][1]->tsi[{ch4}][1];"
"tsi[{ch3}][0]->sdi[{ch1}][0];"
"tsi[{ch3}][1]->sdi[{ch2}][0];"
"tsi[{ch4}][0]->sdi[{ch3}][0];"
"tsi[{ch4}][1]->sdi[{ch4}][0];",
}},
{SDIWireFormat::UHD28K_ST2082_Dual_12G,
{
NTV2_MODE_DISPLAY,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_RGB_Dual_12G,
{
NTV2_MODE_DISPLAY,
2,
2,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
{SDIWireFormat::UHD28K_ST2082_Quad_12G,
{
NTV2_MODE_DISPLAY,
4,
4,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true,
"",
}},
};