Crash in GStreamer qmlglsink pipeline dynamically rebind to different GstGLVideoItem - c++

I've used one of existing qmlglsink examples to stream video feed from 4 IP Cameras.
4 Pipelines are created before engine load.
for(int i = 0; i < maxCameras; ++i) {
GstElement* pipeline = gst_pipeline_new (NULL);
GstElement* src = gst_element_factory_make ("udpsrc", NULL);
GstElement* parse = gst_element_factory_make ("jpegparse", NULL);
GstElement* decoder = gst_element_factory_make ("jpegdec", NULL);
GstElement* glcolorconvert = gst_element_factory_make ("glcolorconvert", NULL);
GstElement* glupload = gst_element_factory_make ("glupload", NULL);
GstElement *sink = gst_element_factory_make ("qmlglsink", NULL);
g_assert (src && parse && decoder && glupload && glcolorconvert && sink);
g_object_set (G_OBJECT (src), "port", startingPort + i, NULL);
g_object_set (G_OBJECT (sink), "sync", FALSE, NULL);
gst_bin_add_many (GST_BIN (pipeline), src, parse, decoder, glupload, glcolorconvert, sink, NULL);
if (!gst_element_link_many ( src, parse, decoder, glupload, glcolorconvert, sink, NULL)) {
qDebug() << "Linking GStreamer pipeline elements failed";
}
sinks.insert(std::make_pair(QString::number(startingPort+i), sink));
pipelines.insert(std::make_pair(QString::number(startingPort+i), pipeline));
}
In Qml sink is connected and processed with
import QtQuick 2.15
import QtQuick.Layouts 1.15
import CustomProject 1.0
import org.freedesktop.gstreamer.GLVideoItem 1.0
Item {
id: root
signal clicked()
required property int udpPort
property var camConnect: undefined
onUdpPortChanged: { setupConnection(); }
onVisibleChanged: {
if (visible) { setupConnection();
} else { camConnect = undefined }
}
GstGLVideoItem {
id: videoItem
anchors.fill: parent
function connect() {
CameraSinksFactory.connectSink(this, udpPort)
}
}
MouseArea {
anchors.fill: parent
onClicked: {
CameraSinksFactory.stopPipeline(udpPort)
root.clicked()
}
}
function setupConnection() {
if (udpPort <= 0 || !root.visible) return;
videoItem.connect()
root.camConnect = CameraSinksFactory.getCamConnection(udpPort);
root.camConnect.resolutionX =// - 15 root.width
root.camConnect.resolutionY = root.height
root.camConnect.bitrate = 15000000
root.camConnect.streaming = root.visible
CameraSinksFactory.startPipeline(udpPort)
}
}
Problem: Main screen display 4 (2x2 grid) items using Model (which provides udpPort as unique ID). When User clicks on one Item - feed from this camera should fill whole screen. In Examples they create GridLayout with 4/6 explicit items and just manipulate their visiblity (in effect clicked item is only one remaining and take whole screen).
In my case - I'm using separate Item for full screen view. So I'm disabling streaming (CamConnection class communicating with Cameras and sending commands) and hide GridView. New GstGLVideoItem binds to qmlglsink in pipeline.
Everything is OK, until I repeat click sequence (back to GridView and to fullview). Every time it ends with:
Bail out! ERROR:../ext/qt/gstqsgtexture.cc:134:virtual void
GstQSGTexture::bind(): code should not be reached
** (KMS:20495): CRITICAL **: 15:47:36.937: gst_video_frame_map_id: assertion 'info->width <= meta->width' failed
** ERROR:../ext/qt/gstqsgtexture.cc:134:virtual void GstQSGTexture::bind(): code should not be reached
From plugins code analysis it's happening when INFO (image size read from CAPS) is bigger then size in metadata in provided buffer. Which is understandable - buffer is too small.
I used GST_DEBUG=4/5/6/7 and logs confirm that autodetected caps are matching request in commands sent to camera.
I can use example, but project assumes another panel with those cameras - so above problem will hit me in near future.
How to make this whole setup working? How to rebind pipeline qmlglsink to new QML VideoItem safely?

Two possible solutions:
set gst_element_set_state (pipeline, GST_STATE_NULL);, change sink widget to new item and start pipeline gst_element_set_state (pipeline, GST_STATE_PLAYING);
use Qt 5 MediaPlayer with gst-pipeline as source. When visible set source and execute start(). When not visible reset source to empty (important) and execute stop().
In general - possible benefits from NOT creating pipeline each time are not worth hassle when we dynamically assign new QML Item to pipeline sink.

