Input optional
Some checks failed
CI / Native Windows Build And Tests (push) Has been cancelled
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-05 22:52:41 +10:00
parent 536f65bf88
commit fecc936a14
2 changed files with 127 additions and 27 deletions

View File

@@ -252,6 +252,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
wglMakeCurrent( NULL, NULL ); wglMakeCurrent( NULL, NULL );
if (pOpenGLComposite->Start()) if (pOpenGLComposite->Start())
break; // success break; // success
MessageBoxA(NULL, "The OpenGL/DeckLink runtime initialized, but playout failed to start. See the previous DeckLink start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
}
else
{
MessageBoxA(NULL, "The OpenGL/DeckLink runtime failed to initialize. See the previous initialization message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
} }
// Failed to initialize - cleanup // Failed to initialize - cleanup

View File

@@ -466,6 +466,7 @@ bool OpenGLComposite::InitDeckLink()
BMDDisplayMode outputDisplayMode = bmdModeHD1080p5994; BMDDisplayMode outputDisplayMode = bmdModeHD1080p5994;
std::string inputDisplayModeName = "1080p59.94"; std::string inputDisplayModeName = "1080p59.94";
std::string outputDisplayModeName = "1080p59.94"; std::string outputDisplayModeName = "1080p59.94";
std::string initFailureReason;
int outputFrameRowBytes; int outputFrameRowBytes;
HRESULT result; HRESULT result;
@@ -550,8 +551,8 @@ bool OpenGLComposite::InitDeckLink()
continue; continue;
} }
// Use a full duplex device as capture and playback, or half-duplex device // Preserve the original input-then-output selection for half-duplex cards.
// as capture or playback. // Input is optional later, but choosing output first can pick the wrong card.
bool inputUsed = false; bool inputUsed = false;
if (!mDLInput && pDL->QueryInterface(IID_IDeckLinkInput, (void**)&mDLInput) == S_OK) if (!mDLInput && pDL->QueryInterface(IID_IDeckLinkInput, (void**)&mDLInput) == S_OK)
inputUsed = true; inputUsed = true;
@@ -575,26 +576,29 @@ bool OpenGLComposite::InitDeckLink()
break; break;
} }
if (! mDLOutput || ! mDLInput) if (!mDLOutput)
{ {
MessageBox(NULL, _T("Expected both Input and Output DeckLink devices"), _T("This application requires two DeckLink devices."), MB_OK); MessageBox(NULL, _T("Expected an Output DeckLink device"), _T("This application requires a DeckLink output device."), MB_OK);
goto error; goto error;
} }
if (mDLInput->GetDisplayModeIterator(&pDLInputDisplayModeIterator) != S_OK) if (mDLInput && mDLInput->GetDisplayModeIterator(&pDLInputDisplayModeIterator) != S_OK)
{ {
MessageBox(NULL, _T("Cannot get input Display Mode Iterator."), _T("DeckLink error."), MB_OK); MessageBox(NULL, _T("Cannot get input Display Mode Iterator."), _T("DeckLink error."), MB_OK);
goto error; goto error;
} }
if (!FindDeckLinkDisplayMode(pDLInputDisplayModeIterator, inputDisplayMode, &pDLInputDisplayMode)) if (mDLInput && !FindDeckLinkDisplayMode(pDLInputDisplayModeIterator, inputDisplayMode, &pDLInputDisplayMode))
{ {
const std::string error = "Cannot get specified input BMDDisplayMode for configured mode: " + inputDisplayModeName; const std::string error = "Cannot get specified input BMDDisplayMode for configured mode: " + inputDisplayModeName;
MessageBoxA(NULL, error.c_str(), "DeckLink input error.", MB_OK); MessageBoxA(NULL, error.c_str(), "DeckLink input error.", MB_OK);
goto error; goto error;
} }
pDLInputDisplayModeIterator->Release(); if (pDLInputDisplayModeIterator)
pDLInputDisplayModeIterator = NULL; {
pDLInputDisplayModeIterator->Release();
pDLInputDisplayModeIterator = NULL;
}
if (mDLOutput->GetDisplayModeIterator(&pDLOutputDisplayModeIterator) != S_OK) if (mDLOutput->GetDisplayModeIterator(&pDLOutputDisplayModeIterator) != S_OK)
{ {
@@ -611,13 +615,18 @@ bool OpenGLComposite::InitDeckLink()
pDLOutputDisplayModeIterator->Release(); pDLOutputDisplayModeIterator->Release();
pDLOutputDisplayModeIterator = NULL; pDLOutputDisplayModeIterator = NULL;
mInputFrameWidth = pDLInputDisplayMode->GetWidth();
mInputFrameHeight = pDLInputDisplayMode->GetHeight();
mOutputFrameWidth = pDLOutputDisplayMode->GetWidth(); mOutputFrameWidth = pDLOutputDisplayMode->GetWidth();
mOutputFrameHeight = pDLOutputDisplayMode->GetHeight(); mOutputFrameHeight = pDLOutputDisplayMode->GetHeight();
mInputFrameWidth = pDLInputDisplayMode ? pDLInputDisplayMode->GetWidth() : mOutputFrameWidth;
mInputFrameHeight = pDLInputDisplayMode ? pDLInputDisplayMode->GetHeight() : mOutputFrameHeight;
if (!mDLInput)
mInputDisplayModeName = "No input - black frame";
if (! CheckOpenGLExtensions()) if (! CheckOpenGLExtensions())
{
initFailureReason = "OpenGL extension checks failed.";
goto error; goto error;
}
if (mInputFrameWidth != mOutputFrameWidth || mInputFrameHeight != mOutputFrameHeight) if (mInputFrameWidth != mOutputFrameWidth || mInputFrameHeight != mOutputFrameHeight)
{ {
mFastTransferExtensionAvailable = false; mFastTransferExtensionAvailable = false;
@@ -625,7 +634,10 @@ bool OpenGLComposite::InitDeckLink()
} }
if (! InitOpenGLState()) if (! InitOpenGLState())
{
initFailureReason = "OpenGL state initialization failed.";
goto error; goto error;
}
if (mRuntimeHost) if (mRuntimeHost)
{ {
@@ -660,26 +672,51 @@ bool OpenGLComposite::InitDeckLink()
} }
} }
if (mDLInput)
{ {
// Use custom allocators so we pin only once then recycle them // Use custom allocators so we pin only once then recycle them
CComPtr<IDeckLinkVideoBufferAllocatorProvider> captureAllocator(new (std::nothrow) InputAllocatorPool(hGLDC, hGLRC)); CComPtr<IDeckLinkVideoBufferAllocatorProvider> captureAllocator(new (std::nothrow) InputAllocatorPool(hGLDC, hGLRC));
if (mDLInput->EnableVideoInputWithAllocatorProvider(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault, captureAllocator) != S_OK) if (mDLInput->EnableVideoInputWithAllocatorProvider(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault, captureAllocator) != S_OK)
goto error; {
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
mDLInput->Release();
mDLInput = NULL;
mHasNoInputSource = true;
mInputDisplayModeName = "No input - black frame";
if (mRuntimeHost)
mRuntimeHost->SetSignalStatus(false, mInputFrameWidth, mInputFrameHeight, mInputDisplayModeName);
}
} }
mCaptureDelegate = new CaptureDelegate(this); if (mDLInput)
if (mDLInput->SetCallback(mCaptureDelegate) != S_OK) {
goto error; mCaptureDelegate = new CaptureDelegate(this);
if (mDLInput->SetCallback(mCaptureDelegate) != S_OK)
{
initFailureReason = "DeckLink input setup failed while installing the capture callback.";
goto error;
}
}
else if (mRuntimeHost)
{
mRuntimeHost->SetSignalStatus(false, mInputFrameWidth, mInputFrameHeight, mInputDisplayModeName);
}
if (mDLOutput->RowBytesForPixelFormat(bmdFormat8BitBGRA, mOutputFrameWidth, &outputFrameRowBytes) != S_OK) if (mDLOutput->RowBytesForPixelFormat(bmdFormat8BitBGRA, mOutputFrameWidth, &outputFrameRowBytes) != S_OK)
{
initFailureReason = "DeckLink output setup failed while calculating BGRA row bytes.";
goto error; goto error;
}
// Use a custom allocator so we pin only once then recycle them // Use a custom allocator so we pin only once then recycle them
mPlayoutAllocator = new PinnedMemoryAllocator(hGLDC, hGLRC, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * mOutputFrameHeight); mPlayoutAllocator = new PinnedMemoryAllocator(hGLDC, hGLRC, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * mOutputFrameHeight);
if (mDLOutput->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK) if (mDLOutput->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK)
{
initFailureReason = "DeckLink output setup failed while enabling video output.";
goto error; goto error;
}
if (mDLOutput->QueryInterface(IID_IDeckLinkKeyer, (void**)&mDLKeyer) == S_OK && mDLKeyer != NULL) if (mDLOutput->QueryInterface(IID_IDeckLinkKeyer, (void**)&mDLKeyer) == S_OK && mDLKeyer != NULL)
mDeckLinkKeyerInterfaceAvailable = true; mDeckLinkKeyerInterfaceAvailable = true;
@@ -734,26 +771,41 @@ bool OpenGLComposite::InitDeckLink()
IDeckLinkVideoBuffer* outputFrameBuffer = NULL; IDeckLinkVideoBuffer* outputFrameBuffer = NULL;
if (mPlayoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK) if (mPlayoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK)
{
initFailureReason = "DeckLink output setup failed while allocating an output frame buffer.";
goto error; goto error;
}
if (mDLOutput->CreateVideoFrameWithBuffer(mOutputFrameWidth, mOutputFrameHeight, outputFrameRowBytes, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, outputFrameBuffer, &outputFrame) != S_OK) if (mDLOutput->CreateVideoFrameWithBuffer(mOutputFrameWidth, mOutputFrameHeight, outputFrameRowBytes, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, outputFrameBuffer, &outputFrame) != S_OK)
{
initFailureReason = "DeckLink output setup failed while creating an output video frame.";
goto error; goto error;
}
mDLOutputVideoFrameQueue.push_back(outputFrame); mDLOutputVideoFrameQueue.push_back(outputFrame);
} }
mPlayoutDelegate = new PlayoutDelegate(this); mPlayoutDelegate = new PlayoutDelegate(this);
if (mPlayoutDelegate == NULL) if (mPlayoutDelegate == NULL)
{
initFailureReason = "DeckLink output setup failed while creating the playout callback.";
goto error; goto error;
}
if (mDLOutput->SetScheduledFrameCompletionCallback(mPlayoutDelegate) != S_OK) if (mDLOutput->SetScheduledFrameCompletionCallback(mPlayoutDelegate) != S_OK)
{
initFailureReason = "DeckLink output setup failed while installing the scheduled-frame callback.";
goto error; goto error;
}
bSuccess = true; bSuccess = true;
error: error:
if (!bSuccess) if (!bSuccess)
{ {
if (!initFailureReason.empty())
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
if (mDLKeyer != NULL) if (mDLKeyer != NULL)
{ {
mDLKeyer->Disable(); mDLKeyer->Disable();
@@ -1195,7 +1247,8 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
if (mFastTransferExtensionAvailable) if (mFastTransferExtensionAvailable)
{ {
// Finished with mCaptureTexture // Finished with mCaptureTexture
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); if (!mHasNoInputSource)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
if (! mPlayoutAllocator->transferFrame(pFrame, mOutputTexture)) if (! mPlayoutAllocator->transferFrame(pFrame, mOutputTexture))
OutputDebugStringA("Playback: transferFrame() failed\n"); OutputDebugStringA("Playback: transferFrame() failed\n");
@@ -1232,6 +1285,16 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
bool OpenGLComposite::Start() bool OpenGLComposite::Start()
{ {
mTotalPlayoutFrames = 0; mTotalPlayoutFrames = 0;
if (!mDLOutput)
{
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
if (mDLOutputVideoFrameQueue.empty())
{
MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
// Preroll frames // Preroll frames
for (unsigned i = 0; i < kPrerollFrameCount; i++) for (unsigned i = 0; i < kPrerollFrameCount; i++)
@@ -1244,11 +1307,15 @@ bool OpenGLComposite::Start()
// Start with a black frame for playout // Start with a black frame for playout
IDeckLinkVideoBuffer* outputVideoFrameBuffer; IDeckLinkVideoBuffer* outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
{
MessageBoxA(NULL, "Could not query the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false; return false;
}
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
{ {
outputVideoFrameBuffer->Release(); outputVideoFrameBuffer->Release();
MessageBoxA(NULL, "Could not write to the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false; return false;
} }
@@ -1260,13 +1327,27 @@ bool OpenGLComposite::Start()
outputVideoFrameBuffer->Release(); outputVideoFrameBuffer->Release();
if (mDLOutput->ScheduleVideoFrame(outputVideoFrame, (mTotalPlayoutFrames * mFrameDuration), mFrameDuration, mFrameTimescale) != S_OK) if (mDLOutput->ScheduleVideoFrame(outputVideoFrame, (mTotalPlayoutFrames * mFrameDuration), mFrameDuration, mFrameTimescale) != S_OK)
{
MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false; return false;
}
mTotalPlayoutFrames++; mTotalPlayoutFrames++;
} }
mDLInput->StartStreams(); if (mDLInput)
mDLOutput->StartScheduledPlayback(0, mFrameTimescale, 1.0); {
if (mDLInput->StartStreams() != S_OK)
{
MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
}
if (mDLOutput->StartScheduledPlayback(0, mFrameTimescale, 1.0) != S_OK)
{
MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
return true; return true;
} }
@@ -1296,11 +1377,17 @@ bool OpenGLComposite::Stop()
} }
} }
mDLInput->StopStreams(); if (mDLInput)
mDLInput->DisableVideoInput(); {
mDLInput->StopStreams();
mDLInput->DisableVideoInput();
}
mDLOutput->StopScheduledPlayback(0, NULL, 0); if (mDLOutput)
mDLOutput->DisableVideoOutput(); {
mDLOutput->StopScheduledPlayback(0, NULL, 0);
mDLOutput->DisableVideoOutput();
}
return true; return true;
} }
@@ -1920,10 +2007,8 @@ void OpenGLComposite::renderEffect()
{ {
PollRuntimeChanges(); PollRuntimeChanges();
if (mHasNoInputSource) const bool hasInputSource = !mHasNoInputSource;
return; if (hasInputSource && mFastTransferExtensionAvailable)
if (mFastTransferExtensionAvailable)
{ {
// Signal that we're about to draw using mCaptureTexture onto mFBOTexture. // Signal that we're about to draw using mCaptureTexture onto mFBOTexture.
VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::CPUtoGPU); VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::CPUtoGPU);
@@ -1931,7 +2016,17 @@ void OpenGLComposite::renderEffect()
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
renderDecodePass(); if (hasInputSource)
{
renderDecodePass();
}
else
{
glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
glViewport(0, 0, mInputFrameWidth, mInputFrameHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mInputFrameWidth, mInputFrameHeight) : std::vector<RuntimeRenderState>(); const std::vector<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mInputFrameWidth, mInputFrameHeight) : std::vector<RuntimeRenderState>();
if (layerStates.empty() || mLayerPrograms.empty()) if (layerStates.empty() || mLayerPrograms.empty())
@@ -1963,7 +2058,7 @@ void OpenGLComposite::renderEffect()
pushFramebufferToHistoryRing(mDecodeFrameBuf, mSourceHistoryRing); pushFramebufferToHistoryRing(mDecodeFrameBuf, mSourceHistoryRing);
if (mFastTransferExtensionAvailable) if (hasInputSource && mFastTransferExtensionAvailable)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
} }