From e3bb32331aa8f0c63a43fb5434082134a6fbed12 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 27 Feb 2016 13:17:14 +0100 Subject: [PATCH] Add support for DeckLink PCI cards through the official driver. --- Makefile | 7 +- README | 5 +- decklink_capture.cpp | 208 +++++++++++++++++++++++++++++++++++++++++++ decklink_capture.h | 97 ++++++++++++++++++++ mixer.cpp | 33 ++++++- 5 files changed, 342 insertions(+), 8 deletions(-) create mode 100644 decklink_capture.cpp create mode 100644 decklink_capture.h diff --git a/Makefile b/Makefile index a935ab5..d19f064 100644 --- 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 179e97f..e5e5964 100644 --- 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 index 0000000..3243788 --- /dev/null +++ b/decklink_capture.cpp @@ -0,0 +1,208 @@ +#include "decklink_capture.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#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 index 0000000..c591cdc --- /dev/null +++ b/decklink_capture.h @@ -0,0 +1,97 @@ +#ifndef _DECKLINK_CAPTURE_H +#define _DECKLINK_CAPTURE_H 1 + +#include +#include +#include +#include +#include + +#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 init, std::function 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 refcount{1}; + bool done_init = false; + std::string description; + uint16_t timecode = 0; + + bool has_dequeue_callbacks = false; + std::function dequeue_init_callback = nullptr; + std::function 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) diff --git a/mixer.cpp b/mixer.cpp index 5f023cf..436b761 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -26,9 +26,11 @@ #include #include #include +#include #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(); } -- 2.39.2