]> git.sesse.net Git - bmusb/commitdiff
Move FakeCapture from Nageru, and make it a little more generic.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 25 Jul 2016 12:17:11 +0000 (14:17 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 25 Jul 2016 12:17:11 +0000 (14:17 +0200)
fake_capture.cpp [new file with mode: 0644]
fake_capture.h [new file with mode: 0644]

diff --git a/fake_capture.cpp b/fake_capture.cpp
new file mode 100644 (file)
index 0000000..9cb2afe
--- /dev/null
@@ -0,0 +1,271 @@
+// 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 <math.h>
+#include <unistd.h>
+#if __SSE2__
+#include <immintrin.h>
+#endif
+#include <cstddef>
+
+#include "bmusb.h"
+
+#define FRAME_SIZE (8 << 20)  // 8 MB.
+
+// 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 bmusb {
+namespace {
+
+// We don't bother with multiversioning for this, because SSE2
+// is on my default for all 64-bit compiles, which is really
+// the target user segment here.
+
+void memset2(uint8_t *s, const uint8_t c[2], size_t n)
+{
+       size_t i = 0;
+#if __SSE2__
+       const uint8_t c_expanded[16] = {
+               c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1],
+               c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1]
+       };
+       __m128i cc = *(__m128i *)c_expanded;
+       __m128i *out = (__m128i *)s;
+
+       for ( ; i < (n & ~15); i += 16) {
+               _mm_storeu_si128(out++, cc);
+               _mm_storeu_si128(out++, cc);
+       }
+
+       s = (uint8_t *)out;
+#endif
+       for ( ; i < n; ++i) {
+               *s++ = c[0];
+               *s++ = c[1];
+       }
+}
+
+void memset4(uint8_t *s, const uint8_t c[4], size_t n)
+{
+       size_t i = 0;
+#if __SSE2__
+       const uint8_t c_expanded[16] = {
+               c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3],
+               c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3]
+       };
+       __m128i cc = *(__m128i *)c_expanded;
+       __m128i *out = (__m128i *)s;
+
+       for ( ; i < (n & ~7); i += 8) {
+               _mm_storeu_si128(out++, cc);
+               _mm_storeu_si128(out++, cc);
+       }
+
+       s = (uint8_t *)out;
+#endif
+       for ( ; i < n; ++i) {
+               *s++ = c[0];
+               *s++ = c[1];
+               *s++ = c[2];
+               *s++ = c[3];
+       }
+}
+
+}  // namespace
+
+FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_frequency, int card_index)
+       : width(width), height(height), fps(fps), audio_frequency(audio_frequency)
+{
+       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), "%ux%u", width, height);
+       mode.name = buf;
+       
+       mode.autodetect = false;
+       mode.width = width;
+       mode.height = height;
+       mode.frame_rate_num = 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);
+}
+
+namespace {
+
+void add_time(double t, timespec *ts)
+{
+       ts->tv_nsec += lrint(t * 1e9);
+       ts->tv_sec += ts->tv_nsec / 1000000000;
+       ts->tv_nsec %= 1000000000;
+}
+
+bool timespec_less_than(const timespec &a, const timespec &b)
+{
+       return make_pair(a.tv_sec, a.tv_nsec) < make_pair(b.tv_sec, b.tv_nsec);
+}
+
+}  // namespace
+
+void FakeCapture::producer_thread_func()
+{
+       uint16_t timecode = 0;
+
+       if (has_dequeue_callbacks) {
+               dequeue_init_callback();
+       }
+
+       timespec next_frame;
+       clock_gettime(CLOCK_MONOTONIC, &next_frame);
+       add_time(1.0 / fps, &next_frame);
+
+       while (!producer_thread_should_quit) {
+               timespec now;
+               clock_gettime(CLOCK_MONOTONIC, &now);
+
+               if (timespec_less_than(now, next_frame)) {
+                       // Wait until the next frame.
+                       if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
+                                            &next_frame, nullptr) == -1) {
+                               if (errno == EINTR) continue;  // Re-check the flag and then sleep again.
+                               perror("clock_nanosleep");
+                               exit(1);
+                       }
+               } else {
+                       // We've seemingly missed a frame. If we're more than one second behind,
+                       // reset the timer; otherwise, just keep going.
+                       timespec limit = next_frame;
+                       ++limit.tv_sec;
+                       if (!timespec_less_than(now, limit)) {
+                               fprintf(stderr, "More than one second of missed fake frames; resetting clock.\n");
+                               next_frame = now;
+                       }
+               }
+
+               // Figure out when the next frame is to be, then compute the current one.
+               add_time(1.0 / fps, &next_frame);
+
+               VideoFormat video_format;
+               video_format.width = width;
+               video_format.height = height;
+               video_format.frame_rate_nom = fps;
+               video_format.frame_rate_den = 1;
+               video_format.has_signal = true;
+               video_format.is_connected = false;
+
+               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) * audio_frequency / fps);
+                       audio_frame.len = 2 * sizeof(uint32_t) * audio_frequency / 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();
+       }
+}
+
+}  // namespace bmusb
diff --git a/fake_capture.h b/fake_capture.h
new file mode 100644 (file)
index 0000000..93f1470
--- /dev/null
@@ -0,0 +1,104 @@
+#ifndef _FAKE_CAPTURE_H
+#define _FAKE_CAPTURE_H 1
+
+#include <stdint.h>
+#include <functional>
+#include <string>
+
+#include "bmusb.h"
+
+namespace bmusb {
+
+class FakeCapture : public CaptureInterface
+{
+public:
+       FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_frequency, 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;
+       bool get_disconnected() const override { return false; }
+
+       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();
+
+       unsigned width, height, fps, audio_frequency;
+       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;
+};
+
+}  // namespace bmusb
+
+#endif  // !defined(_FAKE_CAPTURE_H)