]> git.sesse.net Git - nageru/commitdiff
Add support for DeckLink PCI cards through the official driver.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 27 Feb 2016 12:17:14 +0000 (13:17 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 27 Feb 2016 12:18:06 +0000 (13:18 +0100)
Makefile
README
decklink_capture.cpp [new file with mode: 0644]
decklink_capture.h [new file with mode: 0644]
mixer.cpp

index a935ab5463178207ca8fc71f95e753ca570de14c..d19f0647ecf09d6a8b78c1b6db93e7d979d06702 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 CXX=g++
 PKG_MODULES = Qt5Core Qt5Gui Qt5Widgets Qt5OpenGLExtensions Qt5OpenGL libusb-1.0 movit lua52 libmicrohttpd epoxy
-CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations -Werror -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread -DMOVIT_SHADER_DIR=\"$(shell pkg-config --variable=shaderdir movit)\"
-LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lswscale -lzita-resampler -lasound
+CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations -Werror -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread -DMOVIT_SHADER_DIR=\"$(shell pkg-config --variable=shaderdir movit)\" -Idecklink/
+LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lswscale -lzita-resampler -lasound -ldl
 
 # Qt objects
 OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o correlation_meter.o aboutdialog.o
@@ -10,6 +10,9 @@ OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o correlation
 # Mixer objects
 OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampling_queue.o httpd.o ebu_r128_proc.o flags.o image_input.o stereocompressor.o filter.o alsa_output.o correlation_measurer.o
 
+# DeckLink
+OBJS += decklink_capture.o decklink/DeckLinkAPIDispatch.o
+
 %.o: %.cpp
        $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
 %.o: %.cc
diff --git a/README b/README
index 179e97fe89304c5d4421f331b3ba6019ab783603..e5e59648f77eb40ce98b18ff3145d402cb6f5779 100644 (file)
--- a/README
+++ b/README
@@ -33,8 +33,9 @@ Nageru is in beta stage. It currently needs:
    DRM instead of X11, to use a non-Intel GPU for rendering but still use
    Quick Sync (by giving e.g. “--va-display /dev/dri/renderD128”).
 
- - Two or more Blackmagic USB3 cards, either HDMI or SDI. These are driven
-   through the “bmusb” driver embedded in bmusb/, using libusb-1.0.
+ - Two or more Blackmagic USB3 or PCI cards, either HDMI or SDI.
+   The PCI cards need Blackmagic's own drivers installed. The USB3 cards
+   are driven through the “bmusb” driver embedded in bmusb/, using libusb-1.0.
    Note that you will want a recent Linux kernel to avoid LPM (link power
    management) and bandwidth allocation issues with USB3.
 
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);
+       }
+}
+
diff --git a/decklink_capture.h b/decklink_capture.h
new file mode 100644 (file)
index 0000000..c591cdc
--- /dev/null
@@ -0,0 +1,97 @@
+#ifndef _DECKLINK_CAPTURE_H
+#define _DECKLINK_CAPTURE_H 1
+
+#include <DeckLinkAPI.h>
+#include <stdint.h>
+#include <atomic>
+#include <functional>
+#include <string>
+
+#include "bmusb/bmusb.h"
+
+class IDeckLink;
+class IDeckLinkDisplayMode;
+
+// TODO: Adjust CaptureInterface to be a little less bmusb-centric.
+// There are too many member functions here that don't really do anything.
+class DeckLinkCapture : public CaptureInterface, IDeckLinkInputCallback
+{
+public:
+       DeckLinkCapture(IDeckLink *card, int card_index);
+       ~DeckLinkCapture();
+
+       // IDeckLinkInputCallback.
+       HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *) override;
+       ULONG STDMETHODCALLTYPE AddRef() override;
+       ULONG STDMETHODCALLTYPE Release() override;
+       HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(
+               BMDVideoInputFormatChangedEvents,
+               IDeckLinkDisplayMode*,
+               BMDDetectedVideoInputFormatFlags) override;
+       HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(
+               IDeckLinkVideoInputFrame *video_frame,
+               IDeckLinkAudioInputPacket *audio_frame) override;
+
+       // CaptureInterface.
+       void set_video_frame_allocator(FrameAllocator *allocator) override
+       {
+               video_frame_allocator = allocator;
+       }
+
+       FrameAllocator *get_video_frame_allocator() override
+       {
+               return video_frame_allocator;
+       }
+
+       // Does not take ownership.
+       void set_audio_frame_allocator(FrameAllocator *allocator) override
+       {
+               audio_frame_allocator = allocator;
+       }
+
+       FrameAllocator *get_audio_frame_allocator() override
+       {
+               return audio_frame_allocator;
+       }
+
+       void set_frame_callback(frame_callback_t callback) override
+       {
+               frame_callback = callback;
+       }
+
+       void set_dequeue_thread_callbacks(std::function<void()> init, std::function<void()> cleanup) override
+       {
+               dequeue_init_callback = init;
+               dequeue_cleanup_callback = cleanup;
+               has_dequeue_callbacks = true;
+       }
+
+       std::string get_description() const override
+       {
+               return description;
+       }
+
+       void configure_card() override {}
+       void start_bm_capture() override;
+       void stop_dequeue_thread() override;
+
+private:
+       std::atomic<int> refcount{1};
+       bool done_init = false;
+       std::string description;
+       uint16_t timecode = 0;
+
+       bool has_dequeue_callbacks = false;
+       std::function<void()> dequeue_init_callback = nullptr;
+       std::function<void()> dequeue_cleanup_callback = nullptr;
+
+       FrameAllocator *video_frame_allocator = nullptr;
+       FrameAllocator *audio_frame_allocator = nullptr;
+       frame_callback_t frame_callback = nullptr;
+
+       IDeckLinkInput *input = nullptr;
+       BMDTimeValue frame_duration;
+       BMDTimeScale time_scale;
+};
+
+#endif  // !defined(_DECKLINK_CAPTURE_H)
index 5f023cfd691eba9e0723e6f92c5ca4e6adb82519..436b761d3efd433a2b6addf93c315c05faeaf35c 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
 #include <thread>
 #include <utility>
 #include <vector>
+#include <arpa/inet.h>
 
 #include "bmusb/bmusb.h"
 #include "context.h"
+#include "decklink_capture.h"
 #include "defs.h"
 #include "flags.h"
 #include "h264encode.h"
@@ -135,13 +137,36 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
 
        h264_encoder.reset(new H264Encoder(h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd));
 
-       for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
-               configure_card(card_index, format, new BMUSBCapture(card_index));
+       // First try initializing the PCI devices, then USB, until we have the desired number of cards.
+       unsigned num_pci_devices = 0, num_usb_devices = 0;
+       unsigned card_index = 0;
+
+       IDeckLinkIterator *decklink_iterator = CreateDeckLinkIteratorInstance();
+       if (decklink_iterator != nullptr) {
+               for ( ; card_index < num_cards; ++card_index) {
+                       IDeckLink *decklink;
+                       if (decklink_iterator->Next(&decklink) != S_OK) {
+                               break;
+                       }
+
+                       configure_card(card_index, format, new DeckLinkCapture(decklink, card_index));
+                       ++num_pci_devices;
+               }
+               decklink_iterator->Release();
+               fprintf(stderr, "Found %d DeckLink PCI card(s).\n", num_pci_devices);
+       } else {
+               fprintf(stderr, "DeckLink drivers not found. Probing for USB cards only.\n");
+       }
+       for ( ; card_index < num_cards; ++card_index) {
+               configure_card(card_index, format, new BMUSBCapture(card_index - num_pci_devices));
+               ++num_usb_devices;
        }
 
-       BMUSBCapture::start_bm_thread();
+       if (num_usb_devices > 0) {
+               BMUSBCapture::start_bm_thread();
+       }
 
-       for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+       for (card_index = 0; card_index < num_cards; ++card_index) {
                cards[card_index].capture->start_bm_capture();
        }