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