fff9c4c8d5aadf7e8b11a541b179464f00bc8e31
[apps/camera-gstreamer.git] / app / main.cpp
1 #define GST_USE_UNSTABLE_API
2 #define HAVE_MEMFD_CREATE
3
4 #include <string>
5 #include <iostream>
6 #include <cstring>
7 #include <cstdio>
8 #include <cstdlib>
9
10 #include <signal.h>
11 #include <wayland-client.h>
12 #include <sys/mman.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <assert.h>
16
17 #include "xdg-shell-client-protocol.h"
18 #include "agl-shell-desktop-client-protocol.h"
19 #include "utils.h"
20
21 #include <gst/gst.h>
22
23 #include <gst/video/videooverlay.h>
24 #include <gst/wayland/wayland.h>
25
26 #define DEFAULT_VIDEO_DEVICE    "/dev/video0"
27
28 // these only applies if the window is a dialog/pop-up one
29 // by default the compositor make the window maximized
30 #define WINDOW_WIDTH_SIZE       640
31 #define WINDOW_HEIGHT_SIZE      720
32
33 #define WINDOW_WIDTH_POS_X      640
34 #define WINDOW_WIDTH_POS_Y      180
35
36 // C++ requires a cast and we in wayland we do the cast implictly
37 #define WL_ARRAY_FOR_EACH(pos, array, type) \
38         for (pos = (type)(array)->data; \
39              (const char *) pos < ((const char *) (array)->data + (array)->size); \
40              (pos)++)
41
42 struct display {
43         struct wl_display *wl_display;
44         struct wl_registry *wl_registry;
45         struct wl_compositor *wl_compositor;
46         struct wl_output *wl_output;
47         struct wl_shm *shm;
48
49         struct {
50                 int width;
51                 int height;
52         } output_data;
53
54         struct xdg_wm_base *wm_base;
55         struct agl_shell_desktop *agl_shell_desktop;
56
57         int has_xrgb;
58 };
59
60 struct buffer {
61         struct wl_buffer *buffer;
62         void *shm_data;
63         int busy;
64 };
65
66 struct window {
67         struct display *display;
68
69         int x, y;
70         int width, height;
71
72         struct wl_surface *surface;
73
74         struct xdg_surface *xdg_surface;
75         struct xdg_toplevel *xdg_toplevel;
76         bool wait_for_configure;
77
78         int fullscreen, maximized;
79
80         struct buffer buffers[2];
81         struct wl_callback *callback;
82 };
83
84
85 struct receiver_data {
86         struct window *window;
87
88         GstElement *pipeline;
89         GstWaylandVideo *wl_video;
90         GstVideoOverlay *overlay;
91 };
92
93 static int running = 1;
94
95 static void
96 redraw(void *data, struct wl_callback *callback, uint32_t time);
97
98 static void
99 paint_pixels(void *image, int padding, int width, int height, uint32_t time)
100 {
101         memset(image, 0x00, width * height * 4);
102 }
103
104 static void
105 buffer_release(void *data, struct wl_buffer *buffer)
106 {
107         struct buffer *mybuf = static_cast<struct buffer *>(data);
108         mybuf->busy = 0;
109 }
110
111 static const struct wl_buffer_listener buffer_listener = {
112         buffer_release
113 };
114
115
116 static int
117 create_shm_buffer(struct display *display, struct buffer *buffer,
118                   int width, int height, uint32_t format)
119 {
120         struct wl_shm_pool *pool;
121         int fd, size, stride;
122         void *data;
123
124         stride = width * 4;
125         size = stride * height;
126
127         fd = os_create_anonymous_file(size);
128         if (fd < 0) {
129                 fprintf(stderr, "creating a buffer file for %d B failed: %s\n",
130                                 size, strerror(errno));
131                 return -1;
132         }
133
134         data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
135         if (data == MAP_FAILED) {
136                 fprintf(stderr, "mmap failed: %s\n", strerror(errno));
137                 close(fd);
138                 return -1;
139         }
140
141         pool = wl_shm_create_pool(display->shm, fd, size);
142         buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width,
143                                                    height, stride, format);
144         wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer);
145         wl_shm_pool_destroy(pool);
146         close(fd);
147
148         buffer->shm_data = data;
149         fprintf(stdout, "Created shm buffer with width %d, height %d\n", width, height);
150         return 0;
151 }
152
153 static struct buffer *
154 get_next_buffer(struct window *window)
155 {
156         struct buffer *buffer = NULL;
157         int ret = 0;
158
159         /* we need to create new buffers for the resized value so discard
160          * the 'old' one and force creation of the buffer with the newer
161          * dimensions */
162         if (window->wait_for_configure && window->maximized) {
163                 if (!window->buffers[0].busy) {
164                         wl_buffer_destroy(window->buffers[0].buffer);
165                         window->buffers[0].buffer = NULL;
166                         window->wait_for_configure = false;
167                 }
168         }
169
170         if (!window->buffers[0].busy) {
171                 buffer = &window->buffers[0];
172         } else if (!window->buffers[1].busy) {
173                 buffer = &window->buffers[1];
174         } else {
175                 return NULL;
176         }
177
178         if (!buffer->buffer) {
179                 ret = create_shm_buffer(window->display, buffer, window->width,
180                                         window->height, WL_SHM_FORMAT_XRGB8888);
181
182                 if (ret < 0)
183                         return NULL;
184
185                 /* paint the padding */
186                 memset(buffer->shm_data, 0x00, window->width * window->height * 4);
187         }
188
189         return buffer;
190 }
191
192
193 static const struct wl_callback_listener frame_listener = {
194         redraw
195 };
196
197 static void
198 redraw(void *data, struct wl_callback *callback, uint32_t time)
199 {
200         struct window *window = static_cast<struct window *>(data);
201         struct buffer *buffer;
202
203         buffer = get_next_buffer(window);
204         if (!buffer) {
205                 fprintf(stderr,
206                         !callback ? "Failed to create the first buffer.\n" :
207                         "Both buffers busy at redraw(). Server bug?\n");
208                 abort();
209         }
210
211         // do the actual painting
212         paint_pixels(buffer->shm_data, 0x0, window->width, window->height, time);
213
214         wl_surface_attach(window->surface, buffer->buffer, 0, 0);
215         wl_surface_damage(window->surface, 0, 0, window->width, window->height);
216
217         if (callback)
218                 wl_callback_destroy(callback);
219
220         window->callback = wl_surface_frame(window->surface);
221         wl_callback_add_listener(window->callback, &frame_listener, window);
222         wl_surface_commit(window->surface);
223
224         buffer->busy = 1;
225 }
226
227 static void
228 shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
229 {
230         struct display *d = static_cast<struct display *>(data);
231
232         if (format == WL_SHM_FORMAT_XRGB8888)
233                 d->has_xrgb = true;
234 }
235
236 static const struct wl_shm_listener shm_listener = {
237         shm_format
238 };
239
240 static void
241 xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
242 {
243         xdg_wm_base_pong(shell, serial);
244 }
245
246 static const struct xdg_wm_base_listener xdg_wm_base_listener = {
247         xdg_wm_base_ping,
248 };
249
250 static void
251 display_handle_geometry(void *data, struct wl_output *wl_output,
252                         int x, int y, int physical_width, int physical_height,
253                         int subpixel, const char *make, const char *model, int transform)
254 {
255         (void) data;
256         (void) wl_output;
257         (void) x;
258         (void) y;
259         (void) physical_width;
260         (void) physical_height;
261         (void) subpixel;
262         (void) make;
263         (void) model;
264         (void) transform;
265 }
266
267 static void
268 display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
269                     int width, int height, int refresh)
270 {
271         struct display *d = static_cast<struct display *>(data);
272
273         if (wl_output == d->wl_output && (flags & WL_OUTPUT_MODE_CURRENT)) {
274                 d->output_data.width = width;
275                 d->output_data.height = height;
276
277                 fprintf(stdout, "Found output with width %d and height %d\n",
278                                 d->output_data.width, d->output_data.height);
279         }
280 }
281
282 static void
283 display_handle_scale(void *data, struct wl_output *wl_output, int scale)
284 {
285         (void) data;
286         (void) wl_output;
287         (void) scale;
288 }
289
290 static void
291 display_handle_done(void *data, struct wl_output *wl_output)
292 {
293         (void) data;
294         (void) wl_output;
295 }
296
297 static const struct wl_output_listener output_listener = {
298         display_handle_geometry,
299         display_handle_mode,
300         display_handle_done,
301         display_handle_scale
302 };
303
304 static void
305 application_id(void *data, struct agl_shell_desktop *agl_shell_desktop,
306                 const char *app_id)
307 {
308         (void) data;
309         (void) agl_shell_desktop;
310         (void) app_id;
311 }
312
313 static void
314 application_id_state(void *data, struct agl_shell_desktop *agl_shell_desktop,
315                      const char *app_id, const char *app_data,
316                      uint32_t app_state, uint32_t app_role)
317 {
318         (void) data;
319         (void) app_data;
320         (void) agl_shell_desktop;
321         (void) app_id;
322         (void) app_state;
323         (void) app_role;
324 }
325
326 static const struct agl_shell_desktop_listener agl_shell_desktop_listener = {
327         application_id,
328         application_id_state,
329 };
330
331 static void
332 registry_handle_global(void *data, struct wl_registry *registry, uint32_t id,
333                        const char *interface, uint32_t version)
334 {
335         struct display *d = static_cast<struct display *>(data);
336
337         if (strcmp(interface, "wl_compositor") == 0) {
338                 d->wl_compositor =
339                         static_cast<struct wl_compositor *>(wl_registry_bind(registry, id,
340                                                     &wl_compositor_interface, 1));
341         } else if (strcmp(interface, "xdg_wm_base") == 0) {
342                 d->wm_base = static_cast<struct xdg_wm_base *>(wl_registry_bind(registry,
343                                 id, &xdg_wm_base_interface, 1));
344                 xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d);
345         } else if (strcmp(interface, "wl_shm") == 0) {
346                 d->shm = static_cast<struct wl_shm *>(wl_registry_bind(registry,
347                                 id, &wl_shm_interface, 1));
348                 wl_shm_add_listener(d->shm, &shm_listener, d);
349         } else if (strcmp(interface, "agl_shell_desktop") == 0) {
350                 d->agl_shell_desktop = static_cast<struct agl_shell_desktop *>(wl_registry_bind(registry, id,
351                                                         &agl_shell_desktop_interface, 1));
352                 /* as an example, show how to register for events from the compositor */
353                 agl_shell_desktop_add_listener(d->agl_shell_desktop,
354                                                &agl_shell_desktop_listener, d);
355         } else if (strcmp(interface, "wl_output") == 0) {
356                 d->wl_output = static_cast<struct wl_output *>(wl_registry_bind(registry, id,
357                                              &wl_output_interface, 1));
358                 wl_output_add_listener(d->wl_output, &output_listener, d);
359         }
360 }
361
362 static void
363 registry_handle_global_remove(void *data, struct wl_registry *reg, uint32_t id)
364 {
365         (void) data;
366         (void) reg;
367         (void) id;
368 }
369
370 static const struct wl_registry_listener registry_listener = {
371         registry_handle_global,
372         registry_handle_global_remove,
373 };
374
375
376 static void
377 error_cb(GstBus *bus, GstMessage *msg, gpointer user_data)
378 {
379         struct receiver_data *d =
380                 static_cast<struct receiver_data *>(user_data);
381
382         gchar *debug = NULL;
383         GError *err = NULL;
384
385         gst_message_parse_error(msg, &err, &debug);
386
387         g_print("Error: %s\n", err->message);
388         g_error_free(err);
389
390         if (debug) {
391                 g_print("Debug details: %s\n", debug);
392                 g_free(debug);
393         }
394
395         gst_element_set_state(d->pipeline, GST_STATE_NULL);
396 }
397
398 static GstBusSyncReply
399 bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
400 {
401         struct receiver_data *d =
402                 static_cast<struct receiver_data *>(user_data);
403
404         if (gst_is_wayland_display_handle_need_context_message(message)) {
405                 GstContext *context;
406                 struct wl_display *display_handle = d->window->display->wl_display;
407
408                 context = gst_wayland_display_handle_context_new(display_handle);
409                 d->wl_video = GST_WAYLAND_VIDEO(GST_MESSAGE_SRC(message));
410                 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
411
412                 goto drop;
413         } else if (gst_is_video_overlay_prepare_window_handle_message(message)) {
414                 struct wl_surface *window_handle = d->window->surface;
415
416                 /* GST_MESSAGE_SRC(message) will be the overlay object that we
417                  * have to use. This may be waylandsink, but it may also be
418                  * playbin. In the latter case, we must make sure to use
419                  * playbin instead of waylandsink, because playbin resets the
420                  * window handle and render_rectangle after restarting playback
421                  * and the actual window size is lost */
422                 d->overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message));
423
424                 g_print("setting window handle and size (%d x %d) w %d, h %d\n",
425                                 d->window->x, d->window->y,
426                                 d->window->width, d->window->height);
427
428                 gst_video_overlay_set_window_handle(d->overlay, (guintptr) window_handle);
429                 gst_video_overlay_set_render_rectangle(d->overlay,
430                                                        d->window->x, d->window->y,
431                                                        d->window->width, d->window->height);
432
433                 goto drop;
434         }
435
436         return GST_BUS_PASS;
437
438 drop:
439         gst_message_unref(message);
440         return GST_BUS_DROP;
441 }
442
443 static void
444 handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial)
445 {
446         struct window *window = static_cast<struct window *>(data);
447
448         xdg_surface_ack_configure(surface, serial);
449
450         if (window->wait_for_configure) {
451                 redraw(window, NULL, 0);
452         }
453 }
454
455 static const struct xdg_surface_listener xdg_surface_listener = {
456         handle_xdg_surface_configure,
457 };
458
459 static void
460 handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
461                               int32_t width, int32_t height,
462                               struct wl_array *states)
463 {
464         struct window *window = static_cast<struct window *>(data);
465         uint32_t *p;
466
467         window->fullscreen = 0;
468         window->maximized = 0;
469
470         // use our own macro as C++ can't typecast from (void *) directly
471         WL_ARRAY_FOR_EACH(p, states, uint32_t *) {
472                 uint32_t state = *p; 
473                 switch (state) {
474                 case XDG_TOPLEVEL_STATE_FULLSCREEN:
475                         window->fullscreen = 1;
476                         break;
477                 case XDG_TOPLEVEL_STATE_MAXIMIZED:
478                         window->maximized = 1;
479                         break;
480                 }
481         }
482
483         if (width > 0 && height > 0) {
484                 if (!window->fullscreen && !window->maximized) {
485                         window->width = width;
486                         window->height = height;
487                 }
488                 window->width = width;
489                 window->height = height;
490         } else if (!window->fullscreen && !window->maximized) {
491                 if (width == 0)
492                         window->width = WINDOW_WIDTH_SIZE;
493                 else
494                         window->width = width;
495
496                 if (height == 0)
497                         window->height = WINDOW_HEIGHT_SIZE;
498                 else
499                         window->height = height;
500         }
501
502         /* if we've been resized set wait_for_configure to adjust the fb size 
503          * in the frame callback handler, which will also clear this up */
504         if ((window->width > 0 && window->width != WINDOW_WIDTH_SIZE) &&
505             (window->height > 0 && window->height != WINDOW_HEIGHT_SIZE)) {
506                 window->wait_for_configure = true;
507         }
508 }
509
510 static void
511 handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
512 {
513         running = 0;
514 }
515
516 static const struct xdg_toplevel_listener xdg_toplevel_listener = {
517         handle_xdg_toplevel_configure,
518         handle_xdg_toplevel_close,
519 };
520
521 static struct window *
522 create_window(struct display *display, int width, int height, const char *app_id)
523 {
524         struct window *window;
525
526         assert(display->wm_base != NULL);
527
528         window = static_cast<struct window *>(calloc(1, sizeof(*window)));
529         if (!window)
530                 return NULL;
531
532         window->callback = NULL;
533         window->display = display;
534         window->width = width;
535         window->height = height;
536         window->surface = wl_compositor_create_surface(display->wl_compositor);
537
538         if (display->wm_base) {
539                 window->xdg_surface =
540                         xdg_wm_base_get_xdg_surface(display->wm_base, window->surface);
541                 assert(window->xdg_surface);
542
543                 xdg_surface_add_listener(window->xdg_surface,
544                                          &xdg_surface_listener, window);
545                 window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
546                 assert(window->xdg_toplevel);
547
548                 xdg_toplevel_add_listener(window->xdg_toplevel,
549                                           &xdg_toplevel_listener, window);
550
551                 xdg_toplevel_set_app_id(window->xdg_toplevel, app_id);
552
553                 wl_surface_commit(window->surface);
554                 window->wait_for_configure = true;
555         }
556
557         return window;
558 }
559
560
561 static void
562 destroy_window(struct window *window)
563 {
564         if (window->callback)
565                 wl_callback_destroy(window->callback);
566
567         if (window->buffers[0].buffer)
568                 wl_buffer_destroy(window->buffers[0].buffer);
569         if (window->buffers[1].buffer)
570                 wl_buffer_destroy(window->buffers[1].buffer);
571
572         if (window->xdg_toplevel)
573                 xdg_toplevel_destroy(window->xdg_toplevel);
574
575         if (window->xdg_surface)
576                 xdg_surface_destroy(window->xdg_surface);
577
578         wl_surface_destroy(window->surface);
579         free(window);
580 }
581
582 static void
583 signal_int(int sig, siginfo_t *si, void *_unused)
584 {
585         running = 0;
586 }
587
588 static struct display *
589 create_display(int argc, char *argv[])
590 {
591         struct display *display;
592
593         display = static_cast<struct display *>(calloc(1, sizeof(*display)));
594         if (display == NULL) {
595                 fprintf(stderr, "out of memory\n");
596                 exit(1);
597         }
598         display->wl_display = wl_display_connect(NULL);
599         assert(display->wl_display);
600
601         display->has_xrgb = false;
602         display->wl_registry = wl_display_get_registry(display->wl_display);
603
604         wl_registry_add_listener(display->wl_registry, &registry_listener, display);
605         wl_display_roundtrip(display->wl_display);
606
607         if (display->shm == NULL) {
608                 fprintf(stderr, "No wl_shm global\n");
609                 return NULL;
610         }
611
612         if (display->agl_shell_desktop == NULL) {
613                 fprintf(stderr, "No agl_shell extension present\n");
614                 return NULL;
615         }
616
617         wl_display_roundtrip(display->wl_display);
618
619         if (!display->has_xrgb) {
620                 fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n");
621                 return NULL;
622         }
623
624         return display;
625 }
626
627 static void
628 destroy_display(struct display *display)
629 {
630         if (display->shm)
631                 wl_shm_destroy(display->shm);
632
633         if (display->wm_base)
634                 xdg_wm_base_destroy(display->wm_base);
635
636         if (display->agl_shell_desktop)
637                 agl_shell_desktop_destroy(display->agl_shell_desktop);
638
639         if (display->wl_compositor)
640                 wl_compositor_destroy(display->wl_compositor);
641
642         wl_registry_destroy(display->wl_registry);
643         wl_display_flush(display->wl_display);
644         wl_display_disconnect(display->wl_display);
645         free(display);
646 }
647
648 int main(int argc, char *argv[])
649 {
650         int ret = 0;
651         struct sigaction sa;
652         struct receiver_data receiver_data = {};
653         struct display *display;
654         struct window *window;
655         char pipeline_str[1024];
656         GError *error = NULL;
657         const char *app_id = "camera-gstreamer";
658
659         sa.sa_sigaction = signal_int;
660         sigemptyset(&sa.sa_mask);
661         sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
662         sigaction(SIGINT, &sa, NULL);
663
664         int gargc = 2;
665         char **gargv = static_cast<char **>(calloc(2, sizeof(char *)));
666
667         gargv[0] = strdup(argv[0]);
668         gargv[1] = strdup("--gst-debug-level=2");
669
670         memset(pipeline_str, 0, sizeof(pipeline_str));
671         snprintf(pipeline_str, sizeof(pipeline_str), "v4l2src device=%s ! video/x-raw,width=%d,height=%d ! waylandsink", 
672                 DEFAULT_VIDEO_DEVICE, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE);
673         gst_init(&gargc, &gargv);
674
675         setbuf(stdout, NULL);
676
677         fprintf(stdout, "Using pipeline: %s\n", pipeline_str);
678
679         GstElement *pipeline = gst_parse_launch(pipeline_str, &error);
680         if (error || !pipeline) {
681                 fprintf(stderr, "gstreamer pipeline construction failed!\n");
682                 free(argv);
683                 return EXIT_FAILURE;
684         }
685
686         receiver_data.pipeline = pipeline;
687
688         display = create_display(argc, argv);
689         if (!display)
690                 return -1;
691
692         // if you'd want to place the video in a pop-up/dialog type of window:
693         // agl_shell_desktop_set_app_property(display->agl_shell_desktop, app_id, 
694         //                                 AGL_SHELL_DESKTOP_APP_ROLE_POPUP,
695         //                                 WINDOW_WIDTH_POS_X, WINDOW_WIDTH_POS_Y,
696         //                                 0, 0, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE,
697         //                                 display->wl_output);
698
699         // we use the role to set a correspondence between the top level
700         // surface and our application, with the previous call letting the
701         // compositor know that we're one and the same
702         window = create_window(display, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE, app_id); 
703
704         if (!window) {
705                 free(argv);
706                 return EXIT_FAILURE;
707         }
708
709         window->display = display;
710         receiver_data.window = window;
711
712         /* Initialise damage to full surface, so the padding gets painted */
713         wl_surface_damage(window->surface, 0, 0,
714                           window->width, window->height);
715
716         if (!window->wait_for_configure) {
717                 redraw(window, NULL, 0);
718         }
719
720         GstBus *bus = gst_element_get_bus(pipeline);
721         gst_bus_add_signal_watch(bus);
722
723         g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), &receiver_data);
724         gst_bus_set_sync_handler(bus, bus_sync_handler, &receiver_data, NULL);
725         gst_object_unref(bus);
726
727         gst_element_set_state(pipeline, GST_STATE_PLAYING);
728         fprintf(stdout, "gstreamer pipeline running\n");
729
730         // run the application
731         while (running && ret != -1)
732                 ret = wl_display_dispatch(display->wl_display);
733
734         destroy_window(window);
735         destroy_display(display);
736         free(argv);
737
738         gst_element_set_state(pipeline, GST_STATE_NULL);
739         gst_object_unref(pipeline);
740         return ret;
741 }