]> git.sesse.net Git - nageru/commitdiff
Add an option to add fake capture cards, for easier standalone theme testing.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 11 Jul 2016 18:24:42 +0000 (20:24 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 11 Jul 2016 18:24:42 +0000 (20:24 +0200)
Makefile
fake_capture.cpp [new file with mode: 0644]
fake_capture.h [new file with mode: 0644]
flags.cpp
flags.h
mixer.cpp

index cfcdedd0603b232558ea2ca0f7ec9184377f4766..98558d22ec154b50ec19d26e5741e39298a56b35 100644 (file)
--- 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 (file)
index 0000000..479dc6e
--- /dev/null
@@ -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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <cstddef>
+
+#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<uint32_t, VideoMode> 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<uint32_t, std::string> FakeCapture::get_available_video_inputs() const
+{
+       return {{ 0, "Fake video input (single color)" }};
+}
+
+std::map<uint32_t, std::string> 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 (file)
index 0000000..583b421
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef _FAKE_CAPTURE_H
+#define _FAKE_CAPTURE_H 1
+
+#include <stdint.h>
+#include <functional>
+#include <string>
+
+#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<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;
+
+       std::map<uint32_t, VideoMode> 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<uint32_t, std::string> 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<uint32_t, std::string> 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<void()> dequeue_init_callback = nullptr;
+       std::function<void()> dequeue_cleanup_callback = nullptr;
+
+       FrameAllocator *video_frame_allocator = nullptr;
+       FrameAllocator *audio_frame_allocator = nullptr;
+       std::unique_ptr<FrameAllocator> owned_video_frame_allocator;
+       std::unique_ptr<FrameAllocator> owned_audio_frame_allocator;
+       frame_callback_t frame_callback = nullptr;
+
+       std::string description;
+
+       std::atomic<bool> producer_thread_should_quit{false};
+       std::thread producer_thread;
+};
+
+#endif  // !defined(_FAKE_CAPTURE_H)
index 33d47b9d481e23de79d5c56d10b1624475c2b96a..580b42123e4f08d568f7f33bd12a003eef92ceb7 100644 (file)
--- 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 ad9606e7b7393afcb4d7548216bfb30570b8f261..a73a48e94a4807a6f085b85e96cb371c0177d442 100644 (file)
--- 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;
index b76b6745ed2cc61a1f1d2e05358f682c70acc4d8..6276e2cb20c15b92aaaa4c70dfd00afdf0360322 100644 (file)
--- 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;
        }