camera-gstreamer: Add pipewire source to capture video streams
[apps/camera-gstreamer.git] / app / main.cpp
index 1acb70f..2068143 100644 (file)
@@ -31,6 +31,8 @@
 #define WINDOW_WIDTH_POS_X     640
 #define WINDOW_WIDTH_POS_Y     180
 
+#define MAX_BUFFER_ALLOC       2
+
 // C++ requires a cast and we in wayland we do the cast implictly
 #define WL_ARRAY_FOR_EACH(pos, array, type) \
        for (pos = (type)(array)->data; \
@@ -59,6 +61,9 @@ struct buffer {
        struct wl_buffer *buffer;
        void *shm_data;
        int busy;
+       int width, height;
+       size_t size;    /* width * 4 * height */
+       struct wl_list buffer_link; /** window::buffer_list */
 };
 
 struct window {
@@ -66,6 +71,8 @@ struct window {
 
        int x, y;
        int width, height;
+       int init_width;
+       int init_height;
 
        struct wl_surface *surface;
 
@@ -75,8 +82,9 @@ struct window {
 
        int fullscreen, maximized;
 
-       struct buffer buffers[2];
+       struct wl_list buffer_list;
        struct wl_callback *callback;
+       bool needs_update_buffer;
 };
 
 
@@ -93,6 +101,58 @@ static int running = 1;
 static void
 redraw(void *data, struct wl_callback *callback, uint32_t time);
 
+static struct buffer *
+alloc_buffer(struct window *window, int width, int height)
+{
+       struct buffer *buffer = static_cast<struct buffer *>(calloc(1, sizeof(*buffer)));
+
+       buffer->width = width;
+       buffer->height = height;
+       wl_list_insert(&window->buffer_list, &buffer->buffer_link);
+
+       return buffer;
+}
+
+static void
+destroy_buffer(struct buffer *buffer)
+{
+       if (buffer->buffer)
+               wl_buffer_destroy(buffer->buffer);
+
+       munmap(buffer->shm_data, buffer->size);
+       wl_list_remove(&buffer->buffer_link);
+       free(buffer);
+}
+
+static struct buffer *
+pick_free_buffer(struct window *window)
+{
+       struct buffer *b;
+       struct buffer *buffer = NULL;
+
+       wl_list_for_each(b, &window->buffer_list, buffer_link) {
+               if (!b->busy) {
+                       buffer = b;
+                       break;
+               }
+       }
+
+       return buffer;
+}
+
+static void
+prune_old_released_buffers(struct window *window)
+{
+       struct buffer *b, *b_next;
+
+       wl_list_for_each_safe(b, b_next,
+                             &window->buffer_list, buffer_link) {
+               if (!b->busy && (b->width != window->width ||
+                                b->height != window->height))
+                       destroy_buffer(b);
+       }
+}
+
 static void
 paint_pixels(void *image, int padding, int width, int height, uint32_t time)
 {
@@ -144,6 +204,10 @@ create_shm_buffer(struct display *display, struct buffer *buffer,
        close(fd);
 
        buffer->shm_data = data;
+       buffer->size = size;
+       buffer->width = width;
+       buffer->height = height;
+
        fprintf(stdout, "Created shm buffer with width %d, height %d\n", width, height);
        return 0;
 }
@@ -154,26 +218,19 @@ get_next_buffer(struct window *window)
        struct buffer *buffer = NULL;
        int ret = 0;
 
-       /* we need to create new buffers for the resized value so discard
-        * the 'old' one and force creation of the buffer with the newer
-        * dimensions */
-       if (window->wait_for_configure && window->maximized) {
-               /* The 'old' buffer might not exist if maximized is received
-                * from the start. */
-               if (window->buffers[0].buffer && !window->buffers[0].busy) {
-                       wl_buffer_destroy(window->buffers[0].buffer);
-                       window->buffers[0].buffer = NULL;
-                       window->wait_for_configure = false;
-               }
+       if (window->needs_update_buffer) {
+               int i;
+
+               for (i = 0; i < MAX_BUFFER_ALLOC; i++)
+                       alloc_buffer(window, window->width, window->height);
+
+               window->needs_update_buffer = false;
        }
 
-       if (!window->buffers[0].busy) {
-               buffer = &window->buffers[0];
-       } else if (!window->buffers[1].busy) {
-               buffer = &window->buffers[1];
-       } else {
+       buffer = pick_free_buffer(window);
+       if (!buffer)
                return NULL;
-       }
+
 
        if (!buffer->buffer) {
                ret = create_shm_buffer(window->display, buffer, window->width,
@@ -197,31 +254,33 @@ static const struct wl_callback_listener frame_listener = {
 static void
 redraw(void *data, struct wl_callback *callback, uint32_t time)
 {
-        struct window *window = static_cast<struct window *>(data);
-        struct buffer *buffer;
+       struct window *window = static_cast<struct window *>(data);
+       struct buffer *buffer;
+
+       prune_old_released_buffers(window);
 
-        buffer = get_next_buffer(window);
-        if (!buffer) {
-                fprintf(stderr,
-                        !callback ? "Failed to create the first buffer.\n" :
-                        "Both buffers busy at redraw(). Server bug?\n");
-                abort();
-        }
+       buffer = get_next_buffer(window);
+       if (!buffer) {
+               fprintf(stderr,
+                               !callback ? "Failed to create the first buffer.\n" :
+                               "Both buffers busy at redraw(). Server bug?\n");
+               abort();
+       }
 
        // do the actual painting
        paint_pixels(buffer->shm_data, 0x0, window->width, window->height, time);
 
-        wl_surface_attach(window->surface, buffer->buffer, 0, 0);
-        wl_surface_damage(window->surface, 0, 0, window->width, window->height);
+       wl_surface_attach(window->surface, buffer->buffer, 0, 0);
+       wl_surface_damage(window->surface, 0, 0, window->width, window->height);
 
-        if (callback)
-                wl_callback_destroy(callback);
+       if (callback)
+               wl_callback_destroy(callback);
 
-        window->callback = wl_surface_frame(window->surface);
-        wl_callback_add_listener(window->callback, &frame_listener, window);
-        wl_surface_commit(window->surface);
+       window->callback = wl_surface_frame(window->surface);
+       wl_callback_add_listener(window->callback, &frame_listener, window);
+       wl_surface_commit(window->surface);
 
-        buffer->busy = 1;
+       buffer->busy = 1;
 }
 
 static void
@@ -449,6 +508,7 @@ handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t s
 
        if (window->wait_for_configure) {
                redraw(window, NULL, 0);
+               window->wait_for_configure = false;
        }
 }
 
@@ -469,7 +529,7 @@ handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
 
        // use our own macro as C++ can't typecast from (void *) directly
        WL_ARRAY_FOR_EACH(p, states, uint32_t *) {
-               uint32_t state = *p; 
+               uint32_t state = *p;
                switch (state) {
                case XDG_TOPLEVEL_STATE_FULLSCREEN:
                        window->fullscreen = 1;
@@ -482,29 +542,18 @@ handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
 
        if (width > 0 && height > 0) {
                if (!window->fullscreen && !window->maximized) {
-                       window->width = width;
-                       window->height = height;
+                       window->init_width = width;
+                       window->init_height = height;
                }
                window->width = width;
                window->height = height;
        } else if (!window->fullscreen && !window->maximized) {
-               if (width == 0)
-                       window->width = WINDOW_WIDTH_SIZE;
-               else
-                       window->width = width;
-
-               if (height == 0)
-                       window->height = WINDOW_HEIGHT_SIZE;
-               else
-                       window->height = height;
+               window->width = window->init_width;
+               window->height = window->init_height;
        }
 
-       /* if we've been resized set wait_for_configure to adjust the fb size 
-        * in the frame callback handler, which will also clear this up */
-       if ((window->width > 0 && window->width != WINDOW_WIDTH_SIZE) &&
-           (window->height > 0 && window->height != WINDOW_HEIGHT_SIZE)) {
-               window->wait_for_configure = true;
-       }
+       window->needs_update_buffer = true;
+
 }
 
 static void
@@ -522,6 +571,7 @@ static struct window *
 create_window(struct display *display, int width, int height, const char *app_id)
 {
        struct window *window;
+       int i;
 
        assert(display->wm_base != NULL);
 
@@ -529,6 +579,7 @@ create_window(struct display *display, int width, int height, const char *app_id
        if (!window)
                return NULL;
 
+       wl_list_init(&window->buffer_list);
        window->callback = NULL;
        window->display = display;
        window->width = width;
@@ -554,6 +605,9 @@ create_window(struct display *display, int width, int height, const char *app_id
                window->wait_for_configure = true;
        }
 
+       for (i = 0; i < MAX_BUFFER_ALLOC; i++)
+               alloc_buffer(window, window->width, window->height);
+
        return window;
 }
 
@@ -561,13 +615,14 @@ create_window(struct display *display, int width, int height, const char *app_id
 static void
 destroy_window(struct window *window)
 {
+       struct buffer *buffer, *buffer_next;
+
        if (window->callback)
                wl_callback_destroy(window->callback);
 
-       if (window->buffers[0].buffer)
-               wl_buffer_destroy(window->buffers[0].buffer);
-       if (window->buffers[1].buffer)
-               wl_buffer_destroy(window->buffers[1].buffer);
+       wl_list_for_each_safe(buffer, buffer_next,
+                             &window->buffer_list, buffer_link)
+               destroy_buffer(buffer);
 
        if (window->xdg_toplevel)
                xdg_toplevel_destroy(window->xdg_toplevel);
@@ -652,6 +707,16 @@ int main(int argc, char *argv[])
        struct receiver_data receiver_data = {};
        struct display *display;
        struct window *window;
+       const char *camera_device = NULL;
+       const char *width_str = NULL;
+       const char *height_str = NULL;
+       int width;
+       int height;
+
+       // pipewire is default.
+       char *v4l2_path = getenv("ENABLE_V4L2_PATH");
+       bool v4l2 = false;
+
        char pipeline_str[1024];
        GError *error = NULL;
        const char *app_id = "camera-gstreamer";
@@ -667,9 +732,36 @@ int main(int argc, char *argv[])
        gargv[0] = strdup(argv[0]);
        gargv[1] = strdup("--gst-debug-level=2");
 
+       camera_device = getenv("DEFAULT_V4L2_DEVICE");
+       if (!camera_device)
+               camera_device = get_first_camera_device();
+
+       width_str = getenv("DEFAULT_DEVICE_WIDTH");
+       if (!width_str)
+               width = WINDOW_WIDTH_SIZE;
+       else
+               width = atoi(width_str);
+
+       height_str = getenv("DEFAULT_DEVICE_HEIGHT");
+       if (!height_str)
+               height = WINDOW_HEIGHT_SIZE;
+       else
+               height = atoi(height_str);
+
+       if (v4l2_path == NULL)
+               v4l2 = false;
+       else if (g_str_equal(v4l2_path, "yes") || g_str_equal(v4l2_path, "true"))
+               v4l2 = true;
+
        memset(pipeline_str, 0, sizeof(pipeline_str));
-       snprintf(pipeline_str, sizeof(pipeline_str), "v4l2src device=%s ! video/x-raw,width=%d,height=%d ! waylandsink", 
-               get_first_camera_device(), WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE);
+
+       if (v4l2)
+               snprintf(pipeline_str, sizeof(pipeline_str), "v4l2src device=%s ! video/x-raw,width=%d,height=%d ! waylandsink",
+                       camera_device, width, height);
+       else
+               snprintf(pipeline_str, sizeof(pipeline_str), "pipewiresrc ! video/x-raw,width=%d,height=%d ! waylandsink",
+      width, height);
+
        gst_init(&gargc, &gargv);
 
        setbuf(stdout, NULL);
@@ -690,7 +782,7 @@ int main(int argc, char *argv[])
                return -1;
 
        // if you'd want to place the video in a pop-up/dialog type of window:
-       // agl_shell_desktop_set_app_property(display->agl_shell_desktop, app_id, 
+       // agl_shell_desktop_set_app_property(display->agl_shell_desktop, app_id,
        //                                 AGL_SHELL_DESKTOP_APP_ROLE_POPUP,
        //                                 WINDOW_WIDTH_POS_X, WINDOW_WIDTH_POS_Y,
        //                                 0, 0, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE,
@@ -699,7 +791,7 @@ int main(int argc, char *argv[])
        // we use the role to set a correspondence between the top level
        // surface and our application, with the previous call letting the
        // compositor know that we're one and the same
-       window = create_window(display, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE, app_id); 
+       window = create_window(display, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE, app_id);
 
        if (!window) {
                free(gargv);
@@ -731,11 +823,12 @@ int main(int argc, char *argv[])
        while (running && ret != -1)
                ret = wl_display_dispatch(display->wl_display);
 
+       gst_element_set_state(pipeline, GST_STATE_NULL);
+       gst_object_unref(pipeline);
+
        destroy_window(window);
        destroy_display(display);
        free(gargv);
 
-       gst_element_set_state(pipeline, GST_STATE_NULL);
-       gst_object_unref(pipeline);
        return ret;
 }