Files
dmf-studio-rnd/shared/DeckLinkReceiver.hpp
T
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

218 lines
8.0 KiB
C++

#pragma once
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <numeric>
#include <stdexcept>
#include <string>
#include <vector>
#include <DeckLinkAPI.h>
#include "Signal.hpp"
namespace dmf {
class DeckLinkReceiver {
public:
struct VideoInfo {
int width = 0;
int height = 0;
int fps_num = 0;
int fps_den = 0;
};
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(uint64_t 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:
explicit InputCallback(DeckLinkReceiver& owner) : owner(owner) {}
HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(
BMDVideoInputFormatChangedEvents events,
IDeckLinkDisplayMode* mode,
BMDDetectedVideoInputFormatFlags /*flags*/) override
{
if (!(events & bmdVideoInputDisplayModeChanged)) return S_OK;
{
std::lock_guard<std::mutex> lk(owner.mutex);
owner.video_info.width = static_cast<int>(mode->GetWidth());
owner.video_info.height = static_cast<int>(mode->GetHeight());
BMDTimeValue dur = 0; BMDTimeScale ts = 0;
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();
HRESULT r = owner.decklink_input->StopStreams();
if (r == S_OK) {
r = owner.decklink_input->EnableVideoInput(
mode->GetDisplayMode(), bmdFormat10BitYUV,
bmdVideoInputEnableFormatDetection);
if (r == S_OK) owner.decklink_input->StartStreams();
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(
IDeckLinkVideoInputFrame* video_frame,
IDeckLinkAudioInputPacket* /*audio_packet*/) override
{
if (!video_frame) return S_OK;
void* src = nullptr;
video_frame->GetBytes(&src);
if (!src) return S_OK;
const uint32_t row_bytes = static_cast<uint32_t>(video_frame->GetRowBytes());
const int fw = static_cast<int>(video_frame->GetWidth());
const int fh = static_cast<int>(video_frame->GetHeight());
const size_t sz = static_cast<size_t>(row_bytes) * static_cast<size_t>(fh);
{
std::lock_guard<std::mutex> lk(owner.mutex);
if (owner.frame_buffer.size() < sz) owner.frame_buffer.resize(sz);
std::memcpy(owner.frame_buffer.data(), src, sz);
owner.frame_row_bytes = row_bytes;
owner.frame_width = fw;
owner.frame_height = fh;
owner.frame_ready = true;
}
owner.frame_cv.notify_one();
return S_OK;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*) override { return E_NOINTERFACE; }
ULONG STDMETHODCALLTYPE AddRef() override { return ++ref_count; }
ULONG STDMETHODCALLTYPE Release() override { return --ref_count; }
private:
DeckLinkReceiver& owner;
std::atomic<int32_t> ref_count{1};
};
std::vector<IDeckLink*> raw_devices;
IDeckLink* selected_device = nullptr;
IDeckLinkInput* decklink_input = nullptr;
InputCallback* input_callback = nullptr;
std::mutex mutex;
std::condition_variable format_cv;
std::condition_variable frame_cv;
bool format_detected = false;
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;
uint32_t index = 0;
while (it->Next(&device) == S_OK) {
IDeckLinkInput* inp = nullptr;
if (device->QueryInterface(IID_IDeckLinkInput, (void**)&inp) == S_OK) {
inp->Release();
const char* name = nullptr;
device->GetDisplayName(&name);
devices.push_back({index, name ? name : "?"});
raw_devices.push_back(device);
index++;
} else {
device->Release();
}
}
it->Release();
if (raw_devices.empty())
throw std::runtime_error("No DeckLink input devices found");
}
};
} // namespace dmf