#pragma once #include #include #include #include #include #include #include #include #include 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 frame_buffer; std::atomic 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 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 lk(video_info_mutex); video_info.width = static_cast(video_frame->GetWidth()); video_info.height = static_cast(video_frame->GetHeight()); video_info.stride = static_cast(video_frame->GetRowBytes()); if (frame_duration > 0) { video_info.fps_num = 10000000; video_info.fps_den = static_cast(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 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(video_frame->GetRowBytes()); int fw = static_cast(video_frame->GetWidth()); int fh = static_cast(video_frame->GetHeight()); size_t sz = static_cast(row_bytes) * static_cast(fh); std::lock_guard 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 ref_count; bool detection_enabled = false; std::mutex& format_mutex; std::condition_variable& format_cv; std::atomic video_info_set{false}; std::mutex video_info_mutex; void store_format(IDeckLinkDisplayMode* dm, IDeckLinkVideoInputFrame* /*vf*/) { if (!dm) return; std::lock_guard lk(video_info_mutex); video_info.width = static_cast(dm->GetWidth()); video_info.height = static_cast(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(ts); video_info.fps_den = static_cast(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 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 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 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; } }; }