Decklink abstraction
This commit is contained in:
@@ -89,7 +89,7 @@
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;videoio;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
@@ -111,7 +111,7 @@
|
||||
</Midl>
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;videoio;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
@@ -131,7 +131,7 @@
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;videoio;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
@@ -156,7 +156,7 @@
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>.;control;decklink;gl;videoio;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
@@ -184,6 +184,7 @@
|
||||
<ClCompile Include="gl\PngScreenshotWriter.cpp" />
|
||||
<ClCompile Include="gl\ShaderBuildQueue.cpp" />
|
||||
<ClCompile Include="gl\TemporalHistoryBuffers.cpp" />
|
||||
<ClCompile Include="gl\OpenGLVideoIOBridge.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
@@ -193,8 +194,10 @@
|
||||
<ClCompile Include="DeckLinkAPI_i.c" />
|
||||
<ClCompile Include="control\RuntimeServices.cpp" />
|
||||
<ClCompile Include="decklink\DeckLinkSession.cpp" />
|
||||
<ClCompile Include="decklink\VideoIOFormat.cpp" />
|
||||
<ClCompile Include="decklink\DeckLinkVideoIOFormat.cpp" />
|
||||
<ClCompile Include="runtime\RuntimeClock.cpp" />
|
||||
<ClCompile Include="videoio\VideoIOFormat.cpp" />
|
||||
<ClCompile Include="videoio\VideoPlayoutScheduler.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="gl\GLExtensions.h" />
|
||||
@@ -206,13 +209,17 @@
|
||||
<ClInclude Include="gl\PngScreenshotWriter.h" />
|
||||
<ClInclude Include="gl\ShaderBuildQueue.h" />
|
||||
<ClInclude Include="gl\TemporalHistoryBuffers.h" />
|
||||
<ClInclude Include="gl\OpenGLVideoIOBridge.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
<ClInclude Include="control\RuntimeServices.h" />
|
||||
<ClInclude Include="decklink\DeckLinkSession.h" />
|
||||
<ClInclude Include="decklink\VideoIOFormat.h" />
|
||||
<ClInclude Include="decklink\DeckLinkVideoIOFormat.h" />
|
||||
<ClInclude Include="runtime\RuntimeClock.h" />
|
||||
<ClInclude Include="videoio\VideoIOFormat.h" />
|
||||
<ClInclude Include="videoio\VideoIOTypes.h" />
|
||||
<ClInclude Include="videoio\VideoPlayoutScheduler.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="LoopThroughWithOpenGLCompositing.ico" />
|
||||
|
||||
@@ -45,6 +45,9 @@
|
||||
<ClCompile Include="gl\TemporalHistoryBuffers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="gl\OpenGLVideoIOBridge.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -57,12 +60,18 @@
|
||||
<ClCompile Include="decklink\DeckLinkSession.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="decklink\VideoIOFormat.cpp">
|
||||
<ClCompile Include="decklink\DeckLinkVideoIOFormat.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="runtime\RuntimeClock.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\VideoIOFormat.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="videoio\VideoPlayoutScheduler.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="gl\GLExtensions.h">
|
||||
@@ -92,6 +101,9 @@
|
||||
<ClInclude Include="gl\TemporalHistoryBuffers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="gl\OpenGLVideoIOBridge.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@@ -107,12 +119,21 @@
|
||||
<ClInclude Include="decklink\DeckLinkSession.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="decklink\VideoIOFormat.h">
|
||||
<ClInclude Include="decklink\DeckLinkVideoIOFormat.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="runtime\RuntimeClock.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\VideoIOFormat.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\VideoIOTypes.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="videoio\VideoPlayoutScheduler.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="LoopThroughWithOpenGLCompositing.ico">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "DeckLinkFrameTransfer.h"
|
||||
|
||||
#include "OpenGLComposite.h"
|
||||
#include "DeckLinkSession.h"
|
||||
|
||||
////////////////////////////////////////////
|
||||
// DeckLink Capture Delegate Class
|
||||
////////////////////////////////////////////
|
||||
CaptureDelegate::CaptureDelegate(OpenGLComposite* pOwner) :
|
||||
CaptureDelegate::CaptureDelegate(DeckLinkSession* pOwner) :
|
||||
m_pOwner(pOwner),
|
||||
mRefCount(1)
|
||||
{
|
||||
@@ -39,7 +39,7 @@ HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputF
|
||||
}
|
||||
|
||||
bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource;
|
||||
m_pOwner->VideoFrameArrived(inputFrame, hasNoInputSource);
|
||||
m_pOwner->HandleVideoInputFrame(inputFrame, hasNoInputSource);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvent
|
||||
////////////////////////////////////////////
|
||||
// DeckLink Playout Delegate Class
|
||||
////////////////////////////////////////////
|
||||
PlayoutDelegate::PlayoutDelegate(OpenGLComposite* pOwner) :
|
||||
PlayoutDelegate::PlayoutDelegate(DeckLinkSession* pOwner) :
|
||||
m_pOwner(pOwner),
|
||||
mRefCount(1)
|
||||
{
|
||||
@@ -94,7 +94,7 @@ HRESULT PlayoutDelegate::ScheduledFrameCompleted(IDeckLinkVideoFrame* completedF
|
||||
OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n");
|
||||
}
|
||||
|
||||
m_pOwner->PlayoutFrameCompleted(completedFrame, result);
|
||||
m_pOwner->HandlePlayoutFrameCompleted(completedFrame, result);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
|
||||
class OpenGLComposite;
|
||||
class DeckLinkSession;
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Capture Delegate Class
|
||||
////////////////////////////////////////////
|
||||
class CaptureDelegate : public IDeckLinkInputCallback
|
||||
{
|
||||
OpenGLComposite* m_pOwner;
|
||||
DeckLinkSession* m_pOwner;
|
||||
LONG mRefCount;
|
||||
|
||||
public:
|
||||
CaptureDelegate(OpenGLComposite* pOwner);
|
||||
CaptureDelegate(DeckLinkSession* pOwner);
|
||||
|
||||
// IUnknown needs only a dummy implementation
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
|
||||
@@ -33,11 +33,11 @@ public:
|
||||
////////////////////////////////////////////
|
||||
class PlayoutDelegate : public IDeckLinkVideoOutputCallback
|
||||
{
|
||||
OpenGLComposite* m_pOwner;
|
||||
DeckLinkSession* m_pOwner;
|
||||
LONG mRefCount;
|
||||
|
||||
public:
|
||||
PlayoutDelegate(OpenGLComposite* pOwner);
|
||||
PlayoutDelegate(DeckLinkSession* pOwner);
|
||||
|
||||
// IUnknown needs only a dummy implementation
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
@@ -82,7 +83,7 @@ void DeckLinkSession::ReleaseResources()
|
||||
if (keyer != nullptr)
|
||||
{
|
||||
keyer->Disable();
|
||||
externalKeyingActive = false;
|
||||
mState.externalKeyingActive = false;
|
||||
}
|
||||
keyer.Release();
|
||||
|
||||
@@ -97,8 +98,8 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
|
||||
CComPtr<IDeckLinkDisplayMode> inputMode;
|
||||
CComPtr<IDeckLinkDisplayMode> outputMode;
|
||||
|
||||
inputDisplayModeName = videoModes.input.displayName;
|
||||
outputDisplayModeName = videoModes.output.displayName;
|
||||
mState.inputDisplayModeName = videoModes.input.displayName;
|
||||
mState.outputDisplayModeName = videoModes.output.displayName;
|
||||
|
||||
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&deckLinkIterator));
|
||||
if (FAILED(result))
|
||||
@@ -150,9 +151,9 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
|
||||
output.Release();
|
||||
else
|
||||
{
|
||||
outputModelName = modelName;
|
||||
supportsInternalKeying = deviceSupportsInternalKeying;
|
||||
supportsExternalKeying = deviceSupportsExternalKeying;
|
||||
mState.outputModelName = modelName;
|
||||
mState.supportsInternalKeying = deviceSupportsInternalKeying;
|
||||
mState.supportsExternalKeying = deviceSupportsExternalKeying;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,18 +201,24 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
|
||||
return false;
|
||||
}
|
||||
|
||||
outputFrameSize = { static_cast<unsigned>(outputMode->GetWidth()), static_cast<unsigned>(outputMode->GetHeight()) };
|
||||
inputFrameSize = inputMode
|
||||
mState.outputFrameSize = { static_cast<unsigned>(outputMode->GetWidth()), static_cast<unsigned>(outputMode->GetHeight()) };
|
||||
mState.inputFrameSize = inputMode
|
||||
? FrameSize{ static_cast<unsigned>(inputMode->GetWidth()), static_cast<unsigned>(inputMode->GetHeight()) }
|
||||
: outputFrameSize;
|
||||
: mState.outputFrameSize;
|
||||
if (!input)
|
||||
inputDisplayModeName = "No input - black frame";
|
||||
mState.inputDisplayModeName = "No input - black frame";
|
||||
BMDTimeValue frameDuration = 0;
|
||||
BMDTimeScale frameTimescale = 0;
|
||||
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
|
||||
mScheduler.Configure(frameDuration, frameTimescale);
|
||||
mState.frameBudgetMilliseconds = mScheduler.FrameBudgetMilliseconds();
|
||||
|
||||
inputFrameRowBytes = inputFrameSize.width * 2u;
|
||||
outputFrameRowBytes = outputFrameSize.width * 4u;
|
||||
captureTextureWidth = inputFrameSize.width / 2u;
|
||||
outputPackTextureWidth = outputFrameSize.width;
|
||||
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||
mState.outputFrameRowBytes = mState.outputFrameSize.width * 4u;
|
||||
mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
|
||||
mState.outputPackTextureWidth = mState.outputFrameSize.width;
|
||||
mState.hasInputDevice = input != nullptr;
|
||||
mState.hasInputSource = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -224,95 +231,95 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo
|
||||
return false;
|
||||
}
|
||||
|
||||
formatStatusMessage.clear();
|
||||
mState.formatStatusMessage.clear();
|
||||
|
||||
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV);
|
||||
inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8;
|
||||
mState.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. ";
|
||||
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);
|
||||
outputPixelFormat = ChoosePreferredVideoIOFormat(outputTenBitSupported);
|
||||
mState.outputPixelFormat = outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8;
|
||||
if (!outputTenBitSupported)
|
||||
formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. ";
|
||||
mState.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)
|
||||
if (output->RowBytesForPixelFormat(DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat), mState.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;
|
||||
mState.outputFrameRowBytes = static_cast<unsigned>(deckLinkOutputRowBytes);
|
||||
mState.outputPackTextureWidth = OutputIsTenBit()
|
||||
? PackedTextureWidthFromRowBytes(mState.outputFrameRowBytes)
|
||||
: mState.outputFrameSize.width;
|
||||
|
||||
if (InputIsTenBit())
|
||||
{
|
||||
int deckLinkInputRowBytes = 0;
|
||||
if (output->RowBytesForPixelFormat(bmdFormat10BitYUV, inputFrameSize.width, &deckLinkInputRowBytes) == S_OK)
|
||||
inputFrameRowBytes = static_cast<unsigned>(deckLinkInputRowBytes);
|
||||
if (output->RowBytesForPixelFormat(bmdFormat10BitYUV, mState.inputFrameSize.width, &deckLinkInputRowBytes) == S_OK)
|
||||
mState.inputFrameRowBytes = static_cast<unsigned>(deckLinkInputRowBytes);
|
||||
else
|
||||
inputFrameRowBytes = MinimumV210RowBytes(inputFrameSize.width);
|
||||
mState.inputFrameRowBytes = MinimumV210RowBytes(mState.inputFrameSize.width);
|
||||
}
|
||||
else
|
||||
{
|
||||
inputFrameRowBytes = inputFrameSize.width * 2u;
|
||||
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||
}
|
||||
captureTextureWidth = InputIsTenBit()
|
||||
? PackedTextureWidthFromRowBytes(inputFrameRowBytes)
|
||||
: inputFrameSize.width / 2u;
|
||||
mState.captureTextureWidth = InputIsTenBit()
|
||||
? PackedTextureWidthFromRowBytes(mState.inputFrameRowBytes)
|
||||
: mState.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();
|
||||
status << "DeckLink formats: capture " << (input ? VideoIOPixelFormatName(mState.inputPixelFormat) : "none")
|
||||
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat) << ".";
|
||||
if (!mState.formatStatusMessage.empty())
|
||||
status << " " << mState.formatStatusMessage;
|
||||
mState.formatStatusMessage = status.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& inputVideoMode, std::string& error)
|
||||
bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error)
|
||||
{
|
||||
(void)hdc;
|
||||
(void)hglrc;
|
||||
mInputFrameCallback = std::move(callback);
|
||||
|
||||
if (!input)
|
||||
{
|
||||
hasNoInputSource = true;
|
||||
inputDisplayModeName = "No input - black frame";
|
||||
mState.hasInputSource = false;
|
||||
mState.inputDisplayModeName = "No input - black frame";
|
||||
return true;
|
||||
}
|
||||
|
||||
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(inputPixelFormat);
|
||||
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat);
|
||||
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
|
||||
{
|
||||
if (inputPixelFormat == VideoIOPixelFormat::V210)
|
||||
if (mState.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;
|
||||
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)
|
||||
{
|
||||
std::ostringstream status;
|
||||
status << "DeckLink formats: capture " << VideoIOPixelFormatName(inputPixelFormat)
|
||||
<< ", output " << (OutputIsTenBit() ? "10-bit YUV v210" : "8-bit BGRA")
|
||||
status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat)
|
||||
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat)
|
||||
<< ". DeckLink 10-bit input enable failed; using 8-bit capture.";
|
||||
formatStatusMessage = status.str();
|
||||
mState.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;
|
||||
inputDisplayModeName = "No input - black frame";
|
||||
mState.hasInputDevice = false;
|
||||
mState.hasInputSource = false;
|
||||
mState.inputDisplayModeName = "No input - black frame";
|
||||
return true;
|
||||
}
|
||||
|
||||
input_enabled:
|
||||
captureDelegate.Attach(new (std::nothrow) CaptureDelegate(owner));
|
||||
captureDelegate.Attach(new (std::nothrow) CaptureDelegate(this));
|
||||
if (captureDelegate == nullptr)
|
||||
{
|
||||
error = "DeckLink input setup failed while creating the capture callback.";
|
||||
@@ -327,10 +334,9 @@ input_enabled:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
||||
bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
||||
{
|
||||
(void)hdc;
|
||||
(void)hglrc;
|
||||
mOutputFrameCallback = std::move(callback);
|
||||
|
||||
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK)
|
||||
{
|
||||
@@ -339,39 +345,39 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
|
||||
}
|
||||
|
||||
if (output->QueryInterface(IID_IDeckLinkKeyer, (void**)&keyer) == S_OK && keyer != NULL)
|
||||
keyerInterfaceAvailable = true;
|
||||
mState.keyerInterfaceAvailable = true;
|
||||
|
||||
if (externalKeyingEnabled)
|
||||
{
|
||||
if (!supportsExternalKeying)
|
||||
if (!mState.supportsExternalKeying)
|
||||
{
|
||||
statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
|
||||
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
|
||||
}
|
||||
else if (!keyerInterfaceAvailable)
|
||||
else if (!mState.keyerInterfaceAvailable)
|
||||
{
|
||||
statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface.";
|
||||
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface.";
|
||||
}
|
||||
else if (keyer->Enable(TRUE) != S_OK || keyer->SetLevel(255) != S_OK)
|
||||
{
|
||||
statusMessage = "External keying was requested, but enabling the DeckLink keyer failed.";
|
||||
mState.statusMessage = "External keying was requested, but enabling the DeckLink keyer failed.";
|
||||
}
|
||||
else
|
||||
{
|
||||
externalKeyingActive = true;
|
||||
statusMessage = "External keying is active on the selected DeckLink output.";
|
||||
mState.externalKeyingActive = true;
|
||||
mState.statusMessage = "External keying is active on the selected DeckLink output.";
|
||||
}
|
||||
}
|
||||
else if (supportsExternalKeying)
|
||||
else if (mState.supportsExternalKeying)
|
||||
{
|
||||
statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
||||
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
|
||||
|
||||
const BMDPixelFormat deckLinkOutputPixelFormat = OutputIsTenBit() ? bmdFormat10BitYUV : bmdFormat8BitBGRA;
|
||||
if (output->CreateVideoFrame(outputFrameSize.width, outputFrameSize.height, outputFrameRowBytes, deckLinkOutputPixelFormat, bmdFrameFlagFlipVertical, &outputFrame) != S_OK)
|
||||
const BMDPixelFormat deckLinkOutputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat);
|
||||
if (output->CreateVideoFrame(mState.outputFrameSize.width, mState.outputFrameSize.height, mState.outputFrameRowBytes, deckLinkOutputPixelFormat, bmdFrameFlagFlipVertical, &outputFrame) != S_OK)
|
||||
{
|
||||
error = "DeckLink output setup failed while creating an output video frame.";
|
||||
return false;
|
||||
@@ -380,7 +386,7 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
|
||||
outputVideoFrameQueue.push_back(outputFrame);
|
||||
}
|
||||
|
||||
playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(owner));
|
||||
playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(this));
|
||||
if (playoutDelegate == nullptr)
|
||||
{
|
||||
error = "DeckLink output setup failed while creating the playout callback.";
|
||||
@@ -393,45 +399,73 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!formatStatusMessage.empty())
|
||||
statusMessage = statusMessage.empty() ? formatStatusMessage : formatStatusMessage + " " + statusMessage;
|
||||
if (!mState.formatStatusMessage.empty())
|
||||
mState.statusMessage = mState.statusMessage.empty() ? mState.formatStatusMessage : mState.formatStatusMessage + " " + mState.statusMessage;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
double DeckLinkSession::FrameBudgetMilliseconds() const
|
||||
{
|
||||
return frameTimescale != 0
|
||||
? (static_cast<double>(frameDuration) * 1000.0) / static_cast<double>(frameTimescale)
|
||||
: 0.0;
|
||||
return mScheduler.FrameBudgetMilliseconds();
|
||||
}
|
||||
|
||||
IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame()
|
||||
bool DeckLinkSession::BeginOutputFrame(VideoIOOutputFrame& frame)
|
||||
{
|
||||
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame = outputVideoFrameQueue.front();
|
||||
outputVideoFrameQueue.push_back(outputVideoFrame);
|
||||
outputVideoFrameQueue.pop_front();
|
||||
return outputVideoFrame.p;
|
||||
}
|
||||
|
||||
void DeckLinkSession::AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult)
|
||||
{
|
||||
if (completionResult == bmdOutputFrameDisplayedLate || completionResult == bmdOutputFrameDropped)
|
||||
totalPlayoutFrames += 2;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
|
||||
{
|
||||
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
|
||||
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
|
||||
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
||||
return false;
|
||||
|
||||
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
||||
return false;
|
||||
|
||||
void* pFrame = nullptr;
|
||||
outputVideoFrameBuffer->GetBytes(&pFrame);
|
||||
|
||||
frame.bytes = pFrame;
|
||||
frame.rowBytes = outputVideoFrame->GetRowBytes();
|
||||
frame.width = mState.outputFrameSize.width;
|
||||
frame.height = mState.outputFrameSize.height;
|
||||
frame.pixelFormat = mState.outputPixelFormat;
|
||||
frame.nativeFrame = outputVideoFrame.p;
|
||||
frame.nativeBuffer = outputVideoFrameBuffer.Detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckLinkSession::EndOutputFrame(VideoIOOutputFrame& frame)
|
||||
{
|
||||
IDeckLinkVideoBuffer* outputVideoFrameBuffer = static_cast<IDeckLinkVideoBuffer*>(frame.nativeBuffer);
|
||||
if (outputVideoFrameBuffer != nullptr)
|
||||
{
|
||||
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
||||
outputVideoFrameBuffer->Release();
|
||||
}
|
||||
frame.nativeBuffer = nullptr;
|
||||
frame.bytes = nullptr;
|
||||
}
|
||||
|
||||
void DeckLinkSession::AccountForCompletionResult(VideoIOCompletionResult completionResult)
|
||||
{
|
||||
mScheduler.AccountForCompletionResult(completionResult);
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ScheduleOutputFrame(const VideoIOOutputFrame& frame)
|
||||
{
|
||||
IDeckLinkMutableVideoFrame* outputVideoFrame = static_cast<IDeckLinkMutableVideoFrame*>(frame.nativeFrame);
|
||||
const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime();
|
||||
if (outputVideoFrame == nullptr || output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale) != S_OK)
|
||||
return false;
|
||||
|
||||
totalPlayoutFrames++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::Start()
|
||||
{
|
||||
totalPlayoutFrames = 0;
|
||||
mScheduler.Reset();
|
||||
if (!output)
|
||||
{
|
||||
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
@@ -462,19 +496,18 @@ bool DeckLinkSession::Start()
|
||||
return false;
|
||||
}
|
||||
|
||||
void* pFrame;
|
||||
void* pFrame = nullptr;
|
||||
outputVideoFrameBuffer->GetBytes((void**)&pFrame);
|
||||
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputFrameSize.height);
|
||||
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mState.outputFrameSize.height);
|
||||
|
||||
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
||||
|
||||
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
|
||||
const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime();
|
||||
if (output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale) != S_OK)
|
||||
{
|
||||
MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
totalPlayoutFrames++;
|
||||
}
|
||||
|
||||
if (input)
|
||||
@@ -485,7 +518,7 @@ bool DeckLinkSession::Start()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (output->StartScheduledPlayback(0, frameTimescale, 1.0) != S_OK)
|
||||
if (output->StartScheduledPlayback(0, mScheduler.TimeScale(), 1.0) != S_OK)
|
||||
{
|
||||
MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
@@ -499,7 +532,7 @@ bool DeckLinkSession::Stop()
|
||||
if (keyer != nullptr)
|
||||
{
|
||||
keyer->Disable();
|
||||
externalKeyingActive = false;
|
||||
mState.externalKeyingActive = false;
|
||||
}
|
||||
|
||||
if (input)
|
||||
@@ -516,3 +549,66 @@ bool DeckLinkSession::Stop()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckLinkSession::HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
|
||||
{
|
||||
mState.hasInputSource = !hasNoInputSource;
|
||||
if (hasNoInputSource || mInputFrameCallback == nullptr)
|
||||
{
|
||||
VideoIOFrame frame;
|
||||
frame.width = mState.inputFrameSize.width;
|
||||
frame.height = mState.inputFrameSize.height;
|
||||
frame.pixelFormat = mState.inputPixelFormat;
|
||||
frame.hasNoInputSource = hasNoInputSource;
|
||||
if (mInputFrameCallback)
|
||||
mInputFrameCallback(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
CComPtr<IDeckLinkVideoBuffer> inputFrameBuffer;
|
||||
void* videoPixels = nullptr;
|
||||
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK)
|
||||
return;
|
||||
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
|
||||
return;
|
||||
|
||||
inputFrameBuffer->GetBytes(&videoPixels);
|
||||
|
||||
VideoIOFrame frame;
|
||||
frame.bytes = videoPixels;
|
||||
frame.rowBytes = inputFrame->GetRowBytes();
|
||||
frame.width = static_cast<unsigned>(inputFrame->GetWidth());
|
||||
frame.height = static_cast<unsigned>(inputFrame->GetHeight());
|
||||
frame.pixelFormat = mState.inputPixelFormat;
|
||||
frame.hasNoInputSource = hasNoInputSource;
|
||||
mInputFrameCallback(frame);
|
||||
|
||||
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
|
||||
}
|
||||
|
||||
void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame*, BMDOutputFrameCompletionResult completionResult)
|
||||
{
|
||||
if (!mOutputFrameCallback)
|
||||
return;
|
||||
|
||||
VideoIOCompletion completion;
|
||||
switch (completionResult)
|
||||
{
|
||||
case bmdOutputFrameDisplayedLate:
|
||||
completion.result = VideoIOCompletionResult::DisplayedLate;
|
||||
break;
|
||||
case bmdOutputFrameDropped:
|
||||
completion.result = VideoIOCompletionResult::Dropped;
|
||||
break;
|
||||
case bmdOutputFrameFlushed:
|
||||
completion.result = VideoIOCompletionResult::Flushed;
|
||||
break;
|
||||
case bmdOutputFrameCompleted:
|
||||
completion.result = VideoIOCompletionResult::Completed;
|
||||
break;
|
||||
default:
|
||||
completion.result = VideoIOCompletionResult::Unknown;
|
||||
break;
|
||||
}
|
||||
mOutputFrameCallback(completion);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
#include "DeckLinkAPI_h.h"
|
||||
#include "DeckLinkDisplayMode.h"
|
||||
#include "DeckLinkFrameTransfer.h"
|
||||
#include "DeckLinkVideoIOFormat.h"
|
||||
#include "VideoIOFormat.h"
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutScheduler.h"
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <deque>
|
||||
@@ -11,51 +14,56 @@
|
||||
|
||||
class OpenGLComposite;
|
||||
|
||||
class DeckLinkSession
|
||||
class DeckLinkSession : public VideoIODevice
|
||||
{
|
||||
public:
|
||||
DeckLinkSession() = default;
|
||||
~DeckLinkSession();
|
||||
|
||||
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();
|
||||
bool Stop();
|
||||
void ReleaseResources() override;
|
||||
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) override;
|
||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error) override;
|
||||
bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) override;
|
||||
bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) override;
|
||||
bool Start() override;
|
||||
bool Stop() override;
|
||||
|
||||
bool HasInputDevice() const { return input != nullptr; }
|
||||
bool HasInputSource() const { return !hasNoInputSource; }
|
||||
void SetInputSourceMissing(bool missing) { hasNoInputSource = missing; }
|
||||
bool InputOutputDimensionsDiffer() const { return inputFrameSize != outputFrameSize; }
|
||||
const FrameSize& InputFrameSize() const { return inputFrameSize; }
|
||||
const FrameSize& OutputFrameSize() const { return outputFrameSize; }
|
||||
unsigned InputFrameWidth() const { return inputFrameSize.width; }
|
||||
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; }
|
||||
bool SupportsExternalKeying() const { return supportsExternalKeying; }
|
||||
bool KeyerInterfaceAvailable() const { return keyerInterfaceAvailable; }
|
||||
bool ExternalKeyingActive() const { return externalKeyingActive; }
|
||||
const std::string& StatusMessage() const { return statusMessage; }
|
||||
void SetStatusMessage(const std::string& message) { statusMessage = message; }
|
||||
bool HasInputDevice() const { return mState.hasInputDevice; }
|
||||
bool HasInputSource() const { return mState.hasInputSource; }
|
||||
void SetInputSourceMissing(bool missing) { mState.hasInputSource = !missing; }
|
||||
bool InputOutputDimensionsDiffer() const { return mState.inputFrameSize != mState.outputFrameSize; }
|
||||
const FrameSize& InputFrameSize() const { return mState.inputFrameSize; }
|
||||
const FrameSize& OutputFrameSize() const { return mState.outputFrameSize; }
|
||||
unsigned InputFrameWidth() const { return mState.inputFrameSize.width; }
|
||||
unsigned InputFrameHeight() const { return mState.inputFrameSize.height; }
|
||||
unsigned OutputFrameWidth() const { return mState.outputFrameSize.width; }
|
||||
unsigned OutputFrameHeight() const { return mState.outputFrameSize.height; }
|
||||
VideoIOPixelFormat InputPixelFormat() const { return mState.inputPixelFormat; }
|
||||
VideoIOPixelFormat OutputPixelFormat() const { return mState.outputPixelFormat; }
|
||||
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.inputPixelFormat); }
|
||||
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.outputPixelFormat); }
|
||||
unsigned InputFrameRowBytes() const { return mState.inputFrameRowBytes; }
|
||||
unsigned OutputFrameRowBytes() const { return mState.outputFrameRowBytes; }
|
||||
unsigned CaptureTextureWidth() const { return mState.captureTextureWidth; }
|
||||
unsigned OutputPackTextureWidth() const { return mState.outputPackTextureWidth; }
|
||||
const std::string& FormatStatusMessage() const { return mState.formatStatusMessage; }
|
||||
const std::string& InputDisplayModeName() const { return mState.inputDisplayModeName; }
|
||||
const std::string& OutputModelName() const { return mState.outputModelName; }
|
||||
bool SupportsInternalKeying() const { return mState.supportsInternalKeying; }
|
||||
bool SupportsExternalKeying() const { return mState.supportsExternalKeying; }
|
||||
bool KeyerInterfaceAvailable() const { return mState.keyerInterfaceAvailable; }
|
||||
bool ExternalKeyingActive() const { return mState.externalKeyingActive; }
|
||||
const std::string& StatusMessage() const { return mState.statusMessage; }
|
||||
void SetStatusMessage(const std::string& message) { mState.statusMessage = message; }
|
||||
const VideoIOState& State() const override { return mState; }
|
||||
VideoIOState& MutableState() override { return mState; }
|
||||
double FrameBudgetMilliseconds() const;
|
||||
IDeckLinkMutableVideoFrame* RotateOutputFrame();
|
||||
void AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult);
|
||||
bool ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
||||
void AccountForCompletionResult(VideoIOCompletionResult completionResult) override;
|
||||
bool BeginOutputFrame(VideoIOOutputFrame& frame) override;
|
||||
void EndOutputFrame(VideoIOOutputFrame& frame) override;
|
||||
bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) override;
|
||||
void HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
|
||||
void HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult);
|
||||
|
||||
private:
|
||||
CComPtr<CaptureDelegate> captureDelegate;
|
||||
@@ -64,25 +72,8 @@ private:
|
||||
CComPtr<IDeckLinkOutput> output;
|
||||
CComPtr<IDeckLinkKeyer> keyer;
|
||||
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
|
||||
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;
|
||||
std::string outputModelName;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
bool keyerInterfaceAvailable = false;
|
||||
bool externalKeyingActive = false;
|
||||
std::string statusMessage;
|
||||
std::string formatStatusMessage;
|
||||
VideoIOState mState;
|
||||
VideoPlayoutScheduler mScheduler;
|
||||
InputFrameCallback mInputFrameCallback;
|
||||
OutputFrameCallback mOutputFrameCallback;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "DeckLinkVideoIOFormat.h"
|
||||
|
||||
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VideoIOPixelFormat::V210:
|
||||
return bmdFormat10BitYUV;
|
||||
case VideoIOPixelFormat::Bgra8:
|
||||
return bmdFormat8BitBGRA;
|
||||
case VideoIOPixelFormat::Uyvy8:
|
||||
default:
|
||||
return bmdFormat8BitYUV;
|
||||
}
|
||||
}
|
||||
|
||||
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format)
|
||||
{
|
||||
if (format == bmdFormat10BitYUV)
|
||||
return VideoIOPixelFormat::V210;
|
||||
if (format == bmdFormat8BitBGRA)
|
||||
return VideoIOPixelFormat::Bgra8;
|
||||
return VideoIOPixelFormat::Uyvy8;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format);
|
||||
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format);
|
||||
@@ -3,9 +3,9 @@
|
||||
#include "OpenGLComposite.h"
|
||||
#include "GLExtensions.h"
|
||||
#include "GlRenderConstants.h"
|
||||
#include "OpenGLDeckLinkBridge.h"
|
||||
#include "OpenGLRenderPass.h"
|
||||
#include "OpenGLShaderPrograms.h"
|
||||
#include "OpenGLVideoIOBridge.h"
|
||||
#include "PngScreenshotWriter.h"
|
||||
#include "RuntimeServices.h"
|
||||
#include "ShaderBuildQueue.h"
|
||||
@@ -22,15 +22,15 @@
|
||||
|
||||
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
||||
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
|
||||
mDeckLink(std::make_unique<DeckLinkSession>()),
|
||||
mVideoIO(std::make_unique<DeckLinkSession>()),
|
||||
mRenderer(std::make_unique<OpenGLRenderer>()),
|
||||
mUseCommittedLayerStates(false),
|
||||
mScreenshotRequested(false)
|
||||
{
|
||||
InitializeCriticalSection(&pMutex);
|
||||
mRuntimeHost = std::make_unique<RuntimeHost>();
|
||||
mDeckLinkBridge = std::make_unique<OpenGLDeckLinkBridge>(
|
||||
*mDeckLink,
|
||||
mVideoIOBridge = std::make_unique<OpenGLVideoIOBridge>(
|
||||
*mVideoIO,
|
||||
*mRenderer,
|
||||
*mRuntimeHost,
|
||||
pMutex,
|
||||
@@ -51,13 +51,18 @@ OpenGLComposite::~OpenGLComposite()
|
||||
mRuntimeServices->Stop();
|
||||
if (mShaderBuildQueue)
|
||||
mShaderBuildQueue->Stop();
|
||||
mDeckLink->ReleaseResources();
|
||||
mVideoIO->ReleaseResources();
|
||||
mRenderer->DestroyResources();
|
||||
|
||||
DeleteCriticalSection(&pMutex);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitDeckLink()
|
||||
{
|
||||
return InitVideoIO();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitVideoIO()
|
||||
{
|
||||
VideoFormatSelection videoModes;
|
||||
std::string initFailureReason;
|
||||
@@ -87,7 +92,7 @@ bool OpenGLComposite::InitDeckLink()
|
||||
}
|
||||
}
|
||||
|
||||
if (!mDeckLink->DiscoverDevicesAndModes(videoModes, initFailureReason))
|
||||
if (!mVideoIO->DiscoverDevicesAndModes(videoModes, initFailureReason))
|
||||
{
|
||||
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
|
||||
? "This application requires the DeckLink drivers installed."
|
||||
@@ -95,7 +100,7 @@ bool OpenGLComposite::InitDeckLink()
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
if (!mDeckLink->SelectPreferredFormats(videoModes, initFailureReason))
|
||||
if (!mVideoIO->SelectPreferredFormats(videoModes, initFailureReason))
|
||||
goto error;
|
||||
|
||||
if (! CheckOpenGLExtensions())
|
||||
@@ -110,38 +115,38 @@ bool OpenGLComposite::InitDeckLink()
|
||||
goto error;
|
||||
}
|
||||
|
||||
PublishDeckLinkOutputStatus(mDeckLink->OutputModelName().empty()
|
||||
PublishVideoIOStatus(mVideoIO->OutputModelName().empty()
|
||||
? "DeckLink output device selected."
|
||||
: ("Selected output device: " + mDeckLink->OutputModelName()));
|
||||
: ("Selected output device: " + mVideoIO->OutputModelName()));
|
||||
|
||||
// Resize window to match output video frame, but scale large formats down by half for viewing.
|
||||
if (mDeckLink->OutputFrameWidth() < 1920)
|
||||
resizeWindow(mDeckLink->OutputFrameWidth(), mDeckLink->OutputFrameHeight());
|
||||
if (mVideoIO->OutputFrameWidth() < 1920)
|
||||
resizeWindow(mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
|
||||
else
|
||||
resizeWindow(mDeckLink->OutputFrameWidth() / 2, mDeckLink->OutputFrameHeight() / 2);
|
||||
resizeWindow(mVideoIO->OutputFrameWidth() / 2, mVideoIO->OutputFrameHeight() / 2);
|
||||
|
||||
if (!mDeckLink->ConfigureInput(this, hGLDC, hGLRC, videoModes.input, initFailureReason))
|
||||
if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, videoModes.input, initFailureReason))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
if (!mDeckLink->HasInputDevice() && mRuntimeHost)
|
||||
if (!mVideoIO->HasInputDevice() && mRuntimeHost)
|
||||
{
|
||||
mRuntimeHost->SetSignalStatus(false, mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), mDeckLink->InputDisplayModeName());
|
||||
mRuntimeHost->SetSignalStatus(false, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->InputDisplayModeName());
|
||||
}
|
||||
|
||||
if (!mDeckLink->ConfigureOutput(this, hGLDC, hGLRC, videoModes.output, mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(), initFailureReason))
|
||||
if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, videoModes.output, mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(), initFailureReason))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
PublishDeckLinkOutputStatus(mDeckLink->StatusMessage());
|
||||
PublishVideoIOStatus(mVideoIO->StatusMessage());
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (!initFailureReason.empty())
|
||||
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
|
||||
mDeckLink->ReleaseResources();
|
||||
mVideoIO->ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -153,7 +158,7 @@ void OpenGLComposite::paintGL()
|
||||
return;
|
||||
}
|
||||
|
||||
mRenderer->PresentToWindow(hGLDC, mDeckLink->OutputFrameWidth(), mDeckLink->OutputFrameHeight());
|
||||
mRenderer->PresentToWindow(hGLDC, mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
|
||||
ValidateRect(hGLWnd, NULL);
|
||||
LeaveCriticalSection(&pMutex);
|
||||
}
|
||||
@@ -174,22 +179,23 @@ void OpenGLComposite::resizeWindow(int width, int height)
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLComposite::PublishDeckLinkOutputStatus(const std::string& statusMessage)
|
||||
void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage)
|
||||
{
|
||||
if (!mRuntimeHost)
|
||||
return;
|
||||
|
||||
if (!statusMessage.empty())
|
||||
mDeckLink->SetStatusMessage(statusMessage);
|
||||
mVideoIO->SetStatusMessage(statusMessage);
|
||||
|
||||
mRuntimeHost->SetDeckLinkOutputStatus(
|
||||
mDeckLink->OutputModelName(),
|
||||
mDeckLink->SupportsInternalKeying(),
|
||||
mDeckLink->SupportsExternalKeying(),
|
||||
mDeckLink->KeyerInterfaceAvailable(),
|
||||
mRuntimeHost->SetVideoIOStatus(
|
||||
"decklink",
|
||||
mVideoIO->OutputModelName(),
|
||||
mVideoIO->SupportsInternalKeying(),
|
||||
mVideoIO->SupportsExternalKeying(),
|
||||
mVideoIO->KeyerInterfaceAvailable(),
|
||||
mRuntimeHost->ExternalKeyingEnabled(),
|
||||
mDeckLink->ExternalKeyingActive(),
|
||||
mDeckLink->StatusMessage());
|
||||
mVideoIO->ExternalKeyingActive(),
|
||||
mVideoIO->StatusMessage());
|
||||
}
|
||||
|
||||
bool OpenGLComposite::InitOpenGLState()
|
||||
@@ -223,7 +229,7 @@ bool OpenGLComposite::InitOpenGLState()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
if (!mShaderPrograms->CompileLayerPrograms(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
@@ -234,12 +240,12 @@ bool OpenGLComposite::InitOpenGLState()
|
||||
|
||||
std::string rendererError;
|
||||
if (!mRenderer->InitializeResources(
|
||||
mDeckLink->InputFrameWidth(),
|
||||
mDeckLink->InputFrameHeight(),
|
||||
mDeckLink->CaptureTextureWidth(),
|
||||
mDeckLink->OutputFrameWidth(),
|
||||
mDeckLink->OutputFrameHeight(),
|
||||
mDeckLink->OutputPackTextureWidth(),
|
||||
mVideoIO->InputFrameWidth(),
|
||||
mVideoIO->InputFrameHeight(),
|
||||
mVideoIO->CaptureTextureWidth(),
|
||||
mVideoIO->OutputFrameWidth(),
|
||||
mVideoIO->OutputFrameHeight(),
|
||||
mVideoIO->OutputPackTextureWidth(),
|
||||
rendererError))
|
||||
{
|
||||
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
|
||||
@@ -251,20 +257,9 @@ bool OpenGLComposite::InitOpenGLState()
|
||||
return true;
|
||||
}
|
||||
|
||||
// DeckLink delegates still target OpenGLComposite; the bridge owns the per-frame work.
|
||||
void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
|
||||
{
|
||||
mDeckLinkBridge->VideoFrameArrived(inputFrame, hasNoInputSource);
|
||||
}
|
||||
|
||||
void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
|
||||
{
|
||||
mDeckLinkBridge->PlayoutFrameCompleted(completedFrame, completionResult);
|
||||
}
|
||||
|
||||
bool OpenGLComposite::Start()
|
||||
{
|
||||
return mDeckLink->Start();
|
||||
return mVideoIO->Start();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::Stop()
|
||||
@@ -272,10 +267,10 @@ bool OpenGLComposite::Stop()
|
||||
if (mRuntimeServices)
|
||||
mRuntimeServices->Stop();
|
||||
|
||||
const bool wasExternalKeyingActive = mDeckLink->ExternalKeyingActive();
|
||||
mDeckLink->Stop();
|
||||
const bool wasExternalKeyingActive = mVideoIO->ExternalKeyingActive();
|
||||
mVideoIO->Stop();
|
||||
if (wasExternalKeyingActive)
|
||||
PublishDeckLinkOutputStatus("External keying has been disabled.");
|
||||
PublishVideoIOStatus("External keying has been disabled.");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -303,7 +298,7 @@ void OpenGLComposite::renderEffect()
|
||||
{
|
||||
ProcessRuntimePollResults();
|
||||
|
||||
const bool hasInputSource = mDeckLink->HasInputSource();
|
||||
const bool hasInputSource = mVideoIO->HasInputSource();
|
||||
std::vector<RuntimeRenderState> layerStates;
|
||||
if (mUseCommittedLayerStates)
|
||||
{
|
||||
@@ -313,7 +308,7 @@ void OpenGLComposite::renderEffect()
|
||||
}
|
||||
else if (mRuntimeHost)
|
||||
{
|
||||
if (mRuntimeHost->TryGetLayerRenderStates(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), layerStates))
|
||||
if (mRuntimeHost->TryGetLayerRenderStates(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), layerStates))
|
||||
{
|
||||
mCachedLayerRenderStates = layerStates;
|
||||
}
|
||||
@@ -327,10 +322,10 @@ void OpenGLComposite::renderEffect()
|
||||
mRenderPass->Render(
|
||||
hasInputSource,
|
||||
layerStates,
|
||||
mDeckLink->InputFrameWidth(),
|
||||
mDeckLink->InputFrameHeight(),
|
||||
mDeckLink->CaptureTextureWidth(),
|
||||
mDeckLink->InputPixelFormat(),
|
||||
mVideoIO->InputFrameWidth(),
|
||||
mVideoIO->InputFrameHeight(),
|
||||
mVideoIO->CaptureTextureWidth(),
|
||||
mVideoIO->InputPixelFormat(),
|
||||
historyCap,
|
||||
[this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) {
|
||||
return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error);
|
||||
@@ -345,8 +340,8 @@ void OpenGLComposite::ProcessScreenshotRequest()
|
||||
if (!mScreenshotRequested.exchange(false))
|
||||
return;
|
||||
|
||||
const unsigned width = mDeckLink ? mDeckLink->OutputFrameWidth() : 0;
|
||||
const unsigned height = mDeckLink ? mDeckLink->OutputFrameHeight() : 0;
|
||||
const unsigned width = mVideoIO ? mVideoIO->OutputFrameWidth() : 0;
|
||||
const unsigned height = mVideoIO ? mVideoIO->OutputFrameHeight() : 0;
|
||||
if (width == 0 || height == 0)
|
||||
return;
|
||||
|
||||
@@ -426,7 +421,7 @@ bool OpenGLComposite::ProcessRuntimePollResults()
|
||||
return true;
|
||||
|
||||
char compilerErrorMessage[1024] = {};
|
||||
if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
|
||||
{
|
||||
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
|
||||
mUseCommittedLayerStates = true;
|
||||
@@ -449,13 +444,13 @@ bool OpenGLComposite::ProcessRuntimePollResults()
|
||||
|
||||
void OpenGLComposite::RequestShaderBuild()
|
||||
{
|
||||
if (!mShaderBuildQueue || !mDeckLink)
|
||||
if (!mShaderBuildQueue || !mVideoIO)
|
||||
return;
|
||||
|
||||
mUseCommittedLayerStates = true;
|
||||
if (mRuntimeHost)
|
||||
mRuntimeHost->ClearReloadRequest();
|
||||
mShaderBuildQueue->RequestBuild(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight());
|
||||
mShaderBuildQueue->RequestBuild(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight());
|
||||
}
|
||||
|
||||
void OpenGLComposite::broadcastRuntimeState()
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <objbase.h>
|
||||
#include <atlbase.h>
|
||||
#include <comutil.h>
|
||||
#include "DeckLinkAPI_h.h"
|
||||
|
||||
#include "GLExtensions.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
@@ -25,8 +24,8 @@
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
class DeckLinkSession;
|
||||
class OpenGLDeckLinkBridge;
|
||||
class VideoIODevice;
|
||||
class OpenGLVideoIOBridge;
|
||||
class OpenGLRenderPass;
|
||||
class OpenGLShaderPrograms;
|
||||
class RuntimeServices;
|
||||
@@ -40,6 +39,7 @@ public:
|
||||
~OpenGLComposite();
|
||||
|
||||
bool InitDeckLink();
|
||||
bool InitVideoIO();
|
||||
bool Start();
|
||||
bool Stop();
|
||||
bool ReloadShader();
|
||||
@@ -65,13 +65,10 @@ public:
|
||||
void resizeGL(WORD width, WORD height);
|
||||
void paintGL();
|
||||
|
||||
void VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
|
||||
void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result);
|
||||
|
||||
private:
|
||||
void resizeWindow(int width, int height);
|
||||
bool CheckOpenGLExtensions();
|
||||
void PublishDeckLinkOutputStatus(const std::string& statusMessage);
|
||||
void PublishVideoIOStatus(const std::string& statusMessage);
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
|
||||
HWND hGLWnd;
|
||||
@@ -79,10 +76,10 @@ private:
|
||||
HGLRC hGLRC;
|
||||
CRITICAL_SECTION pMutex;
|
||||
|
||||
std::unique_ptr<DeckLinkSession> mDeckLink;
|
||||
std::unique_ptr<VideoIODevice> mVideoIO;
|
||||
std::unique_ptr<OpenGLRenderer> mRenderer;
|
||||
std::unique_ptr<RuntimeHost> mRuntimeHost;
|
||||
std::unique_ptr<OpenGLDeckLinkBridge> mDeckLinkBridge;
|
||||
std::unique_ptr<OpenGLVideoIOBridge> mVideoIOBridge;
|
||||
std::unique_ptr<OpenGLRenderPass> mRenderPass;
|
||||
std::unique_ptr<OpenGLShaderPrograms> mShaderPrograms;
|
||||
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#include "OpenGLDeckLinkBridge.h"
|
||||
#include "OpenGLVideoIOBridge.h"
|
||||
|
||||
#include "DeckLinkSession.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <gl/gl.h>
|
||||
|
||||
OpenGLDeckLinkBridge::OpenGLDeckLinkBridge(
|
||||
DeckLinkSession& deckLink,
|
||||
OpenGLVideoIOBridge::OpenGLVideoIOBridge(
|
||||
VideoIODevice& videoIO,
|
||||
OpenGLRenderer& renderer,
|
||||
RuntimeHost& runtimeHost,
|
||||
CRITICAL_SECTION& mutex,
|
||||
@@ -17,7 +16,7 @@ OpenGLDeckLinkBridge::OpenGLDeckLinkBridge(
|
||||
RenderEffectCallback renderEffect,
|
||||
OutputReadyCallback outputReady,
|
||||
PaintCallback paint) :
|
||||
mDeckLink(deckLink),
|
||||
mVideoIO(videoIO),
|
||||
mRenderer(renderer),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mMutex(mutex),
|
||||
@@ -29,7 +28,7 @@ OpenGLDeckLinkBridge::OpenGLDeckLinkBridge(
|
||||
{
|
||||
}
|
||||
|
||||
void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult completionResult)
|
||||
void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionResult)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (mLastPlayoutCompletionTime != std::chrono::steady_clock::time_point())
|
||||
@@ -44,11 +43,11 @@ void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult comp
|
||||
}
|
||||
mLastPlayoutCompletionTime = now;
|
||||
|
||||
if (completionResult == bmdOutputFrameDisplayedLate)
|
||||
if (completionResult == VideoIOCompletionResult::DisplayedLate)
|
||||
++mLateFrameCount;
|
||||
else if (completionResult == bmdOutputFrameDropped)
|
||||
else if (completionResult == VideoIOCompletionResult::Dropped)
|
||||
++mDroppedFrameCount;
|
||||
else if (completionResult == bmdOutputFrameFlushed)
|
||||
else if (completionResult == VideoIOCompletionResult::Flushed)
|
||||
++mFlushedFrameCount;
|
||||
|
||||
mRuntimeHost.TrySetFramePacingStats(
|
||||
@@ -60,27 +59,15 @@ void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult comp
|
||||
mFlushedFrameCount);
|
||||
}
|
||||
|
||||
void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
|
||||
void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame)
|
||||
{
|
||||
mDeckLink.SetInputSourceMissing(hasNoInputSource);
|
||||
mRuntimeHost.TrySetSignalStatus(!hasNoInputSource, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), mDeckLink.InputDisplayModeName());
|
||||
const VideoIOState& state = mVideoIO.State();
|
||||
mRuntimeHost.TrySetSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName);
|
||||
|
||||
if (!mDeckLink.HasInputSource())
|
||||
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
||||
return; // don't transfer texture when there's no input
|
||||
|
||||
long textureSize = inputFrame->GetRowBytes() * inputFrame->GetHeight();
|
||||
IDeckLinkVideoBuffer* inputFrameBuffer = NULL;
|
||||
void* videoPixels;
|
||||
|
||||
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK)
|
||||
return;
|
||||
|
||||
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
|
||||
{
|
||||
inputFrameBuffer->Release();
|
||||
return;
|
||||
}
|
||||
inputFrameBuffer->GetBytes(&videoPixels);
|
||||
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
|
||||
|
||||
EnterCriticalSection(&mMutex);
|
||||
|
||||
@@ -89,14 +76,14 @@ void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFram
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, videoPixels, GL_DYNAMIC_DRAW);
|
||||
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
|
||||
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
|
||||
|
||||
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data.
|
||||
if (mDeckLink.InputPixelFormat() == VideoIOPixelFormat::V210)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.CaptureTextureWidth(), mDeckLink.InputFrameHeight(), GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.CaptureTextureWidth(), mDeckLink.InputFrameHeight(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
@@ -104,21 +91,24 @@ void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFram
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
LeaveCriticalSection(&mMutex);
|
||||
|
||||
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
|
||||
inputFrameBuffer->Release();
|
||||
}
|
||||
|
||||
void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
|
||||
void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion)
|
||||
{
|
||||
(void)completedFrame;
|
||||
|
||||
RecordFramePacing(completionResult);
|
||||
RecordFramePacing(completion.result);
|
||||
|
||||
EnterCriticalSection(&mMutex);
|
||||
|
||||
// Get the first frame from the queue
|
||||
IDeckLinkMutableVideoFrame* outputVideoFrame = mDeckLink.RotateOutputFrame();
|
||||
VideoIOOutputFrame outputFrame;
|
||||
if (!mVideoIO.BeginOutputFrame(outputFrame))
|
||||
{
|
||||
LeaveCriticalSection(&mMutex);
|
||||
return;
|
||||
}
|
||||
const VideoIOState& state = mVideoIO.State();
|
||||
RenderPipelineFrameContext frameContext;
|
||||
frameContext.videoState = state;
|
||||
frameContext.completion = completion;
|
||||
|
||||
// make GL context current in this thread
|
||||
wglMakeCurrent(mHdc, mHglrc);
|
||||
@@ -129,14 +119,14 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF
|
||||
mRenderEffect();
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||
glBlitFramebuffer(0, 0, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), 0, 0, mDeckLink.OutputFrameWidth(), mDeckLink.OutputFrameHeight(), GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
glBlitFramebuffer(0, 0, state.inputFrameSize.width, state.inputFrameSize.height, 0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||
if (mOutputReady)
|
||||
mOutputReady();
|
||||
if (mDeckLink.OutputIsTenBit())
|
||||
if (state.outputPixelFormat == VideoIOPixelFormat::V210)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||
glViewport(0, 0, mDeckLink.OutputPackTextureWidth(), mDeckLink.OutputFrameHeight());
|
||||
glViewport(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
@@ -147,9 +137,9 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF
|
||||
const GLint outputResolutionLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uOutputVideoResolution");
|
||||
const GLint activeWordsLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uActiveV210Words");
|
||||
if (outputResolutionLocation >= 0)
|
||||
glUniform2f(outputResolutionLocation, static_cast<float>(mDeckLink.OutputFrameWidth()), static_cast<float>(mDeckLink.OutputFrameHeight()));
|
||||
glUniform2f(outputResolutionLocation, static_cast<float>(state.outputFrameSize.width), static_cast<float>(state.outputFrameSize.height));
|
||||
if (activeWordsLocation >= 0)
|
||||
glUniform1f(activeWordsLocation, static_cast<float>(ActiveV210WordsForWidth(mDeckLink.OutputFrameWidth())));
|
||||
glUniform1f(activeWordsLocation, static_cast<float>(ActiveV210WordsForWidth(state.outputFrameSize.width)));
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
@@ -157,51 +147,31 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF
|
||||
}
|
||||
glFlush();
|
||||
const auto renderEndTime = std::chrono::steady_clock::now();
|
||||
const double frameBudgetMilliseconds = mDeckLink.FrameBudgetMilliseconds();
|
||||
const double frameBudgetMilliseconds = state.frameBudgetMilliseconds;
|
||||
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
|
||||
mRuntimeHost.TrySetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
||||
mRuntimeHost.TryAdvanceFrame();
|
||||
|
||||
IDeckLinkVideoBuffer* outputVideoFrameBuffer;
|
||||
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
||||
{
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
LeaveCriticalSection(&mMutex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
||||
{
|
||||
outputVideoFrameBuffer->Release();
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
LeaveCriticalSection(&mMutex);
|
||||
return;
|
||||
}
|
||||
|
||||
void* pFrame;
|
||||
outputVideoFrameBuffer->GetBytes(&pFrame);
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
if (mDeckLink.OutputIsTenBit())
|
||||
if (state.outputPixelFormat == VideoIOPixelFormat::V210)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||
glReadPixels(0, 0, mDeckLink.OutputPackTextureWidth(), mDeckLink.OutputFrameHeight(), GL_RGBA, GL_UNSIGNED_BYTE, pFrame);
|
||||
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, outputFrame.bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
|
||||
glReadPixels(0, 0, mDeckLink.OutputFrameWidth(), mDeckLink.OutputFrameHeight(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame);
|
||||
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, outputFrame.bytes);
|
||||
}
|
||||
mPaint();
|
||||
|
||||
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
||||
outputVideoFrameBuffer->Release();
|
||||
mVideoIO.EndOutputFrame(outputFrame);
|
||||
|
||||
mDeckLink.AccountForCompletionResult(completionResult);
|
||||
mVideoIO.AccountForCompletionResult(completion.result);
|
||||
|
||||
// Schedule the next frame for playout
|
||||
mDeckLink.ScheduleOutputFrame(outputVideoFrame);
|
||||
mVideoIO.ScheduleOutputFrame(outputFrame);
|
||||
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -8,19 +8,24 @@
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
|
||||
class DeckLinkSession;
|
||||
class OpenGLRenderer;
|
||||
class RuntimeHost;
|
||||
|
||||
class OpenGLDeckLinkBridge
|
||||
struct RenderPipelineFrameContext
|
||||
{
|
||||
VideoIOState videoState;
|
||||
VideoIOCompletion completion;
|
||||
};
|
||||
|
||||
class OpenGLVideoIOBridge
|
||||
{
|
||||
public:
|
||||
using RenderEffectCallback = std::function<void()>;
|
||||
using OutputReadyCallback = std::function<void()>;
|
||||
using PaintCallback = std::function<void()>;
|
||||
|
||||
OpenGLDeckLinkBridge(
|
||||
DeckLinkSession& deckLink,
|
||||
OpenGLVideoIOBridge(
|
||||
VideoIODevice& videoIO,
|
||||
OpenGLRenderer& renderer,
|
||||
RuntimeHost& runtimeHost,
|
||||
CRITICAL_SECTION& mutex,
|
||||
@@ -30,13 +35,13 @@ public:
|
||||
OutputReadyCallback outputReady,
|
||||
PaintCallback paint);
|
||||
|
||||
void VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
|
||||
void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult);
|
||||
void VideoFrameArrived(const VideoIOFrame& inputFrame);
|
||||
void PlayoutFrameCompleted(const VideoIOCompletion& completion);
|
||||
|
||||
private:
|
||||
void RecordFramePacing(BMDOutputFrameCompletionResult completionResult);
|
||||
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
||||
|
||||
DeckLinkSession& mDeckLink;
|
||||
VideoIODevice& mVideoIO;
|
||||
OpenGLRenderer& mRenderer;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
CRITICAL_SECTION& mMutex;
|
||||
@@ -1213,8 +1213,16 @@ void RuntimeHost::SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned
|
||||
|
||||
void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||
{
|
||||
SetVideoIOStatus("decklink", modelName, supportsInternalKeying, supportsExternalKeying, keyerInterfaceAvailable,
|
||||
externalKeyingRequested, externalKeyingActive, statusMessage);
|
||||
}
|
||||
|
||||
void RuntimeHost::SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mDeckLinkOutputStatus.backendName = backendName;
|
||||
mDeckLinkOutputStatus.modelName = modelName;
|
||||
mDeckLinkOutputStatus.supportsInternalKeying = supportsInternalKeying;
|
||||
mDeckLinkOutputStatus.supportsExternalKeying = supportsExternalKeying;
|
||||
@@ -1889,6 +1897,17 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
deckLink.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage));
|
||||
root.set("decklink", deckLink);
|
||||
|
||||
JsonValue videoIO = JsonValue::MakeObject();
|
||||
videoIO.set("backend", JsonValue(mDeckLinkOutputStatus.backendName));
|
||||
videoIO.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName));
|
||||
videoIO.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying));
|
||||
videoIO.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying));
|
||||
videoIO.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable));
|
||||
videoIO.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested));
|
||||
videoIO.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive));
|
||||
videoIO.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage));
|
||||
root.set("videoIO", videoIO);
|
||||
|
||||
JsonValue performance = JsonValue::MakeObject();
|
||||
performance.set("frameBudgetMs", JsonValue(mFrameBudgetMilliseconds));
|
||||
performance.set("renderMs", JsonValue(mRenderMilliseconds));
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
void SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
@@ -86,6 +88,7 @@ private:
|
||||
|
||||
struct DeckLinkOutputStatus
|
||||
{
|
||||
std::string backendName = "decklink";
|
||||
std::string modelName;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
|
||||
@@ -50,7 +50,16 @@ uint16_t Component(uint32_t word, unsigned index)
|
||||
|
||||
const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
|
||||
{
|
||||
return format == VideoIOPixelFormat::V210 ? "10-bit YUV v210" : "8-bit YUV UYVY";
|
||||
switch (format)
|
||||
{
|
||||
case VideoIOPixelFormat::V210:
|
||||
return "10-bit YUV v210";
|
||||
case VideoIOPixelFormat::Bgra8:
|
||||
return "8-bit BGRA";
|
||||
case VideoIOPixelFormat::Uyvy8:
|
||||
default:
|
||||
return "8-bit YUV UYVY";
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
|
||||
@@ -58,21 +67,32 @@ 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 VideoIOBytesPerPixel(VideoIOPixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VideoIOPixelFormat::Uyvy8:
|
||||
return 2u;
|
||||
case VideoIOPixelFormat::Bgra8:
|
||||
return 4u;
|
||||
case VideoIOPixelFormat::V210:
|
||||
default:
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth)
|
||||
{
|
||||
if (format == VideoIOPixelFormat::V210)
|
||||
return MinimumV210RowBytes(frameWidth);
|
||||
return frameWidth * VideoIOBytesPerPixel(format);
|
||||
}
|
||||
|
||||
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes)
|
||||
{
|
||||
return (rowBytes + 3u) / 4u;
|
||||
@@ -1,14 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
enum class VideoIOPixelFormat
|
||||
{
|
||||
Uyvy8,
|
||||
V210
|
||||
V210,
|
||||
Bgra8
|
||||
};
|
||||
|
||||
struct V210CodeValues
|
||||
@@ -27,9 +26,9 @@ struct V210SixPixelBlock
|
||||
|
||||
const char* VideoIOPixelFormatName(VideoIOPixelFormat format);
|
||||
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format);
|
||||
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format);
|
||||
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format);
|
||||
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported);
|
||||
unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format);
|
||||
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth);
|
||||
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes);
|
||||
unsigned MinimumV210RowBytes(unsigned frameWidth);
|
||||
unsigned ActiveV210WordsForWidth(unsigned frameWidth);
|
||||
137
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h
Normal file
137
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkDisplayMode.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
enum class VideoIOBackend
|
||||
{
|
||||
DeckLink
|
||||
};
|
||||
|
||||
enum class VideoIOCompletionResult
|
||||
{
|
||||
Completed,
|
||||
DisplayedLate,
|
||||
Dropped,
|
||||
Flushed,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct VideoIOConfig
|
||||
{
|
||||
VideoFormatSelection videoModes;
|
||||
bool externalKeyingEnabled = false;
|
||||
bool preferTenBit = true;
|
||||
};
|
||||
|
||||
struct VideoIOState
|
||||
{
|
||||
FrameSize inputFrameSize;
|
||||
FrameSize outputFrameSize;
|
||||
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
VideoIOPixelFormat outputPixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
unsigned inputFrameRowBytes = 0;
|
||||
unsigned outputFrameRowBytes = 0;
|
||||
unsigned captureTextureWidth = 0;
|
||||
unsigned outputPackTextureWidth = 0;
|
||||
std::string inputDisplayModeName = "1080p59.94";
|
||||
std::string outputDisplayModeName = "1080p59.94";
|
||||
std::string outputModelName;
|
||||
std::string statusMessage;
|
||||
std::string formatStatusMessage;
|
||||
bool hasInputDevice = false;
|
||||
bool hasInputSource = false;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
bool keyerInterfaceAvailable = false;
|
||||
bool externalKeyingActive = false;
|
||||
double frameBudgetMilliseconds = 0.0;
|
||||
};
|
||||
|
||||
struct VideoIOFrame
|
||||
{
|
||||
void* bytes = nullptr;
|
||||
long rowBytes = 0;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
bool hasNoInputSource = false;
|
||||
};
|
||||
|
||||
struct VideoIOOutputFrame
|
||||
{
|
||||
void* bytes = nullptr;
|
||||
long rowBytes = 0;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
void* nativeFrame = nullptr;
|
||||
void* nativeBuffer = nullptr;
|
||||
};
|
||||
|
||||
struct VideoIOCompletion
|
||||
{
|
||||
VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
|
||||
};
|
||||
|
||||
struct VideoIOScheduleTime
|
||||
{
|
||||
int64_t streamTime = 0;
|
||||
int64_t duration = 0;
|
||||
int64_t timeScale = 0;
|
||||
uint64_t frameIndex = 0;
|
||||
};
|
||||
|
||||
class VideoIODevice
|
||||
{
|
||||
public:
|
||||
using InputFrameCallback = std::function<void(const VideoIOFrame&)>;
|
||||
using OutputFrameCallback = std::function<void(const VideoIOCompletion&)>;
|
||||
|
||||
virtual ~VideoIODevice() = default;
|
||||
virtual void ReleaseResources() = 0;
|
||||
virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0;
|
||||
virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error) = 0;
|
||||
virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0;
|
||||
virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0;
|
||||
virtual bool Start() = 0;
|
||||
virtual bool Stop() = 0;
|
||||
virtual const VideoIOState& State() const = 0;
|
||||
virtual VideoIOState& MutableState() = 0;
|
||||
virtual bool BeginOutputFrame(VideoIOOutputFrame& frame) = 0;
|
||||
virtual void EndOutputFrame(VideoIOOutputFrame& frame) = 0;
|
||||
virtual bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) = 0;
|
||||
virtual void AccountForCompletionResult(VideoIOCompletionResult result) = 0;
|
||||
|
||||
bool HasInputDevice() const { return State().hasInputDevice; }
|
||||
bool HasInputSource() const { return State().hasInputSource; }
|
||||
bool InputOutputDimensionsDiffer() const { return State().inputFrameSize != State().outputFrameSize; }
|
||||
const FrameSize& InputFrameSize() const { return State().inputFrameSize; }
|
||||
const FrameSize& OutputFrameSize() const { return State().outputFrameSize; }
|
||||
unsigned InputFrameWidth() const { return State().inputFrameSize.width; }
|
||||
unsigned InputFrameHeight() const { return State().inputFrameSize.height; }
|
||||
unsigned OutputFrameWidth() const { return State().outputFrameSize.width; }
|
||||
unsigned OutputFrameHeight() const { return State().outputFrameSize.height; }
|
||||
VideoIOPixelFormat InputPixelFormat() const { return State().inputPixelFormat; }
|
||||
VideoIOPixelFormat OutputPixelFormat() const { return State().outputPixelFormat; }
|
||||
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().inputPixelFormat); }
|
||||
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().outputPixelFormat); }
|
||||
unsigned InputFrameRowBytes() const { return State().inputFrameRowBytes; }
|
||||
unsigned OutputFrameRowBytes() const { return State().outputFrameRowBytes; }
|
||||
unsigned CaptureTextureWidth() const { return State().captureTextureWidth; }
|
||||
unsigned OutputPackTextureWidth() const { return State().outputPackTextureWidth; }
|
||||
const std::string& FormatStatusMessage() const { return State().formatStatusMessage; }
|
||||
const std::string& InputDisplayModeName() const { return State().inputDisplayModeName; }
|
||||
const std::string& OutputModelName() const { return State().outputModelName; }
|
||||
bool SupportsInternalKeying() const { return State().supportsInternalKeying; }
|
||||
bool SupportsExternalKeying() const { return State().supportsExternalKeying; }
|
||||
bool KeyerInterfaceAvailable() const { return State().keyerInterfaceAvailable; }
|
||||
bool ExternalKeyingActive() const { return State().externalKeyingActive; }
|
||||
const std::string& StatusMessage() const { return State().statusMessage; }
|
||||
double FrameBudgetMilliseconds() const { return State().frameBudgetMilliseconds; }
|
||||
void SetStatusMessage(const std::string& message) { MutableState().statusMessage = message; }
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "VideoPlayoutScheduler.h"
|
||||
|
||||
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale)
|
||||
{
|
||||
mFrameDuration = frameDuration;
|
||||
mTimeScale = timeScale;
|
||||
Reset();
|
||||
}
|
||||
|
||||
void VideoPlayoutScheduler::Reset()
|
||||
{
|
||||
mScheduledFrameIndex = 0;
|
||||
}
|
||||
|
||||
VideoIOScheduleTime VideoPlayoutScheduler::NextScheduleTime()
|
||||
{
|
||||
VideoIOScheduleTime time;
|
||||
time.streamTime = static_cast<int64_t>(mScheduledFrameIndex) * mFrameDuration;
|
||||
time.duration = mFrameDuration;
|
||||
time.timeScale = mTimeScale;
|
||||
time.frameIndex = mScheduledFrameIndex;
|
||||
++mScheduledFrameIndex;
|
||||
return time;
|
||||
}
|
||||
|
||||
void VideoPlayoutScheduler::AccountForCompletionResult(VideoIOCompletionResult result)
|
||||
{
|
||||
if (result == VideoIOCompletionResult::DisplayedLate || result == VideoIOCompletionResult::Dropped)
|
||||
mScheduledFrameIndex += 2;
|
||||
}
|
||||
|
||||
double VideoPlayoutScheduler::FrameBudgetMilliseconds() const
|
||||
{
|
||||
return mTimeScale != 0
|
||||
? (static_cast<double>(mFrameDuration) * 1000.0) / static_cast<double>(mTimeScale)
|
||||
: 0.0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class VideoPlayoutScheduler
|
||||
{
|
||||
public:
|
||||
void Configure(int64_t frameDuration, int64_t timeScale);
|
||||
void Reset();
|
||||
VideoIOScheduleTime NextScheduleTime();
|
||||
void AccountForCompletionResult(VideoIOCompletionResult result);
|
||||
double FrameBudgetMilliseconds() const;
|
||||
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
||||
int64_t TimeScale() const { return mTimeScale; }
|
||||
|
||||
private:
|
||||
int64_t mFrameDuration = 0;
|
||||
int64_t mTimeScale = 0;
|
||||
uint64_t mScheduledFrameIndex = 0;
|
||||
};
|
||||
Reference in New Issue
Block a user