Related

GStreamer qmlglsink vs gst_parse_launch()

I'm new to Qt and GStreamer, but I need to create a simple player for a QuickTime/H.264 video file into a Qt 5.15.2 application (running on Linux UbuntuĀ 20.04 (Focal Fossa)).
I managed to play a standard videotestsrc (bouncing ball pattern) inside my application, and this is the code (main.cpp):
#include "mainwindow.h"
#include <QApplication>
#include <QQuickView>
#include <QWidget>
#include <QQuickItem>
#include <gst/gst.h>
int main(int argc, char *argv[])
{
GstElement* mPipeline = nullptr;
GstElement* mSource = nullptr;
GstElement* mGLUpload = nullptr;
GstElement* mSink = nullptr;
QQuickView* mView = nullptr;
QWidget* mWidget = nullptr;
QQuickItem* mItem = nullptr;
gst_init(argc, argv);
QApplication app(argc, argv);
MainWindow* window = new MainWindow;
mPipeline = gst_pipeline_new(NULL);
mSource = gst_element_factory_make("videotestsrc", NULL);
mGLUpload = gst_element_factory_make("glupload", NULL);
mSink = gst_element_factory_make("qmlglsink", NULL);
gst_bin_add_many(GST_BIN (mPipeline), mSource, mGLUpload, mSink, NULL);
gst_element_link_many(mSource, mGLUpload, mSink, NULL);
g_object_set(mSource, "pattern", 18, NULL);
mView = new QQuickView;
mView->scheduleRenderJob(new SetPlaying (mPipeline),
QQuickView::BeforeSynchronizingStage);
mView->setSource(QUrl(QStringLiteral("qrc:/video.qml")));
mWidget = QWidget::createWindowContainer(mView, parent);
mItem = mView->findChild<QQuickItem*>("videoItem");
window->setCentralWidget(mWidget);
window->show();
ret = app.exec();
g_object_set(mSink, "widget", mItem, NULL);
gst_deinit();
}
SetPlaying class...
#include <QRunnable>
#include <gst/gst.h>
class SetPlaying : public QRunnable
{
public:
SetPlaying(GstElement *pipeline) {
this->pipeline_ = pipeline ? static_cast<GstElement *> (gst_object_ref (pipeline)) : NULL;
}
~SetPlaying() {
if (this->pipeline_)
gst_object_unref (this->pipeline_);
}
void run () {
if (this->pipeline_)
gst_element_set_state (this->pipeline_, GST_STATE_PLAYING);
}
private:
GstElement * pipeline_;
};
The MainWindow code should not be relevant for the issue management (it's a standard empty window).
This is the source code of the only .qml item that's needed to provide an acceptable widget surface to qmlglsink:
import QtQuick 2.15
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.3
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.1
import org.freedesktop.gstreamer.GLVideoItem 1.0
Item {
anchors.fill: parent
GstGLVideoItem {
id: video
objectName: "videoItem"
anchors.centerIn: parent
width: parent.width
height: parent.height
}
}
Now since the actual pipeline to plays the file is quite long and complex to manage the # code I opted to use a gst_parse_launch() approach.
To proceed step by step, I tried to use such a method to create a videotestsrc pipeline, i.e.:
mPipeline = gst_parse_launch( "videotestsrc ! glupload ! qmlglsink", NULL);
mSink = gst_bin_get_by_name(GST_BIN(mPipeline), "sink");
mSource = gst_bin_get_by_name(GST_BIN(mPipeline), "source");
If I run the code this is the result:
(videotest:14930): GLib-GObject-CRITICAL **: 16:33:08.868: g_object_set: assertion 'G_IS_OBJECT (object)' failed
(videotest:14930): GLib-GObject-CRITICAL **: 16:33:09.342: g_object_set: assertion 'G_IS_OBJECT (object)' failed
Of course, the application window displays nothing.
You should give the elements a name property. They will default to ones, but they include a numerical value and are incremented whenever you rebuild the pipeline. So it is better to not rely on those.
To make your existing code work, try this:
mPipeline = gst_parse_launch( "videotestsrc name=source ! glupload ! qmlglsink name=sink", NULL);

Impossible to convert 'WXWidget' in 'GtkWidget *

Here is my code :
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
// Without this line, GStreamer will create its own new window for the video stream.
gtk_widget_realize(video_window);
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
were pnlView is define as wxPanel* pnlView;
But the consol give me this error : Impossible to convert 'WXWidget' in 'GtkWidget * at the line where I initialize video_window
Can someone has any idea how to fix it ?
I just want to add my gstreamer window in my wxWindow
Thank you
I've never used gstreamer, but I do sometimes use libvlc which is probably very similar. When using libvlc to render into a wxWindow, I need to wait until the wxWindow is fully created before I can set vlc to use it. This is done by adding an event handler for the window creation event.
The process of declaring and binding the event handler looks like this:
class MainWindow : public wxFrame
{
...
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent& event);
}
MainWindow::MainWindow(...)
{
...
#ifdef __WXGTK__
// On GTK+, we have to wait until the window is actually created before we
// can tell VLC to use it for output. So wait for the window create event.
pnlView->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
#elif defined(__WXMSW__)
m_player.setHwnd(pnlView->GetHandle());
#endif
...
}
For libvlc, my window creation event handler looks like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
m_player.setXwindow(
gdk_x11_window_get_xid(gtk_widget_get_window(pnlView->GetHandle()))
);
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Based on the code you posted, I think the body of an event handler that will work for you should look something like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Edit:
Here's a simple example of using GStreamer to draw onto a wxWindow on GTK. This shows how to use the wxEVT_CREATE to get the XID for a window and how to use GStreamer's bus sync handler callback to pass that XID to GStreamer at the correct time.
This is basically a mashup of the 2nd tutorial and the code snippet from the GstVideoOverlay page adjusted for wxWidgets. Since this is based on the 2nd tutorial, it just shows a test pattern. The source variable can be changed to show other videos.
Obviously this assumes GTK is using X11. Some adjustments would be needed if Wayland is used instead, but I don't have a running distro that uses Wayland to test on, so I don't know what changes are needed there.
#include "wx/wx.h"
#ifdef __WXGTK__
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#endif
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
class MainWindow : public wxFrame
{
public:
MainWindow(const wxString& title);
~MainWindow();
private:
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent&);
void OnPlay(wxCommandEvent&);
void OnStop(wxCommandEvent&);
// Helper function
void LoadVideo();
void PlayHelper();
// wx controls
wxWindow* m_renderWindow;
wxButton* m_playButton;
wxButton* m_stopButton;
// GStreamer data
GstElement* m_pipeline;
guintptr m_xid;
};
MainWindow::MainWindow(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
// Create the UI widgets.
wxPanel* bg = new wxPanel(this,wxID_ANY);
m_renderWindow = new wxWindow(bg,wxID_ANY);
m_playButton = new wxButton(bg,wxID_ANY,"Play");
m_stopButton = new wxButton(bg,wxID_ANY,"Stop");
m_renderWindow->SetBackgroundColour(*wxBLACK);
m_playButton->Enable(true);
m_stopButton->Enable(false);
// Layout the UI.
wxBoxSizer* szr1 = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* szr2 = new wxBoxSizer(wxHORIZONTAL);
szr2->Add(m_playButton, wxSizerFlags(0).Border(wxLEFT|wxRIGHT|wxBOTTOM));
szr2->Add(m_stopButton, wxSizerFlags(0).Border(wxRIGHT|wxBOTTOM));
szr1->Add(m_renderWindow, wxSizerFlags(1).Expand().Border(wxBOTTOM));
szr1->Add(szr2, wxSizerFlags(0));
bg->SetSizer(szr1);
Layout();
// Set up the event handlers.
#ifdef __WXGTK__
m_renderWindow->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
m_playButton->Enable(false);
#endif
m_playButton->Bind(wxEVT_BUTTON, &MainWindow::OnPlay, this);
m_stopButton->Bind(wxEVT_BUTTON, &MainWindow::OnStop, this);
// Initialize GStreamer.
m_xid = 0;
m_pipeline = NULL;
gst_init(NULL, NULL);
}
MainWindow::~MainWindow()
{
if ( m_pipeline )
{
gst_element_set_state(m_pipeline, GST_STATE_NULL);
gst_object_unref(m_pipeline);
}
}
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent&)
{
#ifdef __WXGTK__
// This event is no longer needed.
m_renderWindow->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
// Get the XID for this window.
m_xid = GDK_WINDOW_XID(gtk_widget_get_window(m_renderWindow->GetHandle()));
// We can now load and play the video, so enable the play button.
m_playButton->Enable(true);
#endif
}
void MainWindow::OnPlay(wxCommandEvent&)
{
if ( m_pipeline )
{
PlayHelper();
}
else
{
LoadVideo();
}
}
void MainWindow::OnStop(wxCommandEvent&)
{
if ( m_pipeline )
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PAUSED);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the paused state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
}
}
void MainWindow::LoadVideo()
{
// Create the elements
GstElement *source = gst_element_factory_make("videotestsrc", "source");
#ifdef __WXGTK__
GstElement *sink = gst_element_factory_make("xvimagesink", "sink");
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink), m_xid);
#elif defined __WXMSW__
GstElement *sink = gst_element_factory_make("d3dvideosink", "sink");
WXWidget hwnd = m_renderWindow->GetHandle();
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink),
reinterpret_cast<guintptr>(hwnd));
#endif
//Create the empty pipeline
m_pipeline = gst_pipeline_new ("test-pipeline");
if ( !m_pipeline || !source || !sink )
{
wxLogError("Not all elements could be created.");
return;
}
// Build the pipeline
gst_bin_add_many(GST_BIN(m_pipeline), source, sink, NULL);
if ( gst_element_link(source, sink) != TRUE )
{
wxLogWarning("Elements could not be linked.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
return;
}
// Modify the source's properties
g_object_set(source, "pattern", 0, NULL);
PlayHelper();
}
void MainWindow::PlayHelper()
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the playing state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(false);
m_stopButton->Enable(true);
}
}
class MyApp : public wxApp
{
public:
bool OnInit() override
{
MainWindow* mainWindow = new MainWindow("wxWidgets GStreamer demo");
mainWindow->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On mint it looks like this:
On windows it looks like this:

How to display video in a portion of window using d3dvideosink in Windows

I have written following gstreamer function to display the videotestsrc video on a Win32 Window(HWND) in Windows. This function works perfectly and displays videotestsrc in the entire window for the given "win" window handle.
void runGstPipe(HWND win)
{
GstElement *pipeline =
gst_parse_launch
("rtspsrc location=\"...\" ! decodebin ! d3dvideosink name=sink", NULL);
GstElement *sink = gst_bin_get_by_name(GST_BIN(pipeline), "sink");
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink), (guintptr)win);
GstStateChangeReturn sret = gst_element_set_state(pipeline,
GST_STATE_PLAYING);
}
Next I tried to enhance the above function to display the videotestsrc in a portion of the window "win" using the following options.
a) By using glimagesink with render-rectangle option as follows
"rtspsrc location=\"...\" ! decodebin ! glvideosink render-rectange=\"<50, 50, 200, 150>\" name=sink"
b) By using gst_video_overlay_set_render_rectangle as follows
gst_video_overlay_set_render_rectangle(GST_VIDEO_OVERLAY(sink), 50, 50, 200, 150);
Both above options did not change the rendering area. i.e., videotestsrc still occupied whole window, instead of given coordinates. Appreciate, if I can get any suggestions.

