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