]> git.sesse.net Git - nageru/blob - fake_capture.cpp
Upgrade to newer bmusb, with namespacing.
[nageru] / 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 "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 <cstddef>
17
18 #include "bmusb/bmusb.h"
19 #include "defs.h"
20
21 #define FRAME_SIZE (8 << 20)  // 8 MB.
22 #define FAKE_FPS 25  // Must be an integer.
23
24 // Pure-color inputs: Red, green, blue, white.
25 #define NUM_COLORS 4
26 constexpr uint8_t ys[NUM_COLORS] = { 81, 145, 41, 235 };
27 constexpr uint8_t cbs[NUM_COLORS] = { 90, 54, 240, 128 };
28 constexpr uint8_t crs[NUM_COLORS] = { 240, 34, 110, 128 };
29
30 using namespace std;
31
32 namespace bmusb {
33 namespace {
34
35 void memset2(uint8_t *s, const uint8_t c[2], size_t n)
36 {
37         size_t i = 0;
38 #if __SSE2__
39         const uint8_t c_expanded[16] = {
40                 c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1],
41                 c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1]
42         };
43         __m128i cc = *(__m128i *)c_expanded;
44         __m128i *out = (__m128i *)s;
45
46         for ( ; i < (n & ~15); i += 16) {
47                 _mm_storeu_si128(out++, cc);
48                 _mm_storeu_si128(out++, cc);
49         }
50
51         s = (uint8_t *)out;
52 #endif
53         for ( ; i < n; ++i) {
54                 *s++ = c[0];
55                 *s++ = c[1];
56         }
57 }
58
59 void memset4(uint8_t *s, const uint8_t c[4], size_t n)
60 {
61         size_t i = 0;
62 #if __SSE2__
63         const uint8_t c_expanded[16] = {
64                 c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3],
65                 c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3]
66         };
67         __m128i cc = *(__m128i *)c_expanded;
68         __m128i *out = (__m128i *)s;
69
70         for ( ; i < (n & ~7); i += 8) {
71                 _mm_storeu_si128(out++, cc);
72                 _mm_storeu_si128(out++, cc);
73         }
74
75         s = (uint8_t *)out;
76 #endif
77         for ( ; i < n; ++i) {
78                 *s++ = c[0];
79                 *s++ = c[1];
80                 *s++ = c[2];
81                 *s++ = c[3];
82         }
83 }
84
85 }  // namespace
86
87 FakeCapture::FakeCapture(int card_index)
88 {
89         char buf[256];
90         snprintf(buf, sizeof(buf), "Fake card %d", card_index + 1);
91         description = buf;
92
93         y = ys[card_index % NUM_COLORS];
94         cb = cbs[card_index % NUM_COLORS];
95         cr = crs[card_index % NUM_COLORS];
96 }
97
98 FakeCapture::~FakeCapture()
99 {
100         if (has_dequeue_callbacks) {
101                 dequeue_cleanup_callback();
102         }
103 }
104
105 void FakeCapture::configure_card()
106 {
107         if (video_frame_allocator == nullptr) {
108                 owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));
109                 set_video_frame_allocator(owned_video_frame_allocator.get());
110         }
111         if (audio_frame_allocator == nullptr) {
112                 owned_audio_frame_allocator.reset(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES));
113                 set_audio_frame_allocator(owned_audio_frame_allocator.get());
114         }
115 }
116
117 void FakeCapture::start_bm_capture()
118 {
119         producer_thread_should_quit = false;
120         producer_thread = thread(&FakeCapture::producer_thread_func, this);
121 }
122
123 void FakeCapture::stop_dequeue_thread()
124 {
125         producer_thread_should_quit = true;
126         producer_thread.join();
127 }
128         
129 std::map<uint32_t, VideoMode> FakeCapture::get_available_video_modes() const
130 {
131         VideoMode mode;
132
133         char buf[256];
134         snprintf(buf, sizeof(buf), "%dx%d", WIDTH, HEIGHT);
135         mode.name = buf;
136         
137         mode.autodetect = false;
138         mode.width = WIDTH;
139         mode.height = HEIGHT;
140         mode.frame_rate_num = FAKE_FPS;
141         mode.frame_rate_den = 1;
142         mode.interlaced = false;
143
144         return {{ 0, mode }};
145 }
146
147 std::map<uint32_t, std::string> FakeCapture::get_available_video_inputs() const
148 {
149         return {{ 0, "Fake video input (single color)" }};
150 }
151
152 std::map<uint32_t, std::string> FakeCapture::get_available_audio_inputs() const
153 {
154         return {{ 0, "Fake audio input (silence)" }};
155 }
156
157 void FakeCapture::set_video_mode(uint32_t video_mode_id)
158 {
159         assert(video_mode_id == 0);
160 }
161
162 void FakeCapture::set_video_input(uint32_t video_input_id)
163 {
164         assert(video_input_id == 0);
165 }
166
167 void FakeCapture::set_audio_input(uint32_t audio_input_id)
168 {
169         assert(audio_input_id == 0);
170 }
171
172 namespace {
173
174 void add_time(double t, timespec *ts)
175 {
176         ts->tv_nsec += lrint(t * 1e9);
177         ts->tv_sec += ts->tv_nsec / 1000000000;
178         ts->tv_nsec %= 1000000000;
179 }
180
181 bool timespec_less_than(const timespec &a, const timespec &b)
182 {
183         return make_pair(a.tv_sec, a.tv_nsec) < make_pair(b.tv_sec, b.tv_nsec);
184 }
185
186 }  // namespace
187
188 void FakeCapture::producer_thread_func()
189 {
190         uint16_t timecode = 0;
191
192         if (has_dequeue_callbacks) {
193                 dequeue_init_callback();
194         }
195
196         timespec next_frame;
197         clock_gettime(CLOCK_MONOTONIC, &next_frame);
198         add_time(1.0 / FAKE_FPS, &next_frame);
199
200         while (!producer_thread_should_quit) {
201                 timespec now;
202                 clock_gettime(CLOCK_MONOTONIC, &now);
203
204                 if (timespec_less_than(now, next_frame)) {
205                         // Wait until the next frame.
206                         if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
207                                             &next_frame, nullptr) == -1) {
208                                 if (errno == EINTR) continue;  // Re-check the flag and then sleep again.
209                                 perror("clock_nanosleep");
210                                 exit(1);
211                         }
212                 } else {
213                         // We've seemingly missed a frame. If we're more than one second behind,
214                         // reset the timer; otherwise, just keep going.
215                         timespec limit = next_frame;
216                         ++limit.tv_sec;
217                         if (!timespec_less_than(now, limit)) {
218                                 fprintf(stderr, "More than one second of missed fake frames; resetting clock.\n");
219                                 next_frame = now;
220                         }
221                 }
222
223                 // Figure out when the next frame is to be, then compute the current one.
224                 add_time(1.0 / FAKE_FPS, &next_frame);
225
226                 VideoFormat video_format;
227                 video_format.width = WIDTH;
228                 video_format.height = HEIGHT;
229                 video_format.frame_rate_nom = FAKE_FPS;
230                 video_format.frame_rate_den = 1;
231                 video_format.has_signal = true;
232                 video_format.is_connected = false;
233
234                 FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame();
235                 if (video_frame.data != nullptr) {
236                         assert(video_frame.size >= WIDTH * HEIGHT * 2);
237                         if (video_frame.interleaved) {
238                                 uint8_t cbcr[] = { cb, cr };
239                                 memset2(video_frame.data, cbcr, WIDTH * HEIGHT / 2);
240                                 memset(video_frame.data2, y, WIDTH * HEIGHT);
241                         } else {
242                                 uint8_t ycbcr[] = { y, cb, y, cr };
243                                 memset4(video_frame.data, ycbcr, WIDTH * HEIGHT / 2);
244                         }
245                         video_frame.len = WIDTH * HEIGHT * 2;
246                 }
247
248                 AudioFormat audio_format;
249                 audio_format.bits_per_sample = 32;
250                 audio_format.num_channels = 2;
251
252                 FrameAllocator::Frame audio_frame = audio_frame_allocator->alloc_frame();
253                 if (audio_frame.data != nullptr) {
254                         assert(audio_frame.size >= 2 * sizeof(uint32_t) * OUTPUT_FREQUENCY / FAKE_FPS);
255                         audio_frame.len = 2 * sizeof(uint32_t) * OUTPUT_FREQUENCY / FAKE_FPS;
256                         memset(audio_frame.data, 0, audio_frame.len);
257                 }
258
259                 frame_callback(timecode++,
260                                video_frame, 0, video_format,
261                                audio_frame, 0, audio_format);
262         }
263         if (has_dequeue_callbacks) {
264                 dequeue_cleanup_callback();
265         }
266 }
267
268 }  // namespace bmusb