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