16bit processing
All checks were successful
CI / React UI Build (push) Successful in 38s
CI / Native Windows Build And Tests (push) Successful in 1m48s
CI / Windows Release Package (push) Successful in 2m16s

This commit is contained in:
2026-05-08 13:27:41 +10:00
parent fb9122ecdc
commit c9fed70a60
30 changed files with 770 additions and 1129 deletions

View File

@@ -2,311 +2,6 @@
#include "OpenGLComposite.h"
#include <initguid.h>
DEFINE_GUID(IID_PinnedMemoryAllocator,
0xddf921a6, 0x279d, 0x4dcd, 0x86, 0x26, 0x75, 0x7f, 0x58, 0xa8, 0xc4, 0x35);
////////////////////////////////////////////
// PinnedMemoryAllocator
////////////////////////////////////////////
// PinnedMemoryAllocator implements the IDeckLinkVideoBufferAllocator interface to be used instead of the
// built-in buffer allocator.
//
// For this sample application a custom buffer allocator is used to ensure each address
// of buffer memory is aligned on a 4kB boundary required by the OpenGL pinned memory extension.
// If the pinned memory extension is not available, this allocator will still be used and
// demonstrates how to cache buffer allocations for efficiency.
//
// The frame cache delays the releasing of buffers until the cache fills up, thereby avoiding an
// allocate plus pin operation for every frame, followed by an unpin and deallocate on every frame.
PinnedMemoryAllocator::PinnedMemoryAllocator(HDC hdc, HGLRC hglrc, VideoFrameTransfer::Direction direction, unsigned cacheSize, unsigned bufferSize) :
mHGLDC(hdc),
mHGLRC(hglrc),
mRefCount(1),
mDirection(direction),
mBufferSize(bufferSize),
mFrameCacheSize(cacheSize)
{
}
PinnedMemoryAllocator::~PinnedMemoryAllocator()
{
// Cleanup any unused buffers that remain in the cache
while (!mFrameCache.empty())
{
unPinAddress(mFrameCache.back());
VirtualFree(mFrameCache.back(), 0, MEM_RELEASE);
mFrameCache.pop_back();
}
for (auto iter = mFrameTransfer.begin(); iter != mFrameTransfer.end(); ++iter)
delete iter->second;
mFrameTransfer.clear();
}
bool PinnedMemoryAllocator::transferFrame(void* address, GLuint gpuTexture)
{
if (mFrameTransfer.count(address) == 0)
{
// VideoFrameTransfer prepares and pins address
mFrameTransfer[address] = new VideoFrameTransfer(mBufferSize, address, mDirection);
}
return mFrameTransfer[address]->performFrameTransfer();
}
void PinnedMemoryAllocator::waitForTransferComplete(void* address)
{
if (mFrameTransfer.count(address))
mFrameTransfer[address]->waitForTransferComplete();
}
void PinnedMemoryAllocator::unPinAddress(void* address)
{
// un-pin address only if it has been pinned for transfer
if (mFrameTransfer.count(address) > 0)
{
wglMakeCurrent(mHGLDC, mHGLRC);
mFrameTransfer.erase(address);
wglMakeCurrent(NULL, NULL);
}
}
HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::QueryInterface(REFIID iid, LPVOID* ppv)
{
if (!ppv)
return E_POINTER;
if (iid == IID_IUnknown || iid == IID_PinnedMemoryAllocator)
{
*ppv = this;
}
else if (iid == IID_IDeckLinkVideoBufferAllocator)
{
*ppv = static_cast<IDeckLinkVideoBufferAllocator*>(this);
}
else
{
*ppv = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::AddRef(void)
{
return ++mRefCount;
}
ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::Release(void)
{
int newCount = --mRefCount;
if (newCount == 0)
delete this;
return newCount;
}
HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::AllocateVideoBuffer(IDeckLinkVideoBuffer** allocatedBuffer)
{
std::shared_ptr<void> sharedMemBuffer;
// Manage caching of allocated buffers via shared_ptr deleter.
auto deleter = [this](void* buffer) mutable {
if (mFrameCache.size() < mFrameCacheSize)
{
mFrameCache.push_back(buffer);
}
else
{
// No room left in cache, so un-pin (if it was pinned) and free this buffer
unPinAddress(buffer);
VirtualFree(buffer, 0, MEM_RELEASE);
}
// We AddRef this class once the deleter is used because this class owns the mem
Release();
};
if (mFrameCache.empty())
{
// Allocate memory on a page boundary
void* memBuffer = VirtualAlloc(NULL, mBufferSize, MEM_COMMIT | MEM_RESERVE | MEM_WRITE_WATCH, PAGE_READWRITE);
if (!memBuffer)
return E_OUTOFMEMORY;
sharedMemBuffer = std::shared_ptr<void>(memBuffer, deleter);
}
else
{
// Re-use most recently released address
sharedMemBuffer = std::shared_ptr<void>(mFrameCache.back(), deleter);
mFrameCache.pop_back();
}
// This class owns the mem so the buffer we return needs to AddRef() this, and Release() in the deleter
AddRef();
*allocatedBuffer = new DeckLinkVideoBuffer(sharedMemBuffer, this);
return S_OK;
}
////////////////////////////////////////////
// InputAllocatorPool Class
////////////////////////////////////////////
InputAllocatorPool::InputAllocatorPool(HDC hdc, HGLRC hglrc)
{
mHDC = hdc;
mHGLRC = hglrc;
}
HRESULT InputAllocatorPool::QueryInterface(REFIID iid, void** ppv)
{
if (!ppv)
return E_POINTER;
if (iid == IID_IUnknown)
{
*ppv = this;
}
else if (iid == IID_IDeckLinkVideoBufferAllocatorProvider)
{
*ppv = static_cast<IDeckLinkVideoBufferAllocatorProvider*>(this);
}
else
{
*ppv = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG InputAllocatorPool::AddRef(void)
{
return ++mRefCount;
}
ULONG InputAllocatorPool::Release(void)
{
int newCount = --mRefCount;
if (newCount == 0)
delete this;
return newCount;
}
HRESULT InputAllocatorPool::GetVideoBufferAllocator(
/* [in] */ unsigned int bufferSize,
/* [in] */ unsigned int,
/* [in] */ unsigned int,
/* [in] */ unsigned int,
/* [in] */ BMDPixelFormat,
/* [out] */ IDeckLinkVideoBufferAllocator** allocator)
{
if (!allocator)
return E_POINTER;
auto existing = mAllocatorBySize.find(bufferSize);
if (existing != mAllocatorBySize.end())
{
*allocator = &*existing->second;
(*allocator)->AddRef();
return S_OK;
}
CComPtr<PinnedMemoryAllocator> newAllocator;
newAllocator.Attach(new (std::nothrow) PinnedMemoryAllocator(mHDC, mHGLRC, VideoFrameTransfer::CPUtoGPU, 3, bufferSize));
if (!newAllocator)
return E_OUTOFMEMORY;
mAllocatorBySize.emplace(std::make_pair(bufferSize, newAllocator));
*allocator = newAllocator.Detach();
return S_OK;
}
////////////////////////////////////////////
// DeckLink Video Buffer Class
////////////////////////////////////////////
DeckLinkVideoBuffer::DeckLinkVideoBuffer(std::shared_ptr<void>& buffer, PinnedMemoryAllocator* parent) :
mParentAllocator(parent),
mRefCount(1),
mBuffer(buffer)
{
}
HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::QueryInterface(REFIID riid, void** ppvObject)
{
HRESULT result = S_OK;
if (ppvObject == nullptr)
return E_POINTER;
if (riid == IID_IUnknown)
{
*ppvObject = this;
AddRef();
}
else if (riid == IID_IDeckLinkVideoBuffer)
{
*ppvObject = static_cast<IDeckLinkVideoBuffer*>(this);
AddRef();
}
else if (riid == IID_PinnedMemoryAllocator)
{
result = mParentAllocator->QueryInterface(riid, ppvObject);
}
else
{
*ppvObject = nullptr;
result = E_NOINTERFACE;
}
return result;
}
ULONG STDMETHODCALLTYPE DeckLinkVideoBuffer::AddRef()
{
return ++mRefCount;
}
ULONG STDMETHODCALLTYPE DeckLinkVideoBuffer::Release()
{
int newValue = --mRefCount;
if (newValue == 0)
delete this;
return newValue;
}
HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::GetBytes(void** buffer)
{
if (buffer == nullptr)
return E_POINTER;
*buffer = mBuffer.get();
return S_OK;
}
HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::GetSize(uint64_t* size)
{
if (size == nullptr)
return E_POINTER;
*size = mParentAllocator->bufferSize();
return S_OK;
}
HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::StartAccess(BMDBufferAccessFlags)
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE DeckLinkVideoBuffer::EndAccess(BMDBufferAccessFlags)
{
return S_OK;
}
////////////////////////////////////////////
// DeckLink Capture Delegate Class
////////////////////////////////////////////

View File

@@ -1,111 +1,13 @@
#pragma once
#include <windows.h>
#include <gl/gl.h>
#include <atlbase.h>
#include <atomic>
#include <map>
#include <memory>
#include <vector>
#include "DeckLinkAPI_h.h"
#include "VideoFrameTransfer.h"
extern "C" const IID IID_PinnedMemoryAllocator;
class OpenGLComposite;
////////////////////////////////////////////
// PinnedMemoryAllocator
////////////////////////////////////////////
class PinnedMemoryAllocator : public IDeckLinkVideoBufferAllocator
{
public:
PinnedMemoryAllocator(HDC hdc, HGLRC hglrc, VideoFrameTransfer::Direction direction, unsigned cacheSize, unsigned bufferSize);
virtual ~PinnedMemoryAllocator();
bool transferFrame(void* address, GLuint gpuTexture);
void waitForTransferComplete(void* address);
unsigned bufferSize() { return mBufferSize; }
// IUnknown methods
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv) override;
virtual ULONG STDMETHODCALLTYPE AddRef(void) override;
virtual ULONG STDMETHODCALLTYPE Release(void) override;
// IDeckLinkVideoBufferAllocator methods
virtual HRESULT STDMETHODCALLTYPE AllocateVideoBuffer(IDeckLinkVideoBuffer** allocatedBuffer) override;
private:
void unPinAddress(void* address);
private:
HDC mHGLDC;
HGLRC mHGLRC;
std::atomic<ULONG> mRefCount;
VideoFrameTransfer::Direction mDirection;
std::map<void*, VideoFrameTransfer*> mFrameTransfer;
unsigned mBufferSize;
std::vector<void*> mFrameCache;
unsigned mFrameCacheSize;
};
////////////////////////////////////////////
// InputAllocatorPool
////////////////////////////////////////////
class InputAllocatorPool : public IDeckLinkVideoBufferAllocatorProvider
{
public:
InputAllocatorPool(HDC hdc, HGLRC hglrc);
// IUnknown interface
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppv) override;
// IDeckLinkVideoBufferAllocatorProvider interface
HRESULT STDMETHODCALLTYPE GetVideoBufferAllocator(
/* [in] */ unsigned int bufferSize,
/* [in] */ unsigned int width,
/* [in] */ unsigned int height,
/* [in] */ unsigned int rowBytes,
/* [in] */ BMDPixelFormat pixelFormat,
/* [out] */ IDeckLinkVideoBufferAllocator** allocator) override;
private:
std::atomic<ULONG> mRefCount;
std::map<unsigned int, CComPtr<PinnedMemoryAllocator> > mAllocatorBySize;
HDC mHDC;
HGLRC mHGLRC;
};
////////////////////////////////////////////
// DeckLinkVideoBuffer
////////////////////////////////////////////
class DeckLinkVideoBuffer : public IDeckLinkVideoBuffer
{
public:
explicit DeckLinkVideoBuffer(std::shared_ptr<void>& buffer, PinnedMemoryAllocator* parent);
virtual ~DeckLinkVideoBuffer() = default;
// IUnknown interface
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override;
virtual ULONG STDMETHODCALLTYPE AddRef(void) override;
virtual ULONG STDMETHODCALLTYPE Release(void) override;
// IDeckLinkVideoBuffer interface
virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer) override;
virtual HRESULT STDMETHODCALLTYPE GetSize(uint64_t* size) override;
virtual HRESULT STDMETHODCALLTYPE StartAccess(BMDBufferAccessFlags flags) override;
virtual HRESULT STDMETHODCALLTYPE EndAccess(BMDBufferAccessFlags flags) override;
private:
CComPtr<PinnedMemoryAllocator> mParentAllocator;
std::atomic<ULONG> mRefCount;
std::shared_ptr<void> mBuffer;
};
////////////////////////////////////////////
// Capture Delegate Class
////////////////////////////////////////////

View File

@@ -6,6 +6,7 @@
#include <cstdio>
#include <cstring>
#include <new>
#include <sstream>
#include <vector>
namespace
@@ -25,6 +26,42 @@ std::string BstrToUtf8(BSTR value)
return std::string(utf8Name.data());
}
bool InputSupportsFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
{
if (input == nullptr)
return false;
BOOL supported = FALSE;
BMDDisplayMode actualMode = bmdModeUnknown;
const HRESULT result = input->DoesSupportVideoMode(
bmdVideoConnectionUnspecified,
displayMode,
pixelFormat,
bmdNoVideoInputConversion,
bmdSupportedVideoModeDefault,
&actualMode,
&supported);
return result == S_OK && supported != FALSE;
}
bool OutputSupportsFormat(IDeckLinkOutput* output, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
{
if (output == nullptr)
return false;
BOOL supported = FALSE;
BMDDisplayMode actualMode = bmdModeUnknown;
const HRESULT result = output->DoesSupportVideoMode(
bmdVideoConnectionUnspecified,
displayMode,
pixelFormat,
bmdNoVideoOutputConversion,
bmdSupportedVideoModeDefault,
&actualMode,
&supported);
return result == S_OK && supported != FALSE;
}
}
DeckLinkSession::~DeckLinkSession()
@@ -52,8 +89,6 @@ void DeckLinkSession::ReleaseResources()
playoutDelegate.Release();
outputVideoFrameQueue.clear();
output.Release();
playoutAllocator.Release();
}
bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
@@ -173,11 +208,75 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
inputDisplayModeName = "No input - black frame";
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
inputFrameRowBytes = inputFrameSize.width * 2u;
outputFrameRowBytes = outputFrameSize.width * 4u;
captureTextureWidth = inputFrameSize.width / 2u;
outputPackTextureWidth = outputFrameSize.width;
return true;
}
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error)
{
if (!output)
{
error = "Expected an Output DeckLink device";
return false;
}
formatStatusMessage.clear();
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV);
inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8;
if (input != nullptr && !inputTenBitSupported)
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);
outputPixelFormat = ChoosePreferredVideoIOFormat(outputTenBitSupported);
if (!outputTenBitSupported)
formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. ";
int deckLinkOutputRowBytes = 0;
if (output->RowBytesForPixelFormat(OutputIsTenBit() ? bmdFormat10BitYUV : bmdFormat8BitBGRA, outputFrameSize.width, &deckLinkOutputRowBytes) != S_OK)
{
error = "DeckLink output setup failed while calculating output row bytes.";
return false;
}
outputFrameRowBytes = static_cast<unsigned>(deckLinkOutputRowBytes);
outputPackTextureWidth = OutputIsTenBit()
? PackedTextureWidthFromRowBytes(outputFrameRowBytes)
: outputFrameSize.width;
if (InputIsTenBit())
{
int deckLinkInputRowBytes = 0;
if (output->RowBytesForPixelFormat(bmdFormat10BitYUV, inputFrameSize.width, &deckLinkInputRowBytes) == S_OK)
inputFrameRowBytes = static_cast<unsigned>(deckLinkInputRowBytes);
else
inputFrameRowBytes = MinimumV210RowBytes(inputFrameSize.width);
}
else
{
inputFrameRowBytes = inputFrameSize.width * 2u;
}
captureTextureWidth = InputIsTenBit()
? PackedTextureWidthFromRowBytes(inputFrameRowBytes)
: inputFrameSize.width / 2u;
std::ostringstream status;
status << "DeckLink formats: capture " << (input ? VideoIOPixelFormatName(inputPixelFormat) : "none")
<< ", output " << (OutputIsTenBit() ? "10-bit YUV v210" : "8-bit BGRA") << ".";
if (!formatStatusMessage.empty())
status << " " << formatStatusMessage;
formatStatusMessage = status.str();
return true;
}
bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& inputVideoMode, std::string& error)
{
(void)hdc;
(void)hglrc;
if (!input)
{
hasNoInputSource = true;
@@ -185,10 +284,26 @@ bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglr
return true;
}
CComPtr<IDeckLinkVideoBufferAllocatorProvider> captureAllocator(new (std::nothrow) InputAllocatorPool(hdc, hglrc));
if (input->EnableVideoInputWithAllocatorProvider(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault, captureAllocator) != S_OK)
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(inputPixelFormat);
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
{
if (inputPixelFormat == VideoIOPixelFormat::V210)
{
OutputDebugStringA("DeckLink 10-bit input could not be enabled; falling back to 8-bit capture.\n");
inputPixelFormat = VideoIOPixelFormat::Uyvy8;
inputFrameRowBytes = inputFrameSize.width * 2u;
captureTextureWidth = inputFrameSize.width / 2u;
if (input->EnableVideoInput(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK)
{
std::ostringstream status;
status << "DeckLink formats: capture " << VideoIOPixelFormatName(inputPixelFormat)
<< ", output " << (OutputIsTenBit() ? "10-bit YUV v210" : "8-bit BGRA")
<< ". DeckLink 10-bit input enable failed; using 8-bit capture.";
formatStatusMessage = status.str();
goto input_enabled;
}
}
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
input.Release();
hasNoInputSource = true;
@@ -196,6 +311,7 @@ bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglr
return true;
}
input_enabled:
captureDelegate.Attach(new (std::nothrow) CaptureDelegate(owner));
if (captureDelegate == nullptr)
{
@@ -213,19 +329,8 @@ bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglr
bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
{
int outputFrameRowBytes = 0;
if (output->RowBytesForPixelFormat(bmdFormat8BitBGRA, outputFrameSize.width, &outputFrameRowBytes) != S_OK)
{
error = "DeckLink output setup failed while calculating BGRA row bytes.";
return false;
}
playoutAllocator.Attach(new (std::nothrow) PinnedMemoryAllocator(hdc, hglrc, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * outputFrameSize.height));
if (playoutAllocator == nullptr)
{
error = "DeckLink output setup failed while creating the playout allocator.";
return false;
}
(void)hdc;
(void)hglrc;
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK)
{
@@ -264,15 +369,9 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
for (int i = 0; i < 10; i++)
{
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
CComPtr<IDeckLinkVideoBuffer> outputFrameBuffer;
if (playoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK)
{
error = "DeckLink output setup failed while allocating an output frame buffer.";
return false;
}
if (output->CreateVideoFrameWithBuffer(outputFrameSize.width, outputFrameSize.height, outputFrameRowBytes, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, outputFrameBuffer, &outputFrame) != S_OK)
const BMDPixelFormat deckLinkOutputPixelFormat = OutputIsTenBit() ? bmdFormat10BitYUV : bmdFormat8BitBGRA;
if (output->CreateVideoFrame(outputFrameSize.width, outputFrameSize.height, outputFrameRowBytes, deckLinkOutputPixelFormat, bmdFrameFlagFlipVertical, &outputFrame) != S_OK)
{
error = "DeckLink output setup failed while creating an output video frame.";
return false;
@@ -294,6 +393,9 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
return false;
}
if (!formatStatusMessage.empty())
statusMessage = statusMessage.empty() ? formatStatusMessage : formatStatusMessage + " " + statusMessage;
return true;
}
@@ -312,17 +414,6 @@ IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame()
return outputVideoFrame.p;
}
bool DeckLinkSession::TransferPlayoutFrame(void* address, GLuint outputTexture)
{
return playoutAllocator != nullptr && playoutAllocator->transferFrame(address, outputTexture);
}
void DeckLinkSession::WaitForPlayoutTransferComplete(void* address)
{
if (playoutAllocator != nullptr)
playoutAllocator->waitForTransferComplete(address);
}
void DeckLinkSession::AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult)
{
if (completionResult == bmdOutputFrameDisplayedLate || completionResult == bmdOutputFrameDropped)

View File

@@ -3,6 +3,7 @@
#include "DeckLinkAPI_h.h"
#include "DeckLinkDisplayMode.h"
#include "DeckLinkFrameTransfer.h"
#include "VideoIOFormat.h"
#include <atlbase.h>
#include <deque>
@@ -18,6 +19,7 @@ public:
void ReleaseResources();
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error);
bool ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& inputVideoMode, std::string& error);
bool ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
bool Start();
@@ -33,6 +35,15 @@ public:
unsigned InputFrameHeight() const { return inputFrameSize.height; }
unsigned OutputFrameWidth() const { return outputFrameSize.width; }
unsigned OutputFrameHeight() const { return outputFrameSize.height; }
VideoIOPixelFormat InputPixelFormat() const { return inputPixelFormat; }
VideoIOPixelFormat OutputPixelFormat() const { return outputPixelFormat; }
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(inputPixelFormat); }
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(outputPixelFormat); }
unsigned InputFrameRowBytes() const { return inputFrameRowBytes; }
unsigned OutputFrameRowBytes() const { return outputFrameRowBytes; }
unsigned CaptureTextureWidth() const { return captureTextureWidth; }
unsigned OutputPackTextureWidth() const { return outputPackTextureWidth; }
const std::string& FormatStatusMessage() const { return formatStatusMessage; }
const std::string& InputDisplayModeName() const { return inputDisplayModeName; }
const std::string& OutputModelName() const { return outputModelName; }
bool SupportsInternalKeying() const { return supportsInternalKeying; }
@@ -43,8 +54,6 @@ public:
void SetStatusMessage(const std::string& message) { statusMessage = message; }
double FrameBudgetMilliseconds() const;
IDeckLinkMutableVideoFrame* RotateOutputFrame();
bool TransferPlayoutFrame(void* address, GLuint outputTexture);
void WaitForPlayoutTransferComplete(void* address);
void AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult);
bool ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
@@ -55,12 +64,17 @@ private:
CComPtr<IDeckLinkOutput> output;
CComPtr<IDeckLinkKeyer> keyer;
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
CComPtr<PinnedMemoryAllocator> playoutAllocator;
BMDTimeValue frameDuration = 0;
BMDTimeScale frameTimescale = 0;
unsigned totalPlayoutFrames = 0;
FrameSize inputFrameSize;
FrameSize outputFrameSize;
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
VideoIOPixelFormat outputPixelFormat = VideoIOPixelFormat::Uyvy8;
unsigned inputFrameRowBytes = 0;
unsigned outputFrameRowBytes = 0;
unsigned captureTextureWidth = 0;
unsigned outputPackTextureWidth = 0;
std::string inputDisplayModeName = "1080p59.94";
std::string outputDisplayModeName = "1080p59.94";
bool hasNoInputSource = true;
@@ -70,4 +84,5 @@ private:
bool keyerInterfaceAvailable = false;
bool externalKeyingActive = false;
std::string statusMessage;
std::string formatStatusMessage;
};

View File

@@ -0,0 +1,139 @@
#include "VideoIOFormat.h"
#include <algorithm>
#include <cmath>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace
{
uint16_t Clamp10(int value, int minimum, int maximum)
{
return static_cast<uint16_t>(std::max(minimum, std::min(maximum, value)));
}
uint32_t MakeV210Word(uint16_t a, uint16_t b, uint16_t c)
{
return (static_cast<uint32_t>(a) & 0x3ffu)
| ((static_cast<uint32_t>(b) & 0x3ffu) << 10)
| ((static_cast<uint32_t>(c) & 0x3ffu) << 20);
}
void StoreWord(std::array<uint8_t, 16>& bytes, std::size_t wordIndex, uint32_t word)
{
const std::size_t offset = wordIndex * 4;
bytes[offset + 0] = static_cast<uint8_t>(word & 0xffu);
bytes[offset + 1] = static_cast<uint8_t>((word >> 8) & 0xffu);
bytes[offset + 2] = static_cast<uint8_t>((word >> 16) & 0xffu);
bytes[offset + 3] = static_cast<uint8_t>((word >> 24) & 0xffu);
}
uint32_t LoadWord(const std::array<uint8_t, 16>& bytes, std::size_t wordIndex)
{
const std::size_t offset = wordIndex * 4;
return static_cast<uint32_t>(bytes[offset + 0])
| (static_cast<uint32_t>(bytes[offset + 1]) << 8)
| (static_cast<uint32_t>(bytes[offset + 2]) << 16)
| (static_cast<uint32_t>(bytes[offset + 3]) << 24);
}
uint16_t Component(uint32_t word, unsigned index)
{
return static_cast<uint16_t>((word >> (index * 10)) & 0x3ffu);
}
}
const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
{
return format == VideoIOPixelFormat::V210 ? "10-bit YUV v210" : "8-bit YUV UYVY";
}
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
{
return format == VideoIOPixelFormat::V210;
}
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format)
{
return format == VideoIOPixelFormat::V210 ? bmdFormat10BitYUV : bmdFormat8BitYUV;
}
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format)
{
return format == bmdFormat10BitYUV ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Uyvy8;
}
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported)
{
return tenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Uyvy8;
}
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes)
{
return (rowBytes + 3u) / 4u;
}
unsigned MinimumV210RowBytes(unsigned frameWidth)
{
return ((frameWidth + 5u) / 6u) * 16u;
}
unsigned ActiveV210WordsForWidth(unsigned frameWidth)
{
return ((frameWidth + 5u) / 6u) * 4u;
}
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue)
{
red = std::max(0.0f, std::min(1.0f, red));
green = std::max(0.0f, std::min(1.0f, green));
blue = std::max(0.0f, std::min(1.0f, blue));
const float y = 0.2126f * red + 0.7152f * green + 0.0722f * blue;
const float cb = (blue - y) / 1.8556f + 0.5f;
const float cr = (red - y) / 1.5748f + 0.5f;
V210CodeValues values;
values.y = Clamp10(static_cast<int>(std::lround(64.0f + y * 876.0f)), 64, 940);
values.cb = Clamp10(static_cast<int>(std::lround(64.0f + cb * 896.0f)), 64, 960);
values.cr = Clamp10(static_cast<int>(std::lround(64.0f + cr * 896.0f)), 64, 960);
return values;
}
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block)
{
std::array<uint8_t, 16> bytes = {};
StoreWord(bytes, 0, MakeV210Word(block.cb[0], block.y[0], block.cr[0]));
StoreWord(bytes, 1, MakeV210Word(block.y[1], block.cb[1], block.y[2]));
StoreWord(bytes, 2, MakeV210Word(block.cr[1], block.y[3], block.cb[2]));
StoreWord(bytes, 3, MakeV210Word(block.y[4], block.cr[2], block.y[5]));
return bytes;
}
V210SixPixelBlock UnpackV210Block(const std::array<uint8_t, 16>& bytes)
{
const uint32_t word0 = LoadWord(bytes, 0);
const uint32_t word1 = LoadWord(bytes, 1);
const uint32_t word2 = LoadWord(bytes, 2);
const uint32_t word3 = LoadWord(bytes, 3);
V210SixPixelBlock block;
block.cb[0] = Component(word0, 0);
block.y[0] = Component(word0, 1);
block.cr[0] = Component(word0, 2);
block.y[1] = Component(word1, 0);
block.cb[1] = Component(word1, 1);
block.y[2] = Component(word1, 2);
block.cr[1] = Component(word2, 0);
block.y[3] = Component(word2, 1);
block.cb[2] = Component(word2, 2);
block.y[4] = Component(word3, 0);
block.cr[2] = Component(word3, 1);
block.y[5] = Component(word3, 2);
return block;
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include "DeckLinkAPI_h.h"
#include <array>
#include <cstdint>
enum class VideoIOPixelFormat
{
Uyvy8,
V210
};
struct V210CodeValues
{
uint16_t y = 64;
uint16_t cb = 512;
uint16_t cr = 512;
};
struct V210SixPixelBlock
{
std::array<uint16_t, 6> y = {};
std::array<uint16_t, 3> cb = {};
std::array<uint16_t, 3> cr = {};
};
const char* VideoIOPixelFormatName(VideoIOPixelFormat format);
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format);
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format);
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format);
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported);
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes);
unsigned MinimumV210RowBytes(unsigned frameWidth);
unsigned ActiveV210WordsForWidth(unsigned frameWidth);
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue);
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block);
V210SixPixelBlock UnpackV210Block(const std::array<uint8_t, 16>& bytes);