wbcap: support saving to file
[android/external-libkmsxx.git] / utils / wbcap.cpp
1 #include <cstdio>
2 #include <poll.h>
3 #include <unistd.h>
4 #include <algorithm>
5 #include <fstream>
7 #include <kms++/kms++.h>
8 #include <kms++util/kms++util.h>
9 #include <kms++util/videodevice.h>
11 #define CAMERA_BUF_QUEUE_SIZE 5
13 using namespace std;
14 using namespace kms;
16 static vector<DumbFramebuffer*> s_fbs;
17 static vector<DumbFramebuffer*> s_free_fbs;
18 static vector<DumbFramebuffer*> s_wb_fbs;
19 static vector<DumbFramebuffer*> s_ready_fbs;
21 class WBStreamer
22 {
23 public:
24         WBStreamer(VideoStreamer* streamer, Crtc* crtc, PixelFormat pixfmt)
25                 : m_capdev(*streamer)
26         {
27                 Videomode m = crtc->mode();
29                 m_capdev.set_port(crtc->idx());
30                 m_capdev.set_format(pixfmt, m.hdisplay, m.vdisplay / (m.interlace() ? 2 : 1));
31                 m_capdev.set_queue_size(s_fbs.size());
33                 for (auto fb : s_free_fbs) {
34                         m_capdev.queue(fb);
35                         s_wb_fbs.push_back(fb);
36                 }
38                 s_free_fbs.clear();
39         }
41         ~WBStreamer()
42         {
43         }
45         WBStreamer(const WBStreamer& other) = delete;
46         WBStreamer& operator=(const WBStreamer& other) = delete;
48         int fd() const { return m_capdev.fd(); }
50         void start_streaming()
51         {
52                 m_capdev.stream_on();
53         }
55         void stop_streaming()
56         {
57                 m_capdev.stream_off();
58         }
60         DumbFramebuffer* Dequeue()
61         {
62                 auto fb = m_capdev.dequeue();
64                 auto iter = find(s_wb_fbs.begin(), s_wb_fbs.end(), fb);
65                 s_wb_fbs.erase(iter);
67                 s_ready_fbs.insert(s_ready_fbs.begin(), fb);
69                 return fb;
70         }
72         void Queue()
73         {
74                 if (s_free_fbs.size() == 0)
75                         return;
77                 auto fb = s_free_fbs.back();
78                 s_free_fbs.pop_back();
80                 m_capdev.queue(fb);
82                 s_wb_fbs.insert(s_wb_fbs.begin(), fb);
83         }
85 private:
86         VideoStreamer& m_capdev;
87 };
89 class WBFlipState : private PageFlipHandlerBase
90 {
91 public:
92         WBFlipState(Card& card, Crtc* crtc, Plane* plane)
93                 : m_card(card), m_crtc(crtc), m_plane(plane)
94         {
95                 auto fb = s_ready_fbs.back();
96                 s_ready_fbs.pop_back();
98                 AtomicReq req(m_card);
100                 req.add(m_plane, "CRTC_ID", m_crtc->id());
101                 req.add(m_plane, "FB_ID", fb->id());
103                 req.add(m_plane, "CRTC_X", 0);
104                 req.add(m_plane, "CRTC_Y", 0);
105                 req.add(m_plane, "CRTC_W", min((uint32_t)m_crtc->mode().hdisplay, fb->width()));
106                 req.add(m_plane, "CRTC_H", min((uint32_t)m_crtc->mode().vdisplay, fb->height()));
108                 req.add(m_plane, "SRC_X", 0);
109                 req.add(m_plane, "SRC_Y", 0);
110                 req.add(m_plane, "SRC_W", fb->width() << 16);
111                 req.add(m_plane, "SRC_H", fb->height() << 16);
113                 int r = req.commit_sync();
114                 FAIL_IF(r, "initial plane setup failed");
116                 m_current_fb = fb;
117         }
119         void queue_next()
120         {
121                 if (m_queued_fb)
122                         return;
124                 if (s_ready_fbs.size() == 0)
125                         return;
127                 auto fb = s_ready_fbs.back();
128                 s_ready_fbs.pop_back();
130                 AtomicReq req(m_card);
131                 req.add(m_plane, "FB_ID", fb->id());
133                 int r = req.commit(this);
134                 if (r)
135                         EXIT("Flip commit failed: %d\n", r);
137                 m_queued_fb = fb;
138         }
140 private:
141         void handle_page_flip(uint32_t frame, double time)
142         {
143                 if (m_queued_fb) {
144                         if (m_current_fb)
145                                 s_free_fbs.insert(s_free_fbs.begin(), m_current_fb);
147                         m_current_fb = m_queued_fb;
148                         m_queued_fb = nullptr;
149                 }
151                 queue_next();
152         }
154         Card& m_card;
155         Crtc* m_crtc;
156         Plane* m_plane;
158         DumbFramebuffer* m_current_fb = nullptr;
159         DumbFramebuffer* m_queued_fb = nullptr;
160 };
162 class BarFlipState : private PageFlipHandlerBase
164 public:
165         BarFlipState(Card& card, Crtc* crtc, Plane* plane, uint32_t width, uint32_t height)
166                 : m_card(card), m_crtc(crtc), m_plane(plane)
167         {
168                 for (unsigned i = 0; i < s_num_buffers; ++i)
169                         m_fbs[i] = new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888);
170         }
172         ~BarFlipState()
173         {
174                 for (unsigned i = 0; i < s_num_buffers; ++i)
175                         delete m_fbs[i];
176         }
178         void start_flipping()
179         {
180                 m_frame_num = 0;
181                 queue_next();
182         }
184 private:
185         void handle_page_flip(uint32_t frame, double time)
186         {
187                 m_frame_num++;
188                 queue_next();
189         }
191         static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
192         {
193                 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
194         }
196         void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
197         {
198                 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
199                 int new_xpos = get_bar_pos(fb, frame_num);
201                 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
202                 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
203         }
205         void queue_next()
206         {
207                 AtomicReq req(m_card);
209                 unsigned cur = m_frame_num % s_num_buffers;
211                 auto fb = m_fbs[cur];
213                 draw_bar(fb, m_frame_num);
215                 req.add(m_plane, {
216                                 { "CRTC_ID", m_crtc->id() },
217                                 { "FB_ID", fb->id() },
219                                 { "CRTC_X", 0 },
220                                 { "CRTC_Y", 0 },
221                                 { "CRTC_W", min((uint32_t)m_crtc->mode().hdisplay, fb->width()) },
222                                 { "CRTC_H", min((uint32_t)m_crtc->mode().vdisplay, fb->height()) },
224                                 { "SRC_X", 0 },
225                                 { "SRC_Y", 0 },
226                                 { "SRC_W", fb->width() << 16 },
227                                 { "SRC_H", fb->height() << 16 },
228                         });
230                 int r = req.commit(this);
231                 if (r)
232                         EXIT("Flip commit failed: %d\n", r);
233         }
235         static const unsigned s_num_buffers = 3;
237         DumbFramebuffer* m_fbs[s_num_buffers];
239         Card& m_card;
240         Crtc* m_crtc;
241         Plane* m_plane;
243         unsigned m_frame_num;
245         static const unsigned bar_width = 20;
246         static const unsigned bar_speed = 8;
247 };
249 static const char* usage_str =
250                 "Usage: wbcap [OPTIONS]\n\n"
251                 "Options:\n"
252                 "  -s, --src=CONN            Source connector\n"
253                 "  -d, --dst=CONN            Destination connector\n"
254                 "  -m, --smode=MODE          Source connector videomode\n"
255                 "  -M, --dmode=MODE          Destination connector videomode\n"
256                 "  -f, --format=4CC          Format\n"
257                 "  -w, --write               Write captured frames to wbcap.raw file\n"
258                 "  -h, --help                Print this help\n"
259                 ;
261 int main(int argc, char** argv)
263         string src_conn_name;
264         string src_mode_name;
265         string dst_conn_name;
266         string dst_mode_name;
267         PixelFormat pixfmt = PixelFormat::XRGB8888;
268         bool write_file = false;
270         OptionSet optionset = {
271                 Option("s|src=", [&](string s)
272                 {
273                         src_conn_name = s;
274                 }),
275                 Option("m|smode=", [&](string s)
276                 {
277                         src_mode_name = s;
278                 }),
279                 Option("d|dst=", [&](string s)
280                 {
281                         dst_conn_name = s;
282                 }),
283                 Option("M|dmode=", [&](string s)
284                 {
285                         dst_mode_name = s;
286                 }),
287                 Option("f|format=", [&](string s)
288                 {
289                         pixfmt = FourCCToPixelFormat(s);
290                 }),
291                 Option("w|write", [&]()
292                 {
293                         write_file = true;
294                 }),
295                 Option("h|help", [&]()
296                 {
297                         puts(usage_str);
298                         exit(-1);
299                 }),
300         };
302         optionset.parse(argc, argv);
304         if (optionset.params().size() > 0) {
305                 puts(usage_str);
306                 exit(-1);
307         }
309         if (src_conn_name.empty())
310                 EXIT("No source connector defined");
312         if (dst_conn_name.empty())
313                 EXIT("No destination connector defined");
315         VideoDevice vid("/dev/video11");
317         Card card;
318         ResourceManager resman(card);
320         card.disable_all();
322         auto src_conn = resman.reserve_connector(src_conn_name);
323         auto src_crtc = resman.reserve_crtc(src_conn);
324         auto src_plane = resman.reserve_generic_plane(src_crtc, pixfmt);
325         FAIL_IF(!src_plane, "Plane not found");
326         Videomode src_mode = src_mode_name.empty() ? src_conn->get_default_mode() : src_conn->get_mode(src_mode_name);
327         src_crtc->set_mode(src_conn, src_mode);
330         auto dst_conn = resman.reserve_connector(dst_conn_name);
331         auto dst_crtc = resman.reserve_crtc(dst_conn);
332         auto dst_plane = resman.reserve_overlay_plane(dst_crtc, pixfmt);
333         FAIL_IF(!dst_plane, "Plane not found");
334         Videomode dst_mode = dst_mode_name.empty() ? dst_conn->get_default_mode() : dst_conn->get_mode(dst_mode_name);
335         dst_crtc->set_mode(dst_conn, dst_mode);
337         uint32_t src_width = src_mode.hdisplay;
338         uint32_t src_height = src_mode.vdisplay;
340         uint32_t dst_width = src_mode.hdisplay;
341         uint32_t dst_height = src_mode.vdisplay;
342         if (src_mode.interlace())
343                 dst_height /= 2;
345         printf("src %s, crtc %s\n", src_conn->fullname().c_str(), src_mode.to_string().c_str());
347         printf("dst %s, crtc %s\n", dst_conn->fullname().c_str(), dst_mode.to_string().c_str());
349         printf("src_fb %ux%u, dst_fb %ux%u\n", src_width, src_height, dst_width, dst_height);
351         for (int i = 0; i < CAMERA_BUF_QUEUE_SIZE; ++i) {
352                 auto fb = new DumbFramebuffer(card, dst_width, dst_height, pixfmt);
353                 s_fbs.push_back(fb);
354                 s_free_fbs.push_back(fb);
355         }
357         // get one fb for initial setup
358         s_ready_fbs.push_back(s_free_fbs.back());
359         s_free_fbs.pop_back();
361         // This draws a moving bar to SRC display
362         BarFlipState barflipper(card, src_crtc, src_plane, src_width, src_height);
363         barflipper.start_flipping();
365         // This shows the captured SRC frames on DST display
366         WBFlipState wbflipper(card, dst_crtc, dst_plane);
368         WBStreamer wb(vid.get_capture_streamer(), src_crtc, pixfmt);
369         wb.start_streaming();
371         vector<pollfd> fds(3);
373         fds[0].fd = 0;
374         fds[0].events =  POLLIN;
375         fds[1].fd = wb.fd();
376         fds[1].events =  POLLIN;
377         fds[2].fd = card.fd();
378         fds[2].events =  POLLIN;
380         uint32_t dst_frame_num = 0;
382         const string filename = "wbcap.raw";
383         unique_ptr<ofstream> os;
384         if (write_file)
385                 os = unique_ptr<ofstream>(new ofstream(filename, ofstream::binary));
387         while (true) {
388                 int r = poll(fds.data(), fds.size(), -1);
389                 ASSERT(r > 0);
391                 if (fds[0].revents != 0)
392                         break;
394                 if (fds[1].revents) {
395                         fds[1].revents = 0;
397                         DumbFramebuffer* fb = wb.Dequeue();
399                         if (write_file) {
400                                 printf("Writing frame %u to %s\n", dst_frame_num, filename.c_str());
402                                 for (unsigned i = 0; i < fb->num_planes(); ++i)
403                                         os->write((char*)fb->map(i), fb->size(i));
405                                 dst_frame_num++;
406                         }
408                         wbflipper.queue_next();
409                 }
411                 if (fds[2].revents) {
412                         fds[2].revents = 0;
414                         card.call_page_flip_handlers();
415                         wb.Queue();
416                 }
417         }
419         printf("exiting...\n");