UYVY backend
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m22s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-30 19:16:16 +10:00
parent d0b1f63524
commit f0f8b080ca
30 changed files with 733 additions and 239 deletions

View File

@@ -22,7 +22,27 @@ std::string ResolveSenderName(const std::string& configuredName)
return configuredName;
}
constexpr std::size_t kBgraBytesPerPixel = 4;
bool IsSupportedNdiOutputPixelFormat(VideoIOPixelFormat pixelFormat)
{
return pixelFormat == VideoIOPixelFormat::Bgra8 || pixelFormat == VideoIOPixelFormat::Uyvy8;
}
bool NdiFourCcForPixelFormat(VideoIOPixelFormat pixelFormat, NDIlib_FourCC_video_type_e& fourCc)
{
switch (pixelFormat)
{
case VideoIOPixelFormat::Bgra8:
fourCc = NDIlib_FourCC_video_type_BGRA;
return true;
case VideoIOPixelFormat::Uyvy8:
fourCc = NDIlib_FourCC_video_type_UYVY;
return true;
case VideoIOPixelFormat::V210:
case VideoIOPixelFormat::Yuva10:
default:
return false;
}
}
void SetCommonVideoFrameFields(
NDIlib_video_frame_v2_t& ndiFrame,
@@ -30,12 +50,13 @@ void SetCommonVideoFrameFields(
unsigned int height,
int rowBytes,
void* pixels,
NDIlib_FourCC_video_type_e fourCc,
const VideoOutputEdgeConfig& config)
{
std::memset(&ndiFrame, 0, sizeof(ndiFrame));
ndiFrame.xres = static_cast<int>(width);
ndiFrame.yres = static_cast<int>(height);
ndiFrame.FourCC = NDIlib_FourCC_video_type_BGRA;
ndiFrame.FourCC = fourCc;
ndiFrame.frame_rate_N = 60000;
ndiFrame.frame_rate_D = 1001;
if (config.outputVideoMode.frameRate == "60")
@@ -80,15 +101,14 @@ void SetCommonVideoFrameFields(
ndiFrame.line_stride_in_bytes = rowBytes;
}
bool CopyBgra8FrameFlippedVertically(const VideoIOOutputFrame& frame, std::vector<unsigned char>& outputPixels, int& outputRowBytes)
bool CopyFrameFlippedVertically(const VideoIOOutputFrame& frame, std::vector<unsigned char>& outputPixels, int& outputRowBytes)
{
if (frame.bytes == nullptr || frame.width == 0 || frame.height == 0 || frame.rowBytes <= 0)
if (frame.bytes == nullptr || frame.width == 0 || frame.height == 0 || frame.rowBytes <= 0 || !IsSupportedNdiOutputPixelFormat(frame.pixelFormat))
return false;
const std::size_t width = static_cast<std::size_t>(frame.width);
const std::size_t height = static_cast<std::size_t>(frame.height);
const std::size_t sourceRowBytes = static_cast<std::size_t>(frame.rowBytes);
const std::size_t destinationRowBytes = width * kBgraBytesPerPixel;
const std::size_t destinationRowBytes = VideoIORowBytes(frame.pixelFormat, frame.width);
if (sourceRowBytes < destinationRowBytes)
return false;
@@ -122,10 +142,15 @@ bool NdiOutput::Initialize(const VideoOutputEdgeConfig& config, CompletionCallba
mConfig = config;
mCompletionCallback = std::move(completionCallback);
PopulateState(mState, config, mSenderName);
if (!IsSupportedNdiOutputPixelFormat(config.systemFramePixelFormat))
{
error = std::string("NDI output does not support system frame format ") + VideoIOPixelFormatName(config.systemFramePixelFormat) + ".";
return false;
}
if (config.outputAlphaRequired)
{
mState.statusMessage = "NDI output sends BGRA frames with alpha when present; output.keying.alphaRequired is a DeckLink-only requirement.";
mState.statusMessage = "NDI output can carry BGRA alpha when configured; output.keying.alphaRequired is a DeckLink-only requirement.";
}
if (!AcquireNdiRuntime())
@@ -168,7 +193,8 @@ bool NdiOutput::StartScheduledPlayback(std::string& error)
bool NdiOutput::ScheduleFrame(const VideoIOOutputFrame& frame)
{
if (frame.bytes == nullptr || frame.pixelFormat != VideoIOPixelFormat::Bgra8)
NDIlib_FourCC_video_type_e fourCc = NDIlib_FourCC_video_type_BGRA;
if (frame.bytes == nullptr || !NdiFourCcForPixelFormat(frame.pixelFormat, fourCc))
{
++mScheduleFailures;
return false;
@@ -190,14 +216,14 @@ bool NdiOutput::ScheduleFrame(const VideoIOOutputFrame& frame)
std::vector<unsigned char>& stagingBuffer = mStagingBuffers[mNextStagingBuffer];
int stagingRowBytes = 0;
if (!CopyBgra8FrameFlippedVertically(frame, stagingBuffer, stagingRowBytes))
if (!CopyFrameFlippedVertically(frame, stagingBuffer, stagingRowBytes))
{
++mScheduleFailures;
return false;
}
NDIlib_video_frame_v2_t ndiFrame;
SetCommonVideoFrameFields(ndiFrame, frame.width, frame.height, stagingRowBytes, stagingBuffer.data(), mConfig);
SetCommonVideoFrameFields(ndiFrame, frame.width, frame.height, stagingRowBytes, stagingBuffer.data(), fourCc, mConfig);
const auto scheduleStart = std::chrono::steady_clock::now();
NDIlib_send_send_video_async_v2(static_cast<NDIlib_send_instance_t>(sender), &ndiFrame);
@@ -303,14 +329,19 @@ void NdiOutput::CompleteBuffer(void* buffer, VideoIOCompletionResult result)
void NdiOutput::PopulateState(VideoIOState& state, const VideoOutputEdgeConfig& config, const std::string& senderName)
{
const VideoIOPixelFormat outputPixelFormat = IsSupportedNdiOutputPixelFormat(config.systemFramePixelFormat)
? config.systemFramePixelFormat
: VideoIOPixelFormat::Bgra8;
state = VideoIOState();
state.outputFrameSize = config.outputVideoMode.frameSize;
state.inputFrameSize = state.outputFrameSize;
state.outputPixelFormat = VideoIOPixelFormat::Bgra8;
state.inputPixelFormat = VideoIOPixelFormat::Bgra8;
state.outputPixelFormat = outputPixelFormat;
state.inputPixelFormat = outputPixelFormat;
state.outputFrameRowBytes = VideoIORowBytes(state.outputPixelFormat, state.outputFrameSize.width);
state.inputFrameRowBytes = state.outputFrameRowBytes;
state.outputPackTextureWidth = state.outputFrameSize.width;
state.outputPackTextureWidth = outputPixelFormat == VideoIOPixelFormat::Uyvy8
? state.outputFrameSize.width / 2u
: state.outputFrameSize.width;
state.captureTextureWidth = state.inputFrameSize.width;
state.outputDisplayModeName = config.outputVideoMode.displayName;
state.inputDisplayModeName = "No input - NDI output session";
@@ -322,6 +353,6 @@ void NdiOutput::PopulateState(VideoIOState& state, const VideoOutputEdgeConfig&
state.keyerInterfaceAvailable = false;
state.externalKeyingActive = false;
state.frameBudgetMilliseconds = config.outputVideoMode.frameDurationMilliseconds;
state.formatStatusMessage = "NDI output format: BGRA8.";
state.formatStatusMessage = std::string("NDI output format: ") + VideoIOPixelFormatName(outputPixelFormat) + ".";
}
}