camera-gstreamer: Add fallback sink
[apps/camera-gstreamer.git] / app / main.cpp
index 4451261..2d286ba 100644 (file)
@@ -97,6 +97,8 @@ struct receiver_data {
 };
 
 static int running = 1;
+static bool gst_pipeline_failed = FALSE;
+static bool fallback_gst_pipeline_tried = FALSE;
 
 static void
 redraw(void *data, struct wl_callback *callback, uint32_t time);
@@ -147,7 +149,7 @@ prune_old_released_buffers(struct window *window)
 
        wl_list_for_each_safe(b, b_next,
                              &window->buffer_list, buffer_link) {
-               if (!b->busy && (b->width != window->width || 
+               if (!b->busy && (b->width != window->width ||
                                 b->height != window->height))
                        destroy_buffer(b);
        }
@@ -491,6 +493,19 @@ bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
 
                goto drop;
        }
+       else if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ERROR) {
+               GError* err = NULL;
+               gchar* dbg_info = NULL;
+
+               gst_message_parse_error(message, &err, &dbg_info);
+               g_printerr("ERROR from element %s: %s code %d\n",
+                       GST_OBJECT_NAME(message->src), err->message, err->code);
+               g_printerr("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
+               gst_pipeline_failed = TRUE;
+               g_error_free(err);
+               g_free(dbg_info);
+               goto drop;
+       }
 
        return GST_BUS_PASS;
 
@@ -529,7 +544,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;
@@ -700,73 +715,109 @@ destroy_display(struct display *display)
        free(display);
 }
 
-int main(int argc, char *argv[])
+// stringify the un-quoted string to quoted C string
+#define xstr(a) str(a)
+#define str(a) #a
+
+GstElement* create_pipeline(int* argc, char** argv[])
 {
-       int ret = 0;
-       struct sigaction sa;
-       struct receiver_data receiver_data = {};
-       struct display *display;
-       struct window *window;
+       GError *error = NULL;
        const char *camera_device = NULL;
        const char *width_str = NULL;
        const char *height_str = NULL;
        int width;
        int height;
 
-       char pipeline_str[1024];
-       GError *error = NULL;
-       const char *app_id = "camera-gstreamer";
+       // pipewire is default.
+       char *v4l2_path = getenv("ENABLE_V4L2_PATH");
+       bool v4l2 = false;
 
-       sa.sa_sigaction = signal_int;
-       sigemptyset(&sa.sa_mask);
-       sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
-       sigaction(SIGINT, &sa, NULL);
-
-       int gargc = 2;
-       char **gargv = static_cast<char **>(calloc(2, sizeof(char *)));
-
-       gargv[0] = strdup(argv[0]);
-       gargv[1] = strdup("--gst-debug-level=2");
+       char pipeline_str[1024];
 
        camera_device = getenv("DEFAULT_V4L2_DEVICE");
        if (!camera_device)
                camera_device = get_first_camera_device();
-       width_str = getenv("DEFAULT_V4L2_DEVICE_WIDTH");
+
+       width_str = getenv("DEFAULT_DEVICE_WIDTH");
        if (!width_str)
                width = WINDOW_WIDTH_SIZE;
        else
                width = atoi(width_str);
 
-       height_str = getenv("DEFAULT_V4L2_DEVICE_HEIGHT");
+       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", 
-               camera_device, width, height);
-       gst_init(&gargc, &gargv);
 
-       setbuf(stdout, NULL);
+       if (v4l2)
+               snprintf(pipeline_str, sizeof(pipeline_str), "v4l2src device=%s ! video/x-raw,width=%d,height=%d ! waylandsink",
+                       camera_device, width, height);
+       else if (gst_pipeline_failed == TRUE) {
+               snprintf(pipeline_str, sizeof(pipeline_str), "filesrc location=%s/still-image.jpg ! decodebin ! videoconvert ! imagefreeze ! waylandsink fullscreen=true",
+                       xstr(APP_DATA_PATH), width, height);
+               fallback_gst_pipeline_tried = TRUE;
+       }
+       else {
+               snprintf(pipeline_str, sizeof(pipeline_str), "pipewiresrc ! video/x-raw,width=%d,height=%d ! waylandsink", width,
+                       height);
+       }
 
        fprintf(stdout, "Using pipeline: %s\n", pipeline_str);
 
        GstElement *pipeline = gst_parse_launch(pipeline_str, &error);
+
        if (error || !pipeline) {
                fprintf(stderr, "gstreamer pipeline construction failed!\n");
-               free(gargv);
-               return EXIT_FAILURE;
+               free(argv);
+               return NULL;
        }
 
-       receiver_data.pipeline = pipeline;
+       return pipeline;
+}
+
+int main(int argc, char* argv[])
+{
+       int ret = 0;
+       struct sigaction sa;
+       struct receiver_data receiver_data = {};
+       struct display* display;
+       struct window* window;
+       const char* app_id = "camera-gstreamer";
+
+       sa.sa_sigaction = signal_int;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
+       sigaction(SIGINT, &sa, NULL);
+
+       int gargc = 2;
+       char** gargv = static_cast<char**>(calloc(2, sizeof(char*)));
+
+       gargv[0] = strdup(argv[0]);
+       gargv[1] = strdup("--gst-debug-level=2");
+
+       setbuf(stdout, NULL);
+
+       gst_init(&gargc, &gargv);
+
+       receiver_data.pipeline = create_pipeline(&gargc, &gargv);
+
+       if (!receiver_data.pipeline)
+               return EXIT_FAILURE;
 
        display = create_display(argc, argv);
        if (!display)
                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,
@@ -775,7 +826,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);
@@ -793,22 +844,34 @@ int main(int argc, char *argv[])
                redraw(window, NULL, 0);
        }
 
-       GstBus *bus = gst_element_get_bus(pipeline);
+       GstBus *bus = gst_element_get_bus(receiver_data.pipeline);
        gst_bus_add_signal_watch(bus);
 
        g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), &receiver_data);
        gst_bus_set_sync_handler(bus, bus_sync_handler, &receiver_data, NULL);
        gst_object_unref(bus);
 
-       gst_element_set_state(pipeline, GST_STATE_PLAYING);
+       gst_element_set_state(receiver_data.pipeline, GST_STATE_PLAYING);
        fprintf(stdout, "gstreamer pipeline running\n");
 
        // run the application
-       while (running && ret != -1)
+       while (running && ret != -1) {
                ret = wl_display_dispatch(display->wl_display);
-
-       gst_element_set_state(pipeline, GST_STATE_NULL);
-       gst_object_unref(pipeline);
+               if (gst_pipeline_failed && fallback_gst_pipeline_tried == FALSE) {
+                       gst_element_set_state(receiver_data.pipeline, GST_STATE_NULL);
+                       gst_object_unref(receiver_data.pipeline);
+                       /* retry with fallback pipeline */
+                       receiver_data.pipeline = create_pipeline(&gargc, &gargv);
+                       GstBus *bus = gst_element_get_bus(receiver_data.pipeline);
+                       gst_bus_add_signal_watch(bus);
+                       g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), &receiver_data);
+                       gst_bus_set_sync_handler(bus, bus_sync_handler, &receiver_data, NULL);
+                       gst_object_unref(bus);
+                       gst_element_set_state(receiver_data.pipeline, GST_STATE_PLAYING);
+               }
+       }
+       gst_element_set_state(receiver_data.pipeline, GST_STATE_NULL);
+       gst_object_unref(receiver_data.pipeline);
 
        destroy_window(window);
        destroy_display(display);