From fecc936a14f499ee94191a24c2ecefa1dff75afa Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 5 May 2026 22:52:41 +1000 Subject: [PATCH] Input optional --- .../LoopThroughWithOpenGLCompositing.cpp | 5 + .../OpenGLComposite.cpp | 149 ++++++++++++++---- 2 files changed, 127 insertions(+), 27 deletions(-) diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp index 19877df..38591a2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp @@ -252,6 +252,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) wglMakeCurrent( NULL, NULL ); if (pOpenGLComposite->Start()) 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 diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index 54e0939..eb1caf6 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -466,6 +466,7 @@ bool OpenGLComposite::InitDeckLink() BMDDisplayMode outputDisplayMode = bmdModeHD1080p5994; std::string inputDisplayModeName = "1080p59.94"; std::string outputDisplayModeName = "1080p59.94"; + std::string initFailureReason; int outputFrameRowBytes; HRESULT result; @@ -550,8 +551,8 @@ bool OpenGLComposite::InitDeckLink() continue; } - // Use a full duplex device as capture and playback, or half-duplex device - // as capture or playback. + // Preserve the original input-then-output selection for half-duplex cards. + // Input is optional later, but choosing output first can pick the wrong card. bool inputUsed = false; if (!mDLInput && pDL->QueryInterface(IID_IDeckLinkInput, (void**)&mDLInput) == S_OK) inputUsed = true; @@ -575,26 +576,29 @@ bool OpenGLComposite::InitDeckLink() 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; } - 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); 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; MessageBoxA(NULL, error.c_str(), "DeckLink input error.", MB_OK); goto error; } - pDLInputDisplayModeIterator->Release(); - pDLInputDisplayModeIterator = NULL; + if (pDLInputDisplayModeIterator) + { + pDLInputDisplayModeIterator->Release(); + pDLInputDisplayModeIterator = NULL; + } if (mDLOutput->GetDisplayModeIterator(&pDLOutputDisplayModeIterator) != S_OK) { @@ -611,13 +615,18 @@ bool OpenGLComposite::InitDeckLink() pDLOutputDisplayModeIterator->Release(); pDLOutputDisplayModeIterator = NULL; - mInputFrameWidth = pDLInputDisplayMode->GetWidth(); - mInputFrameHeight = pDLInputDisplayMode->GetHeight(); mOutputFrameWidth = pDLOutputDisplayMode->GetWidth(); mOutputFrameHeight = pDLOutputDisplayMode->GetHeight(); + mInputFrameWidth = pDLInputDisplayMode ? pDLInputDisplayMode->GetWidth() : mOutputFrameWidth; + mInputFrameHeight = pDLInputDisplayMode ? pDLInputDisplayMode->GetHeight() : mOutputFrameHeight; + if (!mDLInput) + mInputDisplayModeName = "No input - black frame"; if (! CheckOpenGLExtensions()) + { + initFailureReason = "OpenGL extension checks failed."; goto error; + } if (mInputFrameWidth != mOutputFrameWidth || mInputFrameHeight != mOutputFrameHeight) { mFastTransferExtensionAvailable = false; @@ -625,7 +634,10 @@ bool OpenGLComposite::InitDeckLink() } if (! InitOpenGLState()) + { + initFailureReason = "OpenGL state initialization failed."; goto error; + } if (mRuntimeHost) { @@ -660,26 +672,51 @@ bool OpenGLComposite::InitDeckLink() } } + if (mDLInput) { // Use custom allocators so we pin only once then recycle them CComPtr captureAllocator(new (std::nothrow) InputAllocatorPool(hGLDC, hGLRC)); 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->SetCallback(mCaptureDelegate) != S_OK) - goto error; + if (mDLInput) + { + 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) + { + initFailureReason = "DeckLink output setup failed while calculating BGRA row bytes."; goto error; + } // Use a custom allocator so we pin only once then recycle them mPlayoutAllocator = new PinnedMemoryAllocator(hGLDC, hGLRC, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * mOutputFrameHeight); if (mDLOutput->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK) + { + initFailureReason = "DeckLink output setup failed while enabling video output."; goto error; + } if (mDLOutput->QueryInterface(IID_IDeckLinkKeyer, (void**)&mDLKeyer) == S_OK && mDLKeyer != NULL) mDeckLinkKeyerInterfaceAvailable = true; @@ -734,26 +771,41 @@ bool OpenGLComposite::InitDeckLink() IDeckLinkVideoBuffer* outputFrameBuffer = NULL; if (mPlayoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK) + { + initFailureReason = "DeckLink output setup failed while allocating an output frame buffer."; goto error; + } 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; + } mDLOutputVideoFrameQueue.push_back(outputFrame); } mPlayoutDelegate = new PlayoutDelegate(this); if (mPlayoutDelegate == NULL) + { + initFailureReason = "DeckLink output setup failed while creating the playout callback."; goto error; + } if (mDLOutput->SetScheduledFrameCompletionCallback(mPlayoutDelegate) != S_OK) + { + initFailureReason = "DeckLink output setup failed while installing the scheduled-frame callback."; goto error; + } bSuccess = true; error: if (!bSuccess) { + if (!initFailureReason.empty()) + MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR); + if (mDLKeyer != NULL) { mDLKeyer->Disable(); @@ -1195,7 +1247,8 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, if (mFastTransferExtensionAvailable) { // Finished with mCaptureTexture - VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); + if (!mHasNoInputSource) + VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); if (! mPlayoutAllocator->transferFrame(pFrame, mOutputTexture)) OutputDebugStringA("Playback: transferFrame() failed\n"); @@ -1232,6 +1285,16 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, bool OpenGLComposite::Start() { 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 for (unsigned i = 0; i < kPrerollFrameCount; i++) @@ -1244,11 +1307,15 @@ bool OpenGLComposite::Start() // Start with a black frame for playout IDeckLinkVideoBuffer* outputVideoFrameBuffer; 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; + } if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) { outputVideoFrameBuffer->Release(); + MessageBoxA(NULL, "Could not write to the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR); return false; } @@ -1260,13 +1327,27 @@ bool OpenGLComposite::Start() outputVideoFrameBuffer->Release(); 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; + } mTotalPlayoutFrames++; } - mDLInput->StartStreams(); - mDLOutput->StartScheduledPlayback(0, mFrameTimescale, 1.0); + if (mDLInput) + { + 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; } @@ -1296,11 +1377,17 @@ bool OpenGLComposite::Stop() } } - mDLInput->StopStreams(); - mDLInput->DisableVideoInput(); + if (mDLInput) + { + mDLInput->StopStreams(); + mDLInput->DisableVideoInput(); + } - mDLOutput->StopScheduledPlayback(0, NULL, 0); - mDLOutput->DisableVideoOutput(); + if (mDLOutput) + { + mDLOutput->StopScheduledPlayback(0, NULL, 0); + mDLOutput->DisableVideoOutput(); + } return true; } @@ -1920,10 +2007,8 @@ void OpenGLComposite::renderEffect() { PollRuntimeChanges(); - if (mHasNoInputSource) - return; - - if (mFastTransferExtensionAvailable) + const bool hasInputSource = !mHasNoInputSource; + if (hasInputSource && mFastTransferExtensionAvailable) { // Signal that we're about to draw using mCaptureTexture onto mFBOTexture. VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::CPUtoGPU); @@ -1931,7 +2016,17 @@ void OpenGLComposite::renderEffect() glDisable(GL_BLEND); 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 layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mInputFrameWidth, mInputFrameHeight) : std::vector(); if (layerStates.empty() || mLayerPrograms.empty()) @@ -1963,7 +2058,7 @@ void OpenGLComposite::renderEffect() pushFramebufferToHistoryRing(mDecodeFrameBuf, mSourceHistoryRing); - if (mFastTransferExtensionAvailable) + if (hasInputSource && mFastTransferExtensionAvailable) VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); }