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:
name: React UI Build
runs-on: nubuntu-latest
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@@ -125,7 +125,7 @@ Current native test coverage includes:
- JSON parsing and serialization.
- 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
@@ -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=...`.
## Still todo
Audio
improve text rendering
genlock
find a better UI libary
Logs
refactor, cleanup of source files
display URL (Maybe clicakable) for control in the windows app (Not on the output)
Sound shader as seperate .slang in shader package?
runtime date time UTC and offset from PCs internal clock
![alt text](image.png)
## Still Todo
- Audio.
- Improve text rendering.
- Genlock.
- Find a better UI library.
- Logs.
- Continue source cleanup/refactoring.
- Display the control URL in the Windows app, ideally clickable, without rendering it on the video output.
- 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)

View File

@@ -13,7 +13,7 @@ namespace
{
std::string BstrToUtf8(BSTR value)
{
if (value == NULL)
if (value == nullptr)
return std::string();
const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
@@ -36,84 +36,56 @@ DeckLinkSession::~DeckLinkSession()
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();
}
captureDelegate.Release();
input.Release();
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)
if (keyer != nullptr)
{
playoutAllocator->Release();
playoutAllocator = nullptr;
keyer->Disable();
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 success = false;
IDeckLinkIterator* deckLinkIterator = NULL;
IDeckLink* deckLink = NULL;
IDeckLinkProfileAttributes* deckLinkAttributes = NULL;
IDeckLinkDisplayModeIterator* inputDisplayModeIterator = NULL;
IDeckLinkDisplayModeIterator* outputDisplayModeIterator = NULL;
IDeckLinkDisplayMode* inputMode = NULL;
IDeckLinkDisplayMode* outputMode = NULL;
CComPtr<IDeckLinkIterator> deckLinkIterator;
CComPtr<IDeckLinkDisplayMode> inputMode;
CComPtr<IDeckLinkDisplayMode> outputMode;
inputDisplayModeName = requestedInputDisplayModeName;
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))
{
error = "Please install the Blackmagic DeckLink drivers to use the features of this application.";
return false;
}
CComPtr<IDeckLink> deckLink;
while (deckLinkIterator->Next(&deckLink) == S_OK)
{
int64_t duplexMode;
bool deviceSupportsInternalKeying = false;
bool deviceSupportsExternalKeying = false;
std::string modelName;
CComPtr<IDeckLinkProfileAttributes> deckLinkAttributes;
if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
{
printf("Could not obtain the IDeckLinkProfileAttributes interface\n");
deckLink->Release();
deckLink = NULL;
deckLink.Release();
continue;
}
@@ -124,20 +96,13 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
attributeFlag = FALSE;
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
deviceSupportsExternalKeying = (attributeFlag != FALSE);
BSTR modelNameBstr = NULL;
CComBSTR modelNameBstr;
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;
deckLink.Release();
continue;
}
@@ -148,7 +113,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
if (!output && (!inputUsed || (duplexMode == bmdDuplexFull)))
{
if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK)
output = NULL;
output.Release();
else
{
outputModelName = modelName;
@@ -157,8 +122,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
}
}
deckLink->Release();
deckLink = NULL;
deckLink.Release();
if (output && input)
break;
@@ -167,39 +131,40 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
if (!output)
{
error = "Expected an Output DeckLink device";
goto cleanup;
ReleaseResources();
return false;
}
CComPtr<IDeckLinkDisplayModeIterator> inputDisplayModeIterator;
if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK)
{
error = "Cannot get input Display Mode Iterator.";
goto cleanup;
ReleaseResources();
return false;
}
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, inputDisplayMode, &inputMode))
{
error = "Cannot get specified input BMDDisplayMode for configured mode: " + requestedInputDisplayModeName;
goto cleanup;
}
if (inputDisplayModeIterator)
{
inputDisplayModeIterator->Release();
inputDisplayModeIterator = NULL;
ReleaseResources();
return false;
}
inputDisplayModeIterator.Release();
CComPtr<IDeckLinkDisplayModeIterator> outputDisplayModeIterator;
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
{
error = "Cannot get output Display Mode Iterator.";
goto cleanup;
ReleaseResources();
return false;
}
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, outputDisplayMode, &outputMode))
{
error = "Cannot get specified output BMDDisplayMode for configured mode: " + requestedOutputDisplayModeName;
goto cleanup;
ReleaseResources();
return false;
}
outputDisplayModeIterator->Release();
outputDisplayModeIterator = NULL;
outputFrameWidth = outputMode->GetWidth();
outputFrameHeight = outputMode->GetHeight();
@@ -209,26 +174,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, B
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;
return true;
}
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)
{
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
input->Release();
input = NULL;
input.Release();
hasNoInputSource = true;
inputDisplayModeName = "No input - black frame";
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)
{
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;
}
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)
{
@@ -309,8 +264,8 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl
for (int i = 0; i < 10; i++)
{
IDeckLinkMutableVideoFrame* outputFrame = NULL;
IDeckLinkVideoBuffer* outputFrameBuffer = NULL;
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
CComPtr<IDeckLinkVideoBuffer> outputFrameBuffer;
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)
{
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)
playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(owner));
if (playoutDelegate == nullptr)
{
error = "DeckLink output setup failed while creating the playout callback.";
return false;
@@ -353,10 +307,10 @@ double DeckLinkSession::FrameBudgetMilliseconds() const
IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame()
{
IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front();
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame = outputVideoFrameQueue.front();
outputVideoFrameQueue.push_back(outputVideoFrame);
outputVideoFrameQueue.pop_front();
return outputVideoFrame;
return outputVideoFrame.p;
}
bool DeckLinkSession::TransferPlayoutFrame(void* address, GLuint outputTexture)
@@ -401,11 +355,11 @@ bool DeckLinkSession::Start()
for (unsigned i = 0; i < kPrerollFrameCount; i++)
{
IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front();
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame = outputVideoFrameQueue.front();
outputVideoFrameQueue.push_back(outputVideoFrame);
outputVideoFrameQueue.pop_front();
IDeckLinkVideoBuffer* outputVideoFrameBuffer;
CComPtr<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);
@@ -414,7 +368,6 @@ bool DeckLinkSession::Start()
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;
}
@@ -424,7 +377,6 @@ bool DeckLinkSession::Start()
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputFrameHeight);
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
outputVideoFrameBuffer->Release();
if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK)
{

View File

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