X-Git-Url: https://git.sesse.net/?p=bmusb;a=blobdiff_plain;f=fake_capture.cpp;h=e06ac3a098b4bf3e11b3b4649beae807c9f2a3b0;hp=1410ef4b753960b3482920236e35c2a2c845109e;hb=HEAD;hpb=8e0a25a2663844905f3daf8fcf9bf9ec995d1074 diff --git a/fake_capture.cpp b/fake_capture.cpp index 1410ef4..e06ac3a 100644 --- a/fake_capture.cpp +++ b/fake_capture.cpp @@ -4,6 +4,7 @@ #include "bmusb/fake_capture.h" #include +#include #include #include #include @@ -13,25 +14,27 @@ #if __SSE2__ #include #endif +#include #include #include "bmusb/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 }; +// Pure-color inputs: Red, green, blue, white, two shades of gray. +#define NUM_COLORS 6 +constexpr uint8_t ys[NUM_COLORS] = { 63, 173, 32, 235, 180, 128 }; +constexpr uint8_t cbs[NUM_COLORS] = { 102, 42, 240, 128, 128, 128 }; +constexpr uint8_t crs[NUM_COLORS] = { 240, 26, 118, 128, 128, 128 }; using namespace std; +using namespace std::chrono; 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 +// is on by 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) @@ -84,10 +87,30 @@ void memset4(uint8_t *s, const uint8_t c[4], size_t n) } } +void memset16(uint8_t *s, const uint32_t c[4], size_t n) +{ + size_t i = 0; +#if __SSE2__ + __m128i cc = *(__m128i *)c; + __m128i *out = (__m128i *)s; + + for ( ; i < (n & ~1); i += 2) { + _mm_storeu_si128(out++, cc); + _mm_storeu_si128(out++, cc); + } + + s = (uint8_t *)out; +#endif + for ( ; i < n; ++i) { + memcpy(s, c, 16); + s += 16; + } +} + } // 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) +FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_sample_frequency, int card_index, bool has_audio) + : width(width), height(height), fps(fps), audio_sample_frequency(audio_sample_frequency), card_index(card_index) { char buf[256]; snprintf(buf, sizeof(buf), "Fake card %d", card_index + 1); @@ -96,6 +119,15 @@ FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned y = ys[card_index % NUM_COLORS]; cb = cbs[card_index % NUM_COLORS]; cr = crs[card_index % NUM_COLORS]; + + if (has_audio) { + audio_ref_level = pow(10.0f, -23.0f / 20.0f) * (1u << 31); // -23 dBFS (EBU R128 level). + + float freq = 440.0 * pow(2.0, card_index / 12.0); + sincosf(2 * M_PI * freq / audio_sample_frequency, &audio_sin, &audio_cos); + audio_real = audio_ref_level; + audio_imag = 0.0f; + } } FakeCapture::~FakeCapture() @@ -186,10 +218,31 @@ 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); } +void fill_color_noninterleaved(uint8_t *dst, uint8_t y, uint8_t cb, uint8_t cr, const VideoFormat &video_format, bool ten_bit) +{ + if (ten_bit) { + // Just use the 8-bit-values shifted left by 2. + // It's not 100% correct, but it's close enough. + uint32_t pix[4]; + pix[0] = (cb << 2) | (y << 12) | (cr << 22); + pix[1] = (y << 2) | (cb << 12) | ( y << 22); + pix[2] = (cr << 2) | (y << 12) | (cb << 22); + pix[3] = (y << 2) | (cr << 12) | ( y << 22); + memset16(dst, pix, video_format.stride * video_format.height / sizeof(pix)); + } else { + uint8_t ycbcr[] = { cb, y, cr, y }; + memset4(dst, ycbcr, video_format.width * video_format.height / 2); + } +} + } // namespace void FakeCapture::producer_thread_func() { + char thread_name[16]; + snprintf(thread_name, sizeof(thread_name), "FakeCapture_%d", card_index); + pthread_setname_np(pthread_self(), thread_name); + uint16_t timecode = 0; if (has_dequeue_callbacks) { @@ -222,6 +275,7 @@ void FakeCapture::producer_thread_func() next_frame = now; } } + steady_clock::time_point timestamp = steady_clock::now(); // Figure out when the next frame is to be, then compute the current one. add_time(1.0 / fps, &next_frame); @@ -229,6 +283,11 @@ void FakeCapture::producer_thread_func() VideoFormat video_format; video_format.width = width; video_format.height = height; + if (current_pixel_format == PixelFormat_10BitYCbCr) { + video_format.stride = (width + 5) / 6 * 4 * sizeof(uint32_t); + } else { + video_format.stride = width * 2; + } video_format.frame_rate_nom = fps; video_format.frame_rate_den = 1; video_format.has_signal = true; @@ -238,25 +297,37 @@ void FakeCapture::producer_thread_func() if (video_frame.data != nullptr) { assert(video_frame.size >= width * height * 2); if (video_frame.interleaved) { + assert(current_pixel_format == PixelFormat_8BitYCbCr); 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); + fill_color_noninterleaved(video_frame.data, y, cb, cr, video_format, current_pixel_format == PixelFormat_10BitYCbCr); } - video_frame.len = width * height * 2; + if (video_frame.data_copy != nullptr) { + fill_color_noninterleaved(video_frame.data_copy, y, cb, cr, video_format, current_pixel_format == PixelFormat_10BitYCbCr); + } + video_frame.len = video_format.stride * height; + video_frame.received_timestamp = timestamp; } AudioFormat audio_format; audio_format.bits_per_sample = 32; - audio_format.num_channels = 2; + audio_format.num_channels = 8; 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); + const unsigned num_stereo_samples = audio_sample_frequency / fps; + assert(audio_frame.size >= audio_format.num_channels * sizeof(int32_t) * num_stereo_samples); + audio_frame.len = audio_format.num_channels * sizeof(int32_t) * num_stereo_samples; + audio_frame.received_timestamp = timestamp; + + if (audio_sin == 0.0f) { + // Silence. + memset(audio_frame.data, 0, audio_frame.len); + } else { + make_tone((int32_t *)audio_frame.data, num_stereo_samples, audio_format.num_channels); + } } frame_callback(timecode++, @@ -268,4 +339,27 @@ void FakeCapture::producer_thread_func() } } +void FakeCapture::make_tone(int32_t *out, unsigned num_stereo_samples, unsigned num_channels) +{ + int32_t *ptr = out; + float r = audio_real, i = audio_imag; + for (unsigned sample_num = 0; sample_num < num_stereo_samples; ++sample_num) { + int32_t s = lrintf(r); + for (unsigned i = 0; i < num_channels; ++i) { + *ptr++ = s; + } + + // Rotate the phaser by one sample. + float new_r = r * audio_cos - i * audio_sin; + float new_i = r * audio_sin + i * audio_cos; + r = new_r; + i = new_i; + } + + // Periodically renormalize to counteract precision issues. + double corr = audio_ref_level / hypot(r, i); + audio_real = r * corr; + audio_imag = i * corr; +} + } // namespace bmusb