1 #include <cstdio>
2 #include <poll.h>
3 #include <unistd.h>
4 #include <algorithm>
6 #include <kms++/kms++.h>
7 #include <kms++util/kms++util.h>
8 #include <kms++util/videodevice.h>
10 #define CAMERA_BUF_QUEUE_SIZE 5
12 using namespace std;
13 using namespace kms;
15 static vector<DumbFramebuffer*> s_fbs;
16 static vector<DumbFramebuffer*> s_free_fbs;
17 static vector<DumbFramebuffer*> s_wb_fbs;
18 static vector<DumbFramebuffer*> s_ready_fbs;
20 class WBStreamer
21 {
22 public:
23 WBStreamer(VideoStreamer* streamer, Crtc* crtc, uint32_t width, uint32_t height, PixelFormat pixfmt)
24 : m_capdev(*streamer)
25 {
26 m_capdev.set_port(crtc->idx());
27 m_capdev.set_format(pixfmt, width, height);
28 m_capdev.set_queue_size(s_fbs.size());
30 for (auto fb : s_free_fbs) {
31 m_capdev.queue(fb);
32 s_wb_fbs.push_back(fb);
33 }
35 s_free_fbs.clear();
36 }
38 ~WBStreamer()
39 {
40 }
42 WBStreamer(const WBStreamer& other) = delete;
43 WBStreamer& operator=(const WBStreamer& other) = delete;
45 int fd() const { return m_capdev.fd(); }
47 void start_streaming()
48 {
49 m_capdev.stream_on();
50 }
52 void stop_streaming()
53 {
54 m_capdev.stream_off();
55 }
57 void Dequeue()
58 {
59 auto fb = m_capdev.dequeue();
61 auto iter = find(s_wb_fbs.begin(), s_wb_fbs.end(), fb);
62 s_wb_fbs.erase(iter);
64 s_ready_fbs.insert(s_ready_fbs.begin(), fb);
65 }
67 void Queue()
68 {
69 if (s_free_fbs.size() == 0)
70 return;
72 auto fb = s_free_fbs.back();
73 s_free_fbs.pop_back();
75 m_capdev.queue(fb);
77 s_wb_fbs.insert(s_wb_fbs.begin(), fb);
78 }
80 private:
81 VideoStreamer& m_capdev;
82 };
84 class WBFlipState : private PageFlipHandlerBase
85 {
86 public:
87 WBFlipState(Card& card, Crtc* crtc, Plane* plane)
88 : m_card(card), m_crtc(crtc), m_plane(plane)
89 {
90 }
92 void setup(uint32_t x, uint32_t y, uint32_t width, uint32_t height)
93 {
94 auto fb = s_ready_fbs.back();
95 s_ready_fbs.pop_back();
97 AtomicReq req(m_card);
99 req.add(m_plane, "CRTC_ID", m_crtc->id());
100 req.add(m_plane, "FB_ID", fb->id());
102 req.add(m_plane, "CRTC_X", x);
103 req.add(m_plane, "CRTC_Y", y);
104 req.add(m_plane, "CRTC_W", width);
105 req.add(m_plane, "CRTC_H", height);
107 req.add(m_plane, "SRC_X", 0);
108 req.add(m_plane, "SRC_Y", 0);
109 req.add(m_plane, "SRC_W", fb->width() << 16);
110 req.add(m_plane, "SRC_H", fb->height() << 16);
112 int r = req.commit_sync();
113 FAIL_IF(r, "initial plane setup failed");
115 m_current_fb = fb;
116 }
118 void queue_next()
119 {
120 if (m_queued_fb)
121 return;
123 if (s_ready_fbs.size() == 0)
124 return;
126 auto fb = s_ready_fbs.back();
127 s_ready_fbs.pop_back();
129 AtomicReq req(m_card);
130 req.add(m_plane, "FB_ID", fb->id());
132 int r = req.commit(this);
133 if (r)
134 EXIT("Flip commit failed: %d\n", r);
136 m_queued_fb = fb;
137 }
139 private:
140 void handle_page_flip(uint32_t frame, double time)
141 {
142 if (m_queued_fb) {
143 if (m_current_fb)
144 s_free_fbs.insert(s_free_fbs.begin(), m_current_fb);
146 m_current_fb = m_queued_fb;
147 m_queued_fb = nullptr;
148 }
150 queue_next();
151 }
153 Card& m_card;
154 Crtc* m_crtc;
155 Plane* m_plane;
157 DumbFramebuffer* m_current_fb = nullptr;
158 DumbFramebuffer* m_queued_fb = nullptr;
159 };
161 class BarFlipState : private PageFlipHandlerBase
162 {
163 public:
164 BarFlipState(Card& card, Crtc* crtc)
165 : m_card(card), m_crtc(crtc)
166 {
167 m_plane = m_crtc->get_primary_plane();
169 uint32_t w = m_crtc->mode().hdisplay;
170 uint32_t h = m_crtc->mode().vdisplay;
172 for (unsigned i = 0; i < s_num_buffers; ++i)
173 m_fbs[i] = new DumbFramebuffer(card, w, h, PixelFormat::XRGB8888);
174 }
176 ~BarFlipState()
177 {
178 for (unsigned i = 0; i < s_num_buffers; ++i)
179 delete m_fbs[i];
180 }
182 void start_flipping()
183 {
184 m_frame_num = 0;
185 queue_next();
186 }
188 private:
189 void handle_page_flip(uint32_t frame, double time)
190 {
191 m_frame_num++;
192 queue_next();
193 }
195 static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
196 {
197 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
198 }
200 void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
201 {
202 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
203 int new_xpos = get_bar_pos(fb, frame_num);
205 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
206 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
207 }
209 void queue_next()
210 {
211 AtomicReq req(m_card);
213 unsigned cur = m_frame_num % s_num_buffers;
215 auto fb = m_fbs[cur];
217 draw_bar(fb, m_frame_num);
219 req.add(m_plane, {
220 { "FB_ID", fb->id() },
221 });
223 int r = req.commit(this);
224 if (r)
225 EXIT("Flip commit failed: %d\n", r);
226 }
228 static const unsigned s_num_buffers = 3;
230 DumbFramebuffer* m_fbs[s_num_buffers];
232 Card& m_card;
233 Crtc* m_crtc;
234 Plane* m_plane;
236 unsigned m_frame_num;
238 static const unsigned bar_width = 20;
239 static const unsigned bar_speed = 8;
240 };
242 static const char* usage_str =
243 "Usage: wbcap [OPTIONS]\n\n"
244 "Options:\n"
245 " -s, --src=CONN Source connector\n"
246 " -d, --dst=CONN Destination connector\n"
247 " -f, --format=4CC Format"
248 " -h, --help Print this help\n"
249 ;
251 int main(int argc, char** argv)
252 {
253 string src_conn_name = "unknown";
254 string dst_conn_name = "hdmi";
255 PixelFormat pixfmt = PixelFormat::XRGB8888;
257 OptionSet optionset = {
258 Option("s|src=", [&](string s)
259 {
260 src_conn_name = s;
261 }),
262 Option("d|dst=", [&](string s)
263 {
264 dst_conn_name = s;
265 }),
266 Option("f|format=", [&](string s)
267 {
268 pixfmt = FourCCToPixelFormat(s);
269 }),
270 Option("h|help", [&]()
271 {
272 puts(usage_str);
273 exit(-1);
274 }),
275 };
277 optionset.parse(argc, argv);
279 if (optionset.params().size() > 0) {
280 puts(usage_str);
281 exit(-1);
282 }
284 VideoDevice vid("/dev/video11");
286 Card card;
287 ResourceManager resman(card);
289 auto src_conn = resman.reserve_connector(src_conn_name);
290 auto src_crtc = resman.reserve_crtc(src_conn);
292 uint32_t src_width = src_crtc->mode().hdisplay;
293 uint32_t src_height = src_crtc->mode().vdisplay;
295 printf("src %s, crtc %ux%u\n", src_conn->fullname().c_str(), src_width, src_height);
297 auto dst_conn = resman.reserve_connector(dst_conn_name);
298 auto dst_crtc = resman.reserve_crtc(dst_conn);
299 auto dst_plane = resman.reserve_overlay_plane(dst_crtc, pixfmt);
300 FAIL_IF(!dst_plane, "Plane not found");
302 uint32_t dst_width = min((uint32_t)dst_crtc->mode().hdisplay, src_width);
303 uint32_t dst_height = min((uint32_t)dst_crtc->mode().vdisplay, src_height);
305 printf("dst %s, crtc %ux%u, plane %ux%u\n", dst_conn->fullname().c_str(),
306 dst_crtc->mode().hdisplay, dst_crtc->mode().vdisplay,
307 dst_width, dst_height);
309 for (int i = 0; i < CAMERA_BUF_QUEUE_SIZE; ++i) {
310 auto fb = new DumbFramebuffer(card, src_width, src_height, pixfmt);
311 s_fbs.push_back(fb);
312 s_free_fbs.push_back(fb);
313 }
315 // get one fb for initial setup
316 s_ready_fbs.push_back(s_free_fbs.back());
317 s_free_fbs.pop_back();
319 // This draws a moving bar to SRC display
320 BarFlipState barflipper(card, src_crtc);
321 barflipper.start_flipping();
323 // This shows the captures SRC frames on DST display
324 WBFlipState wbflipper(card, dst_crtc, dst_plane);
325 wbflipper.setup(0, 0, dst_width, dst_height);
327 WBStreamer wb(vid.get_capture_streamer(), src_crtc, src_width, src_height, pixfmt);
328 wb.start_streaming();
330 vector<pollfd> fds(3);
332 fds[0].fd = 0;
333 fds[0].events = POLLIN;
334 fds[1].fd = wb.fd();
335 fds[1].events = POLLIN;
336 fds[2].fd = card.fd();
337 fds[2].events = POLLIN;
339 while (true) {
340 int r = poll(fds.data(), fds.size(), -1);
341 ASSERT(r > 0);
343 if (fds[0].revents != 0)
344 break;
346 if (fds[1].revents) {
347 fds[1].revents = 0;
349 wb.Dequeue();
350 wbflipper.queue_next();
351 }
353 if (fds[2].revents) {
354 fds[2].revents = 0;
356 card.call_page_flip_handlers();
357 wb.Queue();
358 }
359 }
361 printf("exiting...\n");
362 }