X-Git-Url: https://git.sesse.net/?p=bmusb;a=blobdiff_plain;f=fake_capture.cpp;fp=fake_capture.cpp;h=9cb2afe5b3c8a03e31db562da632924b2e53189b;hp=0000000000000000000000000000000000000000;hb=96c41434726ef4b603f58cc3cafbb1630bea269e;hpb=c66728fa32fc79abd5c4fb9188750cccf9039dee 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