Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1456 lines
53 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Device.h"
#include <algorithm>
#include <chrono>
namespace AJA
{
namespace Private
{
static ULWord UEAppType = AJA_FOURCC('U', 'E', '5', '.');
static DeviceCache Cache;
/* DeviceCache implementation
*****************************************************************************/
std::shared_ptr<DeviceConnection> DeviceCache::GetDevice(const AJADeviceOptions& InDeviceOption)
{
if (InDeviceOption.DeviceIndex >= MaxNumberOfDevice || InDeviceOption.DeviceIndex < 0)
{
UE_LOG(LogAjaCore, Error, TEXT("DeviceCache: The device index '%d' is invalid.\n"), InDeviceOption.DeviceIndex);
return std::shared_ptr<DeviceConnection>();
}
{
AJAAutoLock AutoLock(&Cache.Lock);
auto Result = Cache.DeviceInstance[InDeviceOption.DeviceIndex].lock();
if (!Result)
{
Result.reset(new DeviceConnection(InDeviceOption));
Cache.DeviceInstance[InDeviceOption.DeviceIndex] = Result;
}
return Result;
}
}
void DeviceCache::RemoveDevice(const std::shared_ptr<DeviceConnection>& InDevice)
{
if (InDevice)
{
Cache.DeviceInstance[InDevice->DeviceOption.DeviceIndex].reset();
}
}
/* SyncThreadInfoThread implementation
*****************************************************************************/
DeviceConnection::SyncThreadInfoThread::SyncThreadInfoThread(DeviceConnection* InDeviceConnection, ChannelInfo* InChannelInfo)
: ChannelInfoPtr(InChannelInfo)
, DeviceConnectionPtr(InDeviceConnection)
{
}
bool DeviceConnection::SyncThreadInfoThread::WaitForVerticalInterrupt(DeviceConnection* InDeviceConnection, ChannelInfo* InChannelInfo, uint32& OutSyncCount)
{
assert(InDeviceConnection);
assert(InChannelInfo);
bool bResult = true;
if (InChannelInfo->RefCounter > 0)
{
ULWord BeforeCount, AfterCount = 0;
bool bFirstCountValid, bSecondCountValid = false;
if (InChannelInfo->bIsInput)
{
do
{
bFirstCountValid = InDeviceConnection->GetCard()->GetInputVerticalInterruptCount(BeforeCount, InChannelInfo->Channel);
bResult = InDeviceConnection->GetCard()->WaitForInputVerticalInterrupt(InChannelInfo->Channel);
bSecondCountValid = InDeviceConnection->GetCard()->GetInputVerticalInterruptCount(AfterCount, InChannelInfo->Channel);
} while (bFirstCountValid && bSecondCountValid && BeforeCount == AfterCount && bResult);
}
else
{
do
{
bFirstCountValid = InDeviceConnection->GetCard()->GetOutputVerticalInterruptCount(BeforeCount, InChannelInfo->Channel);
bResult = InDeviceConnection->GetCard()->WaitForOutputVerticalInterrupt(InChannelInfo->Channel);
bSecondCountValid = InDeviceConnection->GetCard()->GetOutputVerticalInterruptCount(AfterCount, InChannelInfo->Channel);
} while (bFirstCountValid && bSecondCountValid && BeforeCount == AfterCount && bResult);
}
OutSyncCount = AfterCount;
}
return bResult;
}
bool DeviceConnection::SyncThreadInfoThread::IsCurrentField(DeviceConnection* InDeviceConnection, ChannelInfo* InChannelInfo, NTV2FieldID InFieldId)
{
assert(InDeviceConnection);
assert(InChannelInfo);
bool bResult = false;
if (InChannelInfo->RefCounter > 0)
{
if (InChannelInfo->bIsInput)
{
bResult = Helpers::IsCurrentInputField(InDeviceConnection->GetCard(), InChannelInfo->Channel, InFieldId);
}
else
{
bResult = Helpers::IsCurrentOutputField(InDeviceConnection->GetCard(), InChannelInfo->Channel, InFieldId);
}
}
return bResult;
}
bool DeviceConnection::SyncThreadInfoThread::Wait_ExternalThread()
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> LockGuard(SyncMutex);
bool bResult = SyncCondition.wait_for(LockGuard, 50ms) == std::cv_status::no_timeout && bWaitResult;
return bResult;
}
bool DeviceConnection::SyncThreadInfoThread::ThreadLoop()
{
// do the wait
uint32 NewSyncCount = 0;
bWaitResult = WaitForVerticalInterrupt(DeviceConnectionPtr, ChannelInfoPtr, NewSyncCount);
SyncCondition.notify_all();
OnVerticalInterruptDelegate.Broadcast(NewSyncCount);
return true;
}
/* WaitForInputInfo implementation
*****************************************************************************/
bool DeviceConnection::WaitForInputInfo::WaitForInput()
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> LockGuard(SyncMutex);
return SyncCondition.wait_for(LockGuard, 50ms) == std::cv_status::no_timeout;
}
void DeviceConnection::WaitForInputInfo::NotifyAll()
{
SyncCondition.notify_all();
}
/* ChannelInfo implementation
*****************************************************************************/
DeviceConnection::ChannelInfo::ChannelInfo()
: TransportType(ETransportType::TT_SdiSingle)
, Channel(NTV2_CHANNEL_INVALID)
, VideoFormat(NTV2_FORMAT_UNKNOWN)
, TimecodeFormat(ETimecodeFormat::TCF_None)
, RefCounter(0)
, BaseFrameBufferIndex(InvalidFrameBufferIndex)
, bConnected(false)
, bIsOwned(false)
, bIsInput(false)
, bIsAutoDetected(false)
, bGenlockChannel(false)
{
}
/* CommandList implementation
*****************************************************************************/
DeviceConnection::CommandList::CommandList(DeviceConnection& InConnection)
: Connection(InConnection)
{}
bool DeviceConnection::CommandList::RegisterChannel(ETransportType InTransportType, NTV2InputSource InInputSource, NTV2Channel InChannel, bool bInAsInput, bool bInAsGenlock, bool bConnectChannel, ETimecodeFormat InTimecodeFormat, EPixelFormat InUEPixelFormat, NTV2VideoFormat InDesiredInputFormat, bool bInAsOwner, bool bInIsAutoDetected)
{
FAjaHDROptions UnusedOptions;
return RegisterChannel(InTransportType, InInputSource, InChannel, bInAsInput, bInAsGenlock, bConnectChannel, InTimecodeFormat, InUEPixelFormat, InDesiredInputFormat, UnusedOptions, bInAsOwner, bInIsAutoDetected);
}
bool DeviceConnection::CommandList::RegisterChannel(ETransportType InTransportType, NTV2InputSource InInputSource, NTV2Channel InChannel, bool bInIsInput, bool bInAsGenlock, bool bConnectChannel, ETimecodeFormat InTimecodeFormat, EPixelFormat InUEPixelFormat, NTV2VideoFormat InDesiredInputFormat, FAjaHDROptions& InOutHDROptions, bool bInAsOwner, bool bInIsAutoDetected)
{
AJAAutoLock Lock(&Connection.ChannelLock);
std::vector<ChannelInfo*>& ConnectionChannelInfos = Connection.ChannelInfos;
if (!Connection.Lock_IsInitialize() && !Connection.Lock_Initialize())
{
return false;
}
// Find if we have already register that channel
bool bResult = true;
NTV2VideoFormat VideoFormat = NTV2_FORMAT_UNKNOWN;
auto FoundIterator = std::find_if(std::begin(ConnectionChannelInfos), std::end(ConnectionChannelInfos), [=](const ChannelInfo* Other) { return Other->Channel == InChannel; });
bool bReinitialize = FoundIterator != std::end(ConnectionChannelInfos) && (*FoundIterator)->bIsAutoDetected;
if (FoundIterator == std::end(ConnectionChannelInfos))
{
bResult = Connection.Lock_EnableChannel_SDILinkHelper(Connection.Card, InTransportType, InChannel);
if (bResult)
{
bResult = Connection.Lock_EnableChannel_Helper(Connection.Card, InTransportType, InInputSource, InChannel, bInIsInput, bConnectChannel, bInIsAutoDetected, InTimecodeFormat, InUEPixelFormat, InDesiredInputFormat, VideoFormat, InOutHDROptions);
}
uint32_t NewBaseFrameBufferIndex = InvalidFrameBufferIndex;
if (bResult)
{
NewBaseFrameBufferIndex = Connection.Lock_AcquireBaseFrameIndex(InChannel, VideoFormat);
bResult = NewBaseFrameBufferIndex != InvalidFrameBufferIndex;
}
if (bResult)
{
Connection.Lock_SubscribeChannel_Helper(Connection.Card, InChannel, bInIsInput);
if (bInIsInput)
{
AJA_CHECK(Connection.Card->SetInputFrame(InChannel, NewBaseFrameBufferIndex));
}
else
{
AJA_CHECK(Connection.Card->SetOutputFrame(InChannel, NewBaseFrameBufferIndex));
}
ChannelInfo* Info = new ChannelInfo();
Info->TransportType = InTransportType;
Info->Channel = InChannel;
Info->VideoFormat = VideoFormat;
Info->TimecodeFormat = InTimecodeFormat;
Info->RefCounter = 1;
Info->BaseFrameBufferIndex = NewBaseFrameBufferIndex;
Info->bIsOwned = bInAsOwner;
Info->bIsInput = bInIsInput;
Info->bConnected = bConnectChannel;
Info->bIsAutoDetected = bInIsAutoDetected;
Info->bGenlockChannel = bInAsGenlock;
// Spawn a sync thread if there is more than 1 connection that can potentially wait on the same event
Info->SyncThread.reset(new SyncThreadInfoThread(&Connection, Info));
Info->SyncThread->SetPriority(AJA_ThreadPriority_High);
Info->SyncThread->Start();
{
AJAAutoLock AutoLock(&Connection.ChannelInfoLock);
ConnectionChannelInfos.push_back(Info);
}
}
}
else
{
ChannelInfo* FoundChannel = *FoundIterator;
// If channel was used for Genlock, reinitialize channel since routing wasn't done.
if (bReinitialize || (FoundChannel->bGenlockChannel && !bInAsGenlock))
{
if (!FoundChannel->bIsAutoDetected && !(FoundChannel->bGenlockChannel && !bInAsGenlock))
{
UE_LOG(LogAjaCore, Warning, TEXT("Device: Autodetect reinitialized channel '%d' on device '%S' but the channel was not in autodetect mode.\n")
, uint32_t(InChannel) + 1, Connection.Card->GetDisplayName().c_str());
}
bResult = Connection.Lock_EnableChannel_SDILinkHelper(Connection.Card, InTransportType, InChannel);
if (bResult)
{
bResult = Connection.Lock_EnableChannel_Helper(Connection.Card, InTransportType, InInputSource, InChannel, bInIsInput, bConnectChannel, bInIsAutoDetected, InTimecodeFormat, InUEPixelFormat, InDesiredInputFormat, VideoFormat, InOutHDROptions);
}
}
if (bResult && FoundChannel->bIsOwned && bInAsOwner)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to enable the channel '%d' on device '%S' as the owner but it was already enabled. Do you already have an '%S' running on the same channel?\n")
, uint32_t(InChannel) + 1, Connection.Card->GetDisplayName().c_str()
, FoundChannel->bIsInput ? "input" : "output");
bResult = false;
}
if (bResult && FoundChannel->TransportType != InTransportType)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to enable the channel '%d' on device '%S' as a '%S' but it was already enabled as a '%S'.\n")
, uint32_t(InChannel) + 1
, Connection.Card->GetDisplayName().c_str()
, Helpers::TransportTypeToString(InTransportType)
, Helpers::TransportTypeToString(FoundChannel->TransportType));
bResult = false;
}
if (bResult && FoundChannel->bIsInput != bInIsInput)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to enable the channel '%d' on device '%S' as an '%S' but it was already enabled as an '%S'.\n")
, uint32_t(InChannel) + 1
, Connection.Card->GetDisplayName().c_str()
, bInIsInput ? "input" : "output"
, FoundChannel->bIsInput ? "input" : "output");
bResult = false;
}
if (bResult)
{
std::string FailureReason;
if (bInAsGenlock || FoundChannel->bGenlockChannel)
{
VideoFormat = InDesiredInputFormat;
}
else if (!Helpers::GetInputVideoFormat(Connection.Card, InTransportType, InChannel, InInputSource, InDesiredInputFormat, VideoFormat, false, FailureReason))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Initialization of the input failed for channel %d on device %S. %S"), uint32_t(InChannel) + 1, Connection.Card->GetDisplayName().c_str(), FailureReason.c_str());
bResult = false;
}
if (bResult && !bInIsAutoDetected && VideoFormat != FoundChannel->VideoFormat)
{
const bool bForRetailDisplay = true;
UE_LOG(LogAjaCore, Error, TEXT("Device: Try to enable with the video format '%S' the channel '%d' on device '%S' that was already enabled with '%S'.\n")
, NTV2FrameRateToString(GetNTV2FrameRateFromVideoFormat(VideoFormat), bForRetailDisplay).c_str()
, uint32_t(InChannel) + 1, Connection.Card->GetDisplayName().c_str()
, NTV2FrameRateToString(GetNTV2FrameRateFromVideoFormat(FoundChannel->VideoFormat), bForRetailDisplay).c_str());
bResult = false;
}
}
if (bResult && FoundChannel->TimecodeFormat != InTimecodeFormat && !bInIsAutoDetected && bInAsGenlock)
{
UE_LOG(LogAjaCore, Warning, TEXT("Device: Try to enable the channel '%d' on device '%S' with a timecode format that is not the same as the previous one. Timecode may not be decoded properly.\n")
, uint32_t(InChannel) + 1, Connection.Card->GetDisplayName().c_str());
}
if (bResult)
{
++FoundChannel->RefCounter;
if (bInAsOwner)
{
FoundChannel->bIsOwned = bInAsOwner;
}
}
}
//Verify if Channel1 is used. If not, we need to be sure the card's main format is configured
if (bResult && !::NTV2DeviceCanDoMultiFormat(Connection.Card->GetDeviceID()))
{
auto Found = std::find_if(std::begin(Connection.ChannelInfos), std::end(Connection.ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == NTV2_CHANNEL1; });
if (Found == std::end(Connection.ChannelInfos))
{
//Nothing set on channel 1, set it up so the card is correctly initialized
AJA_CHECK(Connection.Card->SetVideoFormat(VideoFormat, AJA_RETAIL_DEFAULT, false, NTV2_CHANNEL1));
}
}
if (!bResult && !Connection.Lock_IsInitialize())
{
Connection.Lock_UnInitialize();
}
return bResult;
}
bool DeviceConnection::CommandList::UnregisterChannel(NTV2Channel InChannel, bool bInAsOwner)
{
AJAAutoLock Lock(&Connection.ChannelLock);
assert(Connection.Lock_IsInitialize());
std::vector<ChannelInfo*>& ConnectionChannelInfos = Connection.ChannelInfos;
// Find if we have already register that channel
auto FoundIterator = std::find_if(std::begin(ConnectionChannelInfos), std::end(ConnectionChannelInfos), [=](const ChannelInfo* Other) { return Other->Channel == InChannel; });
if (FoundIterator == std::end(ConnectionChannelInfos))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Try to unregister the channel '%d' on device '%S' when it's not registered.\n"), uint32_t(InChannel) + 1, Connection.Card->GetDisplayName().c_str());
assert(false);
return false;
}
ChannelInfo* FoundChannel = *FoundIterator;
--FoundChannel->RefCounter;
if (FoundChannel->RefCounter == 0)
{
// Stop the sync thread
if (FoundChannel->SyncThread)
{
FoundChannel->SyncThread->Stop(50);
FoundChannel->SyncThread.release();
}
if (Helpers::IsTsiRouting(FoundChannel->TransportType))
{
AJA_CHECK(Connection.Card->SetTsiFrameEnable(false, InChannel));
}
else if (FoundChannel->TransportType == ETransportType::TT_SdiDual)
{
AJA_CHECK(Connection.Card->SetSmpte372(false, InChannel));
}
else if (FoundChannel->TransportType == ETransportType::TT_SdiQuadSQ)
{
AJA_CHECK(Connection.Card->Set4kSquaresEnable(false, InChannel));
}
// Do not disable the interrupt. The ControlRoom need the interrupt enabled.
if (FoundChannel->bIsInput)
{
AJA_CHECK(Connection.Card->UnsubscribeInputVerticalEvent(InChannel));
}
else
{
AJA_CHECK(Connection.Card->UnsubscribeOutputVerticalEvent(InChannel));
}
const int32_t NumberOfLinkChannel = Helpers::GetNumberOfLinkChannel(FoundChannel->TransportType);
for (int32_t ChannelIndex = 0; ChannelIndex < NumberOfLinkChannel; ++ChannelIndex)
{
AJA_CHECK(Connection.Card->DisableChannel(NTV2Channel(int32_t(InChannel) + ChannelIndex)));
}
{
AJAAutoLock AutoLock(&Connection.ChannelInfoLock);
ConnectionChannelInfos.erase(FoundIterator);
}
delete FoundChannel;
if (!Connection.Lock_IsInitialize())
{
Connection.Lock_UnInitialize();
}
}
else
{
if (Connection.bOutputReferenceSet)
{
// Connection was also used for genlock but was not initialized as a genlock connection, so mark it as so.
FoundChannel->bGenlockChannel = true;
}
if (bInAsOwner)
{
FoundChannel->bIsOwned = false;
}
}
return true;
}
bool DeviceConnection::CommandList::IsChannelConnect(NTV2Channel InChannel)
{
AJAAutoLock Lock(&Connection.ChannelInfoLock);
std::vector<ChannelInfo*>& ConnectionChannelInfos = Connection.ChannelInfos;
auto Found = std::find_if(std::begin(ConnectionChannelInfos), std::end(ConnectionChannelInfos), [=](const ChannelInfo* Info) {return Info->Channel == InChannel; });
if (Found == std::end(ConnectionChannelInfos))
{
return false;
}
return (*Found)->bConnected;
}
void DeviceConnection::CommandList::SetChannelConnected(NTV2Channel InChannel, bool bConnected)
{
AJAAutoLock Lock(&Connection.ChannelLock);
std::vector<ChannelInfo*>& ConnectionChannelInfos = Connection.ChannelInfos;
auto Found = std::find_if(std::begin(ConnectionChannelInfos), std::end(ConnectionChannelInfos), [=](const ChannelInfo* Info) {return Info->Channel == InChannel; });
assert(Found != std::end(ConnectionChannelInfos));
if (Found != std::end(ConnectionChannelInfos))
{
(*Found)->bConnected = bConnected;
}
}
bool DeviceConnection::CommandList::RegisterAnalogLtc(EAnalogLTCSource InSource, NTV2FrameRate InFrameRate, bool bUseReferencePin)
{
AJAAutoLock Lock(&Connection.ChannelLock);
if (!Connection.Lock_IsInitialize() && !Connection.Lock_Initialize())
{
return false;
}
if (!NTV2_IS_VALID_NTV2FrameRate(InFrameRate))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to read LTC from the reference input '%d' on device '%S' but it is not supported by the device.\n"), uint32_t(InSource) + 1, Connection.Card->GetDisplayName().c_str());
return false;
}
//Make sure LTC index fits with the device
bool bResult = true;
if (bResult && ::NTV2DeviceGetNumLTCInputs(Connection.Card->GetDeviceID()) <= (uint32_t)InSource)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to read LTC from the LTC input '%d' on device '%S' but it is not supported by the device.\n"), uint32_t(InSource) + 1, Connection.Card->GetDisplayName().c_str());
bResult = false;
}
//If there are other LTC readers, exit, only one LTC input is supported
if (Connection.LTCFrameRate != NTV2_FRAMERATE_INVALID)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: The device '%S' is already reading timecode from LTC input with frame rate '%S' and only one is supported."), Connection.Card->GetDisplayName().c_str(), NTV2FrameRateToString(Connection.LTCFrameRate).c_str());
bResult = false;
}
//When using ref pin, make sure it's not already used
if (bResult && bUseReferencePin)
{
if (!::NTV2DeviceCanDoLTCInOnRefPort(Connection.Card->GetDeviceID()))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to read LTC from reference pin on device '%S' but it doesn't have that capability.\n"), Connection.Card->GetDisplayName().c_str());
bResult = false;
}
//Verify if we are already reading LTC from ref pin
if (bResult && Connection.bAnalogLtcFromReferenceInput)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: The reference pin is already used to read LTC timecode on device '%S'.\n"), Connection.Card->GetDisplayName().c_str());
bResult = false;
}
//Verify if we are already using ref pin for genlock
if (bResult && Connection.bOutputReferenceSet && Connection.OutputReferenceType == EAJAReferenceType::EAJA_REFERENCETYPE_EXTERNAL)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to read LTC from the reference input on device '%S' but the reference is already used as a genlock source.\n"), Connection.Card->GetDisplayName().c_str());
bResult = false;
}
}
//Verify if card is used. If not, we need to be sure the card's framerate is configured, especially if channel involved is not SDI1
if (bResult)
{
auto Found = std::find_if(std::begin(Connection.ChannelInfos), std::end(Connection.ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == NTV2_CHANNEL1; });
if (Found != std::end(Connection.ChannelInfos))
{
//LTC reading relies on card/Channel 1 frame rate to be compatible
const ChannelInfo* FoundChannel = *Found;
const NTV2FrameRate ChannelFrameRate = ::GetNTV2FrameRateFromVideoFormat(FoundChannel->VideoFormat);
if (!::IsMultiFormatCompatible(InFrameRate, ChannelFrameRate))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to read LTC with FrameRate '%S' but it's not compatible with card's (channel 1) current FrameRate of '%S' on device '%S'.\n"), NTV2FrameRateToString(InFrameRate).c_str(), NTV2FrameRateToString(ChannelFrameRate).c_str(), Connection.Card->GetDisplayName().c_str());
bResult = false;
}
}
else
{
//Nothing set on channel 1, we need to set it to get LTC in
Connection.Card->SetFrameRate(InFrameRate);
}
}
//Everything went fine, finalize initialization
if (bResult)
{
Connection.LTCFrameRate = InFrameRate;
//If ref pin needs to be use, make sure the card is configured for it.
if (bUseReferencePin)
{
AJA_CHECK(Connection.Card->SetLTCInputEnable(true));
Connection.bAnalogLtcFromReferenceInput = true;
}
}
if(!bResult && !Connection.Lock_IsInitialize())
{
Connection.Lock_UnInitialize();
}
return bResult;
}
void DeviceConnection::CommandList::UnregisterAnalogLtc(bool bUseReferencePin)
{
AJAAutoLock Lock(&Connection.ChannelLock);
assert(Connection.LTCFrameRate != NTV2_FRAMERATE_INVALID);
assert(Connection.Lock_IsInitialize());
//If we are unregistering LTC from ref pin, disable it
if (bUseReferencePin)
{
AJA_CHECK(Connection.Card->SetLTCInputEnable(false));
Connection.bAnalogLtcFromReferenceInput = false;
}
Connection.LTCFrameRate = NTV2_FRAMERATE_INVALID;
if (!Connection.Lock_IsInitialize())
{
Connection.Lock_UnInitialize();
}
}
bool DeviceConnection::CommandList::RegisterReference(EAJAReferenceType InOutputReferenceType, NTV2Channel InOutputReferenceChannel)
{
AJAAutoLock Lock(&Connection.ChannelLock);
if (Connection.bOutputReferenceSet)
{
if (Connection.OutputReferenceType != InOutputReferenceType)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Couldn't set the reference (%S) for output on device %S. The type was already setup with %S.\n"), Helpers::ReferenceTypeToString(InOutputReferenceType), Connection.Card->GetDisplayName().c_str(), Helpers::ReferenceTypeToString(Connection.OutputReferenceType));
return false;
}
if (Connection.OutputReferenceType == EAJAReferenceType::EAJA_REFERENCETYPE_INPUT && Connection.OutputReferenceChannel != InOutputReferenceChannel)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Couldn't set the reference (%S) for output on device %S. The input was already setup with %d.\n"), Helpers::ReferenceTypeToString(InOutputReferenceType), Connection.Card->GetDisplayName().c_str(), Connection.OutputReferenceChannel, Helpers::ReferenceTypeToString(Connection.OutputReferenceType));
return false;
}
return true;
}
switch (InOutputReferenceType)
{
case EAJAReferenceType::EAJA_REFERENCETYPE_EXTERNAL:
{
if (Connection.bAnalogLtcFromReferenceInput)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Couldn't set the reference (%S) for output channel %d on device %S. The reference is used to read analog LTC.\n"), Helpers::ReferenceTypeToString(InOutputReferenceType), InOutputReferenceChannel, Connection.Card->GetDisplayName().c_str());
return false;
}
AJA_CHECK(Connection.Card->SetLTCInputEnable(false));
AJA_CHECK(Connection.Card->SetReference(NTV2_REFERENCE_EXTERNAL));
break;
}
case EAJAReferenceType::EAJA_REFERENCETYPE_INPUT:
{
NTV2ReferenceSource Input = NTV2InputSourceToReferenceSource(NTV2ChannelToInputSource(InOutputReferenceChannel));
if (Input == NTV2_NUM_REFERENCE_INPUTS)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Couldn't set the reference source on device '%S. The Channel index is invalid."), Connection.Card->GetDisplayName().c_str());
return false;
}
AJA_CHECK(Connection.Card->SetReference(Input));
break;
}
case EAJAReferenceType::EAJA_REFERENCETYPE_FREERUN:
default:
AJA_CHECK(Connection.Card->SetReference(NTV2_REFERENCE_FREERUN));
}
Connection.bOutputReferenceSet = true;
Connection.OutputReferenceType = InOutputReferenceType;
Connection.OutputReferenceChannel = InOutputReferenceChannel;
::Sleep(50 * 1); // Wait 1 frame
return true;
}
void DeviceConnection::CommandList::UnregisterReference(NTV2Channel InChannel)
{
AJAAutoLock Lock(&Connection.ChannelLock);
auto Found = std::find_if(std::begin(Connection.ChannelInfos), std::end(Connection.ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == InChannel; });
if (Found == std::end(Connection.ChannelInfos))
{
Connection.bOutputReferenceSet = false;
return;
}
const ChannelInfo* FoundChannel = *Found;
const bool bOutputChannelUsingRefPin = FoundChannel->RefCounter > 1 && Connection.OutputReferenceType == AJA::EAJAReferenceType::EAJA_REFERENCETYPE_EXTERNAL;
if (!bOutputChannelUsingRefPin)
{
Connection.bOutputReferenceSet = false;
}
}
/* Device implementation
*****************************************************************************/
CNTV2Card* DeviceConnection::NewCNTV2Card(uint32_t InDeviceIndex)
{
CNTV2Card* Result = new CNTV2Card(InDeviceIndex);
uint32_t Counter = 0;
while (!Result->IsDeviceReady())
{
const DWORD SleepMilli = 10;
const DWORD TimeoutMilli = 2000;
if (Counter * SleepMilli > TimeoutMilli)
{
UE_LOG(LogAjaCore, Warning, TEXT("Device: Can't get the device initialized.\n"));
delete Result;
return nullptr;
}
++Counter;
::Sleep(SleepMilli);
}
return Result;
}
DeviceConnection::DeviceConnection(const AJADeviceOptions& InDeviceOption)
: DeviceOption(InDeviceOption)
, Card(nullptr)
, TaskMode(NTV2_TASK_MODE_INVALID)
, bHasBiDirectionalSDI(false)
, bAnalogLtcFromReferenceInput(false)
, LTCFrameRate(NTV2FrameRate::NTV2_FRAMERATE_INVALID)
, bOutputReferenceSet(false)
, OutputReferenceType(EAJAReferenceType::EAJA_REFERENCETYPE_FREERUN)
, OutputReferenceChannel(NTV2_CHANNEL_INVALID)
, NotMultiVideoFormat(NTV2_FORMAT_UNKNOWN)
, bStopRequested(false)
{
CommandThread.Attach(&DeviceConnection::StaticThread, this);
CommandThread.SetPriority(AJA_ThreadPriority_Normal);
CommandThread.Start();
}
DeviceConnection::~DeviceConnection()
{
assert(Card == nullptr);
assert(ChannelInfos.size() == 0);
assert(Commands.Size() == 0);
CommandThread.Kill(-1);
}
FDelegateHandle DeviceConnection::SetOnInterruptEvent(NTV2Channel InChannel, AJA::Private::DeviceConnection::FOnVerticalInterrupt::FDelegate OnInterrupt)
{
FDelegateHandle DelegateHandle;
AJAAutoLock Lock(&ChannelInfoLock);
auto FoundIt = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == InChannel; });
if (FoundIt != std::end(ChannelInfos))
{
ChannelInfo* FoundChannel = *FoundIt;
if (FoundChannel->SyncThread)
{
DelegateHandle = FoundChannel->SyncThread->OnVerticalInterrupt().Add(MoveTemp(OnInterrupt));
}
else
{
UE_LOG(LogAjaCore, Error, TEXT("No sync thread available when calling SetOnInterruptEvent."));
}
}
return DelegateHandle;
}
void DeviceConnection::ClearOnInterruptEvent(NTV2Channel InChannel, FDelegateHandle DelegateHandle)
{
AJAAutoLock Lock(&ChannelInfoLock);
auto FoundIt = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == InChannel; });
if (FoundIt == std::end(ChannelInfos))
{
return;
}
ChannelInfo* FoundChannel = *FoundIt;
if (FoundChannel->SyncThread)
{
FoundChannel->SyncThread->OnVerticalInterrupt().Remove(DelegateHandle);
}
}
bool DeviceConnection::WaitForInputOrOutputInterrupt(NTV2Channel InChannel, int32_t InRepeatCount, EInterruptType InInterrupt)
{
// NTV2Card::WaitForInputVerticalInterrupt can wait for only one thread at the same time
//(if 2 threads wants to wait for the same interrupt on the same channel, only one will be able to wait)
//This is a wrapper around the wait to enable x threads to wait on the same channel.
//When more than one thread wants to wait, a special thread is spawn. The special thread will wait and the other thread will wait for the event from the special thread.
//Once the special thread is spawned, it will stay alive even if only one of the multiple thread is still alive.
bool bResult = false;
ChannelInfo* FoundChannel = nullptr;
{
AJAAutoLock Lock(&ChannelInfoLock);
auto Found = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == InChannel; });
if (Found == std::end(ChannelInfos))
{
return false;
}
FoundChannel = *Found;
}
// NTV2Card::WaitForInputVerticalInterrupt will be trigger twice for intercaled, once for progressive and twice for psf.
// NTV2Card::WaitForInputFieldID will be trigger once for intercaled, once for progressive and once for psf
//Always wait for WaitForInputVerticalInterrupt and request a second time if InterruptType is InputField.
//In interlaced, 2 WaitForVerticalInterrupt is required to get a full frame. Wait until the current InputField is the odd/0 field.
const bool bIsFormatB = ::IsVideoFormatB(FoundChannel->VideoFormat);
EInterruptType InterruptType = EInterruptType::InputField;
if (InInterrupt != EInterruptType::Auto)
{
InterruptType = InInterrupt;
}
else if (bIsFormatB)
{
InterruptType = EInterruptType::VerticalInterrupt;
}
while (InRepeatCount > 0)
{
--InRepeatCount;
assert(FoundChannel);
if (FoundChannel->SyncThread)
{
bResult = FoundChannel->SyncThread->Wait_ExternalThread();
}
else
{
bResult = false;
ensureAlwaysMsgf(false, TEXT("Expected a sync thread but there was none."));
}
bool bDoAnotherWaitForField = false;
if (bResult && InterruptType == EInterruptType::InputField)
{
bDoAnotherWaitForField = !SyncThreadInfoThread::IsCurrentField(this, FoundChannel, NTV2_FIELD0);
}
if (bDoAnotherWaitForField)
{
++InRepeatCount;
}
if (bResult && !bDoAnotherWaitForField)
{
break;
}
}
return bResult;
}
bool DeviceConnection::WaitForInputOrOutputInputField(NTV2Channel InChannel, int32_t InRepeatCount, NTV2FieldID& OutInputField)
{
// Similar to WaitForInputOrOutputInterrupt but wait only once in interlaced and return the current InputField
bool bResult = false;
OutInputField = NTV2_FIELD_INVALID;
ChannelInfo* FoundChannel = nullptr;
{
AJAAutoLock Lock(&ChannelInfoLock);
auto Found = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == InChannel; });
if (Found == std::end(ChannelInfos))
{
return false;
}
FoundChannel = *Found;
}
while (InRepeatCount > 0)
{
--InRepeatCount;
assert(FoundChannel);
if (FoundChannel->SyncThread)
{
bResult = FoundChannel->SyncThread->Wait_ExternalThread();
}
else
{
bResult = false;
ensureAlwaysMsgf(false, TEXT("Expected a sync thread but there was none."));
}
if (bResult)
{
OutInputField = SyncThreadInfoThread::IsCurrentField(this, FoundChannel, NTV2_FIELD0) ? NTV2_FIELD0 : NTV2_FIELD1;
break;
}
}
return bResult;
}
NTV2VideoFormat DeviceConnection::GetVideoFormat(NTV2Channel InChannel)
{
AJAAutoLock Lock(&ChannelInfoLock);
auto Found = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) {return Info->Channel == InChannel; });
if (Found == std::end(ChannelInfos))
{
return (*Found)->VideoFormat;
}
return NTV2VideoFormat::NTV2_FORMAT_UNKNOWN;
}
uint32_t DeviceConnection::GetBaseFrameBufferIndex(NTV2Channel InChannel)
{
AJAAutoLock Lock(&ChannelInfoLock);
auto Found = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) {return Info->Channel == InChannel; });
if (Found != std::end(ChannelInfos))
{
return (*Found)->BaseFrameBufferIndex;
}
return 0;
}
bool DeviceConnection::WaitForInputFrameReceived(NTV2Channel InChannel)
{
ChannelInfo* FoundChannelInfo = nullptr;
NTV2VideoFormat VideoFormat = NTV2VideoFormat::NTV2_FORMAT_UNKNOWN;
{
AJAAutoLock Lock(&ChannelInfoLock);
auto Found = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == InChannel; });
if (Found != std::end(ChannelInfos))
{
VideoFormat = (*Found)->VideoFormat;
if ((*Found)->bIsOwned)
{
FoundChannelInfo = *Found;
if (!FoundChannelInfo->WaitForInput)
{
FoundChannelInfo->WaitForInput = std::make_unique<WaitForInputInfo>();
}
}
}
}
bool bResult = false;
if (FoundChannelInfo)
{
bResult = FoundChannelInfo->WaitForInput->WaitForInput();
}
else
{
// Wait for 1 field (interlace == 2, progressive == 1). In PSF, we want to wait for 2 fields.
const DeviceConnection::EInterruptType InterruptType = ::IsPSF(VideoFormat) ? DeviceConnection::EInterruptType::InputField : DeviceConnection::EInterruptType::VerticalInterrupt;
bResult = WaitForInputOrOutputInterrupt(InChannel, 1, InterruptType);
}
return bResult;
}
void DeviceConnection::OnInputFrameReceived(NTV2Channel InChannel)
{
ChannelInfo* FoundChannelInfo = nullptr;
{
AJAAutoLock Lock(&ChannelInfoLock);
auto Found = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Info) { return Info->Channel == InChannel; });
if (Found != std::end(ChannelInfos))
{
FoundChannelInfo = *Found;
}
}
if (FoundChannelInfo && FoundChannelInfo->WaitForInput)
{
FoundChannelInfo->WaitForInput->NotifyAll();
}
}
void DeviceConnection::ClearFormat()
{
NotMultiVideoFormat = NTV2_FORMAT_UNKNOWN;
}
void DeviceConnection::SetFormat(NTV2Channel InChannel, NTV2VideoFormat InFormat)
{
AJAAutoLock Lock(&ChannelLock);
NotMultiVideoFormat = InFormat;
// Update the necessary channel
auto FoundIterator = std::find_if(std::begin(ChannelInfos), std::end(ChannelInfos), [=](const ChannelInfo* Other) { return Other->Channel == InChannel; });
if (FoundIterator != std::end(ChannelInfos))
{
(*FoundIterator)->VideoFormat = InFormat;
}
}
bool DeviceConnection::Lock_EnableChannel_SDILinkHelper(CNTV2Card* InCard, ETransportType InTransportType, NTV2Channel InChannel) const
{
// Check if the Channel match the Transport type.
const NTV2Channel TransportChannel = Helpers::GetTransportTypeChannel(InTransportType, InChannel);
const int32_t NumberOfChannels = Helpers::GetNumberOfLinkChannel(InTransportType);
if (NumberOfChannels != 1)
{
if (TransportChannel != InChannel)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Can't do a %S on channel '%d'. Did you mean channel '%d'?")
, Helpers::TransportTypeToString(InTransportType)
, uint32_t(InChannel) + 1
, uint32_t(TransportChannel) + 1);
return false;
}
}
// ie. if new channel is single 3, need to check that quad 1 is not registered
// ie. if new channel is quad 1, need to check that single 3 is not registered
// ie. if new channel is dual 2, need to check that channel 2 is not registered or registered as dual
const int32_t NewMinChannel = (int32_t)InChannel;
const int32_t NewMaxChannel = (int32_t)InChannel + Helpers::GetNumberOfLinkChannel(InTransportType) - 1;
for (ChannelInfo* Itt : ChannelInfos)
{
if (Itt->Channel == InChannel)
{
if (Itt->TransportType != InTransportType)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to enable the channel '%d' on device '%S' as a '%S' but channel '%d' was already enabled.\n")
, int32_t(InChannel) + 1
, InCard->GetDisplayName().c_str()
, Helpers::TransportTypeToString(InTransportType)
, int32_t(Itt->Channel) + 1);
return false;
}
}
else
{
const int32_t IttMinChannel = (int32_t)Itt->Channel;
const int32_t IttMaxChannel = (int32_t)Itt->Channel + Helpers::GetNumberOfLinkChannel(Itt->TransportType) - 1;
const bool bNewMinInConflict = NewMinChannel >= IttMinChannel && NewMinChannel <= IttMaxChannel;
const bool bNewMaxInConflict = NewMaxChannel >= IttMinChannel && NewMaxChannel <= IttMaxChannel;
if (bNewMinInConflict || bNewMaxInConflict)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Trying to enable the channel '%d' on device '%S' as a '%S' but it was already enabled as a '%S' by channel '%d'.\n")
, uint32_t(InChannel) + 1
, InCard->GetDisplayName().c_str()
, Helpers::TransportTypeToString(InTransportType)
, Helpers::TransportTypeToString(Itt->TransportType)
, uint32_t(Itt->Channel) + 1);
return false;
}
}
}
return true;
}
bool DeviceConnection::Lock_EnableChannel_Helper(CNTV2Card* InCard, ETransportType InTransportType, NTV2InputSource InInputSource, NTV2Channel InChannel, bool bIsInput, bool bConnectChannel, bool bIsAutoDetected, ETimecodeFormat InTimecodeFormat, EPixelFormat InUEPixelFormat, NTV2VideoFormat InDesiredInputFormat, NTV2VideoFormat& OutFoundVideoFormat, FAjaHDROptions& InOutHDROptions)
{
NTV2DeviceID DeviceId = Card->GetDeviceID();
if (InChannel >= ::NTV2DeviceGetNumFrameStores(DeviceId))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: The device '%S' doesn't support channel '%d'"), InCard->GetDisplayName().c_str(), uint32_t(InChannel) + 1);
return false;
}
const int32_t NumberOfLinkChannel = Helpers::GetNumberOfLinkChannel(InTransportType);
if (bConnectChannel || bIsInput)
{
for (int32_t ChannelIndex = 0; ChannelIndex < NumberOfLinkChannel; ++ChannelIndex)
{
AJA_CHECK(InCard->EnableChannel(NTV2Channel(int32_t(InChannel) + ChannelIndex)));
}
}
OutFoundVideoFormat = InDesiredInputFormat;
//If LTC is currently being read, verify frame rate compatibility
if (LTCFrameRate != NTV2_FRAMERATE_INVALID)
{
bool bIsMultiFormatEnabled = false;
AJA_CHECK(InCard->GetMultiFormatMode(bIsMultiFormatEnabled));
if (bIsMultiFormatEnabled)
{
if (InChannel == NTV2Channel::NTV2_CHANNEL1 && !::IsMultiFormatCompatible(LTCFrameRate, ::GetNTV2FrameRateFromVideoFormat(OutFoundVideoFormat)))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: The device '%S' does support Multi Format but channel %d requested format's frame rate ('%S') and it's not compatible with the LTC reference frame rate ('%S')."), InCard->GetDisplayName().c_str(), uint32_t(InChannel) + 1, NTV2FrameRateToString(::GetNTV2FrameRateFromVideoFormat(OutFoundVideoFormat)).c_str(), NTV2FrameRateToString(LTCFrameRate).c_str());
return false;
}
}
else
{
if (LTCFrameRate != ::GetNTV2FrameRateFromVideoFormat(OutFoundVideoFormat))
{
UE_LOG(LogAjaCore, Warning, TEXT("Device: The device '%S' doesn't support Multi Format and channel %d requested format's frame rate ('%S') and it's not the same as the LTC reference frame rate ('%S')."), InCard->GetDisplayName().c_str(), uint32_t(InChannel) + 1, NTV2FrameRateToString(::GetNTV2FrameRateFromVideoFormat(OutFoundVideoFormat)).c_str(), NTV2FrameRateToString(LTCFrameRate).c_str());
return false;
}
}
}
if (NotMultiVideoFormat != NTV2_FORMAT_UNKNOWN && NotMultiVideoFormat != OutFoundVideoFormat)
{
bool bIsMultiFormatEnabled = false;
AJA_CHECK(InCard->GetMultiFormatMode(bIsMultiFormatEnabled));
if (bIsMultiFormatEnabled)
{
if (!::IsMultiFormatCompatible(NotMultiVideoFormat, OutFoundVideoFormat))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: The device '%S' does support Multi Format but channel %d requested format ('%S') and it's not compatible with the previous format ('%S')."), InCard->GetDisplayName().c_str(), uint32_t(InChannel) + 1, NTV2VideoFormatToString(OutFoundVideoFormat).c_str(), NTV2VideoFormatToString(NotMultiVideoFormat).c_str());
return false;
}
}
else if (!bIsAutoDetected)
{
UE_LOG(LogAjaCore, Error, TEXT("Device: The device '%S' doesn't support Multi Format and channel %d requested format ('%S') which is not the same as previous format ('%S')."), InCard->GetDisplayName().c_str(), uint32_t(InChannel) + 1, NTV2VideoFormatToString(OutFoundVideoFormat).c_str(), NTV2VideoFormatToString(NotMultiVideoFormat).c_str());
return false;
}
}
if (InChannel != NTV2_CHANNEL1)
{
// Channel 1 dictates the card's framerate, so make sure we are running at a compatible framerate.
NTV2FrameRate DeviceFramerate = NTV2_FRAMERATE_INVALID;
AJA_CHECK(InCard->GetFrameRate(DeviceFramerate));
NTV2FrameRate NewVideoFormatFrameRate = GetNTV2FrameRateFromVideoFormat(OutFoundVideoFormat);
NTV2FrameRate NewVideoFormatFrameRateFamily = GetFrameRateFamily(NewVideoFormatFrameRate);
if (GetFrameRateFamily(DeviceFramerate) != NewVideoFormatFrameRateFamily)
{
// Device framerate doesn't match... We'll override it if the channel isn't in use.
AJAAutoLock AutoLock(&ChannelInfoLock);
// Check all channels that are already in use before changing the framerate.
// We can't change the card framerate if it's driven by a different channel.
for (ChannelInfo* Info : ChannelInfos)
{
if (Info)
{
NTV2FrameRate ChannelVideoFormatFrameRate = GetNTV2FrameRateFromVideoFormat(Info->VideoFormat);
if (NewVideoFormatFrameRateFamily != GetFrameRateFamily(ChannelVideoFormatFrameRate))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Can't enable channel %d device '%S' because its framerate doesn't match an existing channel's framerate family."), uint32_t(InChannel) + 1, InCard->GetDisplayName().c_str());
return false;
}
}
}
AJA_CHECK(InCard->SetFrameRate(NewVideoFormatFrameRate));
}
}
NotMultiVideoFormat = OutFoundVideoFormat;
const bool bIsSDI = Helpers::IsSdiTransport(InTransportType);
const bool bIsHDMI = Helpers::IsHdmiTransport(InTransportType);
// Disable SDI output from the SDI input being used,
// but only if the device supports bi-directional SDI,
// and only if the input being used is an SDI input...
// If the device supports bi-directional SDI and the
// requested input is SDI, ensure the SDI direction
// is configured for input...
if (bHasBiDirectionalSDI && bIsSDI)
{
for (int32_t ChannelIndex = 0; ChannelIndex < NumberOfLinkChannel; ++ChannelIndex)
{
AJA_CHECK(InCard->SetSDITransmitEnable(NTV2Channel(int32_t(InChannel) + ChannelIndex), !bIsInput));
}
AJATime::Sleep(50 * 12); // ...and give the device 12 frames or so to lock to the input signal
}
if (bIsInput && bIsSDI)
{
std::string FailureReason;
if (!Helpers::GetInputVideoFormat(InCard, InTransportType, InChannel, InInputSource, InDesiredInputFormat, OutFoundVideoFormat, true, FailureReason))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Initialization of the input failed for channel %d on device '%S'. %S"), uint32_t(InChannel) + 1, InCard->GetDisplayName().c_str(), FailureReason.c_str());
return false;
}
if (!Helpers::GetInputHDRMetadata(InCard, InChannel, InOutHDROptions))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Could not fetch HDR metadata from channel %d on device '%S'."), uint32_t(InChannel) + 1, InCard->GetDisplayName().c_str());
return false;
}
}
// Setup basic video to support Interrupt. This will be override by ChannelBase if necessary
if (bIsInput)
{
// Set the frame buffer pixel format for all the channels on the device
for (int32_t ChannelIndex = 0; ChannelIndex < NumberOfLinkChannel; ++ChannelIndex)
{
AJA_CHECK(InCard->SetMode(NTV2Channel(int32_t(InChannel) + ChannelIndex), NTV2_MODE_CAPTURE));
}
}
else
{
// Set the frame buffer pixel format for all the channels on the device
for (int32_t ChannelIndex = 0; ChannelIndex < NumberOfLinkChannel; ++ChannelIndex)
{
AJA_CHECK(InCard->SetMode(NTV2Channel(int32_t(InChannel) + ChannelIndex), NTV2_MODE_DISPLAY));
}
}
const NTV2FrameBufferFormat FrameBufferFormat = Helpers::ConvertPixelFormatToFrameBufferFormat(InUEPixelFormat);
AJA_CHECK(InCard->SetVideoFormat(OutFoundVideoFormat, AJA_RETAIL_DEFAULT, false, InChannel));
const NTV2HDRXferChars EOTF = Helpers::ConvertToAjaHDRXferChars(InOutHDROptions.EOTF);
const NTV2HDRColorimetry Colorimetry = Helpers::ConvertToAjaHDRColorimetry(InOutHDROptions.Gamut);
const NTV2HDRLuminance Luminance = Helpers::ConvertToAjaHDRLuminance(InOutHDROptions.Luminance);
for (int32_t ChannelIndex = 0; ChannelIndex < NumberOfLinkChannel; ++ChannelIndex)
{
AJA_CHECK(InCard->SetFrameBufferFormat(NTV2Channel(int32_t(InChannel) + ChannelIndex), FrameBufferFormat, AJA_RETAIL_DEFAULT, EOTF, Colorimetry, Luminance));
}
// Route the input for the sync, seems required
if (bConnectChannel)
{
if (bIsSDI)
{
const bool bIsSignalRgb = false;
const bool bWillUseKey = false;
Helpers::RouteSdiSignal(InCard, InTransportType, InChannel, OutFoundVideoFormat, FrameBufferFormat, bIsInput, bIsSignalRgb, bWillUseKey);
}
else if (bIsHDMI)
{
Helpers::RouteHdmiSignal(InCard, InTransportType, InInputSource, InChannel, FrameBufferFormat, bIsInput);
}
}
else
{
// route basic output for the sync, seems required for SyncChannel
if (bIsSDI && bIsInput)
{
const bool bIsDS2 = false;
AJA_CHECK(InCard->Connect(::GetFrameBufferInputXptFromChannel(InChannel), ::GetSDIInputOutputXptFromChannel(InChannel, bIsDS2)));
}
else if (bIsSDI && !bIsInput)
{
AJA_CHECK(InCard->Connect(::GetSDIOutputInputXpt(InChannel), ::GetFrameBufferOutputXptFromChannel(InChannel)));
}
else if (bIsHDMI && bIsInput)
{
AJA_CHECK(InCard->Connect(::GetFrameBufferInputXptFromChannel(InChannel), ::GetInputSourceOutputXpt(::GetNTV2HDMIInputSourceForIndex((ULWord)InChannel))));
}
else if (bIsHDMI && !bIsInput)
{
AJA_CHECK(InCard->Connect(::GetOutputDestInputXpt(NTV2_OUTPUTDESTINATION_HDMI), ::GetFrameBufferOutputXptFromChannel(InChannel)));
}
}
// Tell if it's dual or Quad SQ or Quad SI
if (Helpers::IsTsiRouting(InTransportType))
{
AJA_CHECK(InCard->SetTsiFrameEnable(true, InChannel));
}
else if (InTransportType == ETransportType::TT_SdiDual)
{
AJA_CHECK(InCard->SetSmpte372(true, InChannel));
}
else if (InTransportType == ETransportType::TT_SdiQuadSQ)
{
AJA_CHECK(InCard->Set4kSquaresEnable(true, InChannel));
}
if (bIsInput && bIsHDMI)
{
std::string FailureReason;
if (!Helpers::GetInputVideoFormat(InCard, InTransportType, InChannel, InInputSource, InDesiredInputFormat, OutFoundVideoFormat, true, FailureReason))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Initialization of the input failed for channel %d on device '%S'. %S"), uint32_t(InChannel) + 1, InCard->GetDisplayName().c_str(), FailureReason.c_str());
return false;
}
}
if (InTimecodeFormat != ETimecodeFormat::TCF_None)
{
// Be sure the RP188 source for the input channel is being grabbed from the right input...
const ULWord TimecodeFilter = 0xFF; //No filter. We filter out once we read it
AJA_CHECK(InCard->SetRP188SourceFilter(InChannel, TimecodeFilter));
if (bIsInput)
{
AJA_CHECK(InCard->SetRP188Mode(InChannel, NTV2_RP188_INPUT));
}
else
{
AJA_CHECK(InCard->SetRP188Mode(InChannel, NTV2_RP188_OUTPUT));
}
}
return true;
}
void DeviceConnection::Lock_SubscribeChannel_Helper(CNTV2Card* InCard, NTV2Channel InChannel, bool InAsInput) const
{
if (InAsInput)
{
AJA_CHECK(InCard->EnableInputInterrupt(InChannel));
AJA_CHECK(InCard->SubscribeInputVerticalEvent(InChannel));
}
else
{
AJA_CHECK(InCard->EnableOutputInterrupt(InChannel));
AJA_CHECK(InCard->SubscribeOutputVerticalEvent(InChannel));
}
}
bool DeviceConnection::Lock_Initialize()
{
assert(Card == nullptr);
Card = NewCNTV2Card(DeviceOption.DeviceIndex);
//Get the application currently owning the Aja device, if any
ULWord CurrentAppFourCC(AJA_FOURCC('?', '?', '?', '?'));
int32_t CurrentAppPID(0);
if (!Card->GetStreamingApplication(&CurrentAppFourCC, &CurrentAppPID))
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Couldn't get current stream using Aja. Something is wrong when tryin to access registers on device '%S'."), Card->GetDisplayName().c_str());
delete Card;
Card = nullptr;
return false;
}
//Try acquiring the card. If a crashed process still has a hook on it, we'll be able to take over.
if (!Card->AcquireStreamForApplication(UEAppType, static_cast<uint32_t>(AJAProcess::GetPid())))
{
//If there was a process owning it and we were not able to override it, it must still be active. Notify the user who owns it.
if (CurrentAppPID)
{
char FourCCBuffer[5];
#if defined(AJA_LITTLE_ENDIAN)
FourCCBuffer[0] = reinterpret_cast<const char*>(&CurrentAppFourCC)[3];
FourCCBuffer[1] = reinterpret_cast<const char*>(&CurrentAppFourCC)[2];
FourCCBuffer[2] = reinterpret_cast<const char*>(&CurrentAppFourCC)[1];
FourCCBuffer[3] = reinterpret_cast<const char*>(&CurrentAppFourCC)[0];
#else
FourCCBuffer[0] = reinterpret_cast<const char*>(&CurrentAppFourCC)[0];
FourCCBuffer[1] = reinterpret_cast<const char*>(&CurrentAppFourCC)[1];
FourCCBuffer[2] = reinterpret_cast<const char*>(&CurrentAppFourCC)[2];
FourCCBuffer[3] = reinterpret_cast<const char*>(&CurrentAppFourCC)[3];
#endif
FourCCBuffer[4] = '\0';
UE_LOG(LogAjaCore, Error, TEXT("Device: Couldn't acquire stream for Unreal Engine application on device '%S'. Used by application '%S' with PID '%d'."), Card->GetDisplayName().c_str(), FourCCBuffer, CurrentAppPID);
}
else
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Couldn't acquire stream for Unreal Engine application on device '%S'."), Card->GetDisplayName().c_str());
}
delete Card;
Card = nullptr;
return false;
}
{
UE_LOG(LogAjaCore, Display, TEXT("Connected to %S with SDK %d.%d.%d.%d"), Card->GetDeviceVersionString().c_str(), AJA_NTV2_SDK_VERSION_MAJOR, AJA_NTV2_SDK_VERSION_MINOR, AJA_NTV2_SDK_VERSION_POINT, AJA_NTV2_SDK_BUILD_NUMBER);
if (AJA_NTV2_SDK_VERSION_MAJOR < 14)
{
UE_LOG(LogAjaCore, Warning, TEXT("Unreal Engine's implementation of AJA support SDK 14 and up."));
}
}
// save the service level if it was not an unreleased UE app owning the card
TaskMode = NTV2_STANDARD_TASKS;
if (CurrentAppFourCC != UEAppType)
{
Card->GetEveryFrameServices(TaskMode);
}
// Use the OEM service level.
Card->SetEveryFrameServices(NTV2_OEM_TASKS);
// clear the routing
Card->ClearRouting();
::Sleep(50*1); // Wait 1 frame
NTV2DeviceID DeviceId = Card->GetDeviceID();
bHasBiDirectionalSDI = ::NTV2DeviceHasBiDirectionalSDI(DeviceId);
if (DeviceOption.bWantMultiFormatMode)
{
if (::NTV2DeviceCanDoMultiFormat(Card->GetDeviceID()))
{
bool bEnabled = true;
Card->SetMultiFormatMode(bEnabled);
}
else
{
UE_LOG(LogAjaCore, Warning, TEXT("Device: The device '%S' doesn't support Multi Format."), Card->GetDisplayName().c_str());
}
}
return true;
}
void DeviceConnection::Lock_UnInitialize()
{
Card->SetEveryFrameServices(TaskMode);
Card->ReleaseStreamForApplication(UEAppType, static_cast<uint32_t>(AJAProcess::GetPid()));
delete Card;
Card = nullptr;
}
uint32_t DeviceConnection::Lock_AcquireBaseFrameIndex(NTV2Channel InChannel, NTV2VideoFormat InVideoFormat) const
{
const uint32_t MaxValue = ::NTV2DeviceGetNumberFrameBuffers(Card->GetDeviceID());
const uint32_t BaseFrameIndex = InChannel * NumberOfFrameToAquire * 4;
if (BaseFrameIndex < MaxValue)
{
return BaseFrameIndex;
}
else
{
UE_LOG(LogAjaCore, Error, TEXT("Device: Too many Frames Indexes requested on device '%S'.\n"), Card->GetDisplayName().c_str());
return InvalidFrameBufferIndex;
}
}
void DeviceConnection::AddCommand(const std::shared_ptr<DeviceCommand>& Command)
{
Commands.Push(Command);
CommandEvent.Signal();
}
void DeviceConnection::RemoveCommand(const std::shared_ptr<DeviceCommand>& Command)
{
Commands.Erase(Command);
}
void DeviceConnection::StaticThread(AJAThread* pThread, void* pContext)
{
DeviceConnection* DeviceContext = reinterpret_cast<DeviceConnection*>(pContext);
DeviceContext->Thread_CommandLoop();
}
void DeviceConnection::Thread_CommandLoop()
{
while (!bStopRequested)
{
// It's a manual signal. Will stay opened until cleared.
CommandEvent.WaitForSignal();
CommandEvent.Clear();
if (bStopRequested)
{
return;
}
while (true)
{
std::shared_ptr<DeviceCommand> NextCommand;
if (!Commands.Pop(NextCommand) && !NextCommand)
{ // wait for next signal
break;
}
DeviceConnection::CommandList Command(*this);
NextCommand->Execute(Command);
if (bStopRequested)
{
return;
}
}
}
}
/* DeviceCommand implementation
*****************************************************************************/
DeviceCommand::DeviceCommand(const std::shared_ptr<DeviceConnection>& InConnection)
: Device(InConnection)
{
}
}
}