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)?
-
Hi and welcome to devnet,
Aren't you blocking Qt's event loop with your current implementation ?
-
Hi and welcome to devnet,
Aren't you blocking Qt's event loop with your current implementation ?
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. -
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.
-
You run the pipeline in a qprocess. You can stop it by sending Ctrl
+C in this way kill( m_process->processId(), SIGINT );@JoeCFD Thank for the suggestion, I tried to use the regular
getpid()
instead and it worked, the idea is to get pid ofrun_pipeline()
, give widget access to that pid and usekill()
function to sent SIGINT to the process runningrun_pipeline()
. I'm not sure how to find the pid of the qprocess runing therun_pipeline()
. I'm mixing C++ and C in this case, sorun_pipeline()
is in C land and is compiled using a C compiler (gcc), the linker is g++ though. -
@JoeCFD Thank for the suggestion, I tried to use the regular
getpid()
instead and it worked, the idea is to get pid ofrun_pipeline()
, give widget access to that pid and usekill()
function to sent SIGINT to the process runningrun_pipeline()
. I'm not sure how to find the pid of the qprocess runing therun_pipeline()
. I'm mixing C++ and C in this case, sorun_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.
-
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.
@SGaist thank you for the suggestion!
-
@hyperlight auto pid = m_process->processId(); It is in my post.
@JoeCFD no where in my code did I create a QProcess explicitly, so I'm not sure where
m_process
is defined? -
@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