Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to interrupt GStreamer pipeline from Qt?



  • I have a very simple Qt GUI program, it has a push button, when the button is clicked, its slot function will call run_pipeline() which setup and run a GStreamer pipeline (v4l2src -> capsfilter -> xvimagesink). I want to add a Stop button that when clicked will terminate / interrupt the running pipeline. How can I achieve such task?

    I tried to use add signal handler to the pipeline g_unix_signal_add(SIGINT, (GSourceFunc)intr_handler, pipeline);, which send a message that the bus callback function will catch and terminate the pipeline by closing the loop. Below are the code related to GStreamer.

    static guint signal_watch_intr_id;
    
    static gboolean intr_handler(gpointer user_data)
    {
        GstElement *pipeline = (GstElement *)user_data;
    
        /* post an application specific message */
        gst_print("\nPosting UserInterrupt message to bus.\n");
        gst_element_post_message(GST_ELEMENT(pipeline),
                                 gst_message_new_application(GST_OBJECT(pipeline),
                                                             gst_structure_new("UserInterrupt", "message", G_TYPE_STRING, "Pipeline interrupted", NULL)));
    
        /* remove signal handler */
        signal_watch_intr_id = 0;
        return G_SOURCE_REMOVE;
    }
    
    static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data)
    {
        GMainLoop *loop = (GMainLoop *)data;
    
        switch (GST_MESSAGE_TYPE(msg))
        {
        case GST_MESSAGE_EOS:
        {
            g_print("End of stream\n");
            g_main_loop_quit(loop);
            break;
        }
        case GST_MESSAGE_ERROR:
        {
            gchar *debug;
            GError *error;
            gst_message_parse_error(msg, &error, &debug);
            g_printerr("ERROR from element %s: %s\n",
                       GST_OBJECT_NAME(msg->src), error->message);
            if (debug)
                g_printerr("Error details: %s\n", debug);
            g_free(debug);
            g_error_free(error);
            g_main_loop_quit(loop);
            break;
        }
        case GST_MESSAGE_APPLICATION:
        {
            const GstStructure *s;
            s = gst_message_get_structure(msg);
            if (gst_structure_has_name(s, "UserInterrupt"))
            {
                /* this application message is posted when we caught an interrupt and we need to stop the pipeline. */
                g_print("UserInterrupt message posted, stopping pipeline...\n");
                g_main_loop_quit(loop);
            }
            break;
        }
        default:
            break;
        }
        return TRUE;
    }
    
    void run_pipeline()
    {
        GMainLoop *loop = NULL;
        GstElement *pipeline;
        GstBus *bus;
        guint bus_watch_id;
    
        GstElement *source = NULL,
                   *caps_filter = NULL,
                   *sink = NULL;
    
        GstCaps *caps = NULL;
    
        /* Standard GStreamer initialization */
        gst_init(NULL, NULL);
        loop = g_main_loop_new(NULL, FALSE);
    
        pipeline = gst_pipeline_new("gstcam");
        source = gst_element_factory_make("v4l2src", "camera");
        caps_filter = gst_element_factory_make("capsfilter", "cap_filter");
        sink = gst_element_factory_make("xvimagesink", "sink");
    
        if(!pipeline || !source || !caps_filter || !sink)
        {
            g_print("Fail to create elements\n");
            return;
        }
    
        g_object_set(G_OBJECT(source), "device", "/dev/video0", NULL);
        char buffer[100];
        snprintf(buffer, 100,
                 "video/x-raw, width=%d, height=%d, framerate=30/1",
                 640,
                 480);
        caps = gst_caps_from_string(buffer);
        g_object_set(G_OBJECT(caps_filter), "caps", caps, NULL);
        gst_caps_unref(caps);
    
        gst_bin_add_many(GST_BIN(pipeline),
                         source, caps_filter, sink,
                         NULL);
    
        if(!gst_element_link_many(source, sink, NULL))
        {
            g_print("Fail to link elements\n");
            return;
        }
    
        /*Attach signal hanlder to pipeline*/
        signal_watch_intr_id = g_unix_signal_add(SIGINT, (GSourceFunc)intr_handler, pipeline);
    
        /* Add a bus message handler */
        bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
        bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
        gst_object_unref(bus);
    
        /* Set the pipeline to "playing" state */
        g_print("Now playing pipeline\n");
        gst_element_set_state(pipeline, GST_STATE_PLAYING);
    
        /* Wait till pipeline encounters an error or EOS */
        g_print("Running...\n");
        GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "app-playing");
        g_main_loop_run(loop);
    
        /* Out of the main loop, clean up nicely */
        g_print("Returned, stopping playback\n");
        gst_element_set_state(pipeline, GST_STATE_NULL);
        g_print("Deleting pipeline\n");
    
        if (signal_watch_intr_id > 0)
        {
            g_source_remove(signal_watch_intr_id);
        }
    
        g_source_remove(bus_watch_id);
        g_main_loop_unref(loop);
        gst_object_unref(GST_OBJECT(pipeline));
    }
    

    Signal-slot related functions

    void MainWindow::onStartBtnClicked()
    {
        std::cout << "Pipeline started!" << std::endl;
        run_pipeline();
    }
    
    void MainWindow::makeConnections()
    {
        connect(ui->startBtn, &QPushButton::clicked, this, &MainWindow::onStartBtnClicked);
    }
    

    When I click the button, the pipeline start running, but Ctrl+C does not interrupt it.

    How to I interrupt the running pipeline from Qt (by clicking a button)?



  • You run the pipeline in a qprocess. You can stop it by sending Ctrl
    +C in this way kill( m_process->processId(), SIGINT );


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Aren't you blocking Qt's event loop with your current implementation ?



  • You run the pipeline in a qprocess. You can stop it by sending Ctrl
    +C in this way kill( m_process->processId(), SIGINT );



  • @SGaist

    Thank you for the warm welcome.

    Please correct me if I'm wrong. If Qt's event loop is blocked, it means when I click the start button when the pipeline is running, the associated slot function will not be executed right?

    When I clicked the start button when the pipeline is running, onStartBtnClicked() is still executed, the newly created pipeline just complain that the camera is busy (since it's being used by the currently running pipeline) and stop immediately.


  • Lifetime Qt Champion

    Good, then what you should do is encapsulate your GStreamer logic in a class so that you can keep a reference to everything that is created. You can then add a stop function that you will call from your stop button clicked handler.



  • @JoeCFD Thank for the suggestion, I tried to use the regular getpid() instead and it worked, the idea is to get pid of run_pipeline(), give widget access to that pid and use kill() function to sent SIGINT to the process running run_pipeline(). I'm not sure how to find the pid of the qprocess runing the run_pipeline(). I'm mixing C++ and C in this case, so run_pipeline() is in C land and is compiled using a C compiler (gcc), the linker is g++ though.



  • @hyperlight auto pid = m_process->processId(); It is in my post.



  • @SGaist thank you for the suggestion!



  • @JoeCFD no where in my code did I create a QProcess explicitly, so I'm not sure where m_process is defined?



  • @hyperlight This is a member pointer defined in the class header in which it is used.
    Learn some hungary notation. Some people hate it. I use it, but not strictly.
    https://en.wikipedia.org/wiki/Hungarian_notation


Log in to reply