Clean up video IO
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Failing after 2m33s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-22 15:16:35 +10:00
parent 7bf5464fd2
commit 9787ca5f27
15 changed files with 377 additions and 255 deletions

View File

@@ -1,16 +1,50 @@
#include "DeckLinkDisplayMode.h"
#include <cctype>
std::string NormalizeModeToken(const std::string& value)
bool DeckLinkDisplayModeForVideoFormat(const VideoFormat& videoMode, BMDDisplayMode& displayMode)
{
std::string normalized;
for (unsigned char ch : value)
struct ModeOption
{
if (std::isalnum(ch))
normalized.push_back(static_cast<char>(std::tolower(ch)));
const char* displayName;
BMDDisplayMode mode;
};
static const ModeOption options[] =
{
{ "720p50", bmdModeHD720p50 },
{ "720p59.94", bmdModeHD720p5994 },
{ "720p60", bmdModeHD720p60 },
{ "1080i50", bmdModeHD1080i50 },
{ "1080i59.94", bmdModeHD1080i5994 },
{ "1080i60", bmdModeHD1080i6000 },
{ "1080p23.98", bmdModeHD1080p2398 },
{ "1080p24", bmdModeHD1080p24 },
{ "1080p25", bmdModeHD1080p25 },
{ "1080p29.97", bmdModeHD1080p2997 },
{ "1080p30", bmdModeHD1080p30 },
{ "1080p50", bmdModeHD1080p50 },
{ "1080p59.94", bmdModeHD1080p5994 },
{ "1080p60", bmdModeHD1080p6000 },
{ "2160p23.98", bmdMode4K2160p2398 },
{ "2160p24", bmdMode4K2160p24 },
{ "2160p25", bmdMode4K2160p25 },
{ "2160p29.97", bmdMode4K2160p2997 },
{ "2160p30", bmdMode4K2160p30 },
{ "2160p50", bmdMode4K2160p50 },
{ "2160p59.94", bmdMode4K2160p5994 },
{ "2160p60", bmdMode4K2160p60 }
};
const std::string displayToken = NormalizeModeToken(videoMode.displayName);
for (const ModeOption& option : options)
{
if (displayToken == NormalizeModeToken(option.displayName))
{
displayMode = option.mode;
return true;
}
}
return normalized;
return false;
}
bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName)
@@ -19,108 +53,55 @@ bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::str
if (!ResolveConfiguredVideoFormat(videoFormat, frameRate, videoMode))
return false;
displayMode = videoMode.displayMode;
if (!DeckLinkDisplayModeForVideoFormat(videoMode, displayMode))
return false;
displayModeName = videoMode.displayName;
return true;
}
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode)
double FrameDurationMillisecondsFromDisplayMode(BMDDisplayMode displayMode, double fallbackMilliseconds)
{
const std::string formatToken = NormalizeModeToken(videoFormat);
const std::string frameToken = NormalizeModeToken(frameRate);
const std::string combinedToken = formatToken + frameToken;
struct ModeOption
struct ModeRate
{
const char* token;
BMDDisplayMode mode;
const char* displayName;
int64_t frameDuration;
int64_t timeScale;
};
static const ModeOption options[] =
static const ModeRate rates[] =
{
{ "720p50", bmdModeHD720p50, "720p50" },
{ "hd720p50", bmdModeHD720p50, "720p50" },
{ "720p5994", bmdModeHD720p5994, "720p59.94" },
{ "hd720p5994", bmdModeHD720p5994, "720p59.94" },
{ "720p60", bmdModeHD720p60, "720p60" },
{ "hd720p60", bmdModeHD720p60, "720p60" },
{ "1080i50", bmdModeHD1080i50, "1080i50" },
{ "hd1080i50", bmdModeHD1080i50, "1080i50" },
{ "1080i5994", bmdModeHD1080i5994, "1080i59.94" },
{ "hd1080i5994", bmdModeHD1080i5994, "1080i59.94" },
{ "1080i60", bmdModeHD1080i6000, "1080i60" },
{ "hd1080i60", bmdModeHD1080i6000, "1080i60" },
{ "1080p2398", bmdModeHD1080p2398, "1080p23.98" },
{ "hd1080p2398", bmdModeHD1080p2398, "1080p23.98" },
{ "1080p24", bmdModeHD1080p24, "1080p24" },
{ "hd1080p24", bmdModeHD1080p24, "1080p24" },
{ "1080p25", bmdModeHD1080p25, "1080p25" },
{ "hd1080p25", bmdModeHD1080p25, "1080p25" },
{ "1080p2997", bmdModeHD1080p2997, "1080p29.97" },
{ "hd1080p2997", bmdModeHD1080p2997, "1080p29.97" },
{ "1080p30", bmdModeHD1080p30, "1080p30" },
{ "hd1080p30", bmdModeHD1080p30, "1080p30" },
{ "1080p50", bmdModeHD1080p50, "1080p50" },
{ "hd1080p50", bmdModeHD1080p50, "1080p50" },
{ "1080p5994", bmdModeHD1080p5994, "1080p59.94" },
{ "hd1080p5994", bmdModeHD1080p5994, "1080p59.94" },
{ "1080p60", bmdModeHD1080p6000, "1080p60" },
{ "hd1080p60", bmdModeHD1080p6000, "1080p60" },
{ "2160p2398", bmdMode4K2160p2398, "2160p23.98" },
{ "4k2160p2398", bmdMode4K2160p2398, "2160p23.98" },
{ "2160p24", bmdMode4K2160p24, "2160p24" },
{ "4k2160p24", bmdMode4K2160p24, "2160p24" },
{ "2160p25", bmdMode4K2160p25, "2160p25" },
{ "4k2160p25", bmdMode4K2160p25, "2160p25" },
{ "2160p2997", bmdMode4K2160p2997, "2160p29.97" },
{ "4k2160p2997", bmdMode4K2160p2997, "2160p29.97" },
{ "2160p30", bmdMode4K2160p30, "2160p30" },
{ "4k2160p30", bmdMode4K2160p30, "2160p30" },
{ "2160p50", bmdMode4K2160p50, "2160p50" },
{ "4k2160p50", bmdMode4K2160p50, "2160p50" },
{ "2160p5994", bmdMode4K2160p5994, "2160p59.94" },
{ "4k2160p5994", bmdMode4K2160p5994, "2160p59.94" },
{ "2160p60", bmdMode4K2160p60, "2160p60" },
{ "4k2160p60", bmdMode4K2160p60, "2160p60" }
{ bmdModeHD720p50, 1, 50 },
{ bmdModeHD720p5994, 1001, 60000 },
{ bmdModeHD720p60, 1, 60 },
{ bmdModeHD1080i50, 1, 25 },
{ bmdModeHD1080i5994, 1001, 30000 },
{ bmdModeHD1080i6000, 1, 30 },
{ bmdModeHD1080p2398, 1001, 24000 },
{ bmdModeHD1080p24, 1, 24 },
{ bmdModeHD1080p25, 1, 25 },
{ bmdModeHD1080p2997, 1001, 30000 },
{ bmdModeHD1080p30, 1, 30 },
{ bmdModeHD1080p50, 1, 50 },
{ bmdModeHD1080p5994, 1001, 60000 },
{ bmdModeHD1080p6000, 1, 60 },
{ bmdMode4K2160p2398, 1001, 24000 },
{ bmdMode4K2160p24, 1, 24 },
{ bmdMode4K2160p25, 1, 25 },
{ bmdMode4K2160p2997, 1001, 30000 },
{ bmdMode4K2160p30, 1, 30 },
{ bmdMode4K2160p50, 1, 50 },
{ bmdMode4K2160p5994, 1001, 60000 },
{ bmdMode4K2160p60, 1, 60 }
};
for (const ModeOption& option : options)
for (const ModeRate& rate : rates)
{
if (combinedToken == option.token || (frameToken.empty() && formatToken == option.token))
{
videoMode.displayMode = option.mode;
videoMode.displayName = option.displayName;
return true;
}
if (rate.mode == displayMode && rate.timeScale > 0)
return (static_cast<double>(rate.frameDuration) * 1000.0) / static_cast<double>(rate.timeScale);
}
return false;
}
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error)
{
if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input))
{
error = "Unsupported DeckLink inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
inputVideoFormat + " / " + inputFrameRate;
return false;
}
if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output))
{
error = "Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json: " +
outputVideoFormat + " / " + outputFrameRate;
return false;
}
return true;
return fallbackMilliseconds;
}
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode)