Gstreamer basic pipeline running but not displaying on windows 7 virtualbox

I am currently working with Gstreamer on Windows 7 (x86_64) as a VM (Virtualbox) and I wanted to run a basic pipeline:
gst-launch-1.0 -v videotestsrc pattern=snow ! autovideosync
When I run this pipeline I get:
Setting pipeline to PAUSED...
Pipeline is PREROLLING
And then an error occurs:
Pipeline doesn't want to preroll
I solved this error by adding async-handling=true at the end of the pipeline but nothing is still displaying...
I tried to run the same pipeline writing C++ code. Here is a simple main you can run. When I run this code, I get no error but nothing is displaying.
#include <gst/gst.h>
#include <glib.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
GMainLoop *loop;
GstElement *pipeline, *source, *sink;
g_print("Starting...");
/* Initialisation */
gst_init(&argc, &argv);
g_print("Loop is created...");
loop = g_main_loop_new(NULL, FALSE);
/* Create gstreamer elements */
pipeline = gst_pipeline_new("gst-app-sink");
source = gst_element_factory_make("videotestsrc", "src");
sink = gst_element_factory_make("autovideosink", "sink");
if (!pipeline || !source || !sink) {
g_printerr("One element could not be created. Exiting.\n");
return -1;
}
/* Set up the pipeline */
/* we add all elements into the pipeline */
/* source | sink */
gst_bin_add_many(GST_BIN(pipeline), source, sink, NULL);
/* we link the elements together */
/* src -> sink */
gst_element_link(source, sink);
/* Set the pipeline to "playing" state*/
gst_element_set_state(pipeline, GST_STATE_PLAYING);
/* Iterate */
g_print("Running...\n");
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");
gst_object_unref(GST_OBJECT(pipeline));
g_main_loop_unref(loop);
return 0;
}
I really don't know where it could come from. Any ideas?
By default, the VM doesn't enable 2D and 3D video acceleration which is necessary to display these kind of stream. Just right-click on you VM -> Settings -> Display and check "Enable 3D acceleration" and "Enable 2D Video Acceleration".

