From 78e28934f9aecfb4dc9cd573b0c9b4e0bd012729 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Mon, 11 Jul 2016 20:24:42 +0200 Subject: [PATCH] Add an option to add fake capture cards, for easier standalone theme testing. --- Makefile | 2 +- fake_capture.cpp | 188 +++++++++++++++++++++++++++++++++++++++++++++++ fake_capture.h | 98 ++++++++++++++++++++++++ flags.cpp | 21 +++++- flags.h | 1 + mixer.cpp | 46 ++++++++---- 6 files changed, 338 insertions(+), 18 deletions(-) create mode 100644 fake_capture.cpp create mode 100644 fake_capture.h diff --git a/Makefile b/Makefile index cfcdedd..98558d2 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o correlation OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o correlation_meter.moc.o aboutdialog.moc.o # Mixer objects -OBJS += 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 +OBJS += 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 fake_capture.o # Streaming and encoding objects OBJS += quicksync_encoder.o x264_encoder.o x264_speed_control.o video_encoder.o metacube2.o mux.o audio_encoder.o diff --git a/fake_capture.cpp b/fake_capture.cpp new file mode 100644 index 0000000..479dc6e --- /dev/null +++ b/fake_capture.cpp @@ -0,0 +1,188 @@ +// A fake capture device that sends single-color frames at a given rate. +// Mostly useful for testing themes without actually hooking up capture devices. + +#include "fake_capture.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "bmusb/bmusb.h" +#include "defs.h" + +#define FRAME_SIZE (8 << 20) // 8 MB. +#define FAKE_FPS 25 // Must be an integer. + +// Pure-color inputs: Red, green, blue, white. +#define NUM_COLORS 4 +constexpr uint8_t ys[NUM_COLORS] = { 81, 145, 41, 235 }; +constexpr uint8_t cbs[NUM_COLORS] = { 90, 54, 240, 128 }; +constexpr uint8_t crs[NUM_COLORS] = { 240, 34, 110, 128 }; + +using namespace std; + +namespace { + +// TODO: SSE2-optimize (or at least write full int64s) if speed becomes a problem. + +void memset2(uint8_t *s, const uint8_t c[2], size_t n) +{ + for (size_t i = 0; i < n; ++i) { + *s++ = c[0]; + *s++ = c[1]; + } +} + +void memset4(uint8_t *s, const uint8_t c[4], size_t n) +{ + for (size_t i = 0; i < n; ++i) { + *s++ = c[0]; + *s++ = c[1]; + *s++ = c[2]; + *s++ = c[3]; + } +} + +} // namespace + +FakeCapture::FakeCapture(int card_index) +{ + char buf[256]; + snprintf(buf, sizeof(buf), "Fake card %d", card_index + 1); + description = buf; + + y = ys[card_index % NUM_COLORS]; + cb = cbs[card_index % NUM_COLORS]; + cr = crs[card_index % NUM_COLORS]; +} + +FakeCapture::~FakeCapture() +{ + if (has_dequeue_callbacks) { + dequeue_cleanup_callback(); + } +} + +void FakeCapture::configure_card() +{ + if (video_frame_allocator == nullptr) { + owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES)); + set_video_frame_allocator(owned_video_frame_allocator.get()); + } + if (audio_frame_allocator == nullptr) { + owned_audio_frame_allocator.reset(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES)); + set_audio_frame_allocator(owned_audio_frame_allocator.get()); + } +} + +void FakeCapture::start_bm_capture() +{ + producer_thread_should_quit = false; + producer_thread = thread(&FakeCapture::producer_thread_func, this); +} + +void FakeCapture::stop_dequeue_thread() +{ + producer_thread_should_quit = true; + producer_thread.join(); +} + +std::map FakeCapture::get_available_video_modes() const +{ + VideoMode mode; + + char buf[256]; + snprintf(buf, sizeof(buf), "%dx%d", WIDTH, HEIGHT); + mode.name = buf; + + mode.autodetect = false; + mode.width = WIDTH; + mode.height = HEIGHT; + mode.frame_rate_num = FAKE_FPS; + mode.frame_rate_den = 1; + mode.interlaced = false; + + return {{ 0, mode }}; +} + +std::map FakeCapture::get_available_video_inputs() const +{ + return {{ 0, "Fake video input (single color)" }}; +} + +std::map FakeCapture::get_available_audio_inputs() const +{ + return {{ 0, "Fake audio input (silence)" }}; +} + +void FakeCapture::set_video_mode(uint32_t video_mode_id) +{ + assert(video_mode_id == 0); +} + +void FakeCapture::set_video_input(uint32_t video_input_id) +{ + assert(video_input_id == 0); +} + +void FakeCapture::set_audio_input(uint32_t audio_input_id) +{ + assert(audio_input_id == 0); +} + +void FakeCapture::producer_thread_func() +{ + uint16_t timecode = 0; + + if (has_dequeue_callbacks) { + dequeue_init_callback(); + } + while (!producer_thread_should_quit) { + usleep(1000000 / FAKE_FPS); // Rather approximate frame rate. + + if (producer_thread_should_quit) break; + + VideoFormat video_format; + video_format.width = WIDTH; + video_format.height = HEIGHT; + video_format.frame_rate_nom = FAKE_FPS; + video_format.frame_rate_den = 1; + video_format.has_signal = true; + + FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame(); + if (video_frame.data != nullptr) { + assert(video_frame.size >= WIDTH * HEIGHT * 2); + if (video_frame.interleaved) { + uint8_t cbcr[] = { cb, cr }; + memset2(video_frame.data, cbcr, WIDTH * HEIGHT / 2); + memset(video_frame.data2, y, WIDTH * HEIGHT); + } else { + uint8_t ycbcr[] = { y, cb, y, cr }; + memset4(video_frame.data, ycbcr, WIDTH * HEIGHT / 2); + } + video_frame.len = WIDTH * HEIGHT * 2; + } + + AudioFormat audio_format; + audio_format.bits_per_sample = 32; + audio_format.num_channels = 2; + + FrameAllocator::Frame audio_frame = audio_frame_allocator->alloc_frame(); + if (audio_frame.data != nullptr) { + assert(audio_frame.size >= 2 * sizeof(uint32_t) * OUTPUT_FREQUENCY / FAKE_FPS); + audio_frame.len = 2 * sizeof(uint32_t) * OUTPUT_FREQUENCY / FAKE_FPS; + memset(audio_frame.data, 0, audio_frame.len); + } + + frame_callback(timecode++, + video_frame, 0, video_format, + audio_frame, 0, audio_format); + } + if (has_dequeue_callbacks) { + dequeue_cleanup_callback(); + } +} diff --git a/fake_capture.h b/fake_capture.h new file mode 100644 index 0000000..583b421 --- /dev/null +++ b/fake_capture.h @@ -0,0 +1,98 @@ +#ifndef _FAKE_CAPTURE_H +#define _FAKE_CAPTURE_H 1 + +#include +#include +#include + +#include "bmusb/bmusb.h" + +class FakeCapture : public CaptureInterface +{ +public: + FakeCapture(int card_index); + ~FakeCapture(); + + // CaptureInterface. + void set_video_frame_allocator(FrameAllocator *allocator) override + { + video_frame_allocator = allocator; + if (owned_video_frame_allocator.get() != allocator) { + owned_video_frame_allocator.reset(); + } + } + + 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; + if (owned_audio_frame_allocator.get() != allocator) { + owned_audio_frame_allocator.reset(); + } + } + + 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; + + std::map get_available_video_modes() const override; + void set_video_mode(uint32_t video_mode_id) override; + uint32_t get_current_video_mode() const override { return 0; } + + std::map get_available_video_inputs() const override; + void set_video_input(uint32_t video_input_id) override; + uint32_t get_current_video_input() const override { return 0; } + + std::map get_available_audio_inputs() const override; + void set_audio_input(uint32_t audio_input_id) override; + uint32_t get_current_audio_input() const override { return 0; } + +private: + void producer_thread_func(); + + uint8_t y, cb, cr; + + 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; + std::unique_ptr owned_video_frame_allocator; + std::unique_ptr owned_audio_frame_allocator; + frame_callback_t frame_callback = nullptr; + + std::string description; + + std::atomic producer_thread_should_quit{false}; + std::thread producer_thread; +}; + +#endif // !defined(_FAKE_CAPTURE_H) diff --git a/flags.cpp b/flags.cpp index 33d47b9..580b421 100644 --- a/flags.cpp +++ b/flags.cpp @@ -16,7 +16,8 @@ void usage() fprintf(stderr, "Usage: nageru [OPTION]...\n"); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help print usage information\n"); - fprintf(stderr, " -c, --num-cards set number of input cards (default 2)\n"); + fprintf(stderr, " -c, --num-cards set number of input cards, including fake cards (default 2)\n"); + fprintf(stderr, " -C, --num-fake-cards set number of fake cards (default 0)\n"); fprintf(stderr, " -t, --theme=FILE choose theme (default theme.lua)\n"); fprintf(stderr, " -v, --va-display=SPEC VA-API device for H.264 encoding\n"); fprintf(stderr, " ($DISPLAY spec or /dev/dri/render* path)\n"); @@ -54,6 +55,7 @@ void parse_flags(int argc, char * const argv[]) static const option long_options[] = { { "help", no_argument, 0, 'h' }, { "num-cards", required_argument, 0, 'c' }, + { "num-fake-cards", required_argument, 0, 'C' }, { "theme", required_argument, 0, 't' }, { "map-signal", required_argument, 0, 'm' }, { "va-display", required_argument, 0, 1000 }, @@ -78,7 +80,7 @@ void parse_flags(int argc, char * const argv[]) }; for ( ;; ) { int option_index = 0; - int c = getopt_long(argc, argv, "c:t:v:m:", long_options, &option_index); + int c = getopt_long(argc, argv, "c:C:t:v:m:", long_options, &option_index); if (c == -1) { break; @@ -87,6 +89,9 @@ void parse_flags(int argc, char * const argv[]) case 'c': global_flags.num_cards = atoi(optarg); break; + case 'C': + global_flags.num_fake_cards = atoi(optarg); + break; case 't': global_flags.theme_filename = optarg; break; @@ -177,6 +182,18 @@ void parse_flags(int argc, char * const argv[]) fprintf(stderr, "ERROR: --http-uncompressed-video and --http-x264-video are mutually incompatible\n"); exit(1); } + if (global_flags.num_fake_cards > global_flags.num_cards) { + fprintf(stderr, "ERROR: More fake cards then total cards makes no sense\n"); + exit(1); + } + if (global_flags.num_cards <= 0) { + fprintf(stderr, "ERROR: --num-cards must be at least 1\n"); + exit(1); + } + if (global_flags.num_fake_cards < 0) { + fprintf(stderr, "ERROR: --num-fake-cards cannot be negative\n"); + exit(1); + } if (global_flags.x264_speedcontrol) { if (!global_flags.x264_preset.empty() && global_flags.x264_preset != "faster") { fprintf(stderr, "WARNING: --x264-preset is overridden by --x264-speedcontrol (implicitly uses \"faster\" as base preset)\n"); diff --git a/flags.h b/flags.h index ad9606e..a73a48e 100644 --- a/flags.h +++ b/flags.h @@ -9,6 +9,7 @@ struct Flags { int num_cards = 2; + int num_fake_cards = 0; std::string va_display; bool uncompressed_video_to_http = false; bool x264_video_to_http = false; diff --git a/mixer.cpp b/mixer.cpp index b76b674..6276e2c 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -32,6 +32,7 @@ #include "context.h" #include "decklink_capture.h" #include "defs.h" +#include "fake_capture.h" #include "flags.h" #include "video_encoder.h" #include "pbo_frame_allocator.h" @@ -163,28 +164,43 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) // Start listening for clients only once VideoEncoder has written its header, if any. httpd.start(9095); - // First try initializing the PCI devices, then USB, until we have the desired number of cards. + // First try initializing the fake devices, then 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; - } + assert(global_flags.num_fake_cards >= 0); // Enforced in flags.cpp. + unsigned num_fake_cards = global_flags.num_fake_cards; + + assert(num_fake_cards <= num_cards); // Enforced in flags.cpp. + for ( ; card_index < num_fake_cards; ++card_index) { + configure_card(card_index, format, new FakeCapture(card_index)); + } - configure_card(card_index, format, new DeckLinkCapture(decklink, card_index)); - ++num_pci_devices; + if (global_flags.num_fake_cards > 0) { + fprintf(stderr, "Initialized %d fake cards.\n", global_flags.num_fake_cards); + } + + if (card_index < num_cards) { + 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_fake_cards)); + ++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"); } - 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)); + configure_card(card_index, format, new BMUSBCapture(card_index - num_pci_devices - num_fake_cards)); ++num_usb_devices; } -- 2.39.2