// Copyright Epic Games, Inc. All Rights Reserved. #include "Device.h" #include #include namespace AJA { namespace Private { static ULWord UEAppType = AJA_FOURCC('U', 'E', '5', '.'); static DeviceCache Cache; /* DeviceCache implementation *****************************************************************************/ std::shared_ptr 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(); } { 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& 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 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 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& 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& 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& 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& 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(); } } } } 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(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(&CurrentAppFourCC)[3]; FourCCBuffer[1] = reinterpret_cast(&CurrentAppFourCC)[2]; FourCCBuffer[2] = reinterpret_cast(&CurrentAppFourCC)[1]; FourCCBuffer[3] = reinterpret_cast(&CurrentAppFourCC)[0]; #else FourCCBuffer[0] = reinterpret_cast(&CurrentAppFourCC)[0]; FourCCBuffer[1] = reinterpret_cast(&CurrentAppFourCC)[1]; FourCCBuffer[2] = reinterpret_cast(&CurrentAppFourCC)[2]; FourCCBuffer[3] = reinterpret_cast(&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(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& Command) { Commands.Push(Command); CommandEvent.Signal(); } void DeviceConnection::RemoveCommand(const std::shared_ptr& Command) { Commands.Erase(Command); } void DeviceConnection::StaticThread(AJAThread* pThread, void* pContext) { DeviceConnection* DeviceContext = reinterpret_cast(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 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& InConnection) : Device(InConnection) { } } }