OpenCV blocking my GTK interface

I've coded a very simple GUI with GTK3, with three buttons:
Start: calls OpenCV VideoCapture
Stop: stops the VideoCapture
Quit: destroys the window
The problem arises when I hit "Stop", the OpenCV process blocks the GUI and the command is executed after many seconds (typically, a minute).
this is my main:
bool stop = false;
static void start_webcam (GtkWidget *widget, gpointer data)
{
cout<<"Webcam On"<<endl;
int err = 0;
**err = opencv_webcam(); // this function loads the OpenCV Webcam**
// troubles?
if (err != 0)
{
cout<<"Unable to load the webcam. Error Code # "<<err<<endl;
}
}
static void stop_webcam (GtkWidget *widget, gpointer data)
{
stop = true;
cout<<"Webcam Off"<<endl;
}
int main (int argc, char *argv[])
{
// Widgets
GtkWidget *window;
GtkWidget *grid;
GtkWidget *button;
// GTK Init
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "WEBCAM CONTROL PANEL");
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
grid = gtk_grid_new ();
gtk_container_add (GTK_CONTAINER (window), grid);
/// START WEBCAM
button = gtk_button_new_with_label ("Start");
g_signal_connect (button, "clicked", G_CALLBACK (start_webcam), NULL);
gtk_grid_attach (GTK_GRID (grid), button, 0, 0, 1, 1);
/// STOP WEBCAM
button = gtk_button_new_with_label ("Stop");
g_signal_connect (button, "clicked", G_CALLBACK (stop_webcam), NULL);
gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 1);
/// QUIT
button = gtk_button_new_with_label ("Quit");
g_signal_connect (button, "clicked", G_CALLBACK (gtk_main_quit), NULL);
gtk_grid_attach (GTK_GRID (grid), button, 0, 1, 2, 1);
/// GO!
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
I would like to set some high priority to the Quit button. I tried by setting the highest priority in my main by SetPriorityClass() in Windows, but I was unsuccessful.
Thank you very much!
EDIT
this is my openCV code:
extern bool stop;
int opencv_webcam()
{
/// Various stuff
cv::Mat frame;
cv::namedWindow("Webcam Session");
/// Webcam Opening
cv::VideoCapture clip(0);
while(stop != true)
{
// frames extraction
clip.grab();
clip.retrieve(frame);
// troubles?
if ((frame.empty()==1))
{
// errore!
return -1;
}
/// SHOW the frames
cv::imshow("Webcam Session", frame);
if (waitKey(30)>= 0)
{
break;
}
}
return 0;
}
GTK+ is NOT multithreaded. When one of your signal handlers is running, all other GUI processing stops. Your opencv_webcam() function is running on the GUI thread, and runs its main loop while processing the Start button signal. The reason why your Stop and Quit buttons aren't working is because they never get a chance to run (at least not until opencv_webcam() errors out).
I don't know what the appropriate solution is; I don't know enough about OpenCV to suggest a solution that lets you have stable timing while playing nice with GTK+. I know of two possibilities, though:
Doing all OpenCV processing in a gdk_threads_add_idle() callback (stable timing will be harder)
Running OpenCV on a different thread (you will need to communicate frame images across threads if you want to show them in your window)
You will need to search around and figure out what the best solution is. Try Googling for "gtk opencv" and seeing what you get.
Alternatively, if you're doing strict webcam work, you might want to look at libcheese, which is a GTK+ complement specifically intended for webcams.
As for button priorities, there's no signal prioritization that I know of. Given the diagnosis of your bug above, you should be able to see why prioritization wouldn't have helped anyway. In fact, I don't know of any GUI toolkit for any platform that has that ability. You should instead fix threading bugs that prevent the GUI from being snappy.