From: Steinar H. Gunderson Date: Mon, 25 Jul 2016 12:17:11 +0000 (+0200) Subject: Move FakeCapture from Nageru, and make it a little more generic. X-Git-Tag: 0.4~3 X-Git-Url: https://git.sesse.net/?p=bmusb;a=commitdiff_plain;h=96c41434726ef4b603f58cc3cafbb1630bea269e Move FakeCapture from Nageru, and make it a little more generic. --- diff --git a/fake_capture.cpp b/fake_capture.cpp new file mode 100644 index 0000000..9cb2afe --- /dev/null +++ b/fake_capture.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#if __SSE2__ +#include +#endif +#include + +#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 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 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); +} + +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 index 0000000..93f1470 --- /dev/null +++ b/fake_capture.h @@ -0,0 +1,104 @@ +#ifndef _FAKE_CAPTURE_H +#define _FAKE_CAPTURE_H 1 + +#include +#include +#include + +#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 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; + bool get_disconnected() const override { return false; } + + 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(); + + unsigned width, height, fps, audio_frequency; + 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; +}; + +} // namespace bmusb + +#endif // !defined(_FAKE_CAPTURE_H)