]> git.sesse.net Git - nageru/blob - fake_capture.cpp
Fix crash on exit when there are no USB cards.
[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 <unistd.h>
12 #include <cstddef>
13
14 #include "bmusb/bmusb.h"
15 #include "defs.h"
16
17 #define FRAME_SIZE (8 << 20)  // 8 MB.
18 #define FAKE_FPS 25  // Must be an integer.
19
20 // Pure-color inputs: Red, green, blue, white.
21 #define NUM_COLORS 4
22 constexpr uint8_t ys[NUM_COLORS] = { 81, 145, 41, 235 };
23 constexpr uint8_t cbs[NUM_COLORS] = { 90, 54, 240, 128 };
24 constexpr uint8_t crs[NUM_COLORS] = { 240, 34, 110, 128 };
25
26 using namespace std;
27
28 namespace {
29
30 // TODO: SSE2-optimize (or at least write full int64s) if speed becomes a problem.
31
32 void memset2(uint8_t *s, const uint8_t c[2], size_t n)
33 {
34         for (size_t i = 0; i < n; ++i) {
35                 *s++ = c[0];
36                 *s++ = c[1];
37         }
38 }
39
40 void memset4(uint8_t *s, const uint8_t c[4], size_t n)
41 {
42         for (size_t i = 0; i < n; ++i) {
43                 *s++ = c[0];
44                 *s++ = c[1];
45                 *s++ = c[2];
46                 *s++ = c[3];
47         }
48 }
49
50 }  // namespace
51
52 FakeCapture::FakeCapture(int card_index)
53 {
54         char buf[256];
55         snprintf(buf, sizeof(buf), "Fake card %d", card_index + 1);
56         description = buf;
57
58         y = ys[card_index % NUM_COLORS];
59         cb = cbs[card_index % NUM_COLORS];
60         cr = crs[card_index % NUM_COLORS];
61 }
62
63 FakeCapture::~FakeCapture()
64 {
65         if (has_dequeue_callbacks) {
66                 dequeue_cleanup_callback();
67         }
68 }
69
70 void FakeCapture::configure_card()
71 {
72         if (video_frame_allocator == nullptr) {
73                 owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));
74                 set_video_frame_allocator(owned_video_frame_allocator.get());
75         }
76         if (audio_frame_allocator == nullptr) {
77                 owned_audio_frame_allocator.reset(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES));
78                 set_audio_frame_allocator(owned_audio_frame_allocator.get());
79         }
80 }
81
82 void FakeCapture::start_bm_capture()
83 {
84         producer_thread_should_quit = false;
85         producer_thread = thread(&FakeCapture::producer_thread_func, this);
86 }
87
88 void FakeCapture::stop_dequeue_thread()
89 {
90         producer_thread_should_quit = true;
91         producer_thread.join();
92 }
93         
94 std::map<uint32_t, VideoMode> FakeCapture::get_available_video_modes() const
95 {
96         VideoMode mode;
97
98         char buf[256];
99         snprintf(buf, sizeof(buf), "%dx%d", WIDTH, HEIGHT);
100         mode.name = buf;
101         
102         mode.autodetect = false;
103         mode.width = WIDTH;
104         mode.height = HEIGHT;
105         mode.frame_rate_num = FAKE_FPS;
106         mode.frame_rate_den = 1;
107         mode.interlaced = false;
108
109         return {{ 0, mode }};
110 }
111
112 std::map<uint32_t, std::string> FakeCapture::get_available_video_inputs() const
113 {
114         return {{ 0, "Fake video input (single color)" }};
115 }
116
117 std::map<uint32_t, std::string> FakeCapture::get_available_audio_inputs() const
118 {
119         return {{ 0, "Fake audio input (silence)" }};
120 }
121
122 void FakeCapture::set_video_mode(uint32_t video_mode_id)
123 {
124         assert(video_mode_id == 0);
125 }
126
127 void FakeCapture::set_video_input(uint32_t video_input_id)
128 {
129         assert(video_input_id == 0);
130 }
131
132 void FakeCapture::set_audio_input(uint32_t audio_input_id)
133 {
134         assert(audio_input_id == 0);
135 }
136
137 void FakeCapture::producer_thread_func()
138 {
139         uint16_t timecode = 0;
140
141         if (has_dequeue_callbacks) {
142                 dequeue_init_callback();
143         }
144         while (!producer_thread_should_quit) {
145                 usleep(1000000 / FAKE_FPS);  // Rather approximate frame rate.
146
147                 if (producer_thread_should_quit) break;
148
149                 VideoFormat video_format;
150                 video_format.width = WIDTH;
151                 video_format.height = HEIGHT;
152                 video_format.frame_rate_nom = FAKE_FPS;
153                 video_format.frame_rate_den = 1;
154                 video_format.has_signal = true;
155
156                 FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame();
157                 if (video_frame.data != nullptr) {
158                         assert(video_frame.size >= WIDTH * HEIGHT * 2);
159                         if (video_frame.interleaved) {
160                                 uint8_t cbcr[] = { cb, cr };
161                                 memset2(video_frame.data, cbcr, WIDTH * HEIGHT / 2);
162                                 memset(video_frame.data2, y, WIDTH * HEIGHT);
163                         } else {
164                                 uint8_t ycbcr[] = { y, cb, y, cr };
165                                 memset4(video_frame.data, ycbcr, WIDTH * HEIGHT / 2);
166                         }
167                         video_frame.len = WIDTH * HEIGHT * 2;
168                 }
169
170                 AudioFormat audio_format;
171                 audio_format.bits_per_sample = 32;
172                 audio_format.num_channels = 2;
173
174                 FrameAllocator::Frame audio_frame = audio_frame_allocator->alloc_frame();
175                 if (audio_frame.data != nullptr) {
176                         assert(audio_frame.size >= 2 * sizeof(uint32_t) * OUTPUT_FREQUENCY / FAKE_FPS);
177                         audio_frame.len = 2 * sizeof(uint32_t) * OUTPUT_FREQUENCY / FAKE_FPS;
178                         memset(audio_frame.data, 0, audio_frame.len);
179                 }
180
181                 frame_callback(timecode++,
182                                video_frame, 0, video_format,
183                                audio_frame, 0, audio_format);
184         }
185         if (has_dequeue_callbacks) {
186                 dequeue_cleanup_callback();
187         }
188 }