]> git.sesse.net Git - nageru/blobdiff - decklink_capture.cpp
Add support for DeckLink PCI cards through the official driver.
[nageru] / decklink_capture.cpp
diff --git a/decklink_capture.cpp b/decklink_capture.cpp
new file mode 100644 (file)
index 0000000..3243788
--- /dev/null
@@ -0,0 +1,208 @@
+#include "decklink_capture.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <cstddef>
+
+#include <DeckLinkAPI.h>
+#include <DeckLinkAPIConfiguration.h>
+#include <DeckLinkAPIDiscovery.h>
+#include <DeckLinkAPIModes.h>
+#include "bmusb/bmusb.h"
+
+using namespace std;
+using namespace std::placeholders;
+
+namespace {
+
+// TODO: Support stride.
+// TODO: Support AVX2 (adapt from bmusb).
+void memcpy_interleaved(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, size_t n)
+{
+       assert(n % 2 == 0);
+       uint8_t *dptr1 = dest1;
+       uint8_t *dptr2 = dest2;
+
+       for (size_t i = 0; i < n; i += 2) {
+               *dptr1++ = *src++;
+               *dptr2++ = *src++;
+       }
+}
+
+}  // namespace
+
+DeckLinkCapture::DeckLinkCapture(IDeckLink *card, int card_index)
+{
+       {
+               const char *model_name;
+               char buf[256];
+               if (card->GetModelName(&model_name) == S_OK) {
+                       snprintf(buf, sizeof(buf), "Card %d: %s", card_index, model_name);
+               } else {
+                       snprintf(buf, sizeof(buf), "Card %d: Unknown DeckLink card", card_index);
+               }
+               description = buf;
+       }
+
+       if (card->QueryInterface(IID_IDeckLinkInput, (void**)&input) != S_OK) {
+               fprintf(stderr, "Card %d has no inputs\n", card_index);
+               exit(1);
+       }
+
+       /* Set up the video and audio sources. */
+       IDeckLinkConfiguration *config;
+       if (card->QueryInterface(IID_IDeckLinkConfiguration, (void**)&config) != S_OK) {
+               fprintf(stderr, "Failed to get configuration interface for card %d\n", card_index);
+               exit(1);
+       }
+
+       if (config->SetInt(bmdDeckLinkConfigVideoInputConnection, bmdVideoConnectionHDMI) != S_OK) {
+               fprintf(stderr, "Failed to set video input connection for card %d\n", card_index);
+               exit(1);
+       }
+
+       if (config->SetInt(bmdDeckLinkConfigAudioInputConnection, bmdAudioConnectionEmbedded) != S_OK) {
+               fprintf(stderr, "Failed to set video input connection for card %d\n", card_index);
+               exit(1);
+       }
+
+       // TODO: Make the user mode selectable.
+       BMDDisplayModeSupport support;
+       IDeckLinkDisplayMode *display_mode;
+       if (input->DoesSupportVideoMode(bmdModeHD720p5994, bmdFormat8BitYUV, /*flags=*/0, &support, &display_mode)) {
+               fprintf(stderr, "Failed to query display mode for card %d\n", card_index);
+               exit(1);
+       }
+
+       if (support == bmdDisplayModeNotSupported) {
+               fprintf(stderr, "Card %d does not support display mode\n", card_index);
+               exit(1);
+       }
+
+       if (display_mode->GetFrameRate(&frame_duration, &time_scale) != S_OK) {
+               fprintf(stderr, "Could not get frame rate for card %d\n", card_index);
+               exit(1);
+       }
+
+       if (input->EnableVideoInput(bmdModeHD720p5994, bmdFormat8BitYUV, 0) != S_OK) {
+               fprintf(stderr, "Failed to set 720p59.94 connection for card %d\n", card_index);
+               exit(1);
+       }
+
+       if (input->EnableAudioInput(48000, bmdAudioSampleType16bitInteger, 2) != S_OK) {
+               fprintf(stderr, "Failed to enable audio input for card %d\n", card_index);
+               exit(1);
+       }
+
+       input->SetCallback(this);
+}
+
+DeckLinkCapture::~DeckLinkCapture()
+{
+       if (has_dequeue_callbacks) {
+               dequeue_cleanup_callback();
+       }
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkCapture::QueryInterface(REFIID, LPVOID *)
+{
+       return E_NOINTERFACE;
+}
+
+ULONG STDMETHODCALLTYPE DeckLinkCapture::AddRef(void)
+{
+       return refcount.fetch_add(1) + 1;
+}
+
+ULONG STDMETHODCALLTYPE DeckLinkCapture::Release(void)
+{
+       int new_ref = refcount.fetch_sub(1) - 1;
+       if (new_ref == 0)
+               delete this;
+       return new_ref;
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkCapture::VideoInputFormatChanged(
+       BMDVideoInputFormatChangedEvents,
+       IDeckLinkDisplayMode* display_mode,
+       BMDDetectedVideoInputFormatFlags)
+{
+       if (display_mode->GetFrameRate(&frame_duration, &time_scale) != S_OK) {
+               fprintf(stderr, "Could not get new frame rate\n");
+               exit(1);
+       }
+       return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE DeckLinkCapture::VideoInputFrameArrived(
+       IDeckLinkVideoInputFrame *video_frame,
+       IDeckLinkAudioInputPacket *audio_frame)
+{
+       if (!done_init) {
+               if (has_dequeue_callbacks) {
+                       dequeue_init_callback();
+               }
+               done_init = true;
+       }
+
+       FrameAllocator::Frame current_video_frame, current_audio_frame;
+       VideoFormat video_format;
+
+       if (video_frame) {
+               if (video_frame->GetFlags() & bmdFrameHasNoInputSource) {
+                       // TODO: Make a way to propagate this flag down to the theme code,
+                       // independent of the signal resolution.
+                       fprintf(stderr, "Warning: No input signal detected\n");
+               }
+
+               int width = video_frame->GetWidth();
+               int height = video_frame->GetHeight();
+               const int stride = video_frame->GetRowBytes();
+               assert(stride == width * 2);
+
+               current_video_frame = video_frame_allocator->alloc_frame();
+               if (current_video_frame.data != nullptr) {
+                       const uint8_t *frame_bytes;
+                       video_frame->GetBytes((void **)&frame_bytes);
+
+                       memcpy_interleaved(current_video_frame.data, current_video_frame.data2,
+                               frame_bytes, width * height * 2);
+                       current_video_frame.len += width * height * 2;
+
+                       video_format.width = width;
+                       video_format.height = height;
+                       video_format.frame_rate_nom = time_scale;
+                       video_format.frame_rate_den = frame_duration;
+               }
+       }
+
+       if (current_video_frame.data != nullptr || current_audio_frame.data != nullptr) {
+               // TODO: Put into a queue and put into a dequeue thread, if the
+               // BlackMagic drivers don't already do that for us?
+               frame_callback(timecode,
+                       current_video_frame, /*video_offset=*/0, video_format,
+                       current_audio_frame, /*audio_offset=*/0, 0x0000);
+       }
+
+       timecode++;
+       return S_OK;
+}
+
+void DeckLinkCapture::start_bm_capture()
+{
+       if (input->StartStreams() != S_OK) {
+               fprintf(stderr, "StartStreams failed\n");
+               exit(1);
+       }
+}
+
+void DeckLinkCapture::stop_dequeue_thread()
+{
+       if (input->StopStreams() != S_OK) {
+               fprintf(stderr, "StopStreams failed\n");
+               exit(1);
+       }
+}
+