From 6918306336ae50e2e787e63a7fa166aa1199ff14 Mon Sep 17 00:00:00 2001 From: Aiden Date: Wed, 6 May 2026 10:31:21 +1000 Subject: [PATCH] decklink separation --- CMakeLists.txt | 2 + .../LoopThroughWithOpenGLCompositing.vcxproj | 2 + ...roughWithOpenGLCompositing.vcxproj.filters | 6 + .../decklink/DeckLinkSession.cpp | 148 +++++++ .../decklink/DeckLinkSession.h | 44 ++ .../gl/OpenGLComposite.cpp | 388 ++++++------------ .../gl/OpenGLComposite.h | 27 +- 7 files changed, 319 insertions(+), 298 deletions(-) create mode 100644 apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f506e6..69efb7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,8 @@ set(APP_SOURCES "${APP_DIR}/decklink/DeckLinkDisplayMode.h" "${APP_DIR}/decklink/DeckLinkFrameTransfer.cpp" "${APP_DIR}/decklink/DeckLinkFrameTransfer.h" + "${APP_DIR}/decklink/DeckLinkSession.cpp" + "${APP_DIR}/decklink/DeckLinkSession.h" "${APP_DIR}/gl/GLExtensions.cpp" "${APP_DIR}/gl/GLExtensions.h" "${APP_DIR}/gl/GlRenderConstants.h" diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj index 15a8caf..205008f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj @@ -207,6 +207,7 @@ + @@ -220,6 +221,7 @@ + diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters index 3917cbe..c6841d4 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters @@ -48,6 +48,9 @@ DeckLink API + + Source Files + @@ -83,6 +86,9 @@ Header Files + + Header Files + diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp new file mode 100644 index 0000000..890bf0b --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp @@ -0,0 +1,148 @@ +#include "DeckLinkSession.h" + +#include "GlRenderConstants.h" + +#include + +DeckLinkSession::~DeckLinkSession() +{ + ReleaseResources(); +} + +void DeckLinkSession::ReleaseResources() +{ + if (input != nullptr) + { + input->SetCallback(nullptr); + if (captureDelegate != nullptr) + { + captureDelegate->Release(); + captureDelegate = nullptr; + } + input->Release(); + input = nullptr; + } + + while (!outputVideoFrameQueue.empty()) + { + IDeckLinkMutableVideoFrame* frameToRelease = outputVideoFrameQueue.front(); + if (frameToRelease != nullptr) + frameToRelease->Release(); + outputVideoFrameQueue.pop_front(); + } + + if (output != nullptr) + { + if (keyer != nullptr) + { + keyer->Disable(); + keyer->Release(); + keyer = nullptr; + externalKeyingActive = false; + } + output->SetScheduledFrameCompletionCallback(nullptr); + if (playoutDelegate != nullptr) + { + playoutDelegate->Release(); + playoutDelegate = nullptr; + } + output->Release(); + output = nullptr; + } + + if (playoutAllocator != nullptr) + { + playoutAllocator->Release(); + playoutAllocator = nullptr; + } +} + +bool DeckLinkSession::Start(unsigned outputHeight) +{ + totalPlayoutFrames = 0; + if (!output) + { + MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR); + return false; + } + if (outputVideoFrameQueue.empty()) + { + MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR); + return false; + } + + for (unsigned i = 0; i < kPrerollFrameCount; i++) + { + IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front(); + outputVideoFrameQueue.push_back(outputVideoFrame); + outputVideoFrameQueue.pop_front(); + + 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; + } + + void* pFrame; + outputVideoFrameBuffer->GetBytes((void**)&pFrame); + memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputHeight); + + outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); + outputVideoFrameBuffer->Release(); + + if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK) + { + MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR); + return false; + } + + totalPlayoutFrames++; + } + + if (input) + { + if (input->StartStreams() != S_OK) + { + MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR); + return false; + } + } + if (output->StartScheduledPlayback(0, frameTimescale, 1.0) != S_OK) + { + MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR); + return false; + } + + return true; +} + +bool DeckLinkSession::Stop() +{ + if (keyer != nullptr) + { + keyer->Disable(); + externalKeyingActive = false; + } + + if (input) + { + input->StopStreams(); + input->DisableVideoInput(); + } + + if (output) + { + output->StopScheduledPlayback(0, NULL, 0); + output->DisableVideoOutput(); + } + + return true; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h new file mode 100644 index 0000000..1706bde --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h @@ -0,0 +1,44 @@ +#pragma once + +#include "DeckLinkAPI_h.h" +#include "DeckLinkFrameTransfer.h" + +#include +#include + +class OpenGLComposite; + +class DeckLinkSession +{ +public: + DeckLinkSession() = default; + ~DeckLinkSession(); + + void ReleaseResources(); + bool Start(unsigned outputFrameHeight); + bool Stop(); + + CaptureDelegate* captureDelegate = nullptr; + PlayoutDelegate* playoutDelegate = nullptr; + IDeckLinkInput* input = nullptr; + IDeckLinkOutput* output = nullptr; + IDeckLinkKeyer* keyer = nullptr; + std::deque outputVideoFrameQueue; + PinnedMemoryAllocator* playoutAllocator = nullptr; + BMDTimeValue frameDuration = 0; + BMDTimeScale frameTimescale = 0; + unsigned totalPlayoutFrames = 0; + unsigned inputFrameWidth = 0; + unsigned inputFrameHeight = 0; + unsigned outputFrameWidth = 0; + unsigned outputFrameHeight = 0; + std::string inputDisplayModeName = "1080p59.94"; + std::string outputDisplayModeName = "1080p59.94"; + bool hasNoInputSource = true; + std::string outputModelName; + bool supportsInternalKeying = false; + bool supportsExternalKeying = false; + bool keyerInterfaceAvailable = false; + bool externalKeyingActive = false; + std::string statusMessage; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index e475d69..d98dda4 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -41,6 +41,7 @@ #include "ControlServer.h" #include "DeckLinkDisplayMode.h" #include "DeckLinkFrameTransfer.h" +#include "DeckLinkSession.h" #include "OpenGLComposite.h" #include "GLExtensions.h" #include "GlRenderConstants.h" @@ -55,18 +56,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC), - mCaptureDelegate(NULL), mPlayoutDelegate(NULL), - mDLInput(NULL), mDLOutput(NULL), mDLKeyer(NULL), - mPlayoutAllocator(NULL), - mInputFrameWidth(0), mInputFrameHeight(0), - mOutputFrameWidth(0), mOutputFrameHeight(0), - mInputDisplayModeName("1080p59.94"), - mOutputDisplayModeName("1080p59.94"), - mHasNoInputSource(true), - mDeckLinkSupportsInternalKeying(false), - mDeckLinkSupportsExternalKeying(false), - mDeckLinkKeyerInterfaceAvailable(false), - mDeckLinkExternalKeyingActive(false), + mDeckLink(std::make_unique()), mRenderer(std::make_unique()) { InitializeCriticalSection(&pMutex); @@ -79,60 +69,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : OpenGLComposite::~OpenGLComposite() { - // Cleanup for Capture - if (mDLInput != NULL) - { - mDLInput->SetCallback(NULL); - - mDLInput->Release(); - mDLInput = NULL; - } - - if (mCaptureDelegate != NULL) - { - mCaptureDelegate->Release(); - mCaptureDelegate = NULL; - } - - // Cleanup for Playout - while (!mDLOutputVideoFrameQueue.empty()) - { - IDeckLinkMutableVideoFrame* frameToRelease = mDLOutputVideoFrameQueue.front(); - if (frameToRelease != NULL) - { - frameToRelease->Release(); - frameToRelease = NULL; - } - mDLOutputVideoFrameQueue.pop_front(); - } - - if (mDLOutput != NULL) - { - if (mDLKeyer != NULL) - { - mDLKeyer->Disable(); - mDLKeyer->Release(); - mDLKeyer = NULL; - } - - mDLOutput->SetScheduledFrameCompletionCallback(NULL); - - mDLOutput->Release(); - mDLOutput = NULL; - } - - if (mPlayoutDelegate != NULL) - { - mPlayoutDelegate->Release(); - mPlayoutDelegate = NULL; - } - - if (mPlayoutAllocator != NULL) - { - mPlayoutAllocator->Release(); - mPlayoutAllocator = NULL; - } - + mDeckLink->ReleaseResources(); mRenderer->DestroyResources(); if (mOscServer) mOscServer->Stop(); @@ -187,8 +124,8 @@ bool OpenGLComposite::InitDeckLink() return false; } } - mInputDisplayModeName = inputDisplayModeName; - mOutputDisplayModeName = outputDisplayModeName; + mDeckLink->inputDisplayModeName = inputDisplayModeName; + mDeckLink->outputDisplayModeName = outputDisplayModeName; result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&pDLIterator); if (FAILED(result)) @@ -244,41 +181,41 @@ bool OpenGLComposite::InitDeckLink() // 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) + if (!mDeckLink->input && pDL->QueryInterface(IID_IDeckLinkInput, (void**)&mDeckLink->input) == S_OK) inputUsed = true; - if (!mDLOutput && (!inputUsed || (duplexMode == bmdDuplexFull))) + if (!mDeckLink->output && (!inputUsed || (duplexMode == bmdDuplexFull))) { - if (pDL->QueryInterface(IID_IDeckLinkOutput, (void**)&mDLOutput) != S_OK) - mDLOutput = NULL; + if (pDL->QueryInterface(IID_IDeckLinkOutput, (void**)&mDeckLink->output) != S_OK) + mDeckLink->output = NULL; else { - mDeckLinkOutputModelName = modelName; - mDeckLinkSupportsInternalKeying = supportsInternalKeying; - mDeckLinkSupportsExternalKeying = supportsExternalKeying; + mDeckLink->outputModelName = modelName; + mDeckLink->supportsInternalKeying = supportsInternalKeying; + mDeckLink->supportsExternalKeying = supportsExternalKeying; } } pDL->Release(); pDL = NULL; - if (mDLOutput && mDLInput) + if (mDeckLink->output && mDeckLink->input) break; } - if (!mDLOutput) + if (!mDeckLink->output) { MessageBox(NULL, _T("Expected an Output DeckLink device"), _T("This application requires a DeckLink output device."), MB_OK); goto error; } - if (mDLInput && mDLInput->GetDisplayModeIterator(&pDLInputDisplayModeIterator) != S_OK) + if (mDeckLink->input && mDeckLink->input->GetDisplayModeIterator(&pDLInputDisplayModeIterator) != S_OK) { MessageBox(NULL, _T("Cannot get input Display Mode Iterator."), _T("DeckLink error."), MB_OK); goto error; } - if (mDLInput && !FindDeckLinkDisplayMode(pDLInputDisplayModeIterator, inputDisplayMode, &pDLInputDisplayMode)) + if (mDeckLink->input && !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); @@ -290,7 +227,7 @@ bool OpenGLComposite::InitDeckLink() pDLInputDisplayModeIterator = NULL; } - if (mDLOutput->GetDisplayModeIterator(&pDLOutputDisplayModeIterator) != S_OK) + if (mDeckLink->output->GetDisplayModeIterator(&pDLOutputDisplayModeIterator) != S_OK) { MessageBox(NULL, _T("Cannot get output Display Mode Iterator."), _T("DeckLink error."), MB_OK); goto error; @@ -305,19 +242,19 @@ bool OpenGLComposite::InitDeckLink() pDLOutputDisplayModeIterator->Release(); pDLOutputDisplayModeIterator = NULL; - mOutputFrameWidth = pDLOutputDisplayMode->GetWidth(); - mOutputFrameHeight = pDLOutputDisplayMode->GetHeight(); - mInputFrameWidth = pDLInputDisplayMode ? pDLInputDisplayMode->GetWidth() : mOutputFrameWidth; - mInputFrameHeight = pDLInputDisplayMode ? pDLInputDisplayMode->GetHeight() : mOutputFrameHeight; - if (!mDLInput) - mInputDisplayModeName = "No input - black frame"; + mDeckLink->outputFrameWidth = pDLOutputDisplayMode->GetWidth(); + mDeckLink->outputFrameHeight = pDLOutputDisplayMode->GetHeight(); + mDeckLink->inputFrameWidth = pDLInputDisplayMode ? pDLInputDisplayMode->GetWidth() : mDeckLink->outputFrameWidth; + mDeckLink->inputFrameHeight = pDLInputDisplayMode ? pDLInputDisplayMode->GetHeight() : mDeckLink->outputFrameHeight; + if (!mDeckLink->input) + mDeckLink->inputDisplayModeName = "No input - black frame"; if (! CheckOpenGLExtensions()) { initFailureReason = "OpenGL extension checks failed."; goto error; } - if (mInputFrameWidth != mOutputFrameWidth || mInputFrameHeight != mOutputFrameHeight) + if (mDeckLink->inputFrameWidth != mDeckLink->outputFrameWidth || mDeckLink->inputFrameHeight != mDeckLink->outputFrameHeight) { mRenderer->mFastTransferExtensionAvailable = false; OutputDebugStringA("Input/output dimensions differ; using regular OpenGL transfer fallback instead of fast transfer.\n"); @@ -331,58 +268,58 @@ bool OpenGLComposite::InitDeckLink() if (mRuntimeHost) { - mDeckLinkStatusMessage = mDeckLinkOutputModelName.empty() + mDeckLink->statusMessage = mDeckLink->outputModelName.empty() ? "DeckLink output device selected." - : ("Selected output device: " + mDeckLinkOutputModelName); + : ("Selected output device: " + mDeckLink->outputModelName); mRuntimeHost->SetDeckLinkOutputStatus( - mDeckLinkOutputModelName, - mDeckLinkSupportsInternalKeying, - mDeckLinkSupportsExternalKeying, - mDeckLinkKeyerInterfaceAvailable, + mDeckLink->outputModelName, + mDeckLink->supportsInternalKeying, + mDeckLink->supportsExternalKeying, + mDeckLink->keyerInterfaceAvailable, mRuntimeHost->ExternalKeyingEnabled(), - mDeckLinkExternalKeyingActive, - mDeckLinkStatusMessage); + mDeckLink->externalKeyingActive, + mDeckLink->statusMessage); } - pDLOutputDisplayMode->GetFrameRate(&mFrameDuration, &mFrameTimescale); + pDLOutputDisplayMode->GetFrameRate(&mDeckLink->frameDuration, &mDeckLink->frameTimescale); // Resize window to match output video frame, but scale large formats down by half for viewing. - if (mOutputFrameWidth < 1920) - resizeWindow(mOutputFrameWidth, mOutputFrameHeight); + if (mDeckLink->outputFrameWidth < 1920) + resizeWindow(mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight); else - resizeWindow(mOutputFrameWidth / 2, mOutputFrameHeight / 2); + resizeWindow(mDeckLink->outputFrameWidth / 2, mDeckLink->outputFrameHeight / 2); if (mRenderer->mFastTransferExtensionAvailable) { // Initialize fast video frame transfers - if (! VideoFrameTransfer::initialize(mInputFrameWidth, mInputFrameHeight, mRenderer->mCaptureTexture, mRenderer->mOutputTexture)) + if (! VideoFrameTransfer::initialize(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mRenderer->mCaptureTexture, mRenderer->mOutputTexture)) { MessageBox(NULL, _T("Cannot initialize video transfers."), _T("VideoFrameTransfer error."), MB_OK); goto error; } } - if (mDLInput) + if (mDeckLink->input) { // 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) + if (mDeckLink->input->EnableVideoInputWithAllocatorProvider(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault, captureAllocator) != S_OK) { 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"; + mDeckLink->input->Release(); + mDeckLink->input = NULL; + mDeckLink->hasNoInputSource = true; + mDeckLink->inputDisplayModeName = "No input - black frame"; if (mRuntimeHost) - mRuntimeHost->SetSignalStatus(false, mInputFrameWidth, mInputFrameHeight, mInputDisplayModeName); + mRuntimeHost->SetSignalStatus(false, mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mDeckLink->inputDisplayModeName); } } - if (mDLInput) + if (mDeckLink->input) { - mCaptureDelegate = new CaptureDelegate(this); - if (mDLInput->SetCallback(mCaptureDelegate) != S_OK) + mDeckLink->captureDelegate = new CaptureDelegate(this); + if (mDeckLink->input->SetCallback(mDeckLink->captureDelegate) != S_OK) { initFailureReason = "DeckLink input setup failed while installing the capture callback."; goto error; @@ -390,62 +327,62 @@ bool OpenGLComposite::InitDeckLink() } else if (mRuntimeHost) { - mRuntimeHost->SetSignalStatus(false, mInputFrameWidth, mInputFrameHeight, mInputDisplayModeName); + mRuntimeHost->SetSignalStatus(false, mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mDeckLink->inputDisplayModeName); } - if (mDLOutput->RowBytesForPixelFormat(bmdFormat8BitBGRA, mOutputFrameWidth, &outputFrameRowBytes) != S_OK) + if (mDeckLink->output->RowBytesForPixelFormat(bmdFormat8BitBGRA, mDeckLink->outputFrameWidth, &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); + mDeckLink->playoutAllocator = new PinnedMemoryAllocator(hGLDC, hGLRC, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * mDeckLink->outputFrameHeight); - if (mDLOutput->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK) + if (mDeckLink->output->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; + if (mDeckLink->output->QueryInterface(IID_IDeckLinkKeyer, (void**)&mDeckLink->keyer) == S_OK && mDeckLink->keyer != NULL) + mDeckLink->keyerInterfaceAvailable = true; if (mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled()) { - if (!mDeckLinkSupportsExternalKeying) + if (!mDeckLink->supportsExternalKeying) { - mDeckLinkStatusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support."; + mDeckLink->statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support."; } - else if (!mDeckLinkKeyerInterfaceAvailable) + else if (!mDeckLink->keyerInterfaceAvailable) { - mDeckLinkStatusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface."; + mDeckLink->statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface."; } - else if (mDLKeyer->Enable(TRUE) != S_OK || mDLKeyer->SetLevel(255) != S_OK) + else if (mDeckLink->keyer->Enable(TRUE) != S_OK || mDeckLink->keyer->SetLevel(255) != S_OK) { - mDeckLinkStatusMessage = "External keying was requested, but enabling the DeckLink keyer failed."; + mDeckLink->statusMessage = "External keying was requested, but enabling the DeckLink keyer failed."; } else { - mDeckLinkExternalKeyingActive = true; - mDeckLinkStatusMessage = "External keying is active on the selected DeckLink output."; + mDeckLink->externalKeyingActive = true; + mDeckLink->statusMessage = "External keying is active on the selected DeckLink output."; } } - else if (mDeckLinkSupportsExternalKeying) + else if (mDeckLink->supportsExternalKeying) { - mDeckLinkStatusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it."; + mDeckLink->statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it."; } if (mRuntimeHost) { mRuntimeHost->SetDeckLinkOutputStatus( - mDeckLinkOutputModelName, - mDeckLinkSupportsInternalKeying, - mDeckLinkSupportsExternalKeying, - mDeckLinkKeyerInterfaceAvailable, + mDeckLink->outputModelName, + mDeckLink->supportsInternalKeying, + mDeckLink->supportsExternalKeying, + mDeckLink->keyerInterfaceAvailable, mRuntimeHost->ExternalKeyingEnabled(), - mDeckLinkExternalKeyingActive, - mDeckLinkStatusMessage); + mDeckLink->externalKeyingActive, + mDeckLink->statusMessage); } // Create a queue of 10 IDeckLinkMutableVideoFrame objects to use for scheduling output video frames. @@ -460,29 +397,29 @@ bool OpenGLComposite::InitDeckLink() IDeckLinkMutableVideoFrame* outputFrame; IDeckLinkVideoBuffer* outputFrameBuffer = NULL; - if (mPlayoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK) + if (mDeckLink->playoutAllocator->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) + if (mDeckLink->output->CreateVideoFrameWithBuffer(mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight, outputFrameRowBytes, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, outputFrameBuffer, &outputFrame) != S_OK) { initFailureReason = "DeckLink output setup failed while creating an output video frame."; goto error; } - mDLOutputVideoFrameQueue.push_back(outputFrame); + mDeckLink->outputVideoFrameQueue.push_back(outputFrame); } - mPlayoutDelegate = new PlayoutDelegate(this); - if (mPlayoutDelegate == NULL) + mDeckLink->playoutDelegate = new PlayoutDelegate(this); + if (mDeckLink->playoutDelegate == NULL) { initFailureReason = "DeckLink output setup failed while creating the playout callback."; goto error; } - if (mDLOutput->SetScheduledFrameCompletionCallback(mPlayoutDelegate) != S_OK) + if (mDeckLink->output->SetScheduledFrameCompletionCallback(mDeckLink->playoutDelegate) != S_OK) { initFailureReason = "DeckLink output setup failed while installing the scheduled-frame callback."; goto error; @@ -496,23 +433,7 @@ error: if (!initFailureReason.empty()) MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR); - if (mDLKeyer != NULL) - { - mDLKeyer->Disable(); - mDLKeyer->Release(); - mDLKeyer = NULL; - mDeckLinkExternalKeyingActive = false; - } - if (mDLInput != NULL) - { - mDLInput->Release(); - mDLInput = NULL; - } - if (mDLOutput != NULL) - { - mDLOutput->Release(); - mDLOutput = NULL; - } + mDeckLink->ReleaseResources(); } if (pDL != NULL) @@ -562,7 +483,7 @@ void OpenGLComposite::paintGL() return; } - mRenderer->PresentToWindow(hGLDC, mOutputFrameWidth, mOutputFrameHeight); + mRenderer->PresentToWindow(hGLDC, mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight); ValidateRect(hGLWnd, NULL); LeaveCriticalSection(&pMutex); } @@ -609,7 +530,7 @@ bool OpenGLComposite::InitOpenGLState() return false; } - if (!mShaderPrograms->CompileLayerPrograms(mInputFrameWidth, mInputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK); return false; @@ -617,7 +538,7 @@ bool OpenGLComposite::InitOpenGLState() mShaderPrograms->ResetTemporalHistoryState(); std::string rendererError; - if (!mRenderer->InitializeResources(mInputFrameWidth, mInputFrameHeight, mOutputFrameWidth, mOutputFrameHeight, rendererError)) + if (!mRenderer->InitializeResources(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight, rendererError)) { MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK); return false; @@ -632,11 +553,11 @@ bool OpenGLComposite::InitOpenGLState() // void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource) { - mHasNoInputSource = hasNoInputSource; + mDeckLink->hasNoInputSource = hasNoInputSource; if (mRuntimeHost) - mRuntimeHost->SetSignalStatus(!hasNoInputSource, mInputFrameWidth, mInputFrameHeight, mInputDisplayModeName); + mRuntimeHost->SetSignalStatus(!hasNoInputSource, mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mDeckLink->inputDisplayModeName); - if (mHasNoInputSource) + if (mDeckLink->hasNoInputSource) return; // don't transfer texture when there's no input long textureSize = inputFrame->GetRowBytes() * inputFrame->GetHeight(); @@ -673,7 +594,7 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo glBindTexture(GL_TEXTURE_2D, mRenderer->mCaptureTexture); // NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mInputFrameWidth / 2, mInputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink->inputFrameWidth / 2, mDeckLink->inputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); @@ -694,9 +615,9 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, EnterCriticalSection(&pMutex); // Get the first frame from the queue - IDeckLinkMutableVideoFrame* outputVideoFrame = mDLOutputVideoFrameQueue.front(); - mDLOutputVideoFrameQueue.push_back(outputVideoFrame); - mDLOutputVideoFrameQueue.pop_front(); + IDeckLinkMutableVideoFrame* outputVideoFrame = mDeckLink->outputVideoFrameQueue.front(); + mDeckLink->outputVideoFrameQueue.push_back(outputVideoFrame); + mDeckLink->outputVideoFrameQueue.pop_front(); // make GL context current in this thread wglMakeCurrent( hGLDC, hGLRC ); @@ -709,7 +630,7 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, renderEffect(); glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->mIdFrameBuf); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer->mOutputFrameBuf); - glBlitFramebuffer(0, 0, mInputFrameWidth, mInputFrameHeight, 0, 0, mOutputFrameWidth, mOutputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); + glBlitFramebuffer(0, 0, mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, 0, 0, mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer->mOutputFrameBuf); glFlush(); if (mRenderer->mFastTransferExtensionAvailable) @@ -717,8 +638,8 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, const auto renderEndTime = std::chrono::steady_clock::now(); if (mRuntimeHost) { - const double frameBudgetMilliseconds = mFrameTimescale != 0 - ? (static_cast(mFrameDuration) * 1000.0) / static_cast(mFrameTimescale) + const double frameBudgetMilliseconds = mDeckLink->frameTimescale != 0 + ? (static_cast(mDeckLink->frameDuration) * 1000.0) / static_cast(mDeckLink->frameTimescale) : 0.0; const double renderMilliseconds = std::chrono::duration_cast>(renderEndTime - renderStartTime).count(); mRuntimeHost->SetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); @@ -746,21 +667,21 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, if (mRenderer->mFastTransferExtensionAvailable) { // Finished with mRenderer->mCaptureTexture - if (!mHasNoInputSource) + if (!mDeckLink->hasNoInputSource) VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); - if (! mPlayoutAllocator->transferFrame(pFrame, mRenderer->mOutputTexture)) + if (! mDeckLink->playoutAllocator->transferFrame(pFrame, mRenderer->mOutputTexture)) OutputDebugStringA("Playback: transferFrame() failed\n"); paintGL(); // Wait for transfer to system memory to complete - mPlayoutAllocator->waitForTransferComplete(pFrame); + mDeckLink->playoutAllocator->waitForTransferComplete(pFrame); } else { glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->mOutputFrameBuf); - glReadPixels(0, 0, mOutputFrameWidth, mOutputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame); + glReadPixels(0, 0, mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame); paintGL(); } @@ -769,12 +690,12 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, // If the last completed frame was late or dropped, bump the scheduled time further into the future if (completionResult == bmdOutputFrameDisplayedLate || completionResult == bmdOutputFrameDropped) - mTotalPlayoutFrames += 2; + mDeckLink->totalPlayoutFrames += 2; // Schedule the next frame for playout - HRESULT hr = mDLOutput->ScheduleVideoFrame(outputVideoFrame, (mTotalPlayoutFrames * mFrameDuration), mFrameDuration, mFrameTimescale); + HRESULT hr = mDeckLink->output->ScheduleVideoFrame(outputVideoFrame, (mDeckLink->totalPlayoutFrames * mDeckLink->frameDuration), mDeckLink->frameDuration, mDeckLink->frameTimescale); if (SUCCEEDED(hr)) - mTotalPlayoutFrames++; + mDeckLink->totalPlayoutFrames++; wglMakeCurrent( NULL, NULL ); @@ -783,72 +704,7 @@ 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++) - { - // Take each video frame from the front of the queue and move it to the back - IDeckLinkMutableVideoFrame* outputVideoFrame = mDLOutputVideoFrameQueue.front(); - mDLOutputVideoFrameQueue.push_back(outputVideoFrame); - mDLOutputVideoFrameQueue.pop_front(); - - // 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; - } - - void* pFrame; - outputVideoFrameBuffer->GetBytes((void**)&pFrame); - memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mOutputFrameHeight); // 0 is black in BGRA format - - outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); - 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++; - } - - 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; + return mDeckLink->Start(mDeckLink->outputFrameHeight); } bool OpenGLComposite::Stop() @@ -859,33 +715,18 @@ bool OpenGLComposite::Stop() if (mControlServer) mControlServer->Stop(); - if (mDLKeyer != NULL) + const bool wasExternalKeyingActive = mDeckLink->externalKeyingActive; + mDeckLink->Stop(); + if (wasExternalKeyingActive && mRuntimeHost) { - mDLKeyer->Disable(); - mDeckLinkExternalKeyingActive = false; - if (mRuntimeHost) - { - mRuntimeHost->SetDeckLinkOutputStatus( - mDeckLinkOutputModelName, - mDeckLinkSupportsInternalKeying, - mDeckLinkSupportsExternalKeying, - mDeckLinkKeyerInterfaceAvailable, - mRuntimeHost->ExternalKeyingEnabled(), - mDeckLinkExternalKeyingActive, - "External keying has been disabled."); - } - } - - if (mDLInput) - { - mDLInput->StopStreams(); - mDLInput->DisableVideoInput(); - } - - if (mDLOutput) - { - mDLOutput->StopScheduledPlayback(0, NULL, 0); - mDLOutput->DisableVideoOutput(); + mRuntimeHost->SetDeckLinkOutputStatus( + mDeckLink->outputModelName, + mDeckLink->supportsInternalKeying, + mDeckLink->supportsExternalKeying, + mDeckLink->keyerInterfaceAvailable, + mRuntimeHost->ExternalKeyingEnabled(), + mDeckLink->externalKeyingActive, + "External keying has been disabled."); } return true; @@ -898,7 +739,7 @@ bool OpenGLComposite::ReloadShader() EnterCriticalSection(&pMutex); wglMakeCurrent(hGLDC, hGLRC); - bool success = mShaderPrograms->CompileLayerPrograms(mInputFrameWidth, mInputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage); + bool success = mShaderPrograms->CompileLayerPrograms(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage); if (mRuntimeHost) mRuntimeHost->ClearReloadRequest(); @@ -925,14 +766,14 @@ void OpenGLComposite::renderEffect() { PollRuntimeChanges(); - const bool hasInputSource = !mHasNoInputSource; - const std::vector layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mInputFrameWidth, mInputFrameHeight) : std::vector(); + const bool hasInputSource = !mDeckLink->hasNoInputSource; + const std::vector layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight) : std::vector(); const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; mRenderPass->Render( hasInputSource, layerStates, - mInputFrameWidth, - mInputFrameHeight, + mDeckLink->inputFrameWidth, + mDeckLink->inputFrameHeight, historyCap, [this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) { return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error); @@ -964,7 +805,7 @@ bool OpenGLComposite::PollRuntimeChanges() return true; char compilerErrorMessage[1024] = {}; - if (!mShaderPrograms->CompileLayerPrograms(mInputFrameWidth, mInputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage)) { mRuntimeHost->SetCompileStatus(false, compilerErrorMessage); mRuntimeHost->ClearReloadRequest(); @@ -1000,3 +841,4 @@ bool OpenGLComposite::CheckOpenGLExtensions() //////////////////////////////////////////// + diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index c359b93..9aab710 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -67,6 +67,7 @@ class PlayoutDelegate; class CaptureDelegate; class PinnedMemoryAllocator; class ControlServer; +class DeckLinkSession; class OscServer; class OpenGLRenderPass; class OpenGLShaderPrograms; @@ -106,36 +107,12 @@ private: bool CheckOpenGLExtensions(); using LayerProgram = OpenGLRenderer::LayerProgram; - CaptureDelegate* mCaptureDelegate; - PlayoutDelegate* mPlayoutDelegate; HWND hGLWnd; HDC hGLDC; HGLRC hGLRC; CRITICAL_SECTION pMutex; - // DeckLink - IDeckLinkInput* mDLInput; - IDeckLinkOutput* mDLOutput; - IDeckLinkKeyer* mDLKeyer; - std::deque mDLOutputVideoFrameQueue; - PinnedMemoryAllocator* mPlayoutAllocator; - BMDTimeValue mFrameDuration; - BMDTimeScale mFrameTimescale; - unsigned mTotalPlayoutFrames; - unsigned mInputFrameWidth; - unsigned mInputFrameHeight; - unsigned mOutputFrameWidth; - unsigned mOutputFrameHeight; - std::string mInputDisplayModeName; - std::string mOutputDisplayModeName; - bool mHasNoInputSource; - std::string mDeckLinkOutputModelName; - bool mDeckLinkSupportsInternalKeying; - bool mDeckLinkSupportsExternalKeying; - bool mDeckLinkKeyerInterfaceAvailable; - bool mDeckLinkExternalKeyingActive; - std::string mDeckLinkStatusMessage; - + std::unique_ptr mDeckLink; std::unique_ptr mRenderer; std::unique_ptr mRuntimeHost; std::unique_ptr mRenderPass;