#include "DeckLinkSession.h" #include "DeckLinkDisplayMode.h" #include "GlRenderConstants.h" #include #include #include #include #include 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 utf8Name(static_cast(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 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(frameDuration) * 1000.0) / static_cast(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; }