From 4ffbb97abf1659905c68778a66b0371bc9b1552e Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Sat, 9 May 2026 14:15:49 +1000 Subject: [PATCH] Video backend --- CMakeLists.txt | 71 ++++++++++++ .../LoopThroughWithOpenGLCompositing.cpp | 12 +- .../gl/OpenGLComposite.cpp | 78 ++++++------- .../gl/OpenGLComposite.h | 3 +- .../gl/pipeline/OpenGLVideoIOBridge.cpp | 25 +++-- .../gl/pipeline/OpenGLVideoIOBridge.h | 6 +- .../runtime/RuntimeHost.cpp | 105 +++++++++--------- .../runtime/RuntimeHost.h | 36 +++--- .../videoio/VideoIOBackendFactory.cpp | 16 +++ .../videoio/VideoIOBackendFactory.h | 10 ++ .../videoio/VideoIOConfig.cpp | 35 ++++++ .../videoio/VideoIOConfig.h | 44 ++++++++ .../videoio/VideoIOTypes.h | 41 ++++--- .../videoio/decklink/DeckLinkDisplayMode.cpp | 33 +++--- .../videoio/decklink/DeckLinkDisplayMode.h | 40 ++----- .../videoio/decklink/DeckLinkSession.cpp | 59 +++++----- .../videoio/decklink/DeckLinkSession.h | 38 +------ config/runtime-host.json | 1 + tests/RuntimeHostVideoIOStateTests.cpp | 67 +++++++++++ tests/VideoIOBackendFactoryTests.cpp | 41 +++++++ tests/VideoIODeviceFakeTests.cpp | 26 +++-- 21 files changed, 512 insertions(+), 275 deletions(-) create mode 100644 apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.h create mode 100644 apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.h create mode 100644 tests/RuntimeHostVideoIOStateTests.cpp create mode 100644 tests/VideoIOBackendFactoryTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e110aea..47b15a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,10 @@ set(APP_SOURCES "${APP_DIR}/videoio/decklink/DeckLinkSession.h" "${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp" "${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h" + "${APP_DIR}/videoio/VideoIOBackendFactory.cpp" + "${APP_DIR}/videoio/VideoIOBackendFactory.h" + "${APP_DIR}/videoio/VideoIOConfig.cpp" + "${APP_DIR}/videoio/VideoIOConfig.h" "${APP_DIR}/gl/renderer/GLExtensions.cpp" "${APP_DIR}/gl/renderer/GLExtensions.h" "${APP_DIR}/gl/shader/GlobalParamsBuffer.cpp" @@ -204,6 +208,35 @@ endif() add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests) +add_executable(RuntimeHostVideoIOStateTests + "${APP_DIR}/runtime/RuntimeHost.cpp" + "${APP_DIR}/runtime/RuntimeClock.cpp" + "${APP_DIR}/runtime/RuntimeJson.cpp" + "${APP_DIR}/runtime/RuntimeParameterUtils.cpp" + "${APP_DIR}/shader/ShaderCompiler.cpp" + "${APP_DIR}/shader/ShaderPackageRegistry.cpp" + "${APP_DIR}/videoio/VideoIOConfig.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeHostVideoIOStateTests.cpp" +) + +target_include_directories(RuntimeHostVideoIOStateTests PRIVATE + "${APP_DIR}" + "${APP_DIR}/platform" + "${APP_DIR}/runtime" + "${APP_DIR}/shader" + "${APP_DIR}/videoio" +) + +target_link_libraries(RuntimeHostVideoIOStateTests PRIVATE + Advapi32 +) + +if(MSVC) + target_compile_options(RuntimeHostVideoIOStateTests PRIVATE /W3) +endif() + +add_test(NAME RuntimeHostVideoIOStateTests COMMAND RuntimeHostVideoIOStateTests) + add_executable(Std140BufferTests "${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp" ) @@ -318,6 +351,7 @@ endif() add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests) add_executable(VideoIODeviceFakeTests + "${APP_DIR}/videoio/VideoIOConfig.cpp" "${APP_DIR}/videoio/VideoIOFormat.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp" ) @@ -334,6 +368,43 @@ endif() add_test(NAME VideoIODeviceFakeTests COMMAND VideoIODeviceFakeTests) +add_executable(VideoIOBackendFactoryTests + "${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c" + "${APP_DIR}/videoio/decklink/DeckLinkSession.cpp" + "${APP_DIR}/videoio/decklink/DeckLinkSession.h" + "${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.cpp" + "${APP_DIR}/videoio/decklink/DeckLinkDisplayMode.h" + "${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.cpp" + "${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h" + "${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.cpp" + "${APP_DIR}/videoio/decklink/DeckLinkFrameTransfer.h" + "${APP_DIR}/videoio/VideoIOBackendFactory.cpp" + "${APP_DIR}/videoio/VideoIOBackendFactory.h" + "${APP_DIR}/videoio/VideoIOConfig.cpp" + "${APP_DIR}/videoio/VideoIOConfig.h" + "${APP_DIR}/videoio/VideoIOFormat.cpp" + "${APP_DIR}/videoio/VideoPlayoutScheduler.cpp" + "${APP_DIR}/videoio/VideoPlayoutScheduler.h" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIOBackendFactoryTests.cpp" +) + +target_include_directories(VideoIOBackendFactoryTests PRIVATE + "${APP_DIR}" + "${APP_DIR}/gl/renderer" + "${APP_DIR}/videoio" + "${APP_DIR}/videoio/decklink" +) + +target_link_libraries(VideoIOBackendFactoryTests PRIVATE + Ole32 +) + +if(MSVC) + target_compile_options(VideoIOBackendFactoryTests PRIVATE /W3) +endif() + +add_test(NAME VideoIOBackendFactoryTests COMMAND VideoIOBackendFactoryTests) + install(TARGETS LoopThroughWithOpenGLCompositing RUNTIME DESTINATION "." ) diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp index 5ce3e30..16ec07c 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp @@ -412,10 +412,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; } - // Setup OpenGL and DeckLink capture and playout object + // Setup OpenGL and video I/O capture/playout object pOpenGLComposite = new OpenGLComposite(hWnd, hDC, hRC); - if (pOpenGLComposite->InitDeckLink()) + if (pOpenGLComposite->InitializeVideoIO()) { wglMakeCurrent( NULL, NULL ); if (pOpenGLComposite->Start()) @@ -423,11 +423,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) PostMessage(hWnd, kCreateStatusStripMessage, 0, 0); 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); + MessageBoxA(NULL, "The OpenGL/video I/O runtime initialized, but playout failed to start. See the previous 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); + MessageBoxA(NULL, "The OpenGL/video I/O runtime failed to initialize. See the previous initialization message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR); } // Failed to initialize - cleanup @@ -438,7 +438,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } catch (...) { - ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/DeckLink runtime."); + ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/video I/O runtime."); PostMessage(hWnd, WM_CLOSE, 0, 0); break; } @@ -474,7 +474,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } catch (...) { - ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/DeckLink runtime."); + ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/video I/O runtime."); } // Deselect the current rendering context and delete it diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index d46322b..1efc5db 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -1,5 +1,3 @@ -#include "DeckLinkDisplayMode.h" -#include "DeckLinkSession.h" #include "OpenGLComposite.h" #include "GLExtensions.h" #include "GlRenderConstants.h" @@ -10,6 +8,7 @@ #include "PngScreenshotWriter.h" #include "RuntimeServices.h" #include "ShaderBuildQueue.h" +#include "VideoIOBackendFactory.h" #include #include @@ -23,7 +22,6 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC), - mVideoIO(std::make_unique()), mRenderer(std::make_unique()), mUseCommittedLayerStates(false), mScreenshotRequested(false) @@ -37,7 +35,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : [this]() { ProcessScreenshotRequest(); }, [this]() { paintGL(); }); mVideoIOBridge = std::make_unique( - *mVideoIO, + nullptr, *mRenderer, *mRenderPipeline, *mRuntimeHost, @@ -56,20 +54,15 @@ OpenGLComposite::~OpenGLComposite() mRuntimeServices->Stop(); if (mShaderBuildQueue) mShaderBuildQueue->Stop(); - mVideoIO->ReleaseResources(); + if (mVideoIO) + mVideoIO->ReleaseResources(); mRenderer->DestroyResources(); DeleteCriticalSection(&pMutex); } -bool OpenGLComposite::InitDeckLink() +bool OpenGLComposite::InitializeVideoIO() { - return InitVideoIO(); -} - -bool OpenGLComposite::InitVideoIO() -{ - VideoFormatSelection videoModes; std::string initFailureReason; if (mRuntimeHost && mRuntimeHost->GetRepoRoot().empty()) @@ -82,31 +75,31 @@ bool OpenGLComposite::InitVideoIO() } } - if (mRuntimeHost) + if (!mRuntimeHost) { - if (!ResolveConfiguredVideoFormats( - mRuntimeHost->GetInputVideoFormat(), - mRuntimeHost->GetInputFrameRate(), - mRuntimeHost->GetOutputVideoFormat(), - mRuntimeHost->GetOutputFrameRate(), - videoModes, - initFailureReason)) - { - MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK); - return false; - } + initFailureReason = "Runtime host is not available."; + MessageBoxA(NULL, initFailureReason.c_str(), "Video I/O initialization failed", MB_OK | MB_ICONERROR); + return false; } - if (!mVideoIO->DiscoverDevicesAndModes(videoModes, initFailureReason)) + const VideoIOConfiguration videoIOConfig = mRuntimeHost->GetVideoIOConfiguration(); + mVideoIO = CreateVideoIODevice(videoIOConfig.backendId, initFailureReason); + if (!mVideoIO) + { + MessageBoxA(NULL, initFailureReason.c_str(), "Video I/O initialization failed", MB_OK | MB_ICONERROR); + return false; + } + mVideoIOBridge->SetVideoIODevice(mVideoIO.get()); + + if (!mVideoIO->DiscoverDevicesAndModes(videoIOConfig, initFailureReason)) { const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application." - ? "This application requires the DeckLink drivers installed." - : "DeckLink initialization failed"; + ? "This application requires the selected video I/O drivers installed." + : "Video I/O initialization failed"; MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR); return false; } - const bool outputAlphaRequired = mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(); - if (!mVideoIO->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason)) + if (!mVideoIO->SelectPreferredFormats(videoIOConfig, initFailureReason)) goto error; if (! CheckOpenGLExtensions()) @@ -121,9 +114,9 @@ bool OpenGLComposite::InitVideoIO() goto error; } - PublishVideoIOStatus(mVideoIO->OutputModelName().empty() - ? "DeckLink output device selected." - : ("Selected output device: " + mVideoIO->OutputModelName())); + PublishVideoIOStatus(mVideoIO->DeviceName().empty() + ? "Video I/O output device selected." + : ("Selected output device: " + mVideoIO->DeviceName())); // Resize window to match output video frame, but scale large formats down by half for viewing. if (mVideoIO->OutputFrameWidth() < 1920) @@ -131,7 +124,7 @@ bool OpenGLComposite::InitVideoIO() else resizeWindow(mVideoIO->OutputFrameWidth() / 2, mVideoIO->OutputFrameHeight() / 2); - if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, videoModes.input, initFailureReason)) + if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, initFailureReason)) { goto error; } @@ -140,7 +133,7 @@ bool OpenGLComposite::InitVideoIO() mRuntimeHost->SetSignalStatus(false, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->InputDisplayModeName()); } - if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, videoModes.output, mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(), initFailureReason)) + if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, initFailureReason)) { goto error; } @@ -151,13 +144,16 @@ bool OpenGLComposite::InitVideoIO() error: if (!initFailureReason.empty()) - MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR); + MessageBoxA(NULL, initFailureReason.c_str(), "Video I/O initialization failed", MB_OK | MB_ICONERROR); mVideoIO->ReleaseResources(); return false; } void OpenGLComposite::paintGL() { + if (!mVideoIO) + return; + if (!TryEnterCriticalSection(&pMutex)) { ValidateRect(hGLWnd, NULL); @@ -187,21 +183,13 @@ void OpenGLComposite::resizeWindow(int width, int height) void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage) { - if (!mRuntimeHost) + if (!mRuntimeHost || !mVideoIO) return; if (!statusMessage.empty()) mVideoIO->SetStatusMessage(statusMessage); - mRuntimeHost->SetVideoIOStatus( - "decklink", - mVideoIO->OutputModelName(), - mVideoIO->SupportsInternalKeying(), - mVideoIO->SupportsExternalKeying(), - mVideoIO->KeyerInterfaceAvailable(), - mRuntimeHost->ExternalKeyingEnabled(), - mVideoIO->ExternalKeyingActive(), - mVideoIO->StatusMessage()); + mRuntimeHost->SetVideoIOStatus(mVideoIO->State()); } bool OpenGLComposite::InitOpenGLState() diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index 7aa49e4..a889591 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -39,8 +39,7 @@ public: OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC); ~OpenGLComposite(); - bool InitDeckLink(); - bool InitVideoIO(); + bool InitializeVideoIO(); bool Start(); bool Stop(); bool ReloadShader(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp index 658ecd8..904f399 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp @@ -7,7 +7,7 @@ #include OpenGLVideoIOBridge::OpenGLVideoIOBridge( - VideoIODevice& videoIO, + VideoIODevice* videoIO, OpenGLRenderer& renderer, OpenGLRenderPipeline& renderPipeline, RuntimeHost& runtimeHost, @@ -24,6 +24,11 @@ OpenGLVideoIOBridge::OpenGLVideoIOBridge( { } +void OpenGLVideoIOBridge::SetVideoIODevice(VideoIODevice* videoIO) +{ + mVideoIO = videoIO; +} + void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionResult) { const auto now = std::chrono::steady_clock::now(); @@ -57,7 +62,10 @@ void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionRe void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame) { - const VideoIOState& state = mVideoIO.State(); + if (mVideoIO == nullptr) + return; + + const VideoIOState& state = mVideoIO->State(); mRuntimeHost.TrySetSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName); if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr) @@ -91,17 +99,20 @@ void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame) void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion) { + if (mVideoIO == nullptr) + return; + RecordFramePacing(completion.result); EnterCriticalSection(&mMutex); VideoIOOutputFrame outputFrame; - if (!mVideoIO.BeginOutputFrame(outputFrame)) + if (!mVideoIO->BeginOutputFrame(outputFrame)) { LeaveCriticalSection(&mMutex); return; } - const VideoIOState& state = mVideoIO.State(); + const VideoIOState& state = mVideoIO->State(); RenderPipelineFrameContext frameContext; frameContext.videoState = state; frameContext.completion = completion; @@ -111,12 +122,12 @@ void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& complet mRenderPipeline.RenderFrame(frameContext, outputFrame); - mVideoIO.EndOutputFrame(outputFrame); + mVideoIO->EndOutputFrame(outputFrame); - mVideoIO.AccountForCompletionResult(completion.result); + mVideoIO->AccountForCompletionResult(completion.result); // Schedule the next frame for playout - mVideoIO.ScheduleOutputFrame(outputFrame); + mVideoIO->ScheduleOutputFrame(outputFrame); wglMakeCurrent(NULL, NULL); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h index 7cca4f8..47cef08 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h @@ -13,7 +13,7 @@ class OpenGLVideoIOBridge { public: OpenGLVideoIOBridge( - VideoIODevice& videoIO, + VideoIODevice* videoIO, OpenGLRenderer& renderer, OpenGLRenderPipeline& renderPipeline, RuntimeHost& runtimeHost, @@ -21,13 +21,15 @@ public: HDC hdc, HGLRC hglrc); + void SetVideoIODevice(VideoIODevice* videoIO); + void VideoFrameArrived(const VideoIOFrame& inputFrame); void PlayoutFrameCompleted(const VideoIOCompletion& completion); private: void RecordFramePacing(VideoIOCompletionResult completionResult); - VideoIODevice& mVideoIO; + VideoIODevice* mVideoIO; OpenGLRenderer& mRenderer; OpenGLRenderPipeline& mRenderPipeline; RuntimeHost& mRuntimeHost; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index 56b850f..1ce10ba 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -1228,25 +1228,20 @@ void RuntimeHost::MarkRenderStateDirtyLocked() mRenderStateVersion.fetch_add(1, std::memory_order_relaxed); } -void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying, - bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage) -{ - SetVideoIOStatus("decklink", modelName, supportsInternalKeying, supportsExternalKeying, keyerInterfaceAvailable, - externalKeyingRequested, externalKeyingActive, statusMessage); -} - -void RuntimeHost::SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying, - bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage) +void RuntimeHost::SetVideoIOStatus(const VideoIOState& state) { std::lock_guard lock(mMutex); - mDeckLinkOutputStatus.backendName = backendName; - mDeckLinkOutputStatus.modelName = modelName; - mDeckLinkOutputStatus.supportsInternalKeying = supportsInternalKeying; - mDeckLinkOutputStatus.supportsExternalKeying = supportsExternalKeying; - mDeckLinkOutputStatus.keyerInterfaceAvailable = keyerInterfaceAvailable; - mDeckLinkOutputStatus.externalKeyingRequested = externalKeyingRequested; - mDeckLinkOutputStatus.externalKeyingActive = externalKeyingActive; - mDeckLinkOutputStatus.statusMessage = statusMessage; + mVideoIOStatus.backendId = state.backendId; + mVideoIOStatus.deviceName = state.deviceName; + mVideoIOStatus.capabilities = state.capabilities; + mVideoIOStatus.hasInputDevice = state.hasInputDevice; + mVideoIOStatus.hasInputSource = state.hasInputSource; + mVideoIOStatus.inputDisplayModeName = state.inputDisplayModeName; + mVideoIOStatus.outputDisplayModeName = state.outputDisplayModeName; + mVideoIOStatus.externalKeyingRequested = state.externalKeyingRequested; + mVideoIOStatus.externalKeyingActive = state.externalKeyingActive; + mVideoIOStatus.statusMessage = state.statusMessage; + mVideoIOStatus.formatStatusMessage = state.formatStatusMessage; } void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) @@ -1481,61 +1476,67 @@ bool RuntimeHost::LoadConfig(std::string& error) const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast(mConfig.maxTemporalHistoryFrames)); mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast(configuredValue); } + if (const JsonValue* videoBackendValue = configJson.find("videoBackend")) + { + VideoIOBackendId backendId = mConfig.videoIO.backendId; + if (videoBackendValue->isString() && ParseVideoIOBackendId(videoBackendValue->asString(), backendId)) + mConfig.videoIO.backendId = backendId; + } if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying")) - mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying); + mConfig.videoIO.externalKeyingEnabled = enableExternalKeyingValue->asBoolean(mConfig.videoIO.externalKeyingEnabled); if (const JsonValue* videoFormatValue = configJson.find("videoFormat")) { if (videoFormatValue->isString() && !videoFormatValue->asString().empty()) { - mConfig.inputVideoFormat = videoFormatValue->asString(); - mConfig.outputVideoFormat = videoFormatValue->asString(); + mConfig.videoIO.inputMode.videoFormat = videoFormatValue->asString(); + mConfig.videoIO.outputMode.videoFormat = videoFormatValue->asString(); } } if (const JsonValue* frameRateValue = configJson.find("frameRate")) { if (frameRateValue->isString() && !frameRateValue->asString().empty()) { - mConfig.inputFrameRate = frameRateValue->asString(); - mConfig.outputFrameRate = frameRateValue->asString(); + mConfig.videoIO.inputMode.frameRate = frameRateValue->asString(); + mConfig.videoIO.outputMode.frameRate = frameRateValue->asString(); } else if (frameRateValue->isNumber()) { std::ostringstream stream; stream << frameRateValue->asNumber(); - mConfig.inputFrameRate = stream.str(); - mConfig.outputFrameRate = stream.str(); + mConfig.videoIO.inputMode.frameRate = stream.str(); + mConfig.videoIO.outputMode.frameRate = stream.str(); } } if (const JsonValue* inputVideoFormatValue = configJson.find("inputVideoFormat")) { if (inputVideoFormatValue->isString() && !inputVideoFormatValue->asString().empty()) - mConfig.inputVideoFormat = inputVideoFormatValue->asString(); + mConfig.videoIO.inputMode.videoFormat = inputVideoFormatValue->asString(); } if (const JsonValue* inputFrameRateValue = configJson.find("inputFrameRate")) { if (inputFrameRateValue->isString() && !inputFrameRateValue->asString().empty()) - mConfig.inputFrameRate = inputFrameRateValue->asString(); + mConfig.videoIO.inputMode.frameRate = inputFrameRateValue->asString(); else if (inputFrameRateValue->isNumber()) { std::ostringstream stream; stream << inputFrameRateValue->asNumber(); - mConfig.inputFrameRate = stream.str(); + mConfig.videoIO.inputMode.frameRate = stream.str(); } } if (const JsonValue* outputVideoFormatValue = configJson.find("outputVideoFormat")) { if (outputVideoFormatValue->isString() && !outputVideoFormatValue->asString().empty()) - mConfig.outputVideoFormat = outputVideoFormatValue->asString(); + mConfig.videoIO.outputMode.videoFormat = outputVideoFormatValue->asString(); } if (const JsonValue* outputFrameRateValue = configJson.find("outputFrameRate")) { if (outputFrameRateValue->isString() && !outputFrameRateValue->asString().empty()) - mConfig.outputFrameRate = outputFrameRateValue->asString(); + mConfig.videoIO.outputMode.frameRate = outputFrameRateValue->asString(); else if (outputFrameRateValue->isNumber()) { std::ostringstream stream; stream << outputFrameRateValue->asNumber(); - mConfig.outputFrameRate = stream.str(); + mConfig.videoIO.outputMode.frameRate = stream.str(); } } @@ -1867,11 +1868,12 @@ JsonValue RuntimeHost::BuildStateValue() const app.set("oscPort", JsonValue(static_cast(mConfig.oscPort))); app.set("autoReload", JsonValue(mAutoReloadEnabled)); app.set("maxTemporalHistoryFrames", JsonValue(static_cast(mConfig.maxTemporalHistoryFrames))); - app.set("enableExternalKeying", JsonValue(mConfig.enableExternalKeying)); - app.set("inputVideoFormat", JsonValue(mConfig.inputVideoFormat)); - app.set("inputFrameRate", JsonValue(mConfig.inputFrameRate)); - app.set("outputVideoFormat", JsonValue(mConfig.outputVideoFormat)); - app.set("outputFrameRate", JsonValue(mConfig.outputFrameRate)); + app.set("videoBackend", JsonValue(VideoIOBackendName(mConfig.videoIO.backendId))); + app.set("enableExternalKeying", JsonValue(mConfig.videoIO.externalKeyingEnabled)); + app.set("inputVideoFormat", JsonValue(mConfig.videoIO.inputMode.videoFormat)); + app.set("inputFrameRate", JsonValue(mConfig.videoIO.inputMode.frameRate)); + app.set("outputVideoFormat", JsonValue(mConfig.videoIO.outputMode.videoFormat)); + app.set("outputFrameRate", JsonValue(mConfig.videoIO.outputMode.frameRate)); root.set("app", app); JsonValue runtime = JsonValue::MakeObject(); @@ -1887,25 +1889,22 @@ JsonValue RuntimeHost::BuildStateValue() const video.set("modeName", JsonValue(mSignalModeName)); root.set("video", video); - JsonValue deckLink = JsonValue::MakeObject(); - deckLink.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName)); - deckLink.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying)); - deckLink.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying)); - deckLink.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable)); - deckLink.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested)); - deckLink.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive)); - deckLink.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage)); - root.set("decklink", deckLink); - JsonValue videoIO = JsonValue::MakeObject(); - videoIO.set("backend", JsonValue(mDeckLinkOutputStatus.backendName)); - videoIO.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName)); - videoIO.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying)); - videoIO.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying)); - videoIO.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable)); - videoIO.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested)); - videoIO.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive)); - videoIO.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage)); + videoIO.set("backend", JsonValue(VideoIOBackendName(mVideoIOStatus.backendId))); + videoIO.set("deviceName", JsonValue(mVideoIOStatus.deviceName)); + videoIO.set("hasInputDevice", JsonValue(mVideoIOStatus.hasInputDevice)); + videoIO.set("hasInputSource", JsonValue(mVideoIOStatus.hasInputSource)); + videoIO.set("inputModeName", JsonValue(mVideoIOStatus.inputDisplayModeName)); + videoIO.set("outputModeName", JsonValue(mVideoIOStatus.outputDisplayModeName)); + JsonValue capabilities = JsonValue::MakeObject(); + capabilities.set("supportsInternalKeying", JsonValue(mVideoIOStatus.capabilities.supportsInternalKeying)); + capabilities.set("supportsExternalKeying", JsonValue(mVideoIOStatus.capabilities.supportsExternalKeying)); + capabilities.set("keyerInterfaceAvailable", JsonValue(mVideoIOStatus.capabilities.keyerInterfaceAvailable)); + videoIO.set("capabilities", capabilities); + videoIO.set("externalKeyingRequested", JsonValue(mVideoIOStatus.externalKeyingRequested)); + videoIO.set("externalKeyingActive", JsonValue(mVideoIOStatus.externalKeyingActive)); + videoIO.set("statusMessage", JsonValue(mVideoIOStatus.statusMessage)); + videoIO.set("formatStatusMessage", JsonValue(mVideoIOStatus.formatStatusMessage)); root.set("videoIO", videoIO); JsonValue performance = JsonValue::MakeObject(); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index 8c075d8..66b3b39 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -2,6 +2,7 @@ #include "RuntimeJson.h" #include "ShaderTypes.h" +#include "VideoIOTypes.h" #include #include @@ -38,10 +39,7 @@ public: void SetCompileStatus(bool succeeded, const std::string& message); void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); - void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying, - bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage); - void SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying, - bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage); + void SetVideoIOStatus(const VideoIOState& state); void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, @@ -65,11 +63,8 @@ public: unsigned short GetServerPort() const { return mServerPort; } unsigned short GetOscPort() const { return mConfig.oscPort; } unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; } - bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; } - const std::string& GetInputVideoFormat() const { return mConfig.inputVideoFormat; } - const std::string& GetInputFrameRate() const { return mConfig.inputFrameRate; } - const std::string& GetOutputVideoFormat() const { return mConfig.outputVideoFormat; } - const std::string& GetOutputFrameRate() const { return mConfig.outputFrameRate; } + bool ExternalKeyingEnabled() const { return mConfig.videoIO.externalKeyingEnabled; } + VideoIOConfiguration GetVideoIOConfiguration() const { return mConfig.videoIO; } void SetServerPort(unsigned short port); bool AutoReloadEnabled() const { return mAutoReloadEnabled; } @@ -81,23 +76,22 @@ private: unsigned short oscPort = 9000; bool autoReload = true; unsigned maxTemporalHistoryFrames = 4; - bool enableExternalKeying = false; - std::string inputVideoFormat = "1080p"; - std::string inputFrameRate = "59.94"; - std::string outputVideoFormat = "1080p"; - std::string outputFrameRate = "59.94"; + VideoIOConfiguration videoIO; }; - struct DeckLinkOutputStatus + struct VideoIOStatusSnapshot { - std::string backendName = "decklink"; - std::string modelName; - bool supportsInternalKeying = false; - bool supportsExternalKeying = false; - bool keyerInterfaceAvailable = false; + VideoIOBackendId backendId = VideoIOBackendId::DeckLink; + std::string deviceName; + VideoIOCapabilities capabilities; + bool hasInputDevice = false; + bool hasInputSource = false; + std::string inputDisplayModeName = "1080p59.94"; + std::string outputDisplayModeName = "1080p59.94"; bool externalKeyingRequested = false; bool externalKeyingActive = false; std::string statusMessage; + std::string formatStatusMessage; }; struct LayerPersistentState @@ -176,7 +170,7 @@ private: uint64_t mLateFrameCount; uint64_t mDroppedFrameCount; uint64_t mFlushedFrameCount; - DeckLinkOutputStatus mDeckLinkOutputStatus; + VideoIOStatusSnapshot mVideoIOStatus; unsigned short mServerPort; bool mAutoReloadEnabled; std::chrono::steady_clock::time_point mStartTime; diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.cpp new file mode 100644 index 0000000..7ec5b60 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.cpp @@ -0,0 +1,16 @@ +#include "VideoIOBackendFactory.h" + +#include "DeckLinkSession.h" +#include "VideoIOTypes.h" + +std::unique_ptr CreateVideoIODevice(VideoIOBackendId backendId, std::string& error) +{ + switch (backendId) + { + case VideoIOBackendId::DeckLink: + return std::make_unique(); + } + + error = "Unsupported video I/O backend."; + return nullptr; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.h b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.h new file mode 100644 index 0000000..be41432 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOBackendFactory.h @@ -0,0 +1,10 @@ +#pragma once + +#include "VideoIOConfig.h" + +#include +#include + +class VideoIODevice; + +std::unique_ptr CreateVideoIODevice(VideoIOBackendId backendId, std::string& error); diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.cpp new file mode 100644 index 0000000..0e03338 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.cpp @@ -0,0 +1,35 @@ +#include "VideoIOConfig.h" + +#include +#include + +namespace +{ +std::string NormalizeToken(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + return value; +} +} + +const char* VideoIOBackendName(VideoIOBackendId backendId) +{ + switch (backendId) + { + case VideoIOBackendId::DeckLink: + return "decklink"; + } + return "unknown"; +} + +bool ParseVideoIOBackendId(const std::string& value, VideoIOBackendId& backendId) +{ + const std::string normalized = NormalizeToken(value); + if (normalized.empty() || normalized == "decklink") + { + backendId = VideoIOBackendId::DeckLink; + return true; + } + return false; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.h b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.h new file mode 100644 index 0000000..aa45597 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOConfig.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +enum class VideoIOBackendId +{ + DeckLink +}; + +const char* VideoIOBackendName(VideoIOBackendId backendId); +bool ParseVideoIOBackendId(const std::string& value, VideoIOBackendId& backendId); + +struct FrameSize +{ + unsigned width = 0; + unsigned height = 0; + + bool IsEmpty() const { return width == 0 || height == 0; } +}; + +inline bool operator==(const FrameSize& left, const FrameSize& right) +{ + return left.width == right.width && left.height == right.height; +} + +inline bool operator!=(const FrameSize& left, const FrameSize& right) +{ + return !(left == right); +} + +struct VideoIOModeConfiguration +{ + std::string videoFormat = "1080p"; + std::string frameRate = "59.94"; +}; + +struct VideoIOConfiguration +{ + VideoIOBackendId backendId = VideoIOBackendId::DeckLink; + VideoIOModeConfiguration inputMode; + VideoIOModeConfiguration outputMode; + bool externalKeyingEnabled = false; + bool preferTenBit = true; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h index aa75fcf..b5cb65b 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h @@ -1,15 +1,17 @@ #pragma once -#include "DeckLinkDisplayMode.h" +#include "VideoIOConfig.h" #include "VideoIOFormat.h" #include #include #include -enum class VideoIOBackend +struct VideoIOCapabilities { - DeckLink + bool supportsInternalKeying = false; + bool supportsExternalKeying = false; + bool keyerInterfaceAvailable = false; }; enum class VideoIOCompletionResult @@ -21,15 +23,9 @@ enum class VideoIOCompletionResult Unknown }; -struct VideoIOConfig -{ - VideoFormatSelection videoModes; - bool externalKeyingEnabled = false; - bool preferTenBit = true; -}; - struct VideoIOState { + VideoIOBackendId backendId = VideoIOBackendId::DeckLink; FrameSize inputFrameSize; FrameSize outputFrameSize; VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8; @@ -40,14 +36,13 @@ struct VideoIOState unsigned outputPackTextureWidth = 0; std::string inputDisplayModeName = "1080p59.94"; std::string outputDisplayModeName = "1080p59.94"; - std::string outputModelName; + std::string deviceName; std::string statusMessage; std::string formatStatusMessage; bool hasInputDevice = false; bool hasInputSource = false; - bool supportsInternalKeying = false; - bool supportsExternalKeying = false; - bool keyerInterfaceAvailable = false; + VideoIOCapabilities capabilities; + bool externalKeyingRequested = false; bool externalKeyingActive = false; double frameBudgetMilliseconds = 0.0; }; @@ -93,11 +88,12 @@ public: using OutputFrameCallback = std::function; virtual ~VideoIODevice() = default; + virtual VideoIOBackendId BackendId() const = 0; virtual void ReleaseResources() = 0; - virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0; - virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) = 0; - virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0; - virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0; + virtual bool DiscoverDevicesAndModes(const VideoIOConfiguration& config, std::string& error) = 0; + virtual bool SelectPreferredFormats(const VideoIOConfiguration& config, std::string& error) = 0; + virtual bool ConfigureInput(InputFrameCallback callback, std::string& error) = 0; + virtual bool ConfigureOutput(OutputFrameCallback callback, std::string& error) = 0; virtual bool Start() = 0; virtual bool Stop() = 0; virtual const VideoIOState& State() const = 0; @@ -126,10 +122,11 @@ public: unsigned OutputPackTextureWidth() const { return State().outputPackTextureWidth; } const std::string& FormatStatusMessage() const { return State().formatStatusMessage; } const std::string& InputDisplayModeName() const { return State().inputDisplayModeName; } - const std::string& OutputModelName() const { return State().outputModelName; } - bool SupportsInternalKeying() const { return State().supportsInternalKeying; } - bool SupportsExternalKeying() const { return State().supportsExternalKeying; } - bool KeyerInterfaceAvailable() const { return State().keyerInterfaceAvailable; } + const std::string& DeviceName() const { return State().deviceName; } + bool SupportsInternalKeying() const { return State().capabilities.supportsInternalKeying; } + bool SupportsExternalKeying() const { return State().capabilities.supportsExternalKeying; } + bool KeyerInterfaceAvailable() const { return State().capabilities.keyerInterfaceAvailable; } + bool ExternalKeyingRequested() const { return State().externalKeyingRequested; } bool ExternalKeyingActive() const { return State().externalKeyingActive; } const std::string& StatusMessage() const { return State().statusMessage; } double FrameBudgetMilliseconds() const { return State().frameBudgetMilliseconds; } diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.cpp index 85bf5ea..c2efeeb 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.cpp @@ -13,10 +13,10 @@ std::string NormalizeModeToken(const std::string& value) return normalized; } -bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName) +bool ResolveConfiguredDeckLinkDisplayMode(const VideoIOModeConfiguration& mode, BMDDisplayMode& displayMode, std::string& displayModeName) { - VideoFormat videoMode; - if (!ResolveConfiguredVideoFormat(videoFormat, frameRate, videoMode)) + DeckLinkVideoMode videoMode; + if (!ResolveConfiguredDeckLinkVideoMode(mode, videoMode)) return false; displayMode = videoMode.displayMode; @@ -24,10 +24,10 @@ bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::str return true; } -bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode) +bool ResolveConfiguredDeckLinkVideoMode(const VideoIOModeConfiguration& mode, DeckLinkVideoMode& videoMode) { - const std::string formatToken = NormalizeModeToken(videoFormat); - const std::string frameToken = NormalizeModeToken(frameRate); + const std::string formatToken = NormalizeModeToken(mode.videoFormat); + const std::string frameToken = NormalizeModeToken(mode.frameRate); const std::string combinedToken = formatToken + frameToken; struct ModeOption @@ -98,25 +98,22 @@ bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::str return false; } -bool ResolveConfiguredVideoFormats( - const std::string& inputVideoFormat, - const std::string& inputFrameRate, - const std::string& outputVideoFormat, - const std::string& outputFrameRate, - VideoFormatSelection& videoModes, +bool ResolveConfiguredDeckLinkVideoModes( + const VideoIOConfiguration& config, + DeckLinkVideoModeSelection& videoModes, std::string& error) { - if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input)) + if (!ResolveConfiguredDeckLinkVideoMode(config.inputMode, videoModes.input)) { - error = "Unsupported DeckLink inputVideoFormat/inputFrameRate in config/runtime-host.json: " + - inputVideoFormat + " / " + inputFrameRate; + error = "Unsupported DeckLink input mode in config/runtime-host.json: " + + config.inputMode.videoFormat + " / " + config.inputMode.frameRate; return false; } - if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output)) + if (!ResolveConfiguredDeckLinkVideoMode(config.outputMode, videoModes.output)) { - error = "Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json: " + - outputVideoFormat + " / " + outputFrameRate; + error = "Unsupported DeckLink output mode in config/runtime-host.json: " + + config.outputMode.videoFormat + " / " + config.outputMode.frameRate; return false; } diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.h b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.h index 3be24fd..f65c13d 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.h +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkDisplayMode.h @@ -1,47 +1,27 @@ #pragma once #include "DeckLinkAPI_h.h" +#include "VideoIOConfig.h" #include -struct FrameSize -{ - unsigned width = 0; - unsigned height = 0; - - bool IsEmpty() const { return width == 0 || height == 0; } -}; - -inline bool operator==(const FrameSize& left, const FrameSize& right) -{ - return left.width == right.width && left.height == right.height; -} - -inline bool operator!=(const FrameSize& left, const FrameSize& right) -{ - return !(left == right); -} - -struct VideoFormat +struct DeckLinkVideoMode { BMDDisplayMode displayMode = bmdModeHD1080p5994; std::string displayName = "1080p59.94"; }; -struct VideoFormatSelection +struct DeckLinkVideoModeSelection { - VideoFormat input; - VideoFormat output; + DeckLinkVideoMode input; + DeckLinkVideoMode output; }; std::string NormalizeModeToken(const std::string& value); -bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName); -bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode); -bool ResolveConfiguredVideoFormats( - const std::string& inputVideoFormat, - const std::string& inputFrameRate, - const std::string& outputVideoFormat, - const std::string& outputFrameRate, - VideoFormatSelection& videoModes, +bool ResolveConfiguredDeckLinkDisplayMode(const VideoIOModeConfiguration& mode, BMDDisplayMode& displayMode, std::string& displayModeName); +bool ResolveConfiguredDeckLinkVideoMode(const VideoIOModeConfiguration& mode, DeckLinkVideoMode& videoMode); +bool ResolveConfiguredDeckLinkVideoModes( + const VideoIOConfiguration& config, + DeckLinkVideoModeSelection& videoModes, std::string& error); bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode); diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.cpp index 4c8e4fc..19d2d0b 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.cpp @@ -92,14 +92,19 @@ void DeckLinkSession::ReleaseResources() output.Release(); } -bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) +bool DeckLinkSession::DiscoverDevicesAndModes(const VideoIOConfiguration& config, std::string& error) { CComPtr deckLinkIterator; CComPtr inputMode; CComPtr outputMode; - mState.inputDisplayModeName = videoModes.input.displayName; - mState.outputDisplayModeName = videoModes.output.displayName; + mState.backendId = BackendId(); + mState.externalKeyingRequested = config.externalKeyingEnabled; + if (!ResolveConfiguredDeckLinkVideoModes(config, mConfiguredModes, error)) + return false; + + mState.inputDisplayModeName = mConfiguredModes.input.displayName; + mState.outputDisplayModeName = mConfiguredModes.output.displayName; HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast(&deckLinkIterator)); if (FAILED(result)) @@ -151,9 +156,9 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM output.Release(); else { - mState.outputModelName = modelName; - mState.supportsInternalKeying = deviceSupportsInternalKeying; - mState.supportsExternalKeying = deviceSupportsExternalKeying; + mState.deviceName = modelName; + mState.capabilities.supportsInternalKeying = deviceSupportsInternalKeying; + mState.capabilities.supportsExternalKeying = deviceSupportsExternalKeying; } } @@ -178,9 +183,9 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM return false; } - if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, videoModes.input.displayMode, &inputMode)) + if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, mConfiguredModes.input.displayMode, &inputMode)) { - error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName; + error = "Cannot get specified input BMDDisplayMode for configured mode: " + mConfiguredModes.input.displayName; ReleaseResources(); return false; } @@ -194,9 +199,9 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM return false; } - if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, videoModes.output.displayMode, &outputMode)) + if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, mConfiguredModes.output.displayMode, &outputMode)) { - error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName; + error = "Cannot get specified output BMDDisplayMode for configured mode: " + mConfiguredModes.output.displayName; ReleaseResources(); return false; } @@ -223,7 +228,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM return true; } -bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) +bool DeckLinkSession::SelectPreferredFormats(const VideoIOConfiguration& config, std::string& error) { if (!output) { @@ -233,19 +238,19 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo mState.formatStatusMessage.clear(); - const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV); + const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, mConfiguredModes.input.displayMode, bmdFormat10BitYUV); mState.inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8; if (input != nullptr && !inputTenBitSupported) mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. "; - const bool outputTenBitSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUV); - const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUVA); - mState.outputPixelFormat = outputAlphaRequired + const bool outputTenBitSupported = OutputSupportsFormat(output, mConfiguredModes.output.displayMode, bmdFormat10BitYUV); + const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, mConfiguredModes.output.displayMode, bmdFormat10BitYUVA); + mState.outputPixelFormat = config.externalKeyingEnabled ? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8) : (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8); - if (outputAlphaRequired && outputTenBitYuvaSupported) + if (config.externalKeyingEnabled && outputTenBitYuvaSupported) mState.formatStatusMessage += "External keying requires alpha; using 10-bit YUVA output. "; - else if (outputAlphaRequired) + else if (config.externalKeyingEnabled) mState.formatStatusMessage += "External keying requires alpha, but DeckLink output does not report 10-bit YUVA support for the configured mode; using 8-bit BGRA output. "; else if (!outputTenBitSupported) mState.formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. "; @@ -286,7 +291,7 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo return true; } -bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) +bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, std::string& error) { mInputFrameCallback = std::move(callback); @@ -298,7 +303,7 @@ bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFor } const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat); - if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK) + if (input->EnableVideoInput(mConfiguredModes.input.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK) { if (mState.inputPixelFormat == VideoIOPixelFormat::V210) { @@ -306,7 +311,7 @@ bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFor mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8; mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u; mState.captureTextureWidth = mState.inputFrameSize.width / 2u; - if (input->EnableVideoInput(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK) + if (input->EnableVideoInput(mConfiguredModes.input.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK) { std::ostringstream status; status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat) @@ -341,26 +346,26 @@ input_enabled: return true; } -bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) +bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, std::string& error) { mOutputFrameCallback = std::move(callback); - if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK) + if (output->EnableVideoOutput(mConfiguredModes.output.displayMode, bmdVideoOutputFlagDefault) != S_OK) { error = "DeckLink output setup failed while enabling video output."; return false; } if (output->QueryInterface(IID_IDeckLinkKeyer, (void**)&keyer) == S_OK && keyer != NULL) - mState.keyerInterfaceAvailable = true; + mState.capabilities.keyerInterfaceAvailable = true; - if (externalKeyingEnabled) + if (mState.externalKeyingRequested) { - if (!mState.supportsExternalKeying) + if (!mState.capabilities.supportsExternalKeying) { mState.statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support."; } - else if (!mState.keyerInterfaceAvailable) + else if (!mState.capabilities.keyerInterfaceAvailable) { mState.statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface."; } @@ -374,7 +379,7 @@ bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoF mState.statusMessage = "External keying is active on the selected DeckLink output."; } } - else if (mState.supportsExternalKeying) + else if (mState.capabilities.supportsExternalKeying) { mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it."; } diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h index 211914d..02cb6a3 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h @@ -20,41 +20,14 @@ public: DeckLinkSession() = default; ~DeckLinkSession(); + VideoIOBackendId BackendId() const override { return VideoIOBackendId::DeckLink; } void ReleaseResources() override; - bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) override; - bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) override; - bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) override; - bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) override; + bool DiscoverDevicesAndModes(const VideoIOConfiguration& config, std::string& error) override; + bool SelectPreferredFormats(const VideoIOConfiguration& config, std::string& error) override; + bool ConfigureInput(InputFrameCallback callback, std::string& error) override; + bool ConfigureOutput(OutputFrameCallback callback, std::string& error) override; bool Start() override; bool Stop() override; - - bool HasInputDevice() const { return mState.hasInputDevice; } - bool HasInputSource() const { return mState.hasInputSource; } - void SetInputSourceMissing(bool missing) { mState.hasInputSource = !missing; } - bool InputOutputDimensionsDiffer() const { return mState.inputFrameSize != mState.outputFrameSize; } - const FrameSize& InputFrameSize() const { return mState.inputFrameSize; } - const FrameSize& OutputFrameSize() const { return mState.outputFrameSize; } - unsigned InputFrameWidth() const { return mState.inputFrameSize.width; } - unsigned InputFrameHeight() const { return mState.inputFrameSize.height; } - unsigned OutputFrameWidth() const { return mState.outputFrameSize.width; } - unsigned OutputFrameHeight() const { return mState.outputFrameSize.height; } - VideoIOPixelFormat InputPixelFormat() const { return mState.inputPixelFormat; } - VideoIOPixelFormat OutputPixelFormat() const { return mState.outputPixelFormat; } - bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.inputPixelFormat); } - bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.outputPixelFormat); } - unsigned InputFrameRowBytes() const { return mState.inputFrameRowBytes; } - unsigned OutputFrameRowBytes() const { return mState.outputFrameRowBytes; } - unsigned CaptureTextureWidth() const { return mState.captureTextureWidth; } - unsigned OutputPackTextureWidth() const { return mState.outputPackTextureWidth; } - const std::string& FormatStatusMessage() const { return mState.formatStatusMessage; } - const std::string& InputDisplayModeName() const { return mState.inputDisplayModeName; } - const std::string& OutputModelName() const { return mState.outputModelName; } - bool SupportsInternalKeying() const { return mState.supportsInternalKeying; } - bool SupportsExternalKeying() const { return mState.supportsExternalKeying; } - bool KeyerInterfaceAvailable() const { return mState.keyerInterfaceAvailable; } - bool ExternalKeyingActive() const { return mState.externalKeyingActive; } - const std::string& StatusMessage() const { return mState.statusMessage; } - void SetStatusMessage(const std::string& message) { mState.statusMessage = message; } const VideoIOState& State() const override { return mState; } VideoIOState& MutableState() override { return mState; } double FrameBudgetMilliseconds() const; @@ -76,4 +49,5 @@ private: VideoPlayoutScheduler mScheduler; InputFrameCallback mInputFrameCallback; OutputFrameCallback mOutputFrameCallback; + DeckLinkVideoModeSelection mConfiguredModes; }; diff --git a/config/runtime-host.json b/config/runtime-host.json index 5e3a55e..f0587f1 100644 --- a/config/runtime-host.json +++ b/config/runtime-host.json @@ -2,6 +2,7 @@ "shaderLibrary": "shaders", "serverPort": 8080, "oscPort": 9000, + "videoBackend": "decklink", "inputVideoFormat": "1080p", "inputFrameRate": "59.94", "outputVideoFormat": "1080p", diff --git a/tests/RuntimeHostVideoIOStateTests.cpp b/tests/RuntimeHostVideoIOStateTests.cpp new file mode 100644 index 0000000..d078ec1 --- /dev/null +++ b/tests/RuntimeHostVideoIOStateTests.cpp @@ -0,0 +1,67 @@ +#include "RuntimeHost.h" + +#include + +namespace +{ +int gFailures = 0; + +void Expect(bool condition, const char* message) +{ + if (condition) + return; + + std::cerr << "FAIL: " << message << "\n"; + ++gFailures; +} +} + +int main() +{ + RuntimeHost runtimeHost; + std::string error; + Expect(runtimeHost.Initialize(error), "runtime host initializes"); + Expect(error.empty(), "runtime host initialization does not report an error"); + + VideoIOState state; + state.backendId = VideoIOBackendId::DeckLink; + state.deviceName = "Test Device"; + state.hasInputDevice = true; + state.hasInputSource = true; + state.inputDisplayModeName = "fake input"; + state.outputDisplayModeName = "fake output"; + state.capabilities.supportsInternalKeying = true; + state.capabilities.supportsExternalKeying = true; + state.capabilities.keyerInterfaceAvailable = true; + state.externalKeyingRequested = true; + state.externalKeyingActive = true; + state.statusMessage = "ready"; + state.formatStatusMessage = "using fake formats"; + runtimeHost.SetVideoIOStatus(state); + + JsonValue root; + Expect(ParseJson(runtimeHost.BuildStateJson(), root, error), "runtime state json parses"); + Expect(root.find("videoIO") != nullptr, "runtime state exposes videoIO"); + Expect(root.find("decklink") == nullptr, "runtime state no longer exposes a decklink top-level block"); + + const JsonValue* app = root.find("app"); + Expect(app != nullptr, "runtime state exposes app settings"); + Expect(app != nullptr && app->find("videoBackend") != nullptr, "app settings expose videoBackend"); + Expect(app != nullptr && app->find("videoBackend")->asString() == "decklink", "videoBackend serializes as decklink"); + + const JsonValue* videoIO = root.find("videoIO"); + Expect(videoIO != nullptr && videoIO->find("backend") != nullptr, "videoIO exposes backend"); + Expect(videoIO != nullptr && videoIO->find("backend")->asString() == "decklink", "videoIO backend serializes as decklink"); + Expect(videoIO != nullptr && videoIO->find("deviceName") != nullptr, "videoIO exposes device name"); + Expect(videoIO != nullptr && videoIO->find("deviceName")->asString() == "Test Device", "videoIO device name matches"); + Expect(videoIO != nullptr && videoIO->find("capabilities") != nullptr, "videoIO exposes capabilities"); + + if (gFailures != 0) + { + std::cerr << gFailures << " RuntimeHost video I/O state test failure(s).\n"; + return 1; + } + + std::cout << "RuntimeHost video I/O state tests passed.\n"; + return 0; +} diff --git a/tests/VideoIOBackendFactoryTests.cpp b/tests/VideoIOBackendFactoryTests.cpp new file mode 100644 index 0000000..3f4b6cd --- /dev/null +++ b/tests/VideoIOBackendFactoryTests.cpp @@ -0,0 +1,41 @@ +#include "VideoIOBackendFactory.h" +#include "VideoIOTypes.h" + +#include + +namespace +{ +int gFailures = 0; + +void Expect(bool condition, const char* message) +{ + if (condition) + return; + + std::cerr << "FAIL: " << message << "\n"; + ++gFailures; +} +} + +int main() +{ + std::string error; + std::unique_ptr device = CreateVideoIODevice(VideoIOBackendId::DeckLink, error); + Expect(device != nullptr, "decklink backend factory returns a device"); + Expect(!device || device->BackendId() == VideoIOBackendId::DeckLink, "decklink backend reports decklink id"); + Expect(error.empty(), "supported backend does not produce an error"); + + error.clear(); + device = CreateVideoIODevice(static_cast(999), error); + Expect(device == nullptr, "unknown backend id is rejected"); + Expect(!error.empty(), "unknown backend reports an error"); + + if (gFailures != 0) + { + std::cerr << gFailures << " VideoIO backend factory test failure(s).\n"; + return 1; + } + + std::cout << "VideoIO backend factory tests passed.\n"; + return 0; +} diff --git a/tests/VideoIODeviceFakeTests.cpp b/tests/VideoIODeviceFakeTests.cpp index 6dd3788..ee80a54 100644 --- a/tests/VideoIODeviceFakeTests.cpp +++ b/tests/VideoIODeviceFakeTests.cpp @@ -19,20 +19,26 @@ void Expect(bool condition, const char* message) class FakeVideoIODevice : public VideoIODevice { public: + VideoIOBackendId BackendId() const override { return VideoIOBackendId::DeckLink; } void ReleaseResources() override {} - bool DiscoverDevicesAndModes(const VideoFormatSelection&, std::string&) override + bool DiscoverDevicesAndModes(const VideoIOConfiguration&, std::string&) override { + mState.backendId = BackendId(); mState.inputFrameSize = { 1920, 1080 }; mState.outputFrameSize = { 1920, 1080 }; mState.inputDisplayModeName = "fake 1080p"; - mState.outputModelName = "Fake Video IO"; + mState.outputDisplayModeName = "fake 1080p"; + mState.deviceName = "Fake Video IO"; + mState.capabilities.supportsInternalKeying = true; + mState.capabilities.supportsExternalKeying = true; mState.hasInputDevice = true; return true; } - bool SelectPreferredFormats(const VideoFormatSelection&, bool, std::string&) override + bool SelectPreferredFormats(const VideoIOConfiguration& config, std::string&) override { + mState.externalKeyingRequested = config.externalKeyingEnabled; mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8; mState.outputPixelFormat = VideoIOPixelFormat::Bgra8; mState.inputFrameRowBytes = VideoIORowBytes(mState.inputPixelFormat, mState.inputFrameSize.width); @@ -42,13 +48,13 @@ public: return true; } - bool ConfigureInput(InputFrameCallback callback, const VideoFormat&, std::string&) override + bool ConfigureInput(InputFrameCallback callback, std::string&) override { mInputCallback = callback; return true; } - bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat&, bool, std::string&) override + bool ConfigureOutput(OutputFrameCallback callback, std::string&) override { mOutputCallback = callback; return true; @@ -114,19 +120,19 @@ private: int main() { FakeVideoIODevice device; - VideoFormatSelection selection; + VideoIOConfiguration config; std::string error; bool inputSeen = false; bool outputSeen = false; - Expect(device.DiscoverDevicesAndModes(selection, error), "fake discovery succeeds"); - Expect(device.SelectPreferredFormats(selection, false, error), "fake format selection succeeds"); + Expect(device.DiscoverDevicesAndModes(config, error), "fake discovery succeeds"); + Expect(device.SelectPreferredFormats(config, error), "fake format selection succeeds"); Expect(device.ConfigureInput([&](const VideoIOFrame& frame) { inputSeen = frame.bytes != nullptr && frame.width == 1920 && frame.pixelFormat == VideoIOPixelFormat::Uyvy8; - }, selection.input, error), "fake input config succeeds"); + }, error), "fake input config succeeds"); Expect(device.ConfigureOutput([&](const VideoIOCompletion& completion) { outputSeen = completion.result == VideoIOCompletionResult::Completed; - }, selection.output, false, error), "fake output config succeeds"); + }, error), "fake output config succeeds"); Expect(device.Start(), "fake device starts"); VideoIOOutputFrame outputFrame;