367 lines
12 KiB
C++
367 lines
12 KiB
C++
#include "DeckLinkInput.h"
|
|
|
|
#include "DeckLinkVideoIOFormat.h"
|
|
#include "../logging/Logger.h"
|
|
|
|
#include <chrono>
|
|
#include <new>
|
|
|
|
namespace RenderCadenceCompositor
|
|
{
|
|
namespace
|
|
{
|
|
bool FindInputDisplayMode(IDeckLinkInput* input, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode)
|
|
{
|
|
if (input == nullptr || foundMode == nullptr)
|
|
return false;
|
|
|
|
*foundMode = nullptr;
|
|
CComPtr<IDeckLinkDisplayModeIterator> iterator;
|
|
if (input->GetDisplayModeIterator(&iterator) != S_OK)
|
|
return false;
|
|
|
|
return FindDeckLinkDisplayMode(iterator, targetMode, foundMode);
|
|
}
|
|
|
|
}
|
|
|
|
DeckLinkInputCallback::DeckLinkInputCallback(DeckLinkInput& owner) :
|
|
mOwner(owner)
|
|
{
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE DeckLinkInputCallback::QueryInterface(REFIID iid, LPVOID* ppv)
|
|
{
|
|
if (ppv == nullptr)
|
|
return E_POINTER;
|
|
if (iid == IID_IUnknown || iid == IID_IDeckLinkInputCallback)
|
|
{
|
|
*ppv = static_cast<IDeckLinkInputCallback*>(this);
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
*ppv = nullptr;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE DeckLinkInputCallback::AddRef()
|
|
{
|
|
return ++mRefCount;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE DeckLinkInputCallback::Release()
|
|
{
|
|
const ULONG refCount = --mRefCount;
|
|
if (refCount == 0)
|
|
delete this;
|
|
return refCount;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE DeckLinkInputCallback::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket*)
|
|
{
|
|
if (videoFrame != nullptr)
|
|
mOwner.HandleFrameArrived(videoFrame);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE DeckLinkInputCallback::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags)
|
|
{
|
|
mOwner.HandleFormatChanged();
|
|
return S_OK;
|
|
}
|
|
|
|
DeckLinkInput::DeckLinkInput(InputFrameMailbox& mailbox) :
|
|
mMailbox(mailbox)
|
|
{
|
|
}
|
|
|
|
DeckLinkInput::~DeckLinkInput()
|
|
{
|
|
ReleaseResources();
|
|
}
|
|
|
|
bool DeckLinkInput::Initialize(const DeckLinkInputConfig& config, std::string& error)
|
|
{
|
|
ReleaseResources();
|
|
mConfig = config;
|
|
Log("decklink-input", "Initializing DeckLink input for " + config.videoFormat.displayName + ".");
|
|
|
|
if (!DiscoverInput(config, error))
|
|
return false;
|
|
|
|
if (mInput->EnableVideoInput(config.videoFormat.displayMode, mCapturePixelFormat, bmdVideoInputFlagDefault) != S_OK)
|
|
{
|
|
error = "DeckLink input setup failed while enabling " +
|
|
std::string(mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8") +
|
|
" input for " + config.videoFormat.displayName + ".";
|
|
ReleaseResources();
|
|
return false;
|
|
}
|
|
Log(
|
|
"decklink-input",
|
|
std::string("DeckLink input enabled in ") + (mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8 raw capture") + " mode.");
|
|
|
|
mCallback.Attach(new (std::nothrow) DeckLinkInputCallback(*this));
|
|
if (mCallback == nullptr)
|
|
{
|
|
error = "DeckLink input setup failed while creating the capture callback.";
|
|
ReleaseResources();
|
|
return false;
|
|
}
|
|
|
|
if (mInput->SetCallback(mCallback) != S_OK)
|
|
{
|
|
error = "DeckLink input setup failed while installing the capture callback.";
|
|
ReleaseResources();
|
|
return false;
|
|
}
|
|
Log("decklink-input", "DeckLink input callback installed.");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DeckLinkInput::Start(std::string& error)
|
|
{
|
|
if (mInput == nullptr)
|
|
{
|
|
error = "DeckLink input has not been initialized.";
|
|
return false;
|
|
}
|
|
if (mRunning.load(std::memory_order_acquire))
|
|
return true;
|
|
|
|
if (mInput->StartStreams() != S_OK)
|
|
{
|
|
error = "DeckLink input stream failed to start.";
|
|
return false;
|
|
}
|
|
mRunning.store(true, std::memory_order_release);
|
|
Log("decklink-input", "DeckLink input stream started.");
|
|
return true;
|
|
}
|
|
|
|
void DeckLinkInput::Stop()
|
|
{
|
|
if (mInput != nullptr && mRunning.exchange(false, std::memory_order_acq_rel))
|
|
{
|
|
mInput->StopStreams();
|
|
Log("decklink-input", "DeckLink input stream stopped.");
|
|
}
|
|
}
|
|
|
|
void DeckLinkInput::ReleaseResources()
|
|
{
|
|
Stop();
|
|
if (mInput != nullptr)
|
|
{
|
|
mInput->SetCallback(nullptr);
|
|
mInput->DisableVideoInput();
|
|
}
|
|
mCallback.Release();
|
|
mInput.Release();
|
|
}
|
|
|
|
DeckLinkInputMetrics DeckLinkInput::Metrics() const
|
|
{
|
|
DeckLinkInputMetrics metrics;
|
|
metrics.capturedFrames = mCapturedFrames.load(std::memory_order_relaxed);
|
|
metrics.noInputSourceFrames = mNoInputSourceFrames.load(std::memory_order_relaxed);
|
|
metrics.unsupportedFrames = mUnsupportedFrames.load(std::memory_order_relaxed);
|
|
metrics.submitMisses = mSubmitMisses.load(std::memory_order_relaxed);
|
|
metrics.convertMilliseconds = mConvertMilliseconds.load(std::memory_order_relaxed);
|
|
metrics.submitMilliseconds = mSubmitMilliseconds.load(std::memory_order_relaxed);
|
|
metrics.captureFormat = CaptureFormatName();
|
|
return metrics;
|
|
}
|
|
|
|
VideoIOPixelFormat DeckLinkInput::CapturePixelFormat() const
|
|
{
|
|
return mCapturePixelFormat == bmdFormat8BitYUV ? VideoIOPixelFormat::Uyvy8 : VideoIOPixelFormat::Bgra8;
|
|
}
|
|
|
|
void DeckLinkInput::HandleFrameArrived(IDeckLinkVideoInputFrame* inputFrame)
|
|
{
|
|
if (inputFrame == nullptr)
|
|
return;
|
|
|
|
if ((inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource)
|
|
{
|
|
mNoInputSourceFrames.fetch_add(1, std::memory_order_relaxed);
|
|
bool expected = false;
|
|
if (mLoggedNoInputSource.compare_exchange_strong(expected, true, std::memory_order_relaxed))
|
|
LogWarning("decklink-input", "DeckLink input callback reports no input source.");
|
|
return;
|
|
}
|
|
|
|
if (inputFrame->GetWidth() != static_cast<long>(mMailbox.Config().width) ||
|
|
inputFrame->GetHeight() != static_cast<long>(mMailbox.Config().height))
|
|
{
|
|
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
|
|
bool expected = false;
|
|
if (mLoggedUnsupportedFrame.compare_exchange_strong(expected, true, std::memory_order_relaxed))
|
|
LogWarning("decklink-input", "DeckLink input frame dimensions do not match the configured mailbox.");
|
|
return;
|
|
}
|
|
|
|
CComPtr<IDeckLinkVideoBuffer> inputFrameBuffer;
|
|
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, reinterpret_cast<void**>(&inputFrameBuffer)) != S_OK)
|
|
{
|
|
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
|
|
bool expected = false;
|
|
if (mLoggedUnsupportedFrame.compare_exchange_strong(expected, true, std::memory_order_relaxed))
|
|
LogWarning("decklink-input", "DeckLink input frame does not expose IDeckLinkVideoBuffer.");
|
|
return;
|
|
}
|
|
|
|
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
|
|
{
|
|
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
|
|
bool expected = false;
|
|
if (mLoggedUnsupportedFrame.compare_exchange_strong(expected, true, std::memory_order_relaxed))
|
|
LogWarning("decklink-input", "DeckLink input frame buffer could not be opened for read access.");
|
|
return;
|
|
}
|
|
|
|
void* bytes = nullptr;
|
|
inputFrameBuffer->GetBytes(&bytes);
|
|
bool submitted = false;
|
|
if (mCapturePixelFormat == bmdFormat8BitBGRA)
|
|
submitted = SubmitBgra8Frame(inputFrame, bytes);
|
|
else if (mCapturePixelFormat == bmdFormat8BitYUV)
|
|
submitted = SubmitUyvy8Frame(inputFrame, bytes);
|
|
else
|
|
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
|
|
|
|
if (!submitted)
|
|
{
|
|
mSubmitMisses.fetch_add(1, std::memory_order_relaxed);
|
|
bool expected = false;
|
|
if (mLoggedSubmitMiss.compare_exchange_strong(expected, true, std::memory_order_relaxed))
|
|
LogWarning("decklink-input", "DeckLink input frame could not be submitted to InputFrameMailbox.");
|
|
}
|
|
mCapturedFrames.fetch_add(1, std::memory_order_relaxed);
|
|
bool expectedFirstFrame = false;
|
|
if (mLoggedFirstFrame.compare_exchange_strong(expectedFirstFrame, true, std::memory_order_relaxed))
|
|
{
|
|
Log(
|
|
"decklink-input",
|
|
std::string("First DeckLink ") + (mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8 raw") + " input frame submitted to InputFrameMailbox.");
|
|
}
|
|
|
|
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
|
|
}
|
|
|
|
void DeckLinkInput::HandleFormatChanged()
|
|
{
|
|
mUnsupportedFrames.fetch_add(1, std::memory_order_relaxed);
|
|
LogWarning("decklink-input", "DeckLink input format changed; input edge does not auto-switch formats yet.");
|
|
}
|
|
|
|
bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string& error)
|
|
{
|
|
CComPtr<IDeckLinkIterator> iterator;
|
|
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&iterator));
|
|
if (FAILED(result))
|
|
{
|
|
error = "DeckLink input discovery failed. Blackmagic DeckLink drivers may not be installed.";
|
|
return false;
|
|
}
|
|
|
|
CComPtr<IDeckLink> deckLink;
|
|
while (iterator->Next(&deckLink) == S_OK)
|
|
{
|
|
CComPtr<IDeckLinkInput> candidateInput;
|
|
if (deckLink->QueryInterface(IID_IDeckLinkInput, reinterpret_cast<void**>(&candidateInput)) == S_OK && candidateInput != nullptr)
|
|
{
|
|
CComPtr<IDeckLinkDisplayMode> displayMode;
|
|
if (FindInputDisplayMode(candidateInput, config.videoFormat.displayMode, &displayMode) &&
|
|
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitBGRA))
|
|
{
|
|
mInput = candidateInput;
|
|
mCapturePixelFormat = bmdFormat8BitBGRA;
|
|
Log("decklink-input", "DeckLink input device selected for BGRA8 capture.");
|
|
return true;
|
|
}
|
|
if (displayMode != nullptr &&
|
|
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitYUV))
|
|
{
|
|
mInput = candidateInput;
|
|
mCapturePixelFormat = bmdFormat8BitYUV;
|
|
Log("decklink-input", "DeckLink input device selected for UYVY8 raw capture with render-thread GPU decode.");
|
|
return true;
|
|
}
|
|
}
|
|
deckLink.Release();
|
|
}
|
|
|
|
error = "No DeckLink input device supports BGRA8 or UYVY8 capture for " + config.videoFormat.displayName + ".";
|
|
return false;
|
|
}
|
|
|
|
bool DeckLinkInput::SupportsInputFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat) const
|
|
{
|
|
if (input == nullptr)
|
|
return false;
|
|
|
|
BOOL supported = FALSE;
|
|
BMDDisplayMode actualMode = bmdModeUnknown;
|
|
const HRESULT result = input->DoesSupportVideoMode(
|
|
bmdVideoConnectionUnspecified,
|
|
displayMode,
|
|
pixelFormat,
|
|
bmdNoVideoInputConversion,
|
|
bmdSupportedVideoModeDefault,
|
|
&actualMode,
|
|
&supported);
|
|
return result == S_OK && supported != FALSE;
|
|
}
|
|
|
|
bool DeckLinkInput::SubmitBgra8Frame(IDeckLinkVideoInputFrame* inputFrame, const void* bytes)
|
|
{
|
|
if (inputFrame == nullptr || bytes == nullptr)
|
|
return false;
|
|
const uint64_t frameIndex = mCapturedFrames.load(std::memory_order_relaxed);
|
|
mConvertMilliseconds.store(0.0, std::memory_order_relaxed);
|
|
const auto submitStart = std::chrono::steady_clock::now();
|
|
const bool submitted = mMailbox.SubmitFrame(bytes, static_cast<unsigned>(inputFrame->GetRowBytes()), frameIndex);
|
|
const auto submitEnd = std::chrono::steady_clock::now();
|
|
mSubmitMilliseconds.store(
|
|
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(submitEnd - submitStart).count(),
|
|
std::memory_order_relaxed);
|
|
return submitted;
|
|
}
|
|
|
|
bool DeckLinkInput::SubmitUyvy8Frame(IDeckLinkVideoInputFrame* inputFrame, const void* bytes)
|
|
{
|
|
if (inputFrame == nullptr || bytes == nullptr)
|
|
return false;
|
|
|
|
const unsigned width = static_cast<unsigned>(inputFrame->GetWidth());
|
|
const unsigned height = static_cast<unsigned>(inputFrame->GetHeight());
|
|
const long sourceRowBytes = inputFrame->GetRowBytes();
|
|
if (width == 0 || height == 0 || sourceRowBytes < static_cast<long>(width * 2u))
|
|
return false;
|
|
|
|
mConvertMilliseconds.store(0.0, std::memory_order_relaxed);
|
|
const uint64_t frameIndex = mCapturedFrames.load(std::memory_order_relaxed);
|
|
const auto submitStart = std::chrono::steady_clock::now();
|
|
const bool submitted = mMailbox.SubmitFrame(bytes, static_cast<unsigned>(sourceRowBytes), frameIndex);
|
|
const auto submitEnd = std::chrono::steady_clock::now();
|
|
mSubmitMilliseconds.store(
|
|
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(submitEnd - submitStart).count(),
|
|
std::memory_order_relaxed);
|
|
return submitted;
|
|
}
|
|
|
|
const char* DeckLinkInput::CaptureFormatName() const
|
|
{
|
|
if (mInput == nullptr)
|
|
return "none";
|
|
if (mCapturePixelFormat == bmdFormat8BitBGRA)
|
|
return "BGRA8";
|
|
if (mCapturePixelFormat == bmdFormat8BitYUV)
|
|
return "UYVY8";
|
|
return "unsupported";
|
|
}
|
|
}
|