View File

@@ -1,47 +1,11 @@
#pragma once
#include "DeckLinkAPI_h.h"
#include "VideoMode.h"
#include <string>
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
{
BMDDisplayMode displayMode = bmdModeHD1080p5994;
std::string displayName = "1080p59.94";
};
struct VideoFormatSelection
{
VideoFormat input;
VideoFormat output;
};
std::string NormalizeModeToken(const std::string& value);
bool DeckLinkDisplayModeForVideoFormat(const VideoFormat& videoMode, BMDDisplayMode& displayMode);
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,
std::string& error);
double FrameDurationMillisecondsFromDisplayMode(BMDDisplayMode displayMode, double fallbackMilliseconds);
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode);

View File

@@ -86,10 +86,17 @@ bool DeckLinkInput::Initialize(const DeckLinkInputConfig& config, std::string& e
mConfig = config;
Log("decklink-input", "Initializing DeckLink input for " + config.videoFormat.displayName + ".");
BMDDisplayMode displayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(config.videoFormat, displayMode))
{
error = "DeckLink input setup failed while mapping " + config.videoFormat.displayName + " to a DeckLink display mode.";
return false;
}
if (!DiscoverInput(config, error))
return false;
if (mInput->EnableVideoInput(config.videoFormat.displayMode, mCapturePixelFormat, bmdVideoInputFlagDefault) != S_OK)
if (mInput->EnableVideoInput(displayMode, mCapturePixelFormat, bmdVideoInputFlagDefault) != S_OK)
{
error = "DeckLink input setup failed while enabling " +
std::string(mCapturePixelFormat == bmdFormat8BitBGRA ? "BGRA8" : "UYVY8") +
@@ -260,6 +267,13 @@ void DeckLinkInput::HandleFormatChanged()
bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string& error)
{
BMDDisplayMode targetDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(config.videoFormat, targetDisplayMode))
{
error = "DeckLink input discovery failed while mapping " + config.videoFormat.displayName + " to a DeckLink display mode.";
return false;
}
CComPtr<IDeckLinkIterator> iterator;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&iterator));
if (FAILED(result))
@@ -275,8 +289,8 @@ bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string
if (deckLink->QueryInterface(IID_IDeckLinkInput, reinterpret_cast<void**>(&candidateInput)) == S_OK && candidateInput != nullptr)
{
CComPtr<IDeckLinkDisplayMode> displayMode;
if (FindInputDisplayMode(candidateInput, config.videoFormat.displayMode, &displayMode) &&
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitBGRA))
if (FindInputDisplayMode(candidateInput, targetDisplayMode, &displayMode) &&
SupportsInputFormat(candidateInput, targetDisplayMode, bmdFormat8BitBGRA))
{
mInput = candidateInput;
mCapturePixelFormat = bmdFormat8BitBGRA;
@@ -284,7 +298,7 @@ bool DeckLinkInput::DiscoverInput(const DeckLinkInputConfig& config, std::string
return true;
}
if (displayMode != nullptr &&
SupportsInputFormat(candidateInput, config.videoFormat.displayMode, bmdFormat8BitYUV))
SupportsInputFormat(candidateInput, targetDisplayMode, bmdFormat8BitYUV))
{
mInput = candidateInput;
mCapturePixelFormat = bmdFormat8BitYUV;

View File

@@ -169,6 +169,19 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
CComPtr<IDeckLinkIterator> deckLinkIterator;
CComPtr<IDeckLinkDisplayMode> inputMode;
CComPtr<IDeckLinkDisplayMode> outputMode;
BMDDisplayMode inputDisplayMode = bmdModeUnknown;
BMDDisplayMode outputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(videoModes.input, inputDisplayMode))
{
error = "Cannot map configured input mode to DeckLink BMDDisplayMode: " + videoModes.input.displayName;
return false;
}
if (!DeckLinkDisplayModeForVideoFormat(videoModes.output, outputDisplayMode))
{
error = "Cannot map configured output mode to DeckLink BMDDisplayMode: " + videoModes.output.displayName;
return false;
}
mState.inputDisplayModeName = videoModes.input.displayName;
mState.outputDisplayModeName = videoModes.output.displayName;
@@ -250,7 +263,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
return false;
}
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, videoModes.input.displayMode, &inputMode))
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, inputDisplayMode, &inputMode))
{
error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName;
ReleaseResources();
@@ -266,7 +279,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
return false;
}
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, videoModes.output.displayMode, &outputMode))
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, outputDisplayMode, &outputMode))
{
error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName;
ReleaseResources();
@@ -304,14 +317,22 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo
}
mState.formatStatusMessage.clear();
BMDDisplayMode inputDisplayMode = bmdModeUnknown;
BMDDisplayMode outputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(videoModes.input, inputDisplayMode) ||
!DeckLinkDisplayModeForVideoFormat(videoModes.output, outputDisplayMode))
{
error = "DeckLink format selection failed while mapping configured video modes.";
return false;
}
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV);
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, inputDisplayMode, 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);
const bool outputTenBitSupported = OutputSupportsFormat(output, outputDisplayMode, bmdFormat10BitYUV);
const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, outputDisplayMode, bmdFormat10BitYUVA);
mState.outputPixelFormat = outputAlphaRequired
? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8)
: (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8);
@@ -369,8 +390,14 @@ bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFor
return true;
}
BMDDisplayMode inputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(inputVideoMode, inputDisplayMode))
{
error = "DeckLink input setup failed while mapping " + inputVideoMode.displayName + " to a DeckLink display mode.";
return false;
}
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat);
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
if (input->EnableVideoInput(inputDisplayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
{
if (mState.inputPixelFormat == VideoIOPixelFormat::V210)
{
@@ -378,7 +405,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(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK)
{
std::ostringstream status;
status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat)
@@ -417,7 +444,13 @@ bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoF
{
mOutputFrameCallback = std::move(callback);
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK)
BMDDisplayMode outputDisplayMode = bmdModeUnknown;
if (!DeckLinkDisplayModeForVideoFormat(outputVideoMode, outputDisplayMode))
{
error = "DeckLink output setup failed while mapping " + outputVideoMode.displayName + " to a DeckLink display mode.";
return false;
}
if (output->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK)
{
error = "DeckLink output setup failed while enabling video output.";
return false;