com updates
Some checks failed
CI / Native Windows Build And Tests (push) Failing after 4s
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-06 11:41:27 +10:00
parent a526887ff6
commit 02a8a64360
4 changed files with 81 additions and 127 deletions

View File

@@ -32,7 +32,7 @@ jobs:
ui-ubuntu: ui-ubuntu:
name: React UI Build name: React UI Build
runs-on: nubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout

View File

@@ -125,7 +125,7 @@ Current native test coverage includes:
- JSON parsing and serialization. - JSON parsing and serialization.
- Parameter normalization and preset filename safety. - Parameter normalization and preset filename safety.
- Shader manifest parsing and package registry scanning. - Shader manifest parsing, temporal manifest validation, and package registry scanning.
## Runtime Configuration ## Runtime Configuration
@@ -230,14 +230,15 @@ The Gitea workflow expects two act runners:
If your Windows runner stores the Blackmagic SDK outside the repo, configure `GPUDIRECT_DIR` in the runner environment or adjust the workflow configure command to pass `-DGPUDIRECT_DIR=...`. If your Windows runner stores the Blackmagic SDK outside the repo, configure `GPUDIRECT_DIR` in the runner environment or adjust the workflow configure command to pass `-DGPUDIRECT_DIR=...`.
## Still todo ## Still Todo
Audio
improve text rendering - Audio.
genlock - Improve text rendering.
find a better UI libary - Genlock.
Logs - Find a better UI library.
refactor, cleanup of source files - Logs.
display URL (Maybe clicakable) for control in the windows app (Not on the output) - Continue source cleanup/refactoring.
Sound shader as seperate .slang in shader package? - Display the control URL in the Windows app, ideally clickable, without rendering it on the video output.
runtime date time UTC and offset from PCs internal clock - Support a separate sound shader `.slang` file in shader packages.
- Add runtime date/time uniforms using UTC and the PC's local offset.
![alt text](image.png) ![alt text](image.png)

View File

@@ -13,7 +13,7 @@ namespace
{ {
std::string BstrToUtf8(BSTR value) std::string BstrToUtf8(BSTR value)
{ {
if (value == NULL) if (value == nullptr)
return std::string(); return std::string();
const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL); const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
@@ -36,84 +36,56 @@ DeckLinkSession::~DeckLinkSession()
void DeckLinkSession::ReleaseResources() void DeckLinkSession::ReleaseResources()
{ {
if (input != nullptr) if (input != nullptr)
{
input->SetCallback(nullptr); input->SetCallback(nullptr);
if (captureDelegate != nullptr) captureDelegate.Release();
{ input.Release();
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 (output != nullptr)
{
if (keyer != nullptr)
{
keyer->Disable();
keyer->Release();
keyer = nullptr;
externalKeyingActive = false;
}
output->SetScheduledFrameCompletionCallback(nullptr); output->SetScheduledFrameCompletionCallback(nullptr);
if (playoutDelegate != nullptr)
{
playoutDelegate->Release();
playoutDelegate = nullptr;
}
output->Release();
output = nullptr;
}
if (playoutAllocator != nullptr) if (keyer != nullptr)
{ {
playoutAllocator->Release(); keyer->Disable();
playoutAllocator = nullptr; externalKeyingActive = false;
} }
keyer.Release();
playoutDelegate.Release();
outputVideoFrameQueue.clear();
output.Release();
playoutAllocator.Release();
} }
bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, BMDDisplayMode outputDisplayMode, const std::string& requestedInputDisplayModeName, const std::string& requestedOutputDisplayModeName, std::string& error) bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, BMDDisplayMode outputDisplayMode, const std::string& requestedInputDisplayModeName, const std::string& requestedOutputDisplayModeName, std::string& error)
{ {
bool success = false; CComPtr<IDeckLinkIterator> deckLinkIterator;
IDeckLinkIterator* deckLinkIterator = NULL; CComPtr<IDeckLinkDisplayMode> inputMode;
IDeckLink* deckLink = NULL; CComPtr<IDeckLinkDisplayMode> outputMode;
IDeckLinkProfileAttributes* deckLinkAttributes = NULL;
IDeckLinkDisplayModeIterator* inputDisplayModeIterator = NULL;
IDeckLinkDisplayModeIterator* outputDisplayModeIterator = NULL;
IDeckLinkDisplayMode* inputMode = NULL;
IDeckLinkDisplayMode* outputMode = NULL;
inputDisplayModeName = requestedInputDisplayModeName; inputDisplayModeName = requestedInputDisplayModeName;
outputDisplayModeName = requestedOutputDisplayModeName; outputDisplayModeName = requestedOutputDisplayModeName;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator); HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&deckLinkIterator));
if (FAILED(result)) if (FAILED(result))
{ {
error = "Please install the Blackmagic DeckLink drivers to use the features of this application."; error = "Please install the Blackmagic DeckLink drivers to use the features of this application.";
return false; return false;
} }
CComPtr<IDeckLink> deckLink;
while (deckLinkIterator->Next(&deckLink) == S_OK) while (deckLinkIterator->Next(&deckLink) == S_OK)
{ {
int64_t duplexMode; int64_t duplexMode;
bool deviceSupportsInternalKeying = false; bool deviceSupportsInternalKeying = false;
bool deviceSupportsExternalKeying = false; bool deviceSupportsExternalKeying = false;
std::string modelName; std::string modelName;
CComPtr<IDeckLinkProfileAttributes> deckLinkAttributes;
if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK) if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
{ {
printf("Could not obtain the IDeckLinkProfileAttributes interface\n"); printf("Could not obtain the IDeckLinkProfileAttributes interface\n");
deckLink->Release(); deckLink.Release();
deckLink = NULL;
continue; continue;
} }
@@ -124,20 +96,13 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
attributeFlag = FALSE; attributeFlag = FALSE;
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK) if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
deviceSupportsExternalKeying = (attributeFlag != FALSE); deviceSupportsExternalKeying = (attributeFlag != FALSE);
BSTR modelNameBstr = NULL; CComBSTR modelNameBstr;
if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK) if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK)
{
modelName = BstrToUtf8(modelNameBstr); modelName = BstrToUtf8(modelNameBstr);
if (modelNameBstr != NULL)
SysFreeString(modelNameBstr);
}
deckLinkAttributes->Release();
deckLinkAttributes = NULL;
if (result != S_OK || duplexMode == bmdDuplexInactive) if (result != S_OK || duplexMode == bmdDuplexInactive)
{ {
deckLink->Release(); deckLink.Release();
deckLink = NULL;
continue; continue;
} }
@@ -148,7 +113,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
if (!output && (!inputUsed || (duplexMode == bmdDuplexFull))) if (!output && (!inputUsed || (duplexMode == bmdDuplexFull)))
{ {
if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK) if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK)
output = NULL; output.Release();
else else
{ {
outputModelName = modelName; outputModelName = modelName;
@@ -157,8 +122,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
} }
} }
deckLink->Release(); deckLink.Release();
deckLink = NULL;
if (output && input) if (output && input)
break; break;
@@ -167,39 +131,40 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
if (!output) if (!output)
{ {
error = "Expected an Output DeckLink device"; error = "Expected an Output DeckLink device";
goto cleanup; ReleaseResources();
return false;
} }
CComPtr<IDeckLinkDisplayModeIterator> inputDisplayModeIterator;
if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK) if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK)
{ {
error = "Cannot get input Display Mode Iterator."; error = "Cannot get input Display Mode Iterator.";
goto cleanup; ReleaseResources();
return false;
} }
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, inputDisplayMode, &inputMode)) if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, inputDisplayMode, &inputMode))
{ {
error = "Cannot get specified input BMDDisplayMode for configured mode: " + requestedInputDisplayModeName; error = "Cannot get specified input BMDDisplayMode for configured mode: " + requestedInputDisplayModeName;
goto cleanup; ReleaseResources();
} return false;
if (inputDisplayModeIterator)
{
inputDisplayModeIterator->Release();
inputDisplayModeIterator = NULL;
} }
inputDisplayModeIterator.Release();
CComPtr<IDeckLinkDisplayModeIterator> outputDisplayModeIterator;
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK) if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
{ {
error = "Cannot get output Display Mode Iterator."; error = "Cannot get output Display Mode Iterator.";
goto cleanup; ReleaseResources();
return false;
} }
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, outputDisplayMode, &outputMode)) if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, outputDisplayMode, &outputMode))
{ {
error = "Cannot get specified output BMDDisplayMode for configured mode: " + requestedOutputDisplayModeName; error = "Cannot get specified output BMDDisplayMode for configured mode: " + requestedOutputDisplayModeName;
goto cleanup; ReleaseResources();
return false;
} }
outputDisplayModeIterator->Release();
outputDisplayModeIterator = NULL;
outputFrameWidth = outputMode->GetWidth(); outputFrameWidth = outputMode->GetWidth();
outputFrameHeight = outputMode->GetHeight(); outputFrameHeight = outputMode->GetHeight();
@@ -209,26 +174,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
inputDisplayModeName = "No input - black frame"; inputDisplayModeName = "No input - black frame";
outputMode->GetFrameRate(&frameDuration, &frameTimescale); outputMode->GetFrameRate(&frameDuration, &frameTimescale);
success = true; return 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) bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, BMDDisplayMode inputDisplayMode, std::string& error)
@@ -245,14 +191,18 @@ bool DeckLinkSession::ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglr
if (input->EnableVideoInputWithAllocatorProvider(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault, captureAllocator) != S_OK) if (input->EnableVideoInputWithAllocatorProvider(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault, captureAllocator) != S_OK)
{ {
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n"); OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
input->Release(); input.Release();
input = NULL;
hasNoInputSource = true; hasNoInputSource = true;
inputDisplayModeName = "No input - black frame"; inputDisplayModeName = "No input - black frame";
return true; return true;
} }
captureDelegate = new CaptureDelegate(owner); captureDelegate.Attach(new (std::nothrow) CaptureDelegate(owner));
if (captureDelegate == nullptr)
{
error = "DeckLink input setup failed while creating the capture callback.";
return false;
}
if (input->SetCallback(captureDelegate) != S_OK) if (input->SetCallback(captureDelegate) != S_OK)
{ {
error = "DeckLink input setup failed while installing the capture callback."; error = "DeckLink input setup failed while installing the capture callback.";
@@ -271,7 +221,12 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
return false; return false;
} }
playoutAllocator = new PinnedMemoryAllocator(hdc, hglrc, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * outputFrameHeight); playoutAllocator.Attach(new (std::nothrow) PinnedMemoryAllocator(hdc, hglrc, VideoFrameTransfer::GPUtoCPU, 1, outputFrameRowBytes * outputFrameHeight));
if (playoutAllocator == nullptr)
{
error = "DeckLink output setup failed while creating the playout allocator.";
return false;
}
if (output->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK) if (output->EnableVideoOutput(outputDisplayMode, bmdVideoOutputFlagDefault) != S_OK)
{ {
@@ -309,8 +264,8 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
IDeckLinkMutableVideoFrame* outputFrame = NULL; CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
IDeckLinkVideoBuffer* outputFrameBuffer = NULL; CComPtr<IDeckLinkVideoBuffer> outputFrameBuffer;
if (playoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK) if (playoutAllocator->AllocateVideoBuffer(&outputFrameBuffer) != S_OK)
{ {
@@ -321,15 +276,14 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
if (output->CreateVideoFrameWithBuffer(outputFrameWidth, outputFrameHeight, outputFrameRowBytes, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, outputFrameBuffer, &outputFrame) != S_OK) if (output->CreateVideoFrameWithBuffer(outputFrameWidth, outputFrameHeight, outputFrameRowBytes, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, outputFrameBuffer, &outputFrame) != S_OK)
{ {
error = "DeckLink output setup failed while creating an output video frame."; error = "DeckLink output setup failed while creating an output video frame.";
outputFrameBuffer->Release();
return false; return false;
} }
outputVideoFrameQueue.push_back(outputFrame); outputVideoFrameQueue.push_back(outputFrame);
} }
playoutDelegate = new PlayoutDelegate(owner); playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(owner));
if (playoutDelegate == NULL) if (playoutDelegate == nullptr)
{ {
error = "DeckLink output setup failed while creating the playout callback."; error = "DeckLink output setup failed while creating the playout callback.";
return false; return false;
@@ -353,10 +307,10 @@ double DeckLinkSession::FrameBudgetMilliseconds() const
IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame() IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame()
{ {
IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front(); CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame = outputVideoFrameQueue.front();
outputVideoFrameQueue.push_back(outputVideoFrame); outputVideoFrameQueue.push_back(outputVideoFrame);
outputVideoFrameQueue.pop_front(); outputVideoFrameQueue.pop_front();
return outputVideoFrame; return outputVideoFrame.p;
} }
bool DeckLinkSession::TransferPlayoutFrame(void* address, GLuint outputTexture) bool DeckLinkSession::TransferPlayoutFrame(void* address, GLuint outputTexture)
@@ -401,11 +355,11 @@ bool DeckLinkSession::Start()
for (unsigned i = 0; i < kPrerollFrameCount; i++) for (unsigned i = 0; i < kPrerollFrameCount; i++)
{ {
IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front(); CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame = outputVideoFrameQueue.front();
outputVideoFrameQueue.push_back(outputVideoFrame); outputVideoFrameQueue.push_back(outputVideoFrame);
outputVideoFrameQueue.pop_front(); outputVideoFrameQueue.pop_front();
IDeckLinkVideoBuffer* outputVideoFrameBuffer; CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) 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); MessageBoxA(NULL, "Could not query the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
@@ -414,7 +368,6 @@ bool DeckLinkSession::Start()
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) 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); MessageBoxA(NULL, "Could not write to the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false; return false;
} }
@@ -424,7 +377,6 @@ bool DeckLinkSession::Start()
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputFrameHeight); memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputFrameHeight);
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
outputVideoFrameBuffer->Release();
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK) if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
{ {

View File

@@ -3,6 +3,7 @@
#include "DeckLinkAPI_h.h" #include "DeckLinkAPI_h.h"
#include "DeckLinkFrameTransfer.h" #include "DeckLinkFrameTransfer.h"
#include <atlbase.h>
#include <deque> #include <deque>
#include <string> #include <string>
@@ -45,13 +46,13 @@ public:
bool ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame); bool ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
private: private:
CaptureDelegate* captureDelegate = nullptr; CComPtr<CaptureDelegate> captureDelegate;
PlayoutDelegate* playoutDelegate = nullptr; CComPtr<PlayoutDelegate> playoutDelegate;
IDeckLinkInput* input = nullptr; CComPtr<IDeckLinkInput> input;
IDeckLinkOutput* output = nullptr; CComPtr<IDeckLinkOutput> output;
IDeckLinkKeyer* keyer = nullptr; CComPtr<IDeckLinkKeyer> keyer;
std::deque<IDeckLinkMutableVideoFrame*> outputVideoFrameQueue; std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
PinnedMemoryAllocator* playoutAllocator = nullptr; CComPtr<PinnedMemoryAllocator> playoutAllocator;
BMDTimeValue frameDuration = 0; BMDTimeValue frameDuration = 0;
BMDTimeScale frameTimescale = 0; BMDTimeScale frameTimescale = 0;
unsigned totalPlayoutFrames = 0; unsigned totalPlayoutFrames = 0;