1196 lines
35 KiB
C++
1196 lines
35 KiB
C++
#include "aja-card-manager.hpp"
|
|
#include "aja-common.hpp"
|
|
#include "aja-ui-props.hpp"
|
|
#include "aja-source.hpp"
|
|
#include "aja-routing.hpp"
|
|
|
|
#include <util/threading.h>
|
|
#include <util/platform.h>
|
|
#include <util/dstr.h>
|
|
#include <util/util_uint64.h>
|
|
#include <obs-module.h>
|
|
|
|
#include <ajantv2/includes/ntv2card.h>
|
|
#include <ajantv2/includes/ntv2utils.h>
|
|
|
|
#define NSEC_PER_SEC 1000000000LL
|
|
#define NTV2_AUDIOSIZE_MAX (401 * 1024)
|
|
|
|
AJASource::AJASource(obs_source_t *source)
|
|
: mVideoBuffer{},
|
|
mAudioBuffer{},
|
|
mCard{nullptr},
|
|
mSourceName{""},
|
|
mCardID{""},
|
|
mDeviceIndex{0},
|
|
mBuffering{false},
|
|
mIsCapturing{false},
|
|
mSourceProps{},
|
|
mTestPattern{},
|
|
mCaptureThread{nullptr},
|
|
mMutex{},
|
|
mSource{source},
|
|
mCrosspoints{}
|
|
{
|
|
}
|
|
|
|
AJASource::~AJASource()
|
|
{
|
|
Deactivate();
|
|
mTestPattern.clear();
|
|
mVideoBuffer.Deallocate();
|
|
mAudioBuffer.Deallocate();
|
|
mVideoBuffer = 0;
|
|
mAudioBuffer = 0;
|
|
}
|
|
|
|
void AJASource::SetCard(CNTV2Card *card)
|
|
{
|
|
mCard = card;
|
|
}
|
|
|
|
CNTV2Card *AJASource::GetCard()
|
|
{
|
|
return mCard;
|
|
}
|
|
|
|
void AJASource::SetOBSSource(obs_source_t *source)
|
|
{
|
|
mSource = source;
|
|
}
|
|
|
|
obs_source_t *AJASource::GetOBSSource(void) const
|
|
{
|
|
return mSource;
|
|
}
|
|
|
|
void AJASource::SetName(const std::string &name)
|
|
{
|
|
mSourceName = name;
|
|
}
|
|
|
|
std::string AJASource::GetName() const
|
|
{
|
|
return mSourceName;
|
|
}
|
|
|
|
void populate_source_device_list(obs_property_t *list)
|
|
{
|
|
obs_property_list_clear(list);
|
|
auto &cardManager = aja::CardManager::Instance();
|
|
cardManager.EnumerateCards();
|
|
for (const auto &iter : cardManager.GetCardEntries()) {
|
|
if (iter.second) {
|
|
CNTV2Card *card = iter.second->GetCard();
|
|
if (!card)
|
|
continue;
|
|
|
|
if (aja::IsOutputOnlyDevice(iter.second->GetDeviceID()))
|
|
continue;
|
|
|
|
obs_property_list_add_string(
|
|
list, iter.second->GetDisplayName().c_str(),
|
|
iter.second->GetCardID().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Capture Thread stuff
|
|
//
|
|
|
|
struct AudioOffsets {
|
|
ULWord currentAddress = 0;
|
|
ULWord lastAddress = 0;
|
|
ULWord readOffset = 0;
|
|
ULWord wrapAddress = 0;
|
|
ULWord bytesRead = 0;
|
|
};
|
|
|
|
static void ResetAudioBufferOffsets(CNTV2Card *card,
|
|
NTV2AudioSystem audioSystem,
|
|
AudioOffsets &offsets)
|
|
{
|
|
if (!card)
|
|
return;
|
|
|
|
offsets.currentAddress = 0;
|
|
offsets.lastAddress = 0;
|
|
offsets.readOffset = 0;
|
|
offsets.wrapAddress = 0;
|
|
offsets.bytesRead = 0;
|
|
card->GetAudioReadOffset(offsets.readOffset, audioSystem);
|
|
card->GetAudioWrapAddress(offsets.wrapAddress, audioSystem);
|
|
offsets.wrapAddress += offsets.readOffset;
|
|
offsets.lastAddress = offsets.readOffset;
|
|
}
|
|
|
|
void AJASource::GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
|
|
NTV2TestPatternSelect ps)
|
|
{
|
|
NTV2VideoFormat vid_fmt = vf;
|
|
NTV2PixelFormat pix_fmt = pf;
|
|
if (vid_fmt == NTV2_FORMAT_UNKNOWN)
|
|
vid_fmt = NTV2_FORMAT_720p_5994;
|
|
if (pix_fmt == NTV2_FBF_INVALID)
|
|
pix_fmt = kDefaultAJAPixelFormat;
|
|
|
|
NTV2FormatDesc fd(vid_fmt, pix_fmt, NTV2_VANCMODE_OFF);
|
|
auto bufSize = fd.GetTotalRasterBytes();
|
|
if (bufSize != mTestPattern.size()) {
|
|
mTestPattern.clear();
|
|
mTestPattern.resize(bufSize);
|
|
NTV2TestPatternGen gen;
|
|
gen.DrawTestPattern(ps, fd.GetRasterWidth(),
|
|
fd.GetRasterHeight(), pix_fmt,
|
|
mTestPattern);
|
|
}
|
|
if (mTestPattern.size() == 0) {
|
|
blog(LOG_DEBUG,
|
|
"AJASource::GenerateTestPattern: Error generating test pattern!");
|
|
return;
|
|
}
|
|
|
|
const enum video_format obs_vid_fmt =
|
|
aja::AJAPixelFormatToOBSVideoFormat(pix_fmt);
|
|
|
|
struct obs_source_frame2 obsFrame;
|
|
obsFrame.flip = false;
|
|
obsFrame.timestamp = os_gettime_ns();
|
|
obsFrame.width = fd.GetRasterWidth();
|
|
obsFrame.height = fd.GetRasterHeight();
|
|
obsFrame.format = obs_vid_fmt;
|
|
obsFrame.data[0] = mTestPattern.data();
|
|
obsFrame.linesize[0] = fd.GetBytesPerRow();
|
|
video_colorspace colorspace = VIDEO_CS_709;
|
|
if (NTV2_IS_SD_VIDEO_FORMAT(vid_fmt))
|
|
colorspace = VIDEO_CS_601;
|
|
video_format_get_parameters_for_format(colorspace, VIDEO_RANGE_PARTIAL,
|
|
obs_vid_fmt,
|
|
obsFrame.color_matrix,
|
|
obsFrame.color_range_min,
|
|
obsFrame.color_range_max);
|
|
obs_source_output_video2(mSource, &obsFrame);
|
|
blog(LOG_DEBUG, "AJASource::GenerateTestPattern: Black");
|
|
}
|
|
|
|
static inline uint64_t samples_to_ns(size_t frames, uint_fast32_t rate)
|
|
{
|
|
return util_mul_div64(frames, NSEC_PER_SEC, rate);
|
|
}
|
|
|
|
static inline uint64_t get_sample_time(size_t frames, uint_fast32_t rate)
|
|
{
|
|
return os_gettime_ns() - samples_to_ns(frames, rate);
|
|
}
|
|
|
|
static bool CheckTransferAudioDMA(CNTV2Card *card,
|
|
const NTV2AudioSystem audioSystem,
|
|
void *buffer, ULWord bufferSize,
|
|
ULWord dmaOffset, ULWord dmaSize,
|
|
const char *log_id)
|
|
{
|
|
if (dmaSize > bufferSize) {
|
|
blog(LOG_DEBUG,
|
|
"AJASource::CaptureThread: Audio overrun %s! Buffer Size: %u, Bytes Captured: %u",
|
|
log_id, bufferSize, dmaSize);
|
|
return false;
|
|
}
|
|
|
|
card->DMAReadAudio(audioSystem, (ULWord *)buffer, dmaOffset, dmaSize);
|
|
return true;
|
|
}
|
|
|
|
static inline bool TransferAudio(CNTV2Card *card,
|
|
const NTV2AudioSystem audioSystem,
|
|
NTV2_POINTER &audioBuffer,
|
|
AudioOffsets &offsets)
|
|
{
|
|
card->ReadAudioLastIn(offsets.currentAddress, audioSystem);
|
|
offsets.currentAddress += 1;
|
|
offsets.currentAddress += offsets.readOffset;
|
|
|
|
if (offsets.currentAddress < offsets.lastAddress) {
|
|
offsets.bytesRead = offsets.wrapAddress - offsets.lastAddress;
|
|
|
|
if (!CheckTransferAudioDMA(card, audioSystem, audioBuffer,
|
|
audioBuffer.GetByteCount(),
|
|
offsets.lastAddress,
|
|
offsets.bytesRead, "(1)"))
|
|
return false;
|
|
|
|
if (!CheckTransferAudioDMA(
|
|
card, audioSystem,
|
|
audioBuffer.GetHostAddress(offsets.bytesRead),
|
|
audioBuffer.GetByteCount() - offsets.bytesRead,
|
|
offsets.readOffset,
|
|
offsets.currentAddress - offsets.readOffset, "(2)"))
|
|
return false;
|
|
offsets.bytesRead +=
|
|
offsets.currentAddress - offsets.readOffset;
|
|
|
|
} else {
|
|
offsets.bytesRead =
|
|
offsets.currentAddress - offsets.lastAddress;
|
|
if (!CheckTransferAudioDMA(card, audioSystem, audioBuffer,
|
|
audioBuffer.GetByteCount(),
|
|
offsets.lastAddress,
|
|
offsets.bytesRead, "(3)"))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AJASource::CaptureThread(AJAThread *thread, void *data)
|
|
{
|
|
UNUSED_PARAMETER(thread);
|
|
|
|
auto ajaSource = (AJASource *)data;
|
|
if (!ajaSource) {
|
|
blog(LOG_WARNING,
|
|
"AJASource::CaptureThread: Plugin instance is null!");
|
|
return;
|
|
}
|
|
|
|
blog(LOG_INFO,
|
|
"AJASource::CaptureThread: Starting capture thread for AJA source %s",
|
|
ajaSource->GetName().c_str());
|
|
|
|
auto card = ajaSource->GetCard();
|
|
if (!card) {
|
|
blog(LOG_ERROR,
|
|
"AJASource::CaptureThread: Card instance is null!");
|
|
return;
|
|
}
|
|
|
|
auto sourceProps = ajaSource->GetSourceProps();
|
|
ajaSource->ResetVideoBuffer(sourceProps.videoFormat,
|
|
sourceProps.pixelFormat);
|
|
auto inputSource = sourceProps.InitialInputSource();
|
|
auto channel = sourceProps.Channel();
|
|
auto audioSystem = sourceProps.AudioSystem();
|
|
// Current "on-air" frame on the card. The capture thread "Ping-pongs" between
|
|
// two frames, starting at an index corresponding to the framestore channel.
|
|
// For example:
|
|
// Channel 1 (index 0) = frames 0/1
|
|
// Channel 2 (index 1) = frames 2/3
|
|
// Channel 3 (index 2) = frames 4/5
|
|
// Channel 4 (index 3) = frames 6/7
|
|
// etc...
|
|
ULWord currentCardFrame = (uint32_t)channel * 2;
|
|
card->WaitForInputFieldID(NTV2_FIELD0, channel);
|
|
|
|
currentCardFrame ^= 1;
|
|
|
|
card->SetInputFrame(channel, currentCardFrame);
|
|
|
|
AudioOffsets offsets;
|
|
ResetAudioBufferOffsets(card, audioSystem, offsets);
|
|
|
|
obs_data_t *settings = obs_source_get_settings(ajaSource->mSource);
|
|
|
|
while (ajaSource->IsCapturing()) {
|
|
if (card->GetModelName() == "(Not Found)") {
|
|
os_sleep_ms(250);
|
|
obs_source_update(ajaSource->mSource, settings);
|
|
break;
|
|
}
|
|
|
|
auto videoFormat = sourceProps.videoFormat;
|
|
auto pixelFormat = sourceProps.pixelFormat;
|
|
auto ioSelection = sourceProps.ioSelect;
|
|
|
|
card->WaitForInputFieldID(NTV2_FIELD0, channel);
|
|
currentCardFrame ^= 1;
|
|
|
|
// Card format detection -- restarts capture thread via aja_source_update callback
|
|
auto newVideoFormat = card->GetInputVideoFormat(
|
|
inputSource, aja::Is3GLevelB(card, channel));
|
|
if (newVideoFormat == NTV2_FORMAT_UNKNOWN) {
|
|
blog(LOG_DEBUG,
|
|
"AJASource::CaptureThread: Video format unknown!");
|
|
ajaSource->GenerateTestPattern(videoFormat, pixelFormat,
|
|
NTV2_TestPatt_Black);
|
|
os_sleep_ms(250);
|
|
continue;
|
|
}
|
|
|
|
newVideoFormat = aja::HandleSpecialCaseFormats(
|
|
ioSelection, newVideoFormat, sourceProps.deviceID);
|
|
|
|
if (sourceProps.autoDetect && (videoFormat != newVideoFormat)) {
|
|
blog(LOG_INFO,
|
|
"AJASource::CaptureThread: New Video Format detected! Triggering 'aja_source_update' callback and returning...");
|
|
blog(LOG_INFO,
|
|
"AJASource::CaptureThread: Current Video Format: %s, | Want Video Format: %s",
|
|
NTV2VideoFormatToString(videoFormat, true).c_str(),
|
|
NTV2VideoFormatToString(newVideoFormat, true)
|
|
.c_str());
|
|
os_sleep_ms(250);
|
|
obs_source_update(ajaSource->mSource, settings);
|
|
break;
|
|
}
|
|
|
|
if (!TransferAudio(card, audioSystem, ajaSource->mAudioBuffer,
|
|
offsets)) {
|
|
ResetAudioBufferOffsets(card, audioSystem, offsets);
|
|
} else {
|
|
offsets.lastAddress = offsets.currentAddress;
|
|
obs_source_audio audioPacket;
|
|
audioPacket.samples_per_sec = 48000;
|
|
audioPacket.format = AUDIO_FORMAT_32BIT;
|
|
audioPacket.speakers = SPEAKERS_7POINT1;
|
|
audioPacket.frames = offsets.bytesRead / 32;
|
|
audioPacket.timestamp =
|
|
get_sample_time(audioPacket.frames, 48000);
|
|
audioPacket.data[0] = (uint8_t *)ajaSource->mAudioBuffer
|
|
.GetHostPointer();
|
|
obs_source_output_audio(ajaSource->mSource,
|
|
&audioPacket);
|
|
}
|
|
|
|
if (ajaSource->mVideoBuffer.GetByteCount() == 0) {
|
|
blog(LOG_DEBUG,
|
|
"AJASource::CaptureThread: 0 bytes in video buffer! Something went wrong!");
|
|
continue;
|
|
}
|
|
|
|
card->DMAReadFrame(currentCardFrame, ajaSource->mVideoBuffer,
|
|
ajaSource->mVideoBuffer.GetByteCount());
|
|
|
|
auto actualVideoFormat = videoFormat;
|
|
if (aja::Is3GLevelB(card, channel))
|
|
actualVideoFormat = aja::GetLevelAFormatForLevelBFormat(
|
|
videoFormat);
|
|
|
|
const enum video_format obs_vid_fmt =
|
|
aja::AJAPixelFormatToOBSVideoFormat(
|
|
sourceProps.pixelFormat);
|
|
|
|
NTV2FormatDesc fd(actualVideoFormat, pixelFormat);
|
|
struct obs_source_frame2 obsFrame;
|
|
obsFrame.flip = false;
|
|
obsFrame.timestamp = os_gettime_ns();
|
|
obsFrame.width = fd.GetRasterWidth();
|
|
obsFrame.height = fd.GetRasterHeight();
|
|
obsFrame.format = obs_vid_fmt;
|
|
obsFrame.data[0] = reinterpret_cast<uint8_t *>(
|
|
(ULWord *)ajaSource->mVideoBuffer.GetHostPointer());
|
|
obsFrame.linesize[0] = fd.GetBytesPerRow();
|
|
video_colorspace colorspace = VIDEO_CS_709;
|
|
if (NTV2_IS_SD_VIDEO_FORMAT(actualVideoFormat))
|
|
colorspace = VIDEO_CS_601;
|
|
video_format_get_parameters_for_format(
|
|
colorspace, VIDEO_RANGE_PARTIAL, obs_vid_fmt,
|
|
obsFrame.color_matrix, obsFrame.color_range_min,
|
|
obsFrame.color_range_max);
|
|
|
|
obs_source_output_video2(ajaSource->mSource, &obsFrame);
|
|
|
|
card->SetInputFrame(channel, currentCardFrame);
|
|
}
|
|
|
|
blog(LOG_INFO, "AJASource::Capturethread: Thread loop stopped");
|
|
|
|
ajaSource->GenerateTestPattern(sourceProps.videoFormat,
|
|
sourceProps.pixelFormat,
|
|
NTV2_TestPatt_Black);
|
|
|
|
obs_data_release(settings);
|
|
}
|
|
|
|
void AJASource::Deactivate()
|
|
{
|
|
SetCapturing(false);
|
|
|
|
if (mCaptureThread) {
|
|
if (mCaptureThread->Active()) {
|
|
mCaptureThread->Stop();
|
|
blog(LOG_INFO, "AJASource::CaptureThread: Stopped!");
|
|
}
|
|
delete mCaptureThread;
|
|
mCaptureThread = nullptr;
|
|
blog(LOG_INFO, "AJASource::CaptureThread: Destroyed!");
|
|
}
|
|
}
|
|
|
|
void AJASource::Activate(bool enable)
|
|
{
|
|
if (mCaptureThread == nullptr) {
|
|
mCaptureThread = new AJAThread();
|
|
mCaptureThread->Attach(AJASource::CaptureThread, this);
|
|
mCaptureThread->SetPriority(AJA_ThreadPriority_High);
|
|
blog(LOG_INFO, "AJASource::CaptureThread: Created!");
|
|
}
|
|
|
|
if (enable) {
|
|
SetCapturing(true);
|
|
if (!mCaptureThread->Active()) {
|
|
mCaptureThread->Start();
|
|
blog(LOG_INFO, "AJASource::CaptureThread: Started!");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AJASource::IsCapturing() const
|
|
{
|
|
return mIsCapturing;
|
|
}
|
|
|
|
void AJASource::SetCapturing(bool capturing)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mMutex);
|
|
mIsCapturing = capturing;
|
|
}
|
|
|
|
//
|
|
// CardEntry/Device stuff
|
|
//
|
|
std::string AJASource::CardID() const
|
|
{
|
|
return mCardID;
|
|
}
|
|
void AJASource::SetCardID(const std::string &cardID)
|
|
{
|
|
mCardID = cardID;
|
|
}
|
|
uint32_t AJASource::DeviceIndex() const
|
|
{
|
|
return static_cast<uint32_t>(mDeviceIndex);
|
|
}
|
|
void AJASource::SetDeviceIndex(uint32_t index)
|
|
{
|
|
mDeviceIndex = static_cast<UWord>(index);
|
|
}
|
|
|
|
//
|
|
// AJASource Properties stuff
|
|
//
|
|
void AJASource::SetSourceProps(const SourceProps &props)
|
|
{
|
|
mSourceProps = props;
|
|
}
|
|
|
|
SourceProps AJASource::GetSourceProps() const
|
|
{
|
|
return mSourceProps;
|
|
}
|
|
|
|
void AJASource::CacheConnections(const NTV2XptConnections &cnx)
|
|
{
|
|
mCrosspoints.clear();
|
|
mCrosspoints = cnx;
|
|
}
|
|
|
|
void AJASource::ClearConnections()
|
|
{
|
|
for (auto &&xpt : mCrosspoints) {
|
|
mCard->Connect(xpt.first, NTV2_XptBlack);
|
|
}
|
|
mCrosspoints.clear();
|
|
}
|
|
|
|
bool AJASource::ReadChannelVPIDs(NTV2Channel channel, VPIDData &vpids)
|
|
{
|
|
ULWord vpid_a = 0;
|
|
ULWord vpid_b = 0;
|
|
bool read_ok = mCard->ReadSDIInVPID(channel, vpid_a, vpid_b);
|
|
vpids.SetA(vpid_a);
|
|
vpids.SetB(vpid_b);
|
|
vpids.Parse();
|
|
return read_ok;
|
|
}
|
|
|
|
bool AJASource::ReadWireFormats(NTV2DeviceID device_id, IOSelection io_select,
|
|
NTV2VideoFormat &vf, NTV2PixelFormat &pf,
|
|
VPIDDataList &vpids)
|
|
{
|
|
NTV2InputSourceSet input_srcs;
|
|
aja::IOSelectionToInputSources(io_select, input_srcs);
|
|
if (input_srcs.empty()) {
|
|
blog(LOG_INFO,
|
|
"AJASource::ReadWireFormats: No NTV2InputSources found for IOSelection %s",
|
|
aja::IOSelectionToString(io_select).c_str());
|
|
return false;
|
|
}
|
|
|
|
NTV2InputSource initial_src = *input_srcs.begin();
|
|
for (auto &&src : input_srcs) {
|
|
auto channel = NTV2InputSourceToChannel(src);
|
|
mCard->EnableChannel(channel);
|
|
if (NTV2_INPUT_SOURCE_IS_SDI(src)) {
|
|
if (NTV2DeviceHasBiDirectionalSDI(device_id)) {
|
|
mCard->SetSDITransmitEnable(channel, false);
|
|
}
|
|
mCard->WaitForInputVerticalInterrupt(channel);
|
|
VPIDData vpid_data;
|
|
if (ReadChannelVPIDs(channel, vpid_data))
|
|
vpids.push_back(vpid_data);
|
|
} else if (NTV2_INPUT_SOURCE_IS_HDMI(src)) {
|
|
mCard->WaitForInputVerticalInterrupt(channel);
|
|
|
|
ULWord hdmi_version =
|
|
NTV2DeviceGetHDMIVersion(device_id);
|
|
// HDMIv1 handles its own RGB->YCbCr color space conversion
|
|
if (hdmi_version == 1) {
|
|
pf = kDefaultAJAPixelFormat;
|
|
} else {
|
|
NTV2LHIHDMIColorSpace hdmiInputColor;
|
|
mCard->GetHDMIInputColor(hdmiInputColor,
|
|
channel);
|
|
if (hdmiInputColor ==
|
|
NTV2_LHIHDMIColorSpaceYCbCr) {
|
|
pf = kDefaultAJAPixelFormat;
|
|
} else if (hdmiInputColor ==
|
|
NTV2_LHIHDMIColorSpaceRGB) {
|
|
pf = NTV2_FBF_24BIT_BGR;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NTV2Channel initial_channel = NTV2InputSourceToChannel(initial_src);
|
|
mCard->WaitForInputVerticalInterrupt(initial_channel);
|
|
|
|
vf = mCard->GetInputVideoFormat(
|
|
initial_src, aja::Is3GLevelB(mCard, initial_channel));
|
|
|
|
if (NTV2_INPUT_SOURCE_IS_SDI(initial_src)) {
|
|
if (vpids.size() > 0) {
|
|
auto vpid = *vpids.begin();
|
|
if (vpid.Sampling() == VPIDSampling_YUV_422) {
|
|
pf = NTV2_FBF_8BIT_YCBCR;
|
|
blog(LOG_INFO,
|
|
"AJASource::ReadWireFormats - Detected pixel format %s",
|
|
NTV2FrameBufferFormatToString(pf, true)
|
|
.c_str());
|
|
} else if (vpid.Sampling() == VPIDSampling_GBR_444) {
|
|
pf = NTV2_FBF_24BIT_BGR;
|
|
blog(LOG_INFO,
|
|
"AJASource::ReadWireFormats - Detected pixel format %s",
|
|
NTV2FrameBufferFormatToString(pf, true)
|
|
.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
vf = aja::HandleSpecialCaseFormats(io_select, vf, device_id);
|
|
|
|
blog(LOG_INFO, "AJASource::ReadWireFormats - Detected video format %s",
|
|
NTV2VideoFormatToString(vf).c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
void AJASource::ResetVideoBuffer(NTV2VideoFormat vf, NTV2PixelFormat pf)
|
|
{
|
|
if (vf != NTV2_FORMAT_UNKNOWN) {
|
|
auto videoBufferSize = GetVideoWriteSize(vf, pf);
|
|
|
|
if (mVideoBuffer)
|
|
mVideoBuffer.Deallocate();
|
|
|
|
mVideoBuffer.Allocate(videoBufferSize, true);
|
|
|
|
blog(LOG_INFO,
|
|
"AJASource::ResetVideoBuffer: Video Format: %s | Pixel Format: %s | Buffer Size: %d",
|
|
NTV2VideoFormatToString(vf, false).c_str(),
|
|
NTV2FrameBufferFormatToString(pf, true).c_str(),
|
|
videoBufferSize);
|
|
}
|
|
}
|
|
|
|
void AJASource::ResetAudioBuffer(size_t size)
|
|
{
|
|
if (mAudioBuffer)
|
|
mAudioBuffer.Deallocate();
|
|
mAudioBuffer.Allocate(size, true);
|
|
}
|
|
|
|
static const char *aja_source_get_name(void *);
|
|
static void *aja_source_create(obs_data_t *, obs_source_t *);
|
|
static void aja_source_destroy(void *);
|
|
static void aja_source_activate(void *);
|
|
static void aja_source_deactivate(void *);
|
|
static void aja_source_update(void *, obs_data_t *);
|
|
|
|
const char *aja_source_get_name(void *unused)
|
|
{
|
|
UNUSED_PARAMETER(unused);
|
|
return obs_module_text(kUIPropCaptureModule.text);
|
|
}
|
|
|
|
bool aja_source_device_changed(void *data, obs_properties_t *props,
|
|
obs_property_t *list, obs_data_t *settings)
|
|
{
|
|
UNUSED_PARAMETER(list);
|
|
|
|
blog(LOG_DEBUG, "AJA Source Device Changed");
|
|
|
|
auto *ajaSource = (AJASource *)data;
|
|
if (!ajaSource) {
|
|
blog(LOG_DEBUG,
|
|
"aja_source_device_changed: AJA Source instance is null!");
|
|
return false;
|
|
}
|
|
|
|
const char *cardID = obs_data_get_string(settings, kUIPropDevice.id);
|
|
if (!cardID || !cardID[0])
|
|
return false;
|
|
|
|
auto &cardManager = aja::CardManager::Instance();
|
|
auto cardEntry = cardManager.GetCardEntry(cardID);
|
|
if (!cardEntry) {
|
|
blog(LOG_DEBUG,
|
|
"aja_source_device_changed: Card Entry not found for %s",
|
|
cardID);
|
|
return false;
|
|
}
|
|
blog(LOG_DEBUG, "Found CardEntry for %s", cardID);
|
|
CNTV2Card *card = cardEntry->GetCard();
|
|
if (!card) {
|
|
blog(LOG_DEBUG,
|
|
"aja_source_device_changed: Card instance is null!");
|
|
return false;
|
|
}
|
|
|
|
const NTV2DeviceID deviceID = card->GetDeviceID();
|
|
|
|
/* If Channel 1 is actively in use, filter the video format list to only
|
|
* show video formats within the same framerate family. If Channel 1 is
|
|
* not active we just go ahead and try to set all framestores to the same video format.
|
|
* This is because Channel 1's clock rate will govern the card's Free Run clock.
|
|
*/
|
|
NTV2VideoFormat videoFormatChannel1 = NTV2_FORMAT_UNKNOWN;
|
|
if (!cardEntry->ChannelReady(NTV2_CHANNEL1, ajaSource->GetName())) {
|
|
card->GetVideoFormat(videoFormatChannel1, NTV2_CHANNEL1);
|
|
}
|
|
|
|
obs_property_t *devices_list =
|
|
obs_properties_get(props, kUIPropDevice.id);
|
|
obs_property_t *io_select_list =
|
|
obs_properties_get(props, kUIPropInput.id);
|
|
obs_property_t *vid_fmt_list =
|
|
obs_properties_get(props, kUIPropVideoFormatSelect.id);
|
|
obs_property_t *pix_fmt_list =
|
|
obs_properties_get(props, kUIPropPixelFormatSelect.id);
|
|
obs_property_t *sdi_trx_list =
|
|
obs_properties_get(props, kUIPropSDITransport.id);
|
|
obs_property_t *sdi_4k_list =
|
|
obs_properties_get(props, kUIPropSDITransport4K.id);
|
|
|
|
obs_property_list_clear(vid_fmt_list);
|
|
obs_property_list_add_int(vid_fmt_list, obs_module_text("Auto"),
|
|
kAutoDetect);
|
|
populate_video_format_list(deviceID, vid_fmt_list, videoFormatChannel1,
|
|
true);
|
|
|
|
obs_property_list_clear(pix_fmt_list);
|
|
obs_property_list_add_int(pix_fmt_list, obs_module_text("Auto"),
|
|
kAutoDetect);
|
|
populate_pixel_format_list(deviceID, pix_fmt_list);
|
|
|
|
IOSelection io_select = static_cast<IOSelection>(
|
|
obs_data_get_int(settings, kUIPropInput.id));
|
|
obs_property_list_clear(sdi_trx_list);
|
|
populate_sdi_transport_list(sdi_trx_list, deviceID, true);
|
|
|
|
obs_property_list_clear(sdi_4k_list);
|
|
populate_sdi_4k_transport_list(sdi_4k_list);
|
|
|
|
populate_io_selection_input_list(cardID, ajaSource->GetName(), deviceID,
|
|
io_select_list);
|
|
|
|
auto curr_vf = static_cast<NTV2VideoFormat>(
|
|
obs_data_get_int(settings, kUIPropVideoFormatSelect.id));
|
|
|
|
bool have_cards = cardManager.NumCardEntries() > 0;
|
|
obs_property_set_visible(devices_list, have_cards);
|
|
obs_property_set_visible(io_select_list, have_cards);
|
|
obs_property_set_visible(vid_fmt_list, have_cards);
|
|
obs_property_set_visible(pix_fmt_list, have_cards);
|
|
obs_property_set_visible(
|
|
sdi_trx_list, have_cards && aja::IsIOSelectionSDI(io_select));
|
|
obs_property_set_visible(
|
|
sdi_4k_list, have_cards && NTV2_IS_4K_VIDEO_FORMAT(curr_vf));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool aja_io_selection_changed(void *data, obs_properties_t *props,
|
|
obs_property_t *list, obs_data_t *settings)
|
|
{
|
|
UNUSED_PARAMETER(list);
|
|
|
|
AJASource *ajaSource = (AJASource *)data;
|
|
if (!ajaSource) {
|
|
blog(LOG_DEBUG,
|
|
"aja_io_selection_changed: AJA Source instance is null!");
|
|
return false;
|
|
}
|
|
|
|
const char *cardID = obs_data_get_string(settings, kUIPropDevice.id);
|
|
if (!cardID || !cardID[0])
|
|
return false;
|
|
|
|
auto &cardManager = aja::CardManager::Instance();
|
|
auto cardEntry = cardManager.GetCardEntry(cardID);
|
|
if (!cardEntry) {
|
|
blog(LOG_DEBUG,
|
|
"aja_io_selection_changed: Card Entry not found for %s",
|
|
cardID);
|
|
return false;
|
|
}
|
|
|
|
filter_io_selection_input_list(cardID, ajaSource->GetName(),
|
|
obs_properties_get(props,
|
|
kUIPropInput.id));
|
|
obs_property_set_visible(
|
|
obs_properties_get(props, kUIPropSDITransport.id),
|
|
aja::IsIOSelectionSDI(static_cast<IOSelection>(
|
|
obs_data_get_int(settings, kUIPropInput.id))));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool aja_sdi_mode_list_changed(obs_properties_t *props, obs_property_t *list,
|
|
obs_data_t *settings)
|
|
{
|
|
UNUSED_PARAMETER(props);
|
|
UNUSED_PARAMETER(list);
|
|
UNUSED_PARAMETER(settings);
|
|
|
|
return true;
|
|
}
|
|
|
|
void *aja_source_create(obs_data_t *settings, obs_source_t *source)
|
|
{
|
|
blog(LOG_DEBUG, "AJA Source Create");
|
|
|
|
auto ajaSource = new AJASource(source);
|
|
|
|
ajaSource->SetName(obs_source_get_name(source));
|
|
|
|
obs_source_set_async_decoupled(source, true);
|
|
|
|
ajaSource->SetOBSSource(source);
|
|
ajaSource->ResetAudioBuffer(NTV2_AUDIOSIZE_MAX);
|
|
ajaSource->Activate(false);
|
|
|
|
obs_source_update(source, settings);
|
|
|
|
return ajaSource;
|
|
}
|
|
|
|
void aja_source_destroy(void *data)
|
|
{
|
|
blog(LOG_DEBUG, "AJA Source Destroy");
|
|
|
|
auto ajaSource = (AJASource *)data;
|
|
if (!ajaSource) {
|
|
blog(LOG_ERROR, "aja_source_destroy: Plugin instance is null!");
|
|
return;
|
|
}
|
|
|
|
ajaSource->Deactivate();
|
|
|
|
NTV2DeviceID deviceID = DEVICE_ID_NOTFOUND;
|
|
CNTV2Card *card = ajaSource->GetCard();
|
|
if (card) {
|
|
deviceID = card->GetDeviceID();
|
|
aja::Routing::StopSourceAudio(ajaSource->GetSourceProps(),
|
|
card);
|
|
}
|
|
|
|
ajaSource->mVideoBuffer.Deallocate();
|
|
ajaSource->mAudioBuffer.Deallocate();
|
|
ajaSource->mVideoBuffer = 0;
|
|
ajaSource->mAudioBuffer = 0;
|
|
|
|
auto &cardManager = aja::CardManager::Instance();
|
|
const auto &cardID = ajaSource->CardID();
|
|
auto cardEntry = cardManager.GetCardEntry(cardID);
|
|
if (!cardEntry) {
|
|
blog(LOG_DEBUG,
|
|
"aja_source_destroy: Card Entry not found for %s",
|
|
cardID.c_str());
|
|
return;
|
|
}
|
|
|
|
auto ioSelect = ajaSource->GetSourceProps().ioSelect;
|
|
if (!cardEntry->ReleaseInputSelection(ioSelect, deviceID,
|
|
ajaSource->GetName())) {
|
|
blog(LOG_WARNING,
|
|
"aja_source_destroy: Error releasing Input Selection!");
|
|
}
|
|
|
|
delete ajaSource;
|
|
ajaSource = nullptr;
|
|
}
|
|
|
|
static void aja_source_show(void *data)
|
|
{
|
|
auto ajaSource = (AJASource *)data;
|
|
if (!ajaSource) {
|
|
blog(LOG_ERROR,
|
|
"aja_source_show: AJA Source instance is null!");
|
|
return;
|
|
}
|
|
|
|
bool deactivateWhileNotShowing =
|
|
ajaSource->GetSourceProps().deactivateWhileNotShowing;
|
|
bool showing = obs_source_showing(ajaSource->GetOBSSource());
|
|
blog(LOG_DEBUG,
|
|
"aja_source_show: deactivateWhileNotShowing = %s, showing = %s",
|
|
deactivateWhileNotShowing ? "true" : "false",
|
|
showing ? "true" : "false");
|
|
if (deactivateWhileNotShowing && showing && !ajaSource->IsCapturing()) {
|
|
ajaSource->Activate(true);
|
|
blog(LOG_DEBUG, "aja_source_show: activated capture thread!");
|
|
}
|
|
}
|
|
|
|
static void aja_source_hide(void *data)
|
|
{
|
|
auto ajaSource = (AJASource *)data;
|
|
if (!ajaSource)
|
|
return;
|
|
|
|
bool deactivateWhileNotShowing =
|
|
ajaSource->GetSourceProps().deactivateWhileNotShowing;
|
|
bool showing = obs_source_showing(ajaSource->GetOBSSource());
|
|
blog(LOG_DEBUG,
|
|
"aja_source_hide: deactivateWhileNotShowing = %s, showing = %s",
|
|
deactivateWhileNotShowing ? "true" : "false",
|
|
showing ? "true" : "false");
|
|
if (deactivateWhileNotShowing && !showing && ajaSource->IsCapturing()) {
|
|
ajaSource->Deactivate();
|
|
blog(LOG_DEBUG, "aja_source_hide: deactivated capture thread!");
|
|
}
|
|
}
|
|
|
|
static void aja_source_activate(void *data)
|
|
{
|
|
UNUSED_PARAMETER(data);
|
|
}
|
|
|
|
static void aja_source_deactivate(void *data)
|
|
{
|
|
UNUSED_PARAMETER(data);
|
|
}
|
|
|
|
static void aja_source_update(void *data, obs_data_t *settings)
|
|
{
|
|
static bool initialized = false;
|
|
|
|
auto ajaSource = (AJASource *)data;
|
|
if (!ajaSource) {
|
|
blog(LOG_WARNING,
|
|
"aja_source_update: Plugin instance is null!");
|
|
return;
|
|
}
|
|
|
|
auto io_select = static_cast<IOSelection>(
|
|
obs_data_get_int(settings, kUIPropInput.id));
|
|
auto vf_select = static_cast<NTV2VideoFormat>(
|
|
obs_data_get_int(settings, kUIPropVideoFormatSelect.id));
|
|
auto pf_select = static_cast<NTV2PixelFormat>(
|
|
obs_data_get_int(settings, kUIPropPixelFormatSelect.id));
|
|
auto sdi_trx_select = static_cast<SDITransport>(
|
|
obs_data_get_int(settings, kUIPropSDITransport.id));
|
|
auto sdi_t4k_select = static_cast<SDITransport4K>(
|
|
obs_data_get_int(settings, kUIPropSDITransport4K.id));
|
|
bool deactivateWhileNotShowing =
|
|
obs_data_get_bool(settings, kUIPropDeactivateWhenNotShowing.id);
|
|
const std::string &wantCardID =
|
|
obs_data_get_string(settings, kUIPropDevice.id);
|
|
|
|
obs_source_set_async_unbuffered(
|
|
ajaSource->GetOBSSource(),
|
|
!obs_data_get_bool(settings, kUIPropBuffering.id));
|
|
|
|
const std::string ¤tCardID = ajaSource->CardID();
|
|
if (wantCardID != currentCardID) {
|
|
initialized = false;
|
|
ajaSource->Deactivate();
|
|
}
|
|
|
|
auto &cardManager = aja::CardManager::Instance();
|
|
cardManager.EnumerateCards();
|
|
auto cardEntry = cardManager.GetCardEntry(wantCardID);
|
|
if (!cardEntry) {
|
|
blog(LOG_DEBUG,
|
|
"aja_source_update: Card Entry not found for %s",
|
|
wantCardID.c_str());
|
|
return;
|
|
}
|
|
CNTV2Card *card = cardEntry->GetCard();
|
|
if (!card || !card->IsOpen()) {
|
|
blog(LOG_ERROR, "aja_source_update: AJA device %s not open!",
|
|
wantCardID.c_str());
|
|
return;
|
|
}
|
|
if (card->GetModelName() == "(Not Found)") {
|
|
blog(LOG_ERROR,
|
|
"aja_source_update: AJA device %s disconnected?",
|
|
wantCardID.c_str());
|
|
return;
|
|
}
|
|
ajaSource->SetCard(cardEntry->GetCard());
|
|
|
|
SourceProps curr_props = ajaSource->GetSourceProps();
|
|
|
|
// Release Channels from previous card if card ID changes
|
|
if (wantCardID != currentCardID) {
|
|
auto prevCardEntry = cardManager.GetCardEntry(currentCardID);
|
|
if (prevCardEntry) {
|
|
const std::string &ioSelectStr =
|
|
aja::IOSelectionToString(curr_props.ioSelect);
|
|
if (!prevCardEntry->ReleaseInputSelection(
|
|
curr_props.ioSelect, curr_props.deviceID,
|
|
ajaSource->GetName())) {
|
|
blog(LOG_WARNING,
|
|
"aja_source_update: Error releasing IOSelection %s for card ID %s",
|
|
ioSelectStr.c_str(),
|
|
currentCardID.c_str());
|
|
} else {
|
|
blog(LOG_INFO,
|
|
"aja_source_update: Released IOSelection %s for card ID %s",
|
|
ioSelectStr.c_str(),
|
|
currentCardID.c_str());
|
|
ajaSource->SetCardID(wantCardID);
|
|
io_select = IOSelection::Invalid;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (io_select == IOSelection::Invalid) {
|
|
blog(LOG_DEBUG, "aja_source_update: Invalid IOSelection");
|
|
return;
|
|
}
|
|
|
|
SourceProps want_props;
|
|
want_props.deviceID = card->GetDeviceID();
|
|
want_props.ioSelect = io_select;
|
|
want_props.videoFormat =
|
|
((int32_t)vf_select == kAutoDetect)
|
|
? NTV2_FORMAT_UNKNOWN
|
|
: static_cast<NTV2VideoFormat>(vf_select);
|
|
want_props.pixelFormat =
|
|
((int32_t)pf_select == kAutoDetect)
|
|
? NTV2_FBF_INVALID
|
|
: static_cast<NTV2PixelFormat>(pf_select);
|
|
want_props.sdiTransport =
|
|
((int32_t)sdi_trx_select == kAutoDetect)
|
|
? SDITransport::Unknown
|
|
: static_cast<SDITransport>(sdi_trx_select);
|
|
want_props.sdi4kTransport = sdi_t4k_select;
|
|
want_props.vpids.clear();
|
|
want_props.deactivateWhileNotShowing = deactivateWhileNotShowing;
|
|
if (aja::IsIOSelectionSDI(io_select)) {
|
|
want_props.autoDetect = (int)sdi_trx_select == kAutoDetect;
|
|
} else {
|
|
want_props.autoDetect = ((int)vf_select == kAutoDetect ||
|
|
(int)pf_select == kAutoDetect);
|
|
}
|
|
ajaSource->SetCardID(wantCardID);
|
|
ajaSource->SetDeviceIndex((UWord)cardEntry->GetCardIndex());
|
|
|
|
// Release Channels if IOSelection changes
|
|
if (want_props.ioSelect != curr_props.ioSelect) {
|
|
const std::string &ioSelectStr =
|
|
aja::IOSelectionToString(curr_props.ioSelect);
|
|
if (!cardEntry->ReleaseInputSelection(curr_props.ioSelect,
|
|
curr_props.deviceID,
|
|
ajaSource->GetName())) {
|
|
blog(LOG_WARNING,
|
|
"aja_source_update: Error releasing IOSelection %s for card ID %s",
|
|
ioSelectStr.c_str(), currentCardID.c_str());
|
|
} else {
|
|
blog(LOG_INFO,
|
|
"aja_source_update: Released IOSelection %s for card ID %s",
|
|
ioSelectStr.c_str(), currentCardID.c_str());
|
|
}
|
|
}
|
|
|
|
// Acquire Channels for current IOSelection
|
|
if (!cardEntry->AcquireInputSelection(want_props.ioSelect,
|
|
want_props.deviceID,
|
|
ajaSource->GetName())) {
|
|
blog(LOG_ERROR,
|
|
"aja_source_update: Could not acquire IOSelection %s",
|
|
aja::IOSelectionToString(want_props.ioSelect).c_str());
|
|
return;
|
|
}
|
|
|
|
// Read SDI video payload IDs (VPID) used for helping to determine the wire format
|
|
NTV2VideoFormat new_vf = want_props.videoFormat;
|
|
NTV2PixelFormat new_pf = want_props.pixelFormat;
|
|
if (!ajaSource->ReadWireFormats(want_props.deviceID,
|
|
want_props.ioSelect, new_vf, new_pf,
|
|
want_props.vpids)) {
|
|
blog(LOG_ERROR, "aja_source_update: ReadWireFormats failed!");
|
|
cardEntry->ReleaseInputSelection(want_props.ioSelect,
|
|
curr_props.deviceID,
|
|
ajaSource->GetName());
|
|
return;
|
|
}
|
|
|
|
// Set auto-detected formats
|
|
if ((int32_t)vf_select == kAutoDetect)
|
|
want_props.videoFormat = new_vf;
|
|
if ((int32_t)pf_select == kAutoDetect)
|
|
want_props.pixelFormat = new_pf;
|
|
|
|
if (want_props.videoFormat == NTV2_FORMAT_UNKNOWN ||
|
|
want_props.pixelFormat == NTV2_FBF_INVALID) {
|
|
blog(LOG_ERROR,
|
|
"aja_source_update: Unknown video/pixel format(s): %s / %s",
|
|
NTV2VideoFormatToString(want_props.videoFormat).c_str(),
|
|
NTV2FrameBufferFormatToString(want_props.pixelFormat)
|
|
.c_str());
|
|
cardEntry->ReleaseInputSelection(want_props.ioSelect,
|
|
curr_props.deviceID,
|
|
ajaSource->GetName());
|
|
return;
|
|
}
|
|
|
|
// Change capture format and restart capture thread
|
|
if (!initialized || want_props != ajaSource->GetSourceProps()) {
|
|
ajaSource->ClearConnections();
|
|
NTV2XptConnections xpt_cnx;
|
|
aja::Routing::ConfigureSourceRoute(
|
|
want_props, NTV2_MODE_CAPTURE, card, xpt_cnx);
|
|
ajaSource->CacheConnections(xpt_cnx);
|
|
ajaSource->Deactivate();
|
|
initialized = true;
|
|
}
|
|
|
|
ajaSource->SetSourceProps(want_props);
|
|
aja::Routing::StartSourceAudio(want_props, card);
|
|
card->SetReference(NTV2_REFERENCE_FREERUN);
|
|
ajaSource->Activate(true);
|
|
}
|
|
|
|
static obs_properties_t *aja_source_get_properties(void *data)
|
|
{
|
|
obs_properties_t *props = obs_properties_create();
|
|
obs_property_t *device_list = obs_properties_add_list(
|
|
props, kUIPropDevice.id, obs_module_text(kUIPropDevice.text),
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
|
|
populate_source_device_list(device_list);
|
|
obs_property_t *io_select_list = obs_properties_add_list(
|
|
props, kUIPropInput.id, obs_module_text(kUIPropInput.text),
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_property_t *vid_fmt_list = obs_properties_add_list(
|
|
props, kUIPropVideoFormatSelect.id,
|
|
obs_module_text(kUIPropVideoFormatSelect.text),
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_properties_add_list(props, kUIPropPixelFormatSelect.id,
|
|
obs_module_text(kUIPropPixelFormatSelect.text),
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_properties_add_list(props, kUIPropSDITransport.id,
|
|
obs_module_text(kUIPropSDITransport.text),
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_properties_add_list(props, kUIPropSDITransport4K.id,
|
|
obs_module_text(kUIPropSDITransport4K.text),
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_properties_add_bool(
|
|
props, kUIPropDeactivateWhenNotShowing.id,
|
|
obs_module_text(kUIPropDeactivateWhenNotShowing.text));
|
|
obs_properties_add_bool(props, kUIPropBuffering.id,
|
|
obs_module_text(kUIPropBuffering.text));
|
|
|
|
obs_property_set_modified_callback(vid_fmt_list,
|
|
aja_video_format_changed);
|
|
obs_property_set_modified_callback2(device_list,
|
|
aja_source_device_changed, data);
|
|
obs_property_set_modified_callback2(io_select_list,
|
|
aja_io_selection_changed, data);
|
|
|
|
return props;
|
|
}
|
|
|
|
void aja_source_get_defaults(obs_data_t *settings)
|
|
{
|
|
obs_data_set_default_int(settings, kUIPropInput.id,
|
|
static_cast<long long>(IOSelection::Invalid));
|
|
obs_data_set_default_int(settings, kUIPropVideoFormatSelect.id,
|
|
static_cast<long long>(kAutoDetect));
|
|
obs_data_set_default_int(settings, kUIPropPixelFormatSelect.id,
|
|
static_cast<long long>(kAutoDetect));
|
|
obs_data_set_default_int(settings, kUIPropSDITransport.id,
|
|
static_cast<long long>(kAutoDetect));
|
|
obs_data_set_default_int(
|
|
settings, kUIPropSDITransport4K.id,
|
|
static_cast<long long>(SDITransport4K::TwoSampleInterleave));
|
|
obs_data_set_default_bool(settings, kUIPropDeactivateWhenNotShowing.id,
|
|
false);
|
|
}
|
|
|
|
static void aja_source_get_defaults_v1(obs_data_t *settings)
|
|
{
|
|
aja_source_get_defaults(settings);
|
|
obs_data_set_default_bool(settings, kUIPropBuffering.id, true);
|
|
}
|
|
|
|
void aja_source_save(void *data, obs_data_t *settings)
|
|
{
|
|
AJASource *ajaSource = (AJASource *)data;
|
|
if (!ajaSource) {
|
|
blog(LOG_ERROR,
|
|
"aja_source_save: AJA Source instance is null!");
|
|
return;
|
|
}
|
|
|
|
const char *cardID = obs_data_get_string(settings, kUIPropDevice.id);
|
|
if (!cardID || !cardID[0])
|
|
return;
|
|
|
|
auto &cardManager = aja::CardManager::Instance();
|
|
auto cardEntry = cardManager.GetCardEntry(cardID);
|
|
if (!cardEntry) {
|
|
blog(LOG_DEBUG, "aja_source_save: Card Entry not found for %s",
|
|
cardID);
|
|
return;
|
|
}
|
|
|
|
auto oldName = ajaSource->GetName();
|
|
auto newName = obs_source_get_name(ajaSource->GetOBSSource());
|
|
if (oldName != newName &&
|
|
cardEntry->UpdateChannelOwnerName(oldName, newName)) {
|
|
ajaSource->SetName(newName);
|
|
blog(LOG_DEBUG, "aja_source_save: Renamed \"%s\" to \"%s\"",
|
|
oldName.c_str(), newName);
|
|
}
|
|
}
|
|
|
|
void register_aja_source_info()
|
|
{
|
|
struct obs_source_info aja_source_info = {};
|
|
aja_source_info.id = kUIPropCaptureModule.id;
|
|
aja_source_info.type = OBS_SOURCE_TYPE_INPUT;
|
|
aja_source_info.output_flags =
|
|
OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO |
|
|
OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_CAP_OBSOLETE;
|
|
aja_source_info.get_name = aja_source_get_name;
|
|
aja_source_info.create = aja_source_create;
|
|
aja_source_info.destroy = aja_source_destroy;
|
|
aja_source_info.update = aja_source_update;
|
|
aja_source_info.show = aja_source_show;
|
|
aja_source_info.hide = aja_source_hide;
|
|
aja_source_info.activate = aja_source_activate;
|
|
aja_source_info.deactivate = aja_source_deactivate;
|
|
aja_source_info.get_properties = aja_source_get_properties;
|
|
aja_source_info.get_defaults = aja_source_get_defaults_v1;
|
|
aja_source_info.save = aja_source_save;
|
|
aja_source_info.icon_type = OBS_ICON_TYPE_CAMERA;
|
|
obs_register_source(&aja_source_info);
|
|
|
|
aja_source_info.version = 2;
|
|
aja_source_info.output_flags &= ~OBS_SOURCE_CAP_OBSOLETE;
|
|
aja_source_info.get_defaults = aja_source_get_defaults;
|
|
obs_register_source(&aja_source_info);
|
|
}
|