Decklink in #4
Vendored
+2
-1
@@ -5,7 +5,8 @@
|
|||||||
"includePath": [
|
"includePath": [
|
||||||
"${workspaceFolder}/**",
|
"${workspaceFolder}/**",
|
||||||
"${workspaceFolder}/shared",
|
"${workspaceFolder}/shared",
|
||||||
"${HOME}/SDK/NDI/include"
|
"${HOME}/SDK/NDI/include",
|
||||||
|
"${HOME}/SDK/decklink-sdk/Linux/include"
|
||||||
],
|
],
|
||||||
"defines": [],
|
"defines": [],
|
||||||
"compilerPath": "/usr/bin/clang",
|
"compilerPath": "/usr/bin/clang",
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ if(NDI_SDK_DIR)
|
|||||||
add_subdirectory(nodes/ndiin)
|
add_subdirectory(nodes/ndiin)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# ── DeckLink nodes ────────────────────────────────────────────────────────────────
|
||||||
|
set(DECKLINK_SDK_DIR "" CACHE PATH "Path to DeckLink SDK root")
|
||||||
|
if(DECKLINK_SDK_DIR)
|
||||||
|
add_subdirectory(nodes/decklinkin)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_subdirectory(nodes/videoin)
|
add_subdirectory(nodes/videoin)
|
||||||
|
|
||||||
# ── Core server ──────────────────────────────────────────────────────────────
|
# ── Core server ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{ "id": "decklinkin", "type": "decklinkin", "params": {} },
|
||||||
|
{ "id": "ndiout", "type": "ndiout", "params": {} }
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"from": "decklinkin", "from_port": "video_flow_id",
|
||||||
|
"to": "ndiout", "to_port": "video_flow_id",
|
||||||
|
"format": { "kind": "video", "width": 1920, "height": 1080, "fps_num": 25, "fps_den": 1 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
set(DECKLINK_INCLUDE "${DECKLINK_SDK_DIR}/include"
|
||||||
|
CACHE PATH "Path to DeckLink API headers")
|
||||||
|
|
||||||
|
add_executable(dmf-node-decklinkin
|
||||||
|
main.cpp
|
||||||
|
"${DECKLINK_INCLUDE}/DeckLinkAPIDispatch.cpp"
|
||||||
|
)
|
||||||
|
target_compile_features(dmf-node-decklinkin PRIVATE cxx_std_20)
|
||||||
|
target_include_directories(dmf-node-decklinkin PRIVATE "${DECKLINK_INCLUDE}")
|
||||||
|
target_link_libraries(dmf-node-decklinkin PRIVATE dmf-shared ${CMAKE_DL_LIBS})
|
||||||
|
install(TARGETS dmf-node-decklinkin RUNTIME DESTINATION bin)
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#include "NodeBase.hpp"
|
||||||
|
#include "DeckLinkReceiver.hpp"
|
||||||
|
#include "Signal.hpp"
|
||||||
|
#include "FlowDef.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <vector>
|
||||||
|
#include <DeckLinkAPI.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <mxl/flow.h>
|
||||||
|
#include <mxl/time.h>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
class NodeDeckLinkIn: public dmf::NodeBase {
|
||||||
|
void run() override {
|
||||||
|
dmf::DeckLinkReceiver decklink_receiver;
|
||||||
|
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{};
|
||||||
|
if (!decklink_receiver.wait_for_format(5000)) {
|
||||||
|
log("Timeout waiting for format detection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
video_source_info = decklink_receiver.get_input_callback()->video_info;
|
||||||
|
if (video_source_info.width == 0 || video_source_info.fps_num == 0) {
|
||||||
|
log("Invalid format detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const auto video_flow_info = config().at("video_flow_id");
|
||||||
|
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 height = video_flow_info.value("height", video_source_info.height);
|
||||||
|
const int fps_num = video_flow_info.value("fps_num", video_source_info.fps_num);
|
||||||
|
const int fps_den = video_flow_info.value("fps_den", video_source_info.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{};
|
||||||
|
mxlFlowConfigInfo video_cfg{};
|
||||||
|
bool created = false;
|
||||||
|
mxlStatus vst = mxlCreateFlowWriter(
|
||||||
|
instance(),
|
||||||
|
dmf::make_video_flow_def(video_flow_id, node_id(), width, height, fps_num, fps_den).c_str(),
|
||||||
|
"", &video_writer, &video_cfg, &created);
|
||||||
|
if (vst != MXL_STATUS_OK) { log("video mxlCreateFlowWriter failed (%s)", dmf::mxl_status_str(vst)); return; }
|
||||||
|
|
||||||
|
const uint32_t video_stride = video_cfg.discrete.sliceSizes[0];
|
||||||
|
log("video stride=%u B/line grain=%u B ring=%u grains",
|
||||||
|
video_stride, video_stride * static_cast<uint32_t>(height), video_cfg.discrete.grainCount);
|
||||||
|
|
||||||
|
const mxlRational video_rate = {fps_num, fps_den};
|
||||||
|
uint64_t video_index = mxlGetCurrentIndex(&video_rate);
|
||||||
|
log("start video_index=%lu", static_cast<unsigned long>(video_index));
|
||||||
|
|
||||||
|
auto* cb = decklink_receiver.get_input_callback();
|
||||||
|
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)) {
|
||||||
|
// 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{};
|
||||||
|
uint8_t* video_buf = nullptr;
|
||||||
|
vst = mxlFlowWriterOpenGrain(video_writer, video_index, &grain, &video_buf);
|
||||||
|
if (vst == MXL_STATUS_OK) {
|
||||||
|
std::memcpy(video_buf, local_frame.data(), local_frame.size());
|
||||||
|
grain.flags = 0;
|
||||||
|
grain.validSlices = grain.totalSlices;
|
||||||
|
mxlFlowWriterCommitGrain(video_writer, &grain);
|
||||||
|
const uint64_t ns = mxlGetNsUntilIndex(video_index + 1, &video_rate);
|
||||||
|
if (ns > 0 && ns < 2'000'000'000ULL) mxlSleepForNs(ns);
|
||||||
|
video_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main ()
|
||||||
|
{
|
||||||
|
NodeDeckLinkIn node;
|
||||||
|
node.execute();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user