]> git.sesse.net Git - bmusb/blob - fake_capture.cpp
Release 0.7.7 (no code changes, Makefile only).
[bmusb] / fake_capture.cpp
1 // A fake capture device that sends single-color frames at a given rate.
2 // Mostly useful for testing themes without actually hooking up capture devices.
3
4 #include "bmusb/fake_capture.h"
5
6 #include <assert.h>
7 #include <pthread.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <math.h>
13 #include <unistd.h>
14 #if __SSE2__
15 #include <immintrin.h>
16 #endif
17 #include <chrono>
18 #include <cstddef>
19
20 #include "bmusb/bmusb.h"
21
22 #define FRAME_SIZE (8 << 20)  // 8 MB.
23
24 // Pure-color inputs: Red, green, blue, white, two shades of gray.
25 #define NUM_COLORS 6
26 constexpr uint8_t ys[NUM_COLORS] = { 63, 173, 32, 235, 180, 128 };
27 constexpr uint8_t cbs[NUM_COLORS] = { 102, 42, 240, 128, 128, 128 };
28 constexpr uint8_t crs[NUM_COLORS] = { 240, 26, 118, 128, 128, 128 };
29
30 using namespace std;
31 using namespace std::chrono;
32
33 namespace bmusb {
34 namespace {
35
36 // We don't bother with multiversioning for this, because SSE2
37 // is on by default for all 64-bit compiles, which is really
38 // the target user segment here.
39
40 void memset2(uint8_t *s, const uint8_t c[2], size_t n)
41 {
42         size_t i = 0;
43 #if __SSE2__
44         const uint8_t c_expanded[16] = {
45                 c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1],
46                 c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1]
47         };
48         __m128i cc = *(__m128i *)c_expanded;
49         __m128i *out = (__m128i *)s;
50
51         for ( ; i < (n & ~15); i += 16) {
52                 _mm_storeu_si128(out++, cc);
53                 _mm_storeu_si128(out++, cc);
54         }
55
56         s = (uint8_t *)out;
57 #endif
58         for ( ; i < n; ++i) {
59                 *s++ = c[0];
60                 *s++ = c[1];
61         }
62 }
63
64 void memset4(uint8_t *s, const uint8_t c[4], size_t n)
65 {
66         size_t i = 0;
67 #if __SSE2__
68         const uint8_t c_expanded[16] = {
69                 c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3],
70                 c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3]
71         };
72         __m128i cc = *(__m128i *)c_expanded;
73         __m128i *out = (__m128i *)s;
74
75         for ( ; i < (n & ~7); i += 8) {
76                 _mm_storeu_si128(out++, cc);
77                 _mm_storeu_si128(out++, cc);
78         }
79
80         s = (uint8_t *)out;
81 #endif
82         for ( ; i < n; ++i) {
83                 *s++ = c[0];
84                 *s++ = c[1];
85                 *s++ = c[2];
86                 *s++ = c[3];
87         }
88 }
89
90 void memset16(uint8_t *s, const uint32_t c[4], size_t n)
91 {
92         size_t i = 0;
93 #if __SSE2__
94         __m128i cc = *(__m128i *)c;
95         __m128i *out = (__m128i *)s;
96
97         for ( ; i < (n & ~1); i += 2) {
98                 _mm_storeu_si128(out++, cc);
99                 _mm_storeu_si128(out++, cc);
100         }
101
102         s = (uint8_t *)out;
103 #endif
104         for ( ; i < n; ++i) {
105                 memcpy(s, c, 16);
106                 s += 16;
107         }
108 }
109
110 }  // namespace
111
112 FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_sample_frequency, int card_index, bool has_audio)
113         : width(width), height(height), fps(fps), audio_sample_frequency(audio_sample_frequency), card_index(card_index)
114 {
115         char buf[256];
116         snprintf(buf, sizeof(buf), "Fake card %d", card_index + 1);
117         description = buf;
118
119         y = ys[card_index % NUM_COLORS];
120         cb = cbs[card_index % NUM_COLORS];
121         cr = crs[card_index % NUM_COLORS];
122
123         if (has_audio) {
124                 audio_ref_level = pow(10.0f, -23.0f / 20.0f) * (1u << 31);  // -23 dBFS (EBU R128 level).
125
126                 float freq = 440.0 * pow(2.0, card_index / 12.0);
127                 sincosf(2 * M_PI * freq / audio_sample_frequency, &audio_sin, &audio_cos);
128                 audio_real = audio_ref_level;
129                 audio_imag = 0.0f;
130         }
131 }
132
133 FakeCapture::~FakeCapture()
134 {
135         if (has_dequeue_callbacks) {
136                 dequeue_cleanup_callback();
137         }
138 }
139
140 void FakeCapture::configure_card()
141 {
142         if (video_frame_allocator == nullptr) {
143                 owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));
144                 set_video_frame_allocator(owned_video_frame_allocator.get());
145         }
146         if (audio_frame_allocator == nullptr) {
147                 owned_audio_frame_allocator.reset(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES));
148                 set_audio_frame_allocator(owned_audio_frame_allocator.get());
149         }
150 }
151
152 void FakeCapture::start_bm_capture()
153 {
154         producer_thread_should_quit = false;
155         producer_thread = thread(&FakeCapture::producer_thread_func, this);
156 }
157
158 void FakeCapture::stop_dequeue_thread()
159 {
160         producer_thread_should_quit = true;
161         producer_thread.join();
162 }
163         
164 std::map<uint32_t, VideoMode> FakeCapture::get_available_video_modes() const
165 {
166         VideoMode mode;
167
168         char buf[256];
169         snprintf(buf, sizeof(buf), "%ux%u", width, height);
170         mode.name = buf;
171         
172         mode.autodetect = false;
173         mode.width = width;
174         mode.height = height;
175         mode.frame_rate_num = fps;
176         mode.frame_rate_den = 1;
177         mode.interlaced = false;
178
179         return {{ 0, mode }};
180 }
181
182 std::map<uint32_t, std::string> FakeCapture::get_available_video_inputs() const
183 {
184         return {{ 0, "Fake video input (single color)" }};
185 }
186
187 std::map<uint32_t, std::string> FakeCapture::get_available_audio_inputs() const
188 {
189         return {{ 0, "Fake audio input (silence)" }};
190 }
191
192 void FakeCapture::set_video_mode(uint32_t video_mode_id)
193 {
194         assert(video_mode_id == 0);
195 }
196
197 void FakeCapture::set_video_input(uint32_t video_input_id)
198 {
199         assert(video_input_id == 0);
200 }
201
202 void FakeCapture::set_audio_input(uint32_t audio_input_id)
203 {
204         assert(audio_input_id == 0);
205 }
206
207 namespace {
208
209 void add_time(double t, timespec *ts)
210 {
211         ts->tv_nsec += lrint(t * 1e9);
212         ts->tv_sec += ts->tv_nsec / 1000000000;
213         ts->tv_nsec %= 1000000000;
214 }
215
216 bool timespec_less_than(const timespec &a, const timespec &b)
217 {
218         return make_pair(a.tv_sec, a.tv_nsec) < make_pair(b.tv_sec, b.tv_nsec);
219 }
220
221 void fill_color_noninterleaved(uint8_t *dst, uint8_t y, uint8_t cb, uint8_t cr, const VideoFormat &video_format, bool ten_bit)
222 {
223         if (ten_bit) {
224                 // Just use the 8-bit-values shifted left by 2.
225                 // It's not 100% correct, but it's close enough.
226                 uint32_t pix[4];
227                 pix[0] = (cb << 2) | (y  << 12) | (cr << 22);
228                 pix[1] = (y  << 2) | (cb << 12) | ( y << 22);
229                 pix[2] = (cr << 2) | (y  << 12) | (cb << 22);
230                 pix[3] = (y  << 2) | (cr << 12) | ( y << 22);
231                 memset16(dst, pix, video_format.stride * video_format.height / sizeof(pix));
232         } else {
233                 uint8_t ycbcr[] = { cb, y, cr, y };
234                 memset4(dst, ycbcr, video_format.width * video_format.height / 2);
235         }
236 }
237
238 }  // namespace
239
240 void FakeCapture::producer_thread_func()
241 {
242         char thread_name[16];
243         snprintf(thread_name, sizeof(thread_name), "FakeCapture_%d", card_index);
244         pthread_setname_np(pthread_self(), thread_name);
245
246         uint16_t timecode = 0;
247
248         if (has_dequeue_callbacks) {
249                 dequeue_init_callback();
250         }
251
252         timespec next_frame;
253         clock_gettime(CLOCK_MONOTONIC, &next_frame);
254         add_time(1.0 / fps, &next_frame);
255
256         while (!producer_thread_should_quit) {
257                 timespec now;
258                 clock_gettime(CLOCK_MONOTONIC, &now);
259
260                 if (timespec_less_than(now, next_frame)) {
261                         // Wait until the next frame.
262                         if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
263                                             &next_frame, nullptr) == -1) {
264                                 if (errno == EINTR) continue;  // Re-check the flag and then sleep again.
265                                 perror("clock_nanosleep");
266                                 exit(1);
267                         }
268                 } else {
269                         // We've seemingly missed a frame. If we're more than one second behind,
270                         // reset the timer; otherwise, just keep going.
271                         timespec limit = next_frame;
272                         ++limit.tv_sec;
273                         if (!timespec_less_than(now, limit)) {
274                                 fprintf(stderr, "More than one second of missed fake frames; resetting clock.\n");
275                                 next_frame = now;
276                         }
277                 }
278                 steady_clock::time_point timestamp = steady_clock::now();
279
280                 // Figure out when the next frame is to be, then compute the current one.
281                 add_time(1.0 / fps, &next_frame);
282
283                 VideoFormat video_format;
284                 video_format.width = width;
285                 video_format.height = height;
286                 if (current_pixel_format == PixelFormat_10BitYCbCr) {
287                         video_format.stride = (width + 5) / 6 * 4 * sizeof(uint32_t);
288                 } else {
289                         video_format.stride = width * 2;
290                 }
291                 video_format.frame_rate_nom = fps;
292                 video_format.frame_rate_den = 1;
293                 video_format.has_signal = true;
294                 video_format.is_connected = false;
295
296                 FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame();
297                 if (video_frame.data != nullptr) {
298                         assert(video_frame.size >= width * height * 2);
299                         if (video_frame.interleaved) {
300                                 assert(current_pixel_format == PixelFormat_8BitYCbCr);
301                                 uint8_t cbcr[] = { cb, cr };
302                                 memset2(video_frame.data, cbcr, width * height / 2);
303                                 memset(video_frame.data2, y, width * height);
304                         } else {
305                                 fill_color_noninterleaved(video_frame.data, y, cb, cr, video_format, current_pixel_format == PixelFormat_10BitYCbCr);
306                         }
307                         if (video_frame.data_copy != nullptr) {
308                                 fill_color_noninterleaved(video_frame.data_copy, y, cb, cr, video_format, current_pixel_format == PixelFormat_10BitYCbCr);
309                         }
310                         video_frame.len = video_format.stride * height;
311                         video_frame.received_timestamp = timestamp;
312                 }
313
314                 AudioFormat audio_format;
315                 audio_format.bits_per_sample = 32;
316                 audio_format.num_channels = 8;
317
318                 FrameAllocator::Frame audio_frame = audio_frame_allocator->alloc_frame();
319                 if (audio_frame.data != nullptr) {
320                         const unsigned num_stereo_samples = audio_sample_frequency / fps;
321                         assert(audio_frame.size >= audio_format.num_channels * sizeof(int32_t) * num_stereo_samples);
322                         audio_frame.len = audio_format.num_channels * sizeof(int32_t) * num_stereo_samples;
323                         audio_frame.received_timestamp = timestamp;
324
325                         if (audio_sin == 0.0f) {
326                                 // Silence.
327                                 memset(audio_frame.data, 0, audio_frame.len);
328                         } else {
329                                 make_tone((int32_t *)audio_frame.data, num_stereo_samples, audio_format.num_channels);
330                         }
331                 }
332
333                 frame_callback(timecode++,
334                                video_frame, 0, video_format,
335                                audio_frame, 0, audio_format);
336         }
337         if (has_dequeue_callbacks) {
338                 dequeue_cleanup_callback();
339         }
340 }
341
342 void FakeCapture::make_tone(int32_t *out, unsigned num_stereo_samples, unsigned num_channels)
343 {
344         int32_t *ptr = out;
345         float r = audio_real, i = audio_imag;
346         for (unsigned sample_num = 0; sample_num < num_stereo_samples; ++sample_num) {
347                 int32_t s = lrintf(r);
348                 for (unsigned i = 0; i < num_channels; ++i) {
349                         *ptr++ = s;
350                 }
351
352                 // Rotate the phaser by one sample.
353                 float new_r = r * audio_cos - i * audio_sin;
354                 float new_i = r * audio_sin + i * audio_cos;
355                 r = new_r;
356                 i = new_i;
357         }
358
359         // Periodically renormalize to counteract precision issues.
360         double corr = audio_ref_level / hypot(r, i);
361         audio_real = r * corr;
362         audio_imag = i * corr;
363 }
364
365 }  // namespace bmusb