16bit processing
This commit is contained in:
@@ -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
|
||||
////////////////////////////////////////////
|
||||
|
||||
@@ -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
|
||||
////////////////////////////////////////////
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
139
apps/LoopThroughWithOpenGLCompositing/decklink/VideoIOFormat.cpp
Normal file
139
apps/LoopThroughWithOpenGLCompositing/decklink/VideoIOFormat.cpp
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user