Files
UnrealEngine/Engine/Plugins/Media/AjaMedia/Source/Aja/Private/TimecodeChannel.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

328 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TimecodeChannel.h"
#include "Helpers.h"
namespace AJA
{
namespace Private
{
/* TimecodeChannel implementation
*****************************************************************************/
bool TimecodeChannel::Initialize(const AJADeviceOptions& InDeviceOption, const AJATimecodeChannelOptions& InTimecodeOptions)
{
Uninitialize();
TimecodeImplementation.reset(new TimecodeChannelImpl(InDeviceOption, InTimecodeOptions));
bool bResult = TimecodeImplementation->CanInitialize();
if (bResult)
{
Device = DeviceCache::GetDevice(InDeviceOption);
std::shared_ptr<TimecodeChannelInitialize_DeviceCommand> SharedCommand(new TimecodeChannelInitialize_DeviceCommand(Device, TimecodeImplementation));
InitializeCommand = SharedCommand;
Device->AddCommand(std::static_pointer_cast<DeviceCommand>(SharedCommand));
}
return bResult;
}
void TimecodeChannel::Uninitialize()
{
if (TimecodeImplementation)
{
std::shared_ptr<TimecodeChannelInitialize_DeviceCommand> SharedCommand = InitializeCommand.lock();
if (SharedCommand && Device)
{
Device->RemoveCommand(std::static_pointer_cast<DeviceCommand>(SharedCommand));
}
TimecodeImplementation->Uninitialize(); // delete is done in TimecodeChannelUninitialize_DeviceCommand completed
if (Device)
{
std::shared_ptr<TimecodeChannelUninitialize_DeviceCommand> UninitializeCommand;
UninitializeCommand.reset(new TimecodeChannelUninitialize_DeviceCommand(Device, TimecodeImplementation));
Device->AddCommand(std::static_pointer_cast<DeviceCommand>(UninitializeCommand));
}
}
InitializeCommand.reset();
TimecodeImplementation.reset();
Device.reset();
}
bool TimecodeChannel::GetTimecode(FTimecode& OutTimecode)
{
if (TimecodeImplementation)
{
return TimecodeImplementation->GetTimecode(OutTimecode);
}
return false;
}
/* TimecodeChannelInitialize_DeviceCommand implementation
*****************************************************************************/
TimecodeChannelInitialize_DeviceCommand::TimecodeChannelInitialize_DeviceCommand(const std::shared_ptr<DeviceConnection>& InConnection, const std::weak_ptr<TimecodeChannelImpl>& InChannel)
: DeviceCommand(InConnection)
, TimecodeImplementation(InChannel)
{}
void TimecodeChannelInitialize_DeviceCommand::Execute(DeviceConnection::CommandList& InCommandList)
{
std::shared_ptr<TimecodeChannelImpl> Shared = TimecodeImplementation.lock();
if (Shared) // could have been Uninitialize before we have time to execute it
{
if (!Shared->Thread_Initialize(InCommandList))
{
Shared->Thread_Destroy(InCommandList);
}
}
TimecodeImplementation.reset();
}
/* TimecodeChannelUninitialize_DeviceCommand implementation
*****************************************************************************/
TimecodeChannelUninitialize_DeviceCommand::TimecodeChannelUninitialize_DeviceCommand(const std::shared_ptr<DeviceConnection>& InConnection, const std::weak_ptr<TimecodeChannelImpl>& InChannel)
: DeviceCommand(InConnection)
, TimecodeImplementation(InChannel)
{}
void TimecodeChannelUninitialize_DeviceCommand::Execute(DeviceConnection::CommandList& InCommandList)
{
// keep a shared pointer to prevent the destruction of the TimecodeChannel until its turn comes up
TimecodeImplementation->Thread_Destroy(InCommandList);
TimecodeImplementation.reset();
}
/* TimecodeChannelImpl implementation
*****************************************************************************/
TimecodeChannelImpl::TimecodeChannelImpl(const AJADeviceOptions& InDevice, const AJATimecodeChannelOptions& InOptions)
: DeviceOption(InDevice)
, TimecodeOption(InOptions)
//, Card(nullptr)
, Channel(NTV2Channel::NTV2_CHANNEL_INVALID)
, DesiredVideoFormat(NTV2VideoFormat::NTV2_FORMAT_UNKNOWN)
, bRegisteredDedicatedLTC(false)
, bRegisteredReferenceLTC(false)
, bRegisteredChannel(false)
, bHaveTimecodeIssue(false)
, PreviousVerticalInterruptCount(0)
, bStopRequested(false)
{
if (!TimecodeOption.bUseDedicatedPin)
{
if (InOptions.ChannelIndex > NTV2_MAX_NUM_CHANNELS || InOptions.ChannelIndex < 1)
{
UE_LOG(LogAjaCore, Error, TEXT("SyncChannel: The port index '%d' is invalid.\n"), InOptions.ChannelIndex);
}
else
{
Channel = (NTV2Channel)(InOptions.ChannelIndex - 1);
InputSource = GetNTV2InputSourceForIndex(Channel, Helpers::IsHdmiTransport(InOptions.TransportType) ? NTV2_INPUTSOURCES_HDMI : NTV2_INPUTSOURCES_SDI);
}
}
}
TimecodeChannelImpl::~TimecodeChannelImpl()
{
//assert(Card == nullptr);
}
bool TimecodeChannelImpl::CanInitialize() const
{
return TimecodeOption.bUseDedicatedPin || (Channel != NTV2_CHANNEL_INVALID);
}
bool TimecodeChannelImpl::Thread_Initialize(DeviceConnection::CommandList& InCommandList)
{
bool bResult = Thread_Configure(InCommandList);
Thread_OnInitializationCompleted(bResult);
return bResult;
}
void TimecodeChannelImpl::Uninitialize()
{
AJAAutoLock AutoLock(&Lock);
TimecodeOption.CallbackInterface = nullptr;
bStopRequested = true;
}
void TimecodeChannelImpl::Thread_Destroy(DeviceConnection::CommandList& InCommandList)
{
if (Device) // Device is valid when we went in the initialize
{
if (bRegisteredChannel)
{
const bool bAsOwner = false;
InCommandList.UnregisterChannel(Channel, bAsOwner);
}
if (bRegisteredDedicatedLTC)
{
InCommandList.UnregisterAnalogLtc(bRegisteredReferenceLTC);
}
}
//delete Card;
Device.reset();
//Card = nullptr;
bRegisteredReferenceLTC = false;
bRegisteredChannel = false;
bRegisteredDedicatedLTC;
}
void TimecodeChannelImpl::Thread_OnInitializationCompleted(bool bSucceed)
{
AJAAutoLock AutoLock(&Lock);
if (TimecodeOption.CallbackInterface && !bStopRequested)
{
TimecodeOption.CallbackInterface->OnInitializationCompleted(bSucceed);
}
}
bool TimecodeChannelImpl::Thread_Configure(DeviceConnection::CommandList& InCommandList)
{
if (bStopRequested)
{
return false;
}
if (!CanInitialize())
{
return false;
}
assert(Device == nullptr);
Device = DeviceCache::GetDevice(DeviceOption);
if (Device == nullptr)
{
return false;
}
if (bStopRequested)
{
return false;
}
//assert(Card == nullptr);
//Card = DeviceConnection::NewCNTV2Card(DeviceOption.DeviceIndex);
//if (Card == nullptr)
//{
// return false;
//}
if (bStopRequested)
{
return false;
}
if (TimecodeOption.bUseDedicatedPin)
{
//Convert LTC index to enum + validate
EAnalogLTCSource LtcSource = EAnalogLTCSource::LTC1;
switch (TimecodeOption.LTCSourceIndex)
{
case 1: LtcSource = EAnalogLTCSource::LTC1; break;
case 2: LtcSource = EAnalogLTCSource::LTC2; break;
default:
UE_LOG(LogAjaCore, Error, TEXT("TimecodeChannel: The LTC source Index is invalid on device %S.\n"), GetDevice().GetDisplayName().c_str());
return false;
}
if (!InCommandList.RegisterAnalogLtc(LtcSource, Helpers::ConvertToFrameRate(TimecodeOption.LTCFrameRateNumerator, TimecodeOption.LTCFrameRateDenominator), TimecodeOption.bReadTimecodeFromReferenceIn))
{
return false;
}
bRegisteredDedicatedLTC = true;
bRegisteredReferenceLTC = TimecodeOption.bReadTimecodeFromReferenceIn;
}
else
{
bRegisteredDedicatedLTC = false;
if (!Helpers::TryVideoFormatIndexToNTV2VideoFormat(TimecodeOption.VideoFormatIndex, DesiredVideoFormat))
{
UE_LOG(LogAjaCore, Error, TEXT("TimecodeChannel: The expected video format is invalid for %d.\n"), uint32_t(Channel) + 1);
return false;
}
if (!Helpers::ConvertTransportForDevice(Device->GetCard(), DeviceOption.DeviceIndex, TimecodeOption.TransportType, DesiredVideoFormat))
{
return false;
}
constexpr bool bRegisteredAsInput = true;
constexpr bool bConnectChannel = false;
constexpr bool bAsOwner = false;
constexpr bool bAsGenlock = false;
const EPixelFormat DefaultPixelFormat = EPixelFormat::PF_8BIT_YCBCR;
if (!InCommandList.RegisterChannel(TimecodeOption.TransportType, InputSource, Channel, bRegisteredAsInput, bAsGenlock, bConnectChannel, TimecodeOption.TimecodeFormat, DefaultPixelFormat, DesiredVideoFormat, bAsOwner, TimecodeOption.bAutoDetectFormat))
{
return false;
}
bRegisteredChannel = true;
if (bStopRequested)
{
return false;
}
if (!Device->WaitForInputOrOutputInterrupt(Channel, 12 * 2))
{
UE_LOG(LogAjaCore, Error, TEXT("TimecodeChannel: Was not able to lock on device %S for channel %d.\n"), GetDevice().GetDisplayName().c_str(), uint32_t(Channel) + 1);
return false;
}
}
if (bStopRequested)
{
return false;
}
return true;
}
bool TimecodeChannelImpl::GetTimecode(FTimecode& OutTimecode)
{
if (std::chrono::system_clock::now() - LastLogTime > SecondsBetweenLogs)
{
bLogTimecodeError = true;
}
if (!bHaveTimecodeIssue)
{
if (TimecodeOption.bUseDedicatedPin)
{
bHaveTimecodeIssue = !Helpers::GetTimecode(Device->GetCard(), TimecodeOption.LTCSourceIndex == 1 ? EAnalogLTCSource::LTC1 : EAnalogLTCSource::LTC2, NTV2VideoFormat::NTV2_FORMAT_UNKNOWN, bLogTimecodeError, OutTimecode);
return !bHaveTimecodeIssue;
}
else if (TimecodeOption.TimecodeFormat != ETimecodeFormat::TCF_None)
{
uint32_t ReadBackIn;
AJA_CHECK(GetDevice().GetInputFrame(Channel, ReadBackIn));
ReadBackIn ^= 1; // Grab the next frame (processing frame)
FTimecode NewTimecode;
bHaveTimecodeIssue = !Helpers::GetTimecode(Device->GetCard(), Channel, DesiredVideoFormat, ReadBackIn, TimecodeOption.TimecodeFormat, bLogTimecodeError, NewTimecode);
OutTimecode = Helpers::AdjustTimecodeForUE(Device->GetCard(), Channel, DesiredVideoFormat, NewTimecode, PreviousTimecode, PreviousVerticalInterruptCount);
PreviousTimecode = NewTimecode;
return !bHaveTimecodeIssue;
}
}
if (bHaveTimecodeIssue)
{
LastLogTime = std::chrono::system_clock::now();
}
return bHaveTimecodeIssue;
}
}
}