Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the simplest way to fire and forget a thread in c++ / Qt on linux?

I'm writing an app that embeds multiple libVlc instances running simultaneously using Qt. There seems to be a bug in the vlc library where sometimes libvlc_media_player_stop deadlocks if called from the Qt's GUI thread. On one of the videolan forums, the accepted solution was to call the stop function from another thread. I'm looking for the least involved and not-too-ugly method to call stop from a different thread. I looked at using QThreadPool which is meant exactly for this kind of situations but in my particular case, it doesn't make the solution pretty.

Here's a piece of my code:

VlcWidget.h

    class VlcWidget : public QWidget
    {
        Q_OBJECT

    private:

        // State
        bool _isPlaying;

        // The streaming source, title and quality data
        VideoData _videoData;
        VIDEO_QUALITY _quality;

        // LibVlc related members
        libvlc_instance_t *_vlcInstance;
        libvlc_media_player_t *_vlcMediaPlayer;
        libvlc_media_t *_vlcMedia;
        int _vlcTrackID;
    }

VlcWidget.c

    void VlcWidget::Play()
    {
        if(_videoData.Source() != "" && !_isPlaying)
        {
            // Create a new media descriptor
            _vlcMedia = libvlc_media_new_location(
                          _vlcInstance,
                          _videoData.Source().toStdString().c_str());

            // Tell the user about incorrect URL
            if(_vlcMedia == NULL)
            {
                QMessageBox::information(this,
                                         _videoData.Title(),
                                         "Unable to open source Url.\nPlease check the source and try again.");
                return;
            }

            libvlc_media_player_set_media(_vlcMediaPlayer, _vlcMedia);
            libvlc_media_release(_vlcMedia);
            libvlc_media_player_set_xwindow(_vlcMediaPlayer, parentWidget()->winId());
            libvlc_media_player_play(_vlcMediaPlayer);
            _vlcTrackID = libvlc_audio_get_track(_vlcMediaPlayer);
            _isPlaying = true;
        }
    }

    void VlcWidget::Stop()
    {
        if(_isPlaying)
        {
            libvlc_media_player_stop(_vlcMediaPlayer);
            _vlcTrackID = -1;
            _isPlaying = false;
        }
    }

My solution using QthreadPool looked like:

    class AsyncVlcPlay : public QRunnable
    {
    private:
         // State
        bool *_isPlaying;

        // LibVlc related members
        libvlc_instance_t *_vlcInstance;
        libvlc_media_player_t *_vlcMediaPlayer;
        libvlc_media_t *_vlcMedia;
        int *_vlcTrackID;

    public:
        virtual void run();
    }

And AsyncVlcPlay::run() does exactly what VlcWidget::Play() does with simple locking added to it. And I'll also need a similar class for VlcWidget::Stop(). I don't like this solution because I shouldn't really need 2 new classes for what I'm trying to achieve. And worse than that is the fact that I'll have to pass VlcWidgets private members to another class' object. I'm pretty sure there's an extremely simple way that I'm not aware of and hope that one of you guys can help me out here. Thanks!

(In fact, I don't really need VlcWidget::Play() to be on another thread, but I'd like to keep Play and Stop symmetric)

like image 258
Kulki Avatar asked Nov 12 '22 21:11

Kulki


1 Answers

I would tackle this problem with QThread. Its name is actually misleading as it's not actually a thread, but a thread controller and very easy to use.

Any class inherited from QObject can be moved to a thread and communication between threads can be done with the signal / slot mechanism. Therefore, you can do something like this: -

class VlcObject : public QObject
{
    Q_OBJECT

    public slots:
        void Run();

    private slots;
        void StopVlc();
};

This class can contain all the Vlc objects / instances. You then create a thread controller object and move the VlcObject instance to the new thread: -

QThread* pThread = new QThread(this); // where this is a parent, running on the main thread
VlcObject* pVlcObj = new VlcObject;

pVlcObj->moveToThread(pThread);

// Note, this is Qt 5 connect style - Qt 4 connections also work
connect(pThread, &QThread::started, pVlcOj, &VlcObject::Run();

// Start the thread running
pThread->start();

Assuming QVlcWidget is a GUI class with a button called pStopVlc, you then call stop on the other thread's VlcObject by connecting the button to the VlcObject's StopVlc function: -

connect(pStopVlc, &QPushButton::released, pVlcObj, &VlcObject::StopVlc);

Alternatively, you could have StopVlc called when the thread quits and it is possible for the QThread to clean itself up when it stops: -

connect(pThread, &QThread::finished, pThread, &Qthread::deleteLater);
like image 197
TheDarkKnight Avatar answered Nov 15 '22 11:11

TheDarkKnight