Files
dmf-studio-rnd/shared/DeckLinkReceiver.hpp
T
2026-07-06 01:27:19 +03:00

374 lines
14 KiB
C++

#pragma once
#include <cstring>
#include <stdexcept>
#include <atomic>
#include <numeric>
#include <string>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <DeckLinkAPI.h>
namespace dmf { class DeckLinkReceiver; }
namespace dmf {
struct SourceInfo {
int width = 0;
int height = 0;
int fps_num = 0;
int fps_den = 0;
int stride = 0;
};
class DeckLinkInputCallback: public IDeckLinkInputCallback
{
public:
bool is_video_info_set() const { return video_info_set.load(std::memory_order_acquire); }
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(
BMDVideoInputFormatChangedEvents notification_events,
IDeckLinkDisplayMode *new_display_mode,
BMDDetectedVideoInputFormatFlags detected_signal_flags
) override
{
store_format(new_display_mode, nullptr);
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);
frame_ready.store(false, std::memory_order_release);
frame_width = 0;
frame_height = 0;
frame_buffer.clear();
}
// Re-enable video input with the detected display mode (per SDK samples)
if (deckLink_input && (notification_events & bmdVideoInputDisplayModeChanged)) {
HRESULT r = deckLink_input->StopStreams();
if (r == S_OK) {
r = deckLink_input->EnableVideoInput(
new_display_mode->GetDisplayMode(),
bmdFormat10BitYUV,
bmdVideoInputEnableFormatDetection);
if (r == S_OK) {
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;
}
HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(
IDeckLinkVideoInputFrame* video_frame,
IDeckLinkAudioInputPacket* audio_packet
) override
{
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;
buf->GetBytes(&src);
if (src) {
uint32_t row_bytes = static_cast<uint32_t>(video_frame->GetRowBytes());
int fw = static_cast<int>(video_frame->GetWidth());
int fh = static_cast<int>(video_frame->GetHeight());
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::memcpy(frame_buffer.data(), src, sz);
frame_size = sz;
frame_row_bytes = row_bytes;
frame_width = fw;
frame_height = fh;
frame_ready.store(true, std::memory_order_release);
frame_cv.notify_one();
}
buf->EndAccess(bmdBufferAccessRead);
buf->Release();
return S_OK;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) override
{
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() override
{
return ++ref_count;
}
ULONG STDMETHODCALLTYPE Release() override
{
return --ref_count; // DeckLinkReceiver destructor owns deletion
}
private:
dmf::DeckLinkReceiver* decklink_receiver;
IDeckLinkInput* deckLink_input = nullptr;
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};
std::mutex video_info_mutex;
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;
IDeckLinkConfiguration* device_config;
IDeckLinkStatus* device_status;
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()
{
IDeckLinkIterator* decklink_iterator = CreateDeckLinkIteratorInstance();
if (!decklink_iterator) {
throw std::runtime_error("A DeckLink iterator could not be created. The DeckLink drivers may not be installed");
return false;
}
IDeckLink* device = nullptr;
IDeckLinkInput* input = nullptr;
uint32_t index = 0;
while (true) {
device = nullptr;
HRESULT result = decklink_iterator->Next(&device);
if (result != S_OK) break;
input = nullptr;
result = device->QueryInterface(IID_IDeckLinkInput, (void**)&input);
if (input) input->Release();
if (result != S_OK) { device->Release(); continue; }
const char* display_name = nullptr;
device->GetDisplayName(&display_name);
devices_list.push_back({index, device, std::string(display_name ? display_name : "?")});
index++;
}
if (decklink_iterator) decklink_iterator->Release();
return index > 0;
}
HRESULT setup(uint32_t device_index, bool detection_enabled) {
selected_device_index = device_index;
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;
}
};
}