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