--- /dev/null
+#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);
+ }
+}
+
--- /dev/null
+#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)
#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"
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();
}