UYVY backend
This commit is contained in:
@@ -55,6 +55,7 @@ struct VideoOutputEdgeMetrics
|
||||
struct VideoOutputEdgeConfig
|
||||
{
|
||||
VideoFormat outputVideoMode;
|
||||
VideoIOPixelFormat systemFramePixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
bool externalKeyingEnabled = false;
|
||||
bool outputAlphaRequired = false;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ bool DeckLinkOutput::Initialize(const VideoOutputEdgeConfig& config, CompletionC
|
||||
formats.output = config.outputVideoMode;
|
||||
if (!mSession.DiscoverDevicesAndModes(formats, error))
|
||||
return false;
|
||||
if (!mSession.SelectPreferredFormats(formats, config.outputAlphaRequired, error))
|
||||
if (!mSession.SelectPreferredFormats(formats, config.systemFramePixelFormat, config.outputAlphaRequired, error))
|
||||
return false;
|
||||
if (!mSession.ConfigureOutput(
|
||||
[this](const VideoIOCompletion& completion) { HandleCompletion(completion); },
|
||||
|
||||
@@ -117,6 +117,11 @@ bool OutputSupportsFormat(IDeckLinkOutput* output, BMDDisplayMode displayMode, B
|
||||
&supported);
|
||||
return result == S_OK && supported != FALSE;
|
||||
}
|
||||
|
||||
bool RenderReadbackSupportsOutputFormat(VideoIOPixelFormat pixelFormat)
|
||||
{
|
||||
return pixelFormat == VideoIOPixelFormat::Bgra8 || pixelFormat == VideoIOPixelFormat::Uyvy8;
|
||||
}
|
||||
}
|
||||
|
||||
DeckLinkSession::~DeckLinkSession()
|
||||
@@ -254,7 +259,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
|
||||
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, VideoIOPixelFormat systemFramePixelFormat, bool outputAlphaRequired, std::string& error)
|
||||
{
|
||||
if (!output)
|
||||
{
|
||||
@@ -271,18 +276,30 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo
|
||||
}
|
||||
|
||||
mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
if (!RenderReadbackSupportsOutputFormat(systemFramePixelFormat))
|
||||
{
|
||||
error = "DeckLink output requested " + std::string(VideoIOPixelFormatName(systemFramePixelFormat)) +
|
||||
", but render readback currently supports only BGRA8 and UYVY8 system frames.";
|
||||
return false;
|
||||
}
|
||||
if (outputAlphaRequired && systemFramePixelFormat != VideoIOPixelFormat::Bgra8)
|
||||
{
|
||||
error = "DeckLink alpha output requires BGRA8 system frames until a YUVA render packer exists.";
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
if (outputAlphaRequired && outputTenBitYuvaSupported)
|
||||
mState.formatStatusMessage += "External keying requires alpha; using 10-bit YUVA output. ";
|
||||
else if (outputAlphaRequired)
|
||||
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. ";
|
||||
const BMDPixelFormat requestedOutputPixelFormat = DeckLinkPixelFormatForVideoIO(systemFramePixelFormat);
|
||||
if (!OutputSupportsFormat(output, outputDisplayMode, requestedOutputPixelFormat))
|
||||
{
|
||||
error = "DeckLink output does not report support for " +
|
||||
std::string(VideoIOPixelFormatName(systemFramePixelFormat)) +
|
||||
" in the configured display mode.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mState.outputPixelFormat = systemFramePixelFormat;
|
||||
if (outputAlphaRequired)
|
||||
mState.formatStatusMessage += "External keying requires alpha; using BGRA8 system frames. ";
|
||||
|
||||
int deckLinkOutputRowBytes = 0;
|
||||
if (output->RowBytesForPixelFormat(DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat), mState.outputFrameSize.width, &deckLinkOutputRowBytes) != S_OK)
|
||||
@@ -291,9 +308,12 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo
|
||||
return false;
|
||||
}
|
||||
mState.outputFrameRowBytes = static_cast<unsigned>(deckLinkOutputRowBytes);
|
||||
mState.outputPackTextureWidth = OutputIsTenBit()
|
||||
? PackedTextureWidthFromRowBytes(mState.outputFrameRowBytes)
|
||||
: mState.outputFrameSize.width;
|
||||
if (OutputIsTenBit())
|
||||
mState.outputPackTextureWidth = PackedTextureWidthFromRowBytes(mState.outputFrameRowBytes);
|
||||
else if (mState.outputPixelFormat == VideoIOPixelFormat::Uyvy8)
|
||||
mState.outputPackTextureWidth = mState.outputFrameSize.width / 2u;
|
||||
else
|
||||
mState.outputPackTextureWidth = mState.outputFrameSize.width;
|
||||
|
||||
if (InputIsTenBit())
|
||||
{
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
void ReleaseResources();
|
||||
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
|
||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
|
||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, VideoIOPixelFormat systemFramePixelFormat, bool outputAlphaRequired, std::string& error);
|
||||
bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
|
||||
bool PrepareOutputSchedule();
|
||||
bool StartScheduledPlayback();
|
||||
|
||||
@@ -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) + ".";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user