PDA

View Full Version : How to display QPaint Images from other Threads



vijayQt
2nd June 2011, 16:20
Hi,
I am using 2 threads in my program. 1 is GUI thread and other one is created from main application. I am getting frames in other thread and painting and displaying Images in main thread.

After getting frames from other thread I am calling main thread paint function to paint on the widget. Here I am facing problem. I am getting 3 errors

1. "QPixmap: It is not safe to use pixmaps outside the GUI thread"
2. "QPainter::begin: Paint device returned engine == 0, type: 2"
3. "QPainter::end: Painter not active, aborted"

Here is my sudo code

connect(thr, SIGNAL(sDisplayOnWidget()), this, SLOT(convertImg2Pix()))

void frameThread::run() {
mPlayer.loadVideo("/tmp/stream.bin");
while(1) {
mPlayer.loadVideoGetFrame();
printf("we r in thread func \n");
emit sDisplayOnWidget();
}
}



convertImg2Pix() {
p = QPixmap(img.size());
QPainter painter;
painter.begin(&p);
painter.drawImage(0,0,img);
painter.end();
mLabelWidget->setPixmap(p);
}


please help me to get out of this problem.

Thanks in advance...

stampede
2nd June 2011, 16:33
QPixmap belongs to QtGui module, it's not safe to use it in threads other than gui. If you need to draw in other thread, use QImage and then send the result for display to gui thread.

vijayQt
2nd June 2011, 16:50
Thanks for your quick reply... I am doing QPixmap in main thread only.

in my above code "convertImg2Pix()" function is in gui thread.

stampede
2nd June 2011, 17:19
Are you sure about that ? This warning says different.
Add this debug code in both methods:


qDebug() << /*method name here*/ << QThread::currentThread();


in my above code "convertImg2Pix()" function is in gui thread
If it is, then maybe you are using QPixmaps somewhere else in your second thread code.

vijayQt
2nd June 2011, 17:41
Its not calling SLOT :(. Its always in the while loop and printing non-gui thread id :(.

Non GUI thread frameThread(0x9ef78a8)

why its not calling SLOT function?? Please guide me.

stampede
2nd June 2011, 17:56
It's some kind of video player, so I think you should consider the video file fps, setup a QTimer in order to query frames in regular intervals and send them to display.
Now it looks like you want to play the video as fast as possible, it's definitely not the way to go.
Something like this (a pseudo-code):


void VideoThread::run(){
player.loadVideo("video file path");
//...
if( fps > 0 ){ // video fps
timer.setInterval( 1000.0 / fps ); //
connect(timer, SIGNAL(timeout()), this, SLOT(grabFrame()));
timer.start();
} else{
// some kind of error handling
}
}

void VideoThread::grabFrame(){
QImage img = player.getFrame();
emit newFrame(img); // signal connected to some object from the gui thread, with Qt::QueuedConnection
}

//...

void MainGui::frameCallback( const QImage& img ){
qDebug() << "got frame";
QPixmap p = /*img to pixmap*/;
displayWidget->setPixmap(p);
}


I think you can work on the details yourself.

joyer83
2nd June 2011, 18:16
Do your main thread have event loop running? It will not call the convertImg2Pix() slot if it is not running.

vijayQt
2nd June 2011, 18:31
Your guess is correct... I am working on video frames displaying on widget.



void VideoThread::grabFrame(){
QImage img = player.getFrame();
emit newFrame(img); // signal connected to some object from the gui thread, with Qt::QueuedConnection
}

I can not emit from here because this peace of code is belong to MainGUI. What I am doing here is

while(1) {
gPlayer->nextFrame();
QImage img = gPlayer->displayFrame();
emit sDisplayOnWidget(img);
qDebug() << "Non GUI thread" << QThread::currentThread();
msleep(400);
}


but it's not emiting signal. what I am doing is wrong way or am I missing some thing??

what is event loop??? I am using connect call to emit signal from other thread and slot to render the image.
I don't know what it is??... Please can you give sudo code to run event loop.

joyer83
2nd June 2011, 18:38
The event loop is what calls the slots. Read Qt's documentation to find out more.
The main event loop is started with QCoreApplication::exec();

Try running your application inside a debugger, pause execution and check if main thread's call stack contains the exec() call.

EDIT: Also do check that the object which contains the convertImg2Pix()-slot is living in main thread. You can get the object's thread with method QObject::thread().

You should also check that the connect() call successes, like this:


bool ok = connect( ... );
Q_ASSERT( ok );

stampede
2nd June 2011, 18:45
If you can use gui, you can be sure there is an event loop running.

I can not emit from here because this peace of code is belong to MainGUI
This piece of code does not belong to anything, it's just a pseudo-code, you've asked about advice and here it is - I suggest you to setup a timer to grab frames. Don't forget to call "exec()" in your thread's run() method as well.

vijayQt
2nd June 2011, 20:10
Hi Stampede,
I am talking about my code, not your pseudo-code. In comment#8 I pasted my code and that is from MainGUI.



void thread::run() {
while(1) {
gPlayer->nextFrame(); // This function belong to MainGUI thread
QImage img = gPlayer->displayFrame(); // This function belong to Main GUI thread
emit sDisplayOnWidget(img); // from here I am calling SLOT function which is there in again MainGUI thread
qDebug() << "Non GUI thread" << QThread::currentThread();
msleep(400);
}
}




Please let me know whether this is correct way of doing or not???

If it is correct it's not uploading frame on the Label Widget...

stampede
2nd June 2011, 20:46
I don't get it:


// This function belong to MainGUI thread
// This function belong to Main GUI thread
// from here I am calling SLOT function which is there in again MainGUI thread

It looks like you don't need a capture thread at all, if everything you call is from Main thread ;)

Please let me know whether this is correct way of doing or not???
The way it's done now, it's definitely not ok.
Why do you want to stick with the "while(1) + sleep()" approach ? Why are you calling methods from gui thread in capture thread directly ?
In my opinion, you need to *really* understand what's going on and how things are supposed to work before writing the code.
If you really need separate thread, put the capture code in this thread (class) only, and make it emit the 'new frame' signal - make it the only way of communication between GUI and capture thread (plus, some signals for pause, stop etc.).
Think about the interface first, how do you wish to use your player class ?
I think it could be nice to have something like this:


// gui thread:
void MainGui::startVideo( const QString& file ){
delete this->player;
this->player = new VideoPlayer(file);
connect( player, SIGNAL(frame(const QImage&)), this, SLOT(frameCallback(const QImage&)) );
// few more connections, for stop, pause, reverse, fast forward, whatever...
this->player->start();
}

void MainGui::frameCallback( const QImage& img ){
QPixmap p = QPixmap::fromImage(img);
this->ui.display->setPixmap(img);
}

As you can see, all video capture details are hidden from GUI, you dont need to worry how VideoPlayer grabs frames, it's enough to know that it emits a signal and connect to it.
Try to do it this way - hide all video capture details in one class, separate it from gui. Then you can start thinking about how to run it in separate thread.

vijayQt
3rd June 2011, 10:38
Hi stampede
I changed my code little bit. I spawn one thread where I am doing decode frame and final Image will give it to Qt MainGUI thread. it's working fine.

Now my problem is, If I call callback function and spawn decoder thread inside that, it's not working.

Here is my decoder thread:


void QVideoDecoder::run()
{

timer->setInterval(40);
connect(timer, SIGNAL(timeout()), this, SLOT(vDecoderFrame()));
timer->start();
}

void QVideoDecoder::vDecoderFrame()
{
seekNextFrame();
emit vDEmitSignal(LastFrame);
}


Here is my normal working code:


void videoPlayer::playAFile() {
This SLOT belong to MainGUI thread
loadVideo("/tmp/stream.bin");
decoderThr->start();
decoderThr->wait();
}


void videoPlayer::displayOnWidget(const QImage& img) {
printf("We are in videoPlayer::displayOnWidget func()...\n");
mLabelWidget->setPixmap(QPixmap::fromImage(img));
}


This part is working normal way. I am able to see the video on widget

Problem code:


void videoPlayer::pauseAFile()
{
rtspInterface->startDesktopClient();
This is my callback function (It calls only one time)
}

CALLBACK FUNCTION:
void frameReturn(unsigned char* data, int dataSize, void* decoder) {
loadVideo("/tmp/stream.bin");
decoderThr->start();
Here I am spawning decoder thread
decoderThr->wait();
}

This part is not working...
I put some debug logs here and It's going and hitting the decoder run function and not calling vDecoderFrame() SLOT from run function.

Please can you let me know the what I am doing wrong here??

stampede
3rd June 2011, 11:14
Now my problem is, If I call callback function and spawn decoder thread inside that, it's not working.
Again, I don't understand something - how are you supposed to capture frames ? With frame callback method launched by some kind of underlying video capture library, or manually, calling some getNextFrame() method yourself ?

joyer83
3rd June 2011, 14:41
The QVideoDecoder object is created in main thread, so it it lives in that thread too. And as such, its slot vDecoderFrame() is also called by main thread's event loop.
Or, it would be called if the main thread wouldn't be blocked in this call:
decoderThr->wait();

Added after 20 minutes:


I changed my code little bit. I spawn one thread where I am doing decode frame and final Image will give it to Qt MainGUI thread. it's working fine.
As explained above, if it is vDecoderFrame() where you decode it, then your assumption is wrong. You are actually decoding in the main thread.
You need to make the QVideoDecoder to live the new thread you have just created.

Starting the decoder should look something like this:


QThread *decoderThr = new QThread; // just a plain QThread object, not any derived class
QVideoDecoder *decoder = new QVideoDecoder;
decoder->moveToThread( decoderThr ); // move decoder to decoder thread so that it lives in it
bool ok = connect( decoderThr, SIGNAL(started()), decoder, SLOT(run()) ); // execute run()-slot from decoder thread's event loop when the thread has been started
Q_ASSERT( ok );
decoderThr->start(); // start decoder thread

You need to also change QVideoDecoder's base class to QObject and change the run() method to a slot.

vijayQt
3rd June 2011, 15:11
How to resolve this issue?? I removed decoderThr->wait(), still I am facing the same problem :(...
And one more QA here... In back-end I am wrinting data to file and I am loading that same file to read. When callback function called, I am spawning thread to decode that file frame-by-frame and QImage I am emiting to maingui widget.

This is what I am doing.

1. opening file I am doing in maingui function.
2. spawing thread to decode frame by frame.
3. final Image I am emiting from decoder thread
4. catch that Image in maingui widget function and rendering.

Added after 22 minutes:

Here is my pseudo-code:

This is my videodecoder class.

class QVideoDecoder: public QThread
{
public:
void run();

signals:
void vDEmitSignal(QImage);

public slots:
void vDecoderFrame();

}


Here is my maingui class (videoPlayer) :

#include "QVideoDecoder.h"

class videoPlayer : public QMainWindow
{
public:
QVideoDecoder *decoderThr;

public slots:
void openAFile();
void playAFile();
}


Here is my main class:

#include <QApplication>
#include <QtCore>
#include "../inc/videoplayer.h"

int main(int argc, char *argv[]) {
QApplication app(argc, argv);
videoPlayer vp(argc, argv);
vp.show();
app.exec();
return (0);
}


Here is my videoplayer.cpp file:

videoPlayer::videoPlayer(int urlSize, char *url[], QWidget *parent) :
QMainWindow(parent)
{
decoderThr = new QVideoDecoder(*this);
connect(decoderThr, SIGNAL(vDEmitSignal(QImage)), this, SLOT(displayOnWidget(const QImage&)));
}

void frameReturn(unsigned char* data, int dataSize, void* decoder) {
printf("*********************We are in CALLBACK func() . . . ******************\n");
gPlayer->loadVideo("/tmp/stream.bin");
gPlayer->decoderThr->start();
gPlayer->decoderThr->wait();
}