Compare commits

...

2 Commits

Author SHA1 Message Date
JohannesItten e335461aac refactor: DeckLinkReceiver readability and error handling
DeckLinkReceiver:
- Group private members with section comments (SDK objects / sync / frame state)
- Change wait_for_format timeout param from uint64_t to int (matches
  std::chrono::milliseconds and all call sites)

decklinkin main.cpp:
- Wrap start_capture in try/catch — logs error and returns cleanly on
  device init failure (consistent with NDIInNode pattern)
- Remove redundant zero-init on frame_buf (vector<uint8_t> zero-inits anyway)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 01:38:13 +03:00
JohannesItten 640271384e refactor: DeckLinkReceiver cleanup and encapsulation
DeckLinkReceiver:
- Make InputCallback a private nested class — no longer exposed publicly
- DeckLinkReceiver owns all shared state (mutex, CVs, frame buffer)
- Replace get_input_callback() with clean wait_for_frame() API
- Fix device list leak: enumerate_devices stores raw IDeckLink* in
  raw_devices; destructor releases all of them + selected_device's AddRef
- Remove dead members: device_config, device_status, deckLink_notification
- Remove dead SourceInfo::stride field; rename SourceInfo → VideoInfo
- Remove dead video_source_info public member
- Remove printf; use no logging in receiver (caller logs)
- Remove commented-out notification code
- Fix dead return false after throw in enumerate_devices
- Fix dead null check after new InputCallback
- Replace IDeckLinkVideoBuffer QueryInterface with simpler GetBytes()
- Replace plain bool frame_ready/format_detected with consistent usage
  under mutex (no longer mixing atomic + CV pattern)
- Call StopStreams/DisableVideoInput in destructor
- Consistent snake_case naming throughout

decklinkin main.cpp:
- Rename NodeDeckLinkIn → DeckLinkInNode
- Get device_index from config().value("device_index", 0u)
- Remove unused includes: <time.h>, <algorithm>, <cstdio>, <DeckLinkAPI.h>
- Use clean receiver.wait_for_frame() instead of reaching into callback
- Use mxlGetCurrentIndex resync after sleep (consistent with other nodes)
- Fix return node.execute() (was node.execute(); return 0)
- Fix main() spacing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 01:34:01 +03:00
2 changed files with 227 additions and 415 deletions
+44 -78
View File
@@ -1,59 +1,55 @@
#include "NodeBase.hpp" #include <cstring>
#include "DeckLinkReceiver.hpp"
#include "Signal.hpp"
#include "FlowDef.hpp"
#include <cstdio>
#include <vector> #include <vector>
#include <DeckLinkAPI.h>
#include <time.h>
#include <mxl/flow.h> #include <mxl/flow.h>
#include <mxl/time.h> #include <mxl/time.h>
#include <string> #include "NodeBase.hpp"
#include <algorithm> #include "FlowDef.hpp"
#include "DeckLinkReceiver.hpp"
class NodeDeckLinkIn: public dmf::NodeBase { class DeckLinkInNode : public dmf::NodeBase {
void run() override { void run() override {
dmf::DeckLinkReceiver decklink_receiver; const uint32_t device_index = config().value("device_index", 0u);
log("Available DeckLink input devices:");
for (auto device : decklink_receiver.devices_list) {
log("%i) %s", device.index, device.display_name.c_str());
}
int selected_device = 0;
decklink_receiver.start_capture(selected_device);
log(
"DeckLink feed for device '%s':",
decklink_receiver.devices_list.at(selected_device).display_name.c_str()
);
dmf::SourceInfo video_source_info{}; dmf::DeckLinkReceiver receiver;
if (!decklink_receiver.wait_for_format(5000)) { try {
log("Available DeckLink devices:");
for (const auto& d : receiver.devices)
log(" %u) %s", d.index, d.name.c_str());
receiver.start_capture(device_index);
log("Capturing from: %s", receiver.devices[device_index].name.c_str());
} catch (const std::runtime_error& e) {
log("DeckLink init error: %s", e.what());
return;
}
if (!receiver.wait_for_format(5000)) {
log("Timeout waiting for format detection"); log("Timeout waiting for format detection");
return; return;
} }
video_source_info = decklink_receiver.get_input_callback()->video_info; const auto& vi = receiver.video_info;
if (video_source_info.width == 0 || video_source_info.fps_num == 0) { if (vi.width == 0 || vi.fps_num == 0) { log("Invalid format detected"); return; }
log("Invalid format detected"); log("Detected: %dx%d @ %d/%d fps", vi.width, vi.height, vi.fps_num, vi.fps_den);
return;
}
const auto video_flow_info = config().at("video_flow_id"); const auto video_flow_info = config().at("video_flow_id");
const auto video_flow_id = video_flow_info.at("id").get<std::string>(); const auto video_flow_id = video_flow_info.at("id").get<std::string>();
const int width = video_flow_info.value("width", video_source_info.width); const int width = video_flow_info.value("width", vi.width);
const int height = video_flow_info.value("height", video_source_info.height); const int height = video_flow_info.value("height", vi.height);
const int fps_num = video_flow_info.value("fps_num", video_source_info.fps_num); const int fps_num = video_flow_info.value("fps_num", vi.fps_num);
const int fps_den = video_flow_info.value("fps_den", video_source_info.fps_den); const int fps_den = video_flow_info.value("fps_den", vi.fps_den);
log("video flow=%s %dx%d @ %d/%d fps", video_flow_id.c_str(), width, height, fps_num, fps_den); log("video flow=%s %dx%d @ %d/%d fps", video_flow_id.c_str(), width, height, fps_num, fps_den);
mxlFlowWriter video_writer{}; mxlFlowWriter video_writer = nullptr;
mxlFlowConfigInfo video_cfg{}; mxlFlowConfigInfo video_cfg{};
bool created = false; bool created = false;
mxlStatus vst = mxlCreateFlowWriter( mxlStatus vst = mxlCreateFlowWriter(
instance(), instance(),
dmf::make_video_flow_def(video_flow_id, node_id(), width, height, fps_num, fps_den).c_str(), dmf::make_video_flow_def(video_flow_id, node_id(), width, height, fps_num, fps_den).c_str(),
"", &video_writer, &video_cfg, &created); "", &video_writer, &video_cfg, &created);
if (vst != MXL_STATUS_OK) { log("video mxlCreateFlowWriter failed (%s)", dmf::mxl_status_str(vst)); return; } if (vst != MXL_STATUS_OK) {
log("mxlCreateFlowWriter failed (%s)", dmf::mxl_status_str(vst));
return;
}
const uint32_t video_stride = video_cfg.discrete.sliceSizes[0]; const uint32_t video_stride = video_cfg.discrete.sliceSizes[0];
log("video stride=%u B/line grain=%u B ring=%u grains", log("video stride=%u B/line grain=%u B ring=%u grains",
@@ -61,63 +57,33 @@ class NodeDeckLinkIn: public dmf::NodeBase {
const mxlRational video_rate = {fps_num, fps_den}; const mxlRational video_rate = {fps_num, fps_den};
uint64_t video_index = mxlGetCurrentIndex(&video_rate); uint64_t video_index = mxlGetCurrentIndex(&video_rate);
log("start video_index=%lu", static_cast<unsigned long>(video_index)); log("start video_index=%llu", video_index);
auto* cb = decklink_receiver.get_input_callback(); std::vector<uint8_t> frame_buf(static_cast<size_t>(video_stride) * static_cast<size_t>(height));
const uint32_t dst_row = video_stride;
std::vector<uint8_t> local_frame(dst_row * height, 0); while (dmf::g_running.load(std::memory_order_relaxed)) {
if (!receiver.wait_for_frame(frame_buf.data(), video_stride, width, height)) break;
while(dmf::g_running.load(std::memory_order_relaxed)) {
// Wait for a fresh frame from the callback
uint32_t src_row;
int src_w, src_h;
{
std::unique_lock<std::mutex> lk(cb->frame_mutex);
cb->frame_cv.wait(lk, [&] {
return cb->frame_ready.load(std::memory_order_acquire)
|| !dmf::g_running.load(std::memory_order_relaxed);
});
if (!dmf::g_running.load(std::memory_order_relaxed)) break;
src_row = cb->frame_row_bytes ? cb->frame_row_bytes : dst_row;
src_w = cb->frame_width;
src_h = cb->frame_height;
// Row-by-row copy with zero padding for stride difference
const uint8_t* src = cb->frame_buffer.data();
uint8_t* dst = local_frame.data();
const uint32_t copy_row = std::min<uint32_t>(src_row, dst_row);
const int rows = std::min(src_h, height);
std::memset(local_frame.data(), 0, local_frame.size());
for (int y = 0; y < rows; ++y) {
std::memcpy(dst, src, copy_row);
src += src_row;
dst += dst_row;
}
cb->frame_ready.store(false, std::memory_order_release);
}
if (src_w != width || src_h != height) {
log("frame dim mismatch: src=%dx%d mxl=%dx%d (skipping)", src_w, src_h, width, height);
continue;
}
// MXL write — no lock held, callback can fill next frame in parallel
mxlGrainInfo grain{}; mxlGrainInfo grain{};
uint8_t* video_buf = nullptr; uint8_t* video_buf = nullptr;
vst = mxlFlowWriterOpenGrain(video_writer, video_index, &grain, &video_buf); vst = mxlFlowWriterOpenGrain(video_writer, video_index, &grain, &video_buf);
if (vst == MXL_STATUS_OK) { if (vst == MXL_STATUS_OK) {
std::memcpy(video_buf, local_frame.data(), local_frame.size()); std::memcpy(video_buf, frame_buf.data(), frame_buf.size());
grain.flags = 0; grain.flags = 0;
grain.validSlices = grain.totalSlices; grain.validSlices = grain.totalSlices;
mxlFlowWriterCommitGrain(video_writer, &grain); mxlFlowWriterCommitGrain(video_writer, &grain);
const uint64_t ns = mxlGetNsUntilIndex(video_index + 1, &video_rate); const uint64_t ns = mxlGetNsUntilIndex(video_index + 1, &video_rate);
if (ns > 0 && ns < 2'000'000'000ULL) mxlSleepForNs(ns); if (ns > 0 && ns < 2'000'000'000ULL) mxlSleepForNs(ns);
video_index++; video_index = mxlGetCurrentIndex(&video_rate);
} }
} }
log("stopped at video_index=%llu", video_index);
mxlReleaseFlowWriter(instance(), video_writer);
} }
}; };
int main () int main() {
{ DeckLinkInNode node;
NodeDeckLinkIn node; return node.execute();
node.execute();
return 0;
} }
+168 -322
View File
@@ -1,374 +1,220 @@
#pragma once #pragma once
#include <cstring> #include <algorithm>
#include <stdexcept>
#include <atomic> #include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <numeric> #include <numeric>
#include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
#include <mutex>
#include <condition_variable>
#include <DeckLinkAPI.h> #include <DeckLinkAPI.h>
#include "Signal.hpp"
namespace dmf { class DeckLinkReceiver; }
namespace dmf { namespace dmf {
struct SourceInfo { class DeckLinkReceiver {
public:
struct VideoInfo {
int width = 0; int width = 0;
int height = 0; int height = 0;
int fps_num = 0; int fps_num = 0;
int fps_den = 0; int fps_den = 0;
int stride = 0; };
};
class DeckLinkInputCallback: public IDeckLinkInputCallback struct DeviceInfo {
{ uint32_t index;
std::string name;
};
std::vector<DeviceInfo> devices;
VideoInfo video_info{};
DeckLinkReceiver() { enumerate_devices(); }
~DeckLinkReceiver() {
if (decklink_input) {
decklink_input->StopStreams();
decklink_input->DisableVideoInput();
decklink_input->SetCallback(nullptr);
decklink_input->Release();
}
delete input_callback;
for (auto* d : raw_devices) d->Release();
if (selected_device) selected_device->Release();
}
void start_capture(uint32_t device_index) {
if (device_index >= raw_devices.size())
throw std::runtime_error("Device index out of range");
selected_device = raw_devices[device_index];
selected_device->AddRef();
HRESULT r = selected_device->QueryInterface(IID_IDeckLinkInput, (void**)&decklink_input);
if (r != S_OK) throw std::runtime_error("Could not obtain IDeckLinkInput");
input_callback = new InputCallback(*this);
r = decklink_input->SetCallback(input_callback);
if (r != S_OK) throw std::runtime_error("Could not set input callback");
r = decklink_input->EnableVideoInput(bmdModeNTSC, bmdFormat10BitYUV,
bmdVideoInputEnableFormatDetection);
if (r != S_OK) throw std::runtime_error("Could not enable video input");
r = decklink_input->StartStreams();
if (r != S_OK) throw std::runtime_error("Could not start streams");
}
bool wait_for_format(int timeout_ms = 5000) {
std::unique_lock<std::mutex> lk(mutex);
return format_cv.wait_for(lk, std::chrono::milliseconds(timeout_ms),
[this] { return format_detected; });
}
// Blocks until a fresh frame arrives or g_running goes false.
// Copies frame data into dst with MXL stride. Returns false on shutdown.
bool wait_for_frame(uint8_t* dst, uint32_t dst_stride, int width, int height) {
std::unique_lock<std::mutex> lk(mutex);
frame_cv.wait(lk, [this] {
return frame_ready || !dmf::g_running.load(std::memory_order_relaxed);
});
if (!dmf::g_running.load(std::memory_order_relaxed)) return false;
const uint32_t src_stride = frame_row_bytes;
const int rows = std::min(frame_height, height);
const uint32_t copy_bytes = std::min(src_stride, dst_stride);
std::memset(dst, 0, static_cast<size_t>(dst_stride) * static_cast<size_t>(height));
const uint8_t* src = frame_buffer.data();
uint8_t* d = dst;
for (int y = 0; y < rows; ++y, src += src_stride, d += dst_stride)
std::memcpy(d, src, copy_bytes);
frame_ready = false;
return true;
}
private:
class InputCallback : public IDeckLinkInputCallback {
public: public:
bool is_video_info_set() const { return video_info_set.load(std::memory_order_acquire); } explicit InputCallback(DeckLinkReceiver& owner) : owner(owner) {}
SourceInfo video_info{};
std::mutex frame_mutex;
std::condition_variable frame_cv;
std::vector<uint8_t> frame_buffer;
std::atomic<bool> frame_ready{false};
size_t frame_size = 0;
uint32_t frame_row_bytes = 0; // actual stride from GetRowBytes()
int frame_width = 0;
int frame_height = 0;
DeckLinkInputCallback(
dmf::DeckLinkReceiver* receiver,
bool detection_enabled,
std::mutex& mtx,
std::condition_variable& cv
)
: decklink_receiver(receiver)
, ref_count(1)
, detection_enabled(detection_enabled)
, format_mutex(mtx)
, format_cv(cv)
{}
void set_decklink_input(IDeckLinkInput* input) { deckLink_input = input; }
HRESULT STDMETHODCALLTYPE VideoInputFormatChanged( HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(
BMDVideoInputFormatChangedEvents notification_events, BMDVideoInputFormatChangedEvents events,
IDeckLinkDisplayMode *new_display_mode, IDeckLinkDisplayMode* mode,
BMDDetectedVideoInputFormatFlags detected_signal_flags BMDDetectedVideoInputFormatFlags /*flags*/) override
) override
{ {
store_format(new_display_mode, nullptr); if (!(events & bmdVideoInputDisplayModeChanged)) return S_OK;
long w = new_display_mode->GetWidth();
long h = new_display_mode->GetHeight();
BMDTimeValue dur; BMDTimeScale ts;
new_display_mode->GetFrameRate(&dur, &ts);
double fps = (double)ts / (double)dur;
const char* name = nullptr;
new_display_mode->GetName(&name);
printf("Input changed to: %ldx%ld @ %.2f fps (%s)\n", w, h, fps, name ? name : "?");
// Discard any stale frame from the placeholder mode
{ {
std::lock_guard<std::mutex> lk(frame_mutex); std::lock_guard<std::mutex> lk(owner.mutex);
frame_ready.store(false, std::memory_order_release); owner.video_info.width = static_cast<int>(mode->GetWidth());
frame_width = 0; owner.video_info.height = static_cast<int>(mode->GetHeight());
frame_height = 0; BMDTimeValue dur = 0; BMDTimeScale ts = 0;
frame_buffer.clear(); mode->GetFrameRate(&dur, &ts);
if (dur > 0 && ts > 0) {
owner.video_info.fps_num = static_cast<int>(ts);
owner.video_info.fps_den = static_cast<int>(dur);
const int g = std::gcd(owner.video_info.fps_num, owner.video_info.fps_den);
if (g > 1) { owner.video_info.fps_num /= g; owner.video_info.fps_den /= g; }
} }
owner.frame_ready = false;
owner.frame_buffer.clear();
owner.format_detected = true;
}
owner.format_cv.notify_one();
// Re-enable video input with the detected display mode (per SDK samples) HRESULT r = owner.decklink_input->StopStreams();
if (deckLink_input && (notification_events & bmdVideoInputDisplayModeChanged)) {
HRESULT r = deckLink_input->StopStreams();
if (r == S_OK) { if (r == S_OK) {
r = deckLink_input->EnableVideoInput( r = owner.decklink_input->EnableVideoInput(
new_display_mode->GetDisplayMode(), mode->GetDisplayMode(), bmdFormat10BitYUV,
bmdFormat10BitYUV,
bmdVideoInputEnableFormatDetection); bmdVideoInputEnableFormatDetection);
if (r == S_OK) { if (r == S_OK) owner.decklink_input->StartStreams();
r = deckLink_input->StartStreams();
if (r != S_OK) {
printf("StartStreams after reconfig failed (0x%08x)\n", r);
}
} else {
printf("EnableVideoInput after reconfig failed (0x%08x)\n", r);
}
} else {
printf("StopStreams for reconfig failed (0x%08x)\n", r);
}
} }
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(
IDeckLinkVideoInputFrame* video_frame, IDeckLinkVideoInputFrame* video_frame,
IDeckLinkAudioInputPacket* audio_packet IDeckLinkAudioInputPacket* /*audio_packet*/) override
) override
{ {
if (!video_frame) return S_OK; if (!video_frame) return S_OK;
// One-time format detection from first frame (only when auto-detection
// is OFF — otherwise VideoInputFormatChanged is authoritative).
if (!detection_enabled
&& !video_info_set.load(std::memory_order_relaxed))
{
BMDTimeValue frame_time = 0, frame_duration = 0;
video_frame->GetStreamTime(&frame_time, &frame_duration, 10000000);
{
std::lock_guard<std::mutex> lk(video_info_mutex);
video_info.width = static_cast<int>(video_frame->GetWidth());
video_info.height = static_cast<int>(video_frame->GetHeight());
video_info.stride = static_cast<int>(video_frame->GetRowBytes());
if (frame_duration > 0) {
video_info.fps_num = 10000000;
video_info.fps_den = static_cast<int>(frame_duration);
int g = std::gcd(video_info.fps_num, video_info.fps_den);
if (g > 1) { video_info.fps_num /= g; video_info.fps_den /= g; }
}
if (video_info.width > 0 && video_info.height > 0 && video_info.fps_num > 0) {
video_info_set.store(true, std::memory_order_release);
std::lock_guard<std::mutex> flk(format_mutex);
format_cv.notify_one();
}
}
}
// Always capture the frame
IDeckLinkVideoBuffer* buf = nullptr;
if (video_frame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&buf) != S_OK)
return S_OK;
buf->StartAccess(bmdBufferAccessRead);
void* src = nullptr; void* src = nullptr;
buf->GetBytes(&src); video_frame->GetBytes(&src);
if (src) { if (!src) return S_OK;
uint32_t row_bytes = static_cast<uint32_t>(video_frame->GetRowBytes());
int fw = static_cast<int>(video_frame->GetWidth()); const uint32_t row_bytes = static_cast<uint32_t>(video_frame->GetRowBytes());
int fh = static_cast<int>(video_frame->GetHeight()); const int fw = static_cast<int>(video_frame->GetWidth());
size_t sz = static_cast<size_t>(row_bytes) const int fh = static_cast<int>(video_frame->GetHeight());
* static_cast<size_t>(fh); const size_t sz = static_cast<size_t>(row_bytes) * static_cast<size_t>(fh);
std::lock_guard<std::mutex> lk(frame_mutex);
if (frame_buffer.size() < sz) {
frame_buffer.resize(sz); std::lock_guard<std::mutex> lk(owner.mutex);
std::memcpy(frame_buffer.data(), src, sz); if (owner.frame_buffer.size() < sz) owner.frame_buffer.resize(sz);
frame_size = sz; std::memcpy(owner.frame_buffer.data(), src, sz);
frame_row_bytes = row_bytes; owner.frame_row_bytes = row_bytes;
frame_width = fw; owner.frame_width = fw;
frame_height = fh; owner.frame_height = fh;
frame_ready.store(true, std::memory_order_release); owner.frame_ready = true;
frame_cv.notify_one();
} }
buf->EndAccess(bmdBufferAccessRead); owner.frame_cv.notify_one();
buf->Release();
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) override HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*) override { return E_NOINTERFACE; }
{ ULONG STDMETHODCALLTYPE AddRef() override { return ++ref_count; }
return E_NOINTERFACE; ULONG STDMETHODCALLTYPE Release() override { return --ref_count; }
}
ULONG STDMETHODCALLTYPE AddRef() override
{
return ++ref_count;
}
ULONG STDMETHODCALLTYPE Release() override
{
return --ref_count; // DeckLinkReceiver destructor owns deletion
}
private: private:
dmf::DeckLinkReceiver* decklink_receiver; DeckLinkReceiver& owner;
IDeckLinkInput* deckLink_input = nullptr; std::atomic<int32_t> ref_count{1};
std::atomic<int32_t> ref_count; };
bool detection_enabled = false;
std::mutex& format_mutex;
std::condition_variable& format_cv;
std::atomic<bool> video_info_set{false}; // DeckLink SDK objects
std::mutex video_info_mutex; std::vector<IDeckLink*> raw_devices;
void store_format(IDeckLinkDisplayMode* dm, IDeckLinkVideoInputFrame* /*vf*/) {
if (!dm) return;
std::lock_guard<std::mutex> lk(video_info_mutex);
video_info.width = static_cast<int>(dm->GetWidth());
video_info.height = static_cast<int>(dm->GetHeight());
video_info.stride = video_info.width * 8 / 3; // V210
BMDTimeValue dur = 0; BMDTimeScale ts = 0;
dm->GetFrameRate(&dur, &ts);
if (dur > 0 && ts > 0) {
video_info.fps_num = static_cast<int>(ts);
video_info.fps_den = static_cast<int>(dur);
int g = std::gcd(video_info.fps_num, video_info.fps_den);
if (g > 1) { video_info.fps_num /= g; video_info.fps_den /= g; }
}
if (video_info.width > 0 && video_info.height > 0 && video_info.fps_num > 0) {
video_info_set.store(true, std::memory_order_release);
{
std::lock_guard<std::mutex> lk(format_mutex);
format_cv.notify_one();
}
printf("Format from change: %dx%d @ %d/%d fps stride=%d\n",
video_info.width, video_info.height,
video_info.fps_num, video_info.fps_den, video_info.stride);
}
}
};
struct DeviceInfo {
uint32_t index;
IDeckLink* device;
std::string display_name;
};
class DeckLinkReceiver
{
public:
SourceInfo video_source_info{};
std::vector<DeviceInfo> devices_list;
DeckLinkReceiver() {
selected_device_index = 0;
selected_device = nullptr;
device_config = nullptr;
device_status = nullptr;
deckLink_notification = nullptr;
// notificationCallback = nullptr;
deckLink_input = nullptr;
input_callback = nullptr;
if (!get_input_device_list()) {
throw std::runtime_error("DeckLink devices not found");
}
}
~DeckLinkReceiver() {
if (input_callback) {
if (deckLink_input) deckLink_input->SetCallback(nullptr);
delete input_callback;
input_callback = nullptr;
}
// if (m_notificationCallback)
// {
// m_deckLinkNotification->Unsubscribe(bmdStatusChanged, m_notificationCallback);
// m_notificationCallback->Release();
// }
if (selected_device) selected_device->Release();
if (device_config) device_config->Release();
if (device_status) device_status->Release();
if (deckLink_input) deckLink_input->Release();
if (deckLink_notification) deckLink_notification->Release();
}
HRESULT start_capture(uint32_t device_index)
{
const bool detection = true; // format-detection mode
setup(device_index, detection);
HRESULT result = deckLink_input->EnableVideoInput(
bmdModeNTSC, // placeholder; detection will override
bmdFormat10BitYUV,
bmdVideoInputEnableFormatDetection
);
if (result != S_OK)
{
throw std::runtime_error("Could not enable video input");
}
result = deckLink_input->StartStreams();
if (result != S_OK)
{
throw std::runtime_error("Could not start capture");
}
return S_OK;
}
DeckLinkInputCallback* get_input_callback() {
return input_callback;
}
IDeckLinkInput* get_input() { return deckLink_input; }
bool wait_for_format(uint64_t timeout_ms = 5000) {
std::unique_lock<std::mutex> lk(mutex);
return signal_condition.wait_for(lk, std::chrono::milliseconds(timeout_ms),
[this] { return input_callback && input_callback->is_video_info_set(); });
}
private:
uint32_t selected_device_index = 0;
IDeckLink* selected_device = nullptr; IDeckLink* selected_device = nullptr;
IDeckLinkConfiguration* device_config; IDeckLinkInput* decklink_input = nullptr;
IDeckLinkStatus* device_status; InputCallback* input_callback = nullptr;
IDeckLinkNotification* deckLink_notification;
// NotificationCallback* m_notificationCallback;
IDeckLinkInput* deckLink_input;
DeckLinkInputCallback* input_callback;
std::mutex mutex;
std::condition_variable signal_condition;
bool get_input_device_list() // Synchronisation — one mutex guards all shared state below
{ std::mutex mutex;
IDeckLinkIterator* decklink_iterator = CreateDeckLinkIteratorInstance(); std::condition_variable format_cv; // signalled when format is detected
if (!decklink_iterator) { std::condition_variable frame_cv; // signalled when a frame arrives
throw std::runtime_error("A DeckLink iterator could not be created. The DeckLink drivers may not be installed"); bool format_detected = false;
return false;
} // Frame state — written by InputCallback, read by wait_for_frame (both under mutex)
std::vector<uint8_t> frame_buffer;
uint32_t frame_row_bytes = 0;
int frame_width = 0;
int frame_height = 0;
bool frame_ready = false;
void enumerate_devices() {
IDeckLinkIterator* it = CreateDeckLinkIteratorInstance();
if (!it) throw std::runtime_error("DeckLink drivers not installed");
IDeckLink* device = nullptr; IDeckLink* device = nullptr;
IDeckLinkInput* input = nullptr;
uint32_t index = 0; uint32_t index = 0;
while (true) { while (it->Next(&device) == S_OK) {
device = nullptr; IDeckLinkInput* inp = nullptr;
HRESULT result = decklink_iterator->Next(&device); if (device->QueryInterface(IID_IDeckLinkInput, (void**)&inp) == S_OK) {
if (result != S_OK) break; inp->Release();
input = nullptr; const char* name = nullptr;
result = device->QueryInterface(IID_IDeckLinkInput, (void**)&input); device->GetDisplayName(&name);
if (input) input->Release(); devices.push_back({index, name ? name : "?"});
if (result != S_OK) { device->Release(); continue; } raw_devices.push_back(device);
const char* display_name = nullptr;
device->GetDisplayName(&display_name);
devices_list.push_back({index, device, std::string(display_name ? display_name : "?")});
index++; index++;
} else {
device->Release();
} }
if (decklink_iterator) decklink_iterator->Release();
return index > 0;
} }
it->Release();
HRESULT setup(uint32_t device_index, bool detection_enabled) { if (raw_devices.empty())
selected_device_index = device_index; throw std::runtime_error("No DeckLink input devices found");
selected_device = devices_list.at(selected_device_index).device;
selected_device->AddRef();
HRESULT result = selected_device->QueryInterface(IID_IDeckLinkConfiguration, (void**)&device_config);
if (result != S_OK) {
throw std::runtime_error("Could not obtain the IDeckLinkConfiguration interface");
}
result = selected_device->QueryInterface(IID_IDeckLinkStatus, (void**)&device_status);
if (result != S_OK) {
throw std::runtime_error("Could not obtain the IDeckLinkStatus interface");
}
result = selected_device->QueryInterface(IID_IDeckLinkInput, (void**)&deckLink_input);
if (result != S_OK) {
throw std::runtime_error("Could not obtain the IDeckLinkInput interface - result");
}
input_callback = new DeckLinkInputCallback(this, detection_enabled, mutex, signal_condition);
if (!input_callback) {
throw std::runtime_error("Could not create input callback object");
}
result = deckLink_input->SetCallback(input_callback);
if (result != S_OK) {
throw std::runtime_error("Could not set input callback - result");
}
input_callback->set_decklink_input(deckLink_input);
return S_OK;
} }
}; };
} } // namespace dmf