477 lines
14 KiB
C++
477 lines
14 KiB
C++
#include "DeckLinkSession.h"
|
|
|
|
#include "DeckLinkDisplayMode.h"
|
|
#include "GlRenderConstants.h"
|
|
|
|
#include <atlbase.h>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <new>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
std::string BstrToUtf8(BSTR value)
|
|
{
|
|
if (value == NULL)
|
|
return std::string();
|
|
|
|
const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
|
|
if (requiredBytes <= 1)
|
|
return std::string();
|
|
|
|
std::vector<char> utf8Name(static_cast<std::size_t>(requiredBytes), '\0');
|
|
if (WideCharToMultiByte(CP_UTF8, 0, value, -1, utf8Name.data(), requiredBytes, NULL, NULL) <= 0)
|
|
return std::string();
|
|
|
|
return std::string(utf8Name.data());
|
|
}
|
|
}
|
|
|
|
DeckLinkSession::~DeckLinkSession()
|
|
{
|
|
ReleaseResources();
|
|
}
|
|
|
|
void DeckLinkSession::ReleaseResources()
|
|
{
|
|
if (input != nullptr)
|
|
{
|
|
input->SetCallback(nullptr);
|
|
if (captureDelegate != nullptr)
|
|
{
|
|
captureDelegate->Release();
|
|
captureDelegate = nullptr;
|
|
}
|
|
input->Release();
|
|
input = nullptr;
|
|
}
|
|
|
|
while (!outputVideoFrameQueue.empty())
|
|
{
|
|
IDeckLinkMutableVideoFrame* frameToRelease = outputVideoFrameQueue.front();
|
|
if (frameToRelease != nullptr)
|
|
frameToRelease->Release();
|
|
outputVideoFrameQueue.pop_front();
|
|
}
|
|
|
|
if (output != nullptr)
|
|
{
|
|
if (keyer != nullptr)
|
|
{
|
|
keyer->Disable();
|
|
keyer->Release();
|
|
keyer = nullptr;
|
|
externalKeyingActive = false;
|
|
}
|
|
output->SetScheduledFrameCompletionCallback(nullptr);
|
|
if (playoutDelegate != nullptr)
|
|
{
|
|
playoutDelegate->Release();
|
|
playoutDelegate = nullptr;
|
|
}
|
|
output->Release();
|
|
output = nullptr;
|
|
}
|
|
|
|
if (playoutAllocator != nullptr)
|
|
{
|
|
playoutAllocator->Release();
|
|
playoutAllocator = nullptr;
|
|
}
|
|
}
|
|
|
|
bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, BMDDisplayMode outputDisplayMode, const std::string& requestedInputDisplayModeName, const std::string& requestedOutputDisplayModeName, std::string& error)
|
|
{
|
|
bool success = false;
|
|
IDeckLinkIterator* deckLinkIterator = NULL;
|
|
IDeckLink* deckLink = NULL;
|
|
IDeckLinkProfileAttributes* deckLinkAttributes = NULL;
|
|
IDeckLinkDisplayModeIterator* inputDisplayModeIterator = NULL;
|
|
IDeckLinkDisplayModeIterator* outputDisplayModeIterator = NULL;
|
|
IDeckLinkDisplayMode* inputMode = NULL;
|
|
IDeckLinkDisplayMode* outputMode = NULL;
|
|
|
|
inputDisplayModeName = requestedInputDisplayModeName;
|
|
outputDisplayModeName = requestedOutputDisplayModeName;
|
|
|
|
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);
|
|
if (FAILED(result))
|
|
{
|
|
error = "Please install the Blackmagic DeckLink drivers to use the features of this application.";
|
|
return false;
|
|
}
|
|
|
|
while (deckLinkIterator->Next(&deckLink) == S_OK)
|
|
{
|
|
int64_t duplexMode;
|
|
bool deviceSupportsInternalKeying = false;
|
|
bool deviceSupportsExternalKeying = false;
|
|
std::string modelName;
|
|
|
|
if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
|
|
{
|
|
printf("Could not obtain the IDeckLinkProfileAttributes interface\n");
|
|
deckLink->Release();
|
|
deckLink = NULL;
|
|
continue;
|
|
}
|
|
|
|
result = deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &duplexMode);
|
|
BOOL attributeFlag = FALSE;
|
|
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &attributeFlag) == S_OK)
|
|
deviceSupportsInternalKeying = (attributeFlag != FALSE);
|
|
attributeFlag = FALSE;
|
|
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
|
|
deviceSupportsExternalKeying = (attributeFlag != FALSE);
|
|
BSTR modelNameBstr = NULL;
|
|
if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK)
|
|
{
|
|
modelName = BstrToUtf8(modelNameBstr);
|
|
if (modelNameBstr != NULL)
|
|
SysFreeString(modelNameBstr);
|
|
}
|
|
deckLinkAttributes->Release();
|
|
deckLinkAttributes = NULL;
|
|
|
|
if (result != S_OK || duplexMode == bmdDuplexInactive)
|
|
{
|
|
deckLink->Release();
|
|
deckLink = NULL;
|
|
continue;
|
|
}
|
|
|
|
bool inputUsed = false;
|
|
if (!input && deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&input) == S_OK)
|
|
inputUsed = true;
|
|
|
|
if (!output && (!inputUsed || (duplexMode == bmdDuplexFull)))
|
|
{
|
|
if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK)
|
|
output = NULL;
|
|
else
|
|
{
|
|
outputModelName = modelName;
|
|
supportsInternalKeying = deviceSupportsInternalKeying;
|
|
supportsExternalKeying = deviceSupportsExternalKeying;
|
|
}
|
|
}
|
|
|
|
deckLink->Release();
|
|
deckLink = NULL;
|
|
|
|
if (output && input)
|
|
break;
|
|
}
|
|
|
|
if (!output)
|
|
{
|
|
error = "Expected an Output DeckLink device";
|
|
goto cleanup;
|
|
}
|
|
|
|
if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK)
|
|
{
|
|
error = "Cannot get input Display Mode Iterator.";
|
|
goto cleanup;
|
|
}
|
|
|
|
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, inputDisplayMode, &inputMode))
|
|
{
|
|
error = "Cannot get specified input BMDDisplayMode for configured mode: " + requestedInputDisplayModeName;
|
|
goto cleanup;
|
|
}
|
|
if (inputDisplayModeIterator)
|
|
{
|
|
inputDisplayModeIterator->Release();
|
|
inputDisplayModeIterator = NULL;
|
|
}
|
|
|
|
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
|
|
{
|
|
error = "Cannot get output Display Mode Iterator.";
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, outputDisplayMode, &outputMode))
|
|
{
|
|
error = "Cannot get specified output BMDDisplayMode for configured mode: " + requestedOutputDisplayModeName;
|
|
goto cleanup;
|
|
}
|
|
outputDisplayModeIterator->Release();
|
|
outputDisplayModeIterator = NULL;
|
|
|
|
outputFrameWidth = outputMode->GetWidth();
|
|
outputFrameHeight = outputMode->GetHeight();
|
|
inputFrameWidth = inputMode ? inputMode->GetWidth() : outputFrameWidth;
|
|
inputFrameHeight = inputMode ? inputMode->GetHeight() : outputFrameHeight;
|
|
if (!input)
|
|
inputDisplayModeName = "No input - black frame";
|
|
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
|
|
|
|
success = true;
|
|
|
|
cleanup:
|
|
if (!success)
|
|
ReleaseResources();
|
|
if (deckLink != NULL)
|
|
deckLink->Release();
|
|
if (deckLinkAttributes != NULL)
|
|
deckLinkAttributes->Release();
|
|
if (inputMode != NULL)
|
|
inputMode->Release();
|
|
if (outputMode != NULL)
|
|
outputMode->Release();
|
|
if (inputDisplayModeIterator != NULL)
|
|
inputDisplayModeIterator->Release();
|
|
if (outputDisplayModeIterator != NULL)
|
|
outputDisplayModeIterator->Release();
|
|
if (deckLinkIterator != NULL)
|
|
deckLinkIterator->Release();
|
|
return success;
|
|
}
|
|
|
|
bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, BMDDisplayMode inputDisplayMode, std::string& error)
|
|
{
|
|
if (!input)
|
|
{
|
|
hasNoInputSource = true;
|
|
inputDisplayModeName = "No input - black frame";
|
|
return true;
|
|
}
|
|
|
|
CComPtr<IDeckLinkVideoBufferAllocatorProvider> captureAllocator(new (std::nothrow) InputAllocatorPool(hdc, hglrc));
|
|
|
|
if (input->EnableVideoInputWithAllocatorProvider(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault, captureAllocator) != S_OK)
|
|
{
|
|
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
|
|
input->Release();
|
|
input = NULL;
|
|
hasNoInputSource = true;
|
|
inputDisplayModeName = "No input - black frame";
|
|
return true;
|
|
}
|
|
|
|
captureDelegate = new CaptureDelegate(owner);
|
|
if (input->SetCallback(captureDelegate) != S_OK)
|
|
{
|
|
error = "DeckLink input setup failed while installing the capture callback.";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, BMDDisplayMode outputDisplayMode, bool externalKeyingEnabled, std::string& error)
|
|
{
|
|
int outputFrameRowBytes = 0;
|
|
if (output->RowBytesForPixelFormat(bmdFormat8BitBGRA, outputFrameWidth, &outputFrameRowBytes) != S_OK)
|
|
{
|
|
error = "DeckLink output setup failed while calculating BGRA row bytes.";
|
|
return false;
|
|
}
|
|
|
|
playoutAllocator = new PinnedMemoryAllocator(hdc, hglrc, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * outputFrameHeight);
|
|
|
|
if (output->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK)
|
|
{
|
|
error = "DeckLink output setup failed while enabling video output.";
|
|
return false;
|
|
}
|
|
|
|
if (output->QueryInterface(IID_IDeckLinkKeyer, (void**)&keyer) == S_OK && keyer != NULL)
|
|
keyerInterfaceAvailable = true;
|
|
|
|
if (externalKeyingEnabled)
|
|
{
|
|
if (!supportsExternalKeying)
|
|
{
|
|
statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
|
|
}
|
|
else if (!keyerInterfaceAvailable)
|
|
{
|
|
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.";
|
|
}
|
|
else
|
|
{
|
|
externalKeyingActive = true;
|
|
statusMessage = "External keying is active on the selected DeckLink output.";
|
|
}
|
|
}
|
|
else if (supportsExternalKeying)
|
|
{
|
|
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++)
|
|
{
|
|
IDeckLinkMutableVideoFrame* outputFrame = NULL;
|
|
IDeckLinkVideoBuffer* outputFrameBuffer = NULL;
|
|
|
|
if (playoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK)
|
|
{
|
|
error = "DeckLink output setup failed while allocating an output frame buffer.";
|
|
return false;
|
|
}
|
|
|
|
if (output->CreateVideoFrameWithBuffer(outputFrameWidth, outputFrameHeight, outputFrameRowBytes, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, outputFrameBuffer, &outputFrame) != S_OK)
|
|
{
|
|
error = "DeckLink output setup failed while creating an output video frame.";
|
|
outputFrameBuffer->Release();
|
|
return false;
|
|
}
|
|
|
|
outputVideoFrameQueue.push_back(outputFrame);
|
|
}
|
|
|
|
playoutDelegate = new PlayoutDelegate(owner);
|
|
if (playoutDelegate == NULL)
|
|
{
|
|
error = "DeckLink output setup failed while creating the playout callback.";
|
|
return false;
|
|
}
|
|
|
|
if (output->SetScheduledFrameCompletionCallback(playoutDelegate) != S_OK)
|
|
{
|
|
error = "DeckLink output setup failed while installing the scheduled-frame callback.";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
double DeckLinkSession::FrameBudgetMilliseconds() const
|
|
{
|
|
return frameTimescale != 0
|
|
? (static_cast<double>(frameDuration) * 1000.0) / static_cast<double>(frameTimescale)
|
|
: 0.0;
|
|
}
|
|
|
|
IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame()
|
|
{
|
|
IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front();
|
|
outputVideoFrameQueue.push_back(outputVideoFrame);
|
|
outputVideoFrameQueue.pop_front();
|
|
return outputVideoFrame;
|
|
}
|
|
|
|
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)
|
|
totalPlayoutFrames += 2;
|
|
}
|
|
|
|
bool DeckLinkSession::ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
|
|
{
|
|
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
|
|
return false;
|
|
|
|
totalPlayoutFrames++;
|
|
return true;
|
|
}
|
|
|
|
bool DeckLinkSession::Start()
|
|
{
|
|
totalPlayoutFrames = 0;
|
|
if (!output)
|
|
{
|
|
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
if (outputVideoFrameQueue.empty())
|
|
{
|
|
MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
for (unsigned i = 0; i < kPrerollFrameCount; i++)
|
|
{
|
|
IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front();
|
|
outputVideoFrameQueue.push_back(outputVideoFrame);
|
|
outputVideoFrameQueue.pop_front();
|
|
|
|
IDeckLinkVideoBuffer* outputVideoFrameBuffer;
|
|
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
|
{
|
|
MessageBoxA(NULL, "Could not query the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
|
{
|
|
outputVideoFrameBuffer->Release();
|
|
MessageBoxA(NULL, "Could not write to the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
void* pFrame;
|
|
outputVideoFrameBuffer->GetBytes((void**)&pFrame);
|
|
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputFrameHeight);
|
|
|
|
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
|
outputVideoFrameBuffer->Release();
|
|
|
|
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
|
|
{
|
|
MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
totalPlayoutFrames++;
|
|
}
|
|
|
|
if (input)
|
|
{
|
|
if (input->StartStreams() != S_OK)
|
|
{
|
|
MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
}
|
|
if (output->StartScheduledPlayback(0, frameTimescale, 1.0) != S_OK)
|
|
{
|
|
MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DeckLinkSession::Stop()
|
|
{
|
|
if (keyer != nullptr)
|
|
{
|
|
keyer->Disable();
|
|
externalKeyingActive = false;
|
|
}
|
|
|
|
if (input)
|
|
{
|
|
input->StopStreams();
|
|
input->DisableVideoInput();
|
|
}
|
|
|
|
if (output)
|
|
{
|
|
output->StopScheduledPlayback(0, NULL, 0);
|
|
output->DisableVideoOutput();
|
|
}
|
|
|
|
return true;
|
|
}
|