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

How to properly close QThread



  • Hey,
    I'm trying to play an audio file via QAudioOutput.
    The Idea is, that the User can start an audio playback and stop an audio playback at any time. This is the reason for wrapping QAudioOutput in a QThread.
    Now I'm not sure if I'm doing it the right way.
    I'm using QT 5.12.8.

     class IRCAM2020_MOD_AUDIO_DECL AudioOutputNewThread : public QThread, public AudioSettings
      {
        Q_OBJECT
      public:
    
        /**
         * @brief Constructor
         * @param strFileNameArg Filename to play
         * @param audioOutputDeviceArg Outputdevice where to play the Audio
         * @param parent Parent Element
        */
        explicit AudioOutputNewThread(const std::string& strFileNameArg, QAudioDeviceInfo audioOutputDeviceArg = QAudioDeviceInfo::defaultOutputDevice(), QObject* parent = nullptr);
    
        ///Destruktor
        ~AudioOutputNewThread();
    
      protected:
        /**
         * @brief Runs this thread
        */
        void run() override;
    
      private:
    
        ///Path to the source file to play
        const std::string strFileName;
    
        ///the Src File to play => set by constructor
        QFile sourceFile;
    
        ///Pointer to the AudioOutput
        std::unique_ptr<QAudioOutput> m_pAudio;
    
        ///the audio output device where to play the music
        const QAudioDeviceInfo audioOutputDevice;
    
        /**
         * @brief Starts playing the File
         * @return true if file exists and if audio output was started correctly
        */
        bool StartPlayFile();
    
      signals:
    
        /**
         * @brief This thread signals an error if an error occurs
        */
        void error(const std::string strErrorMsg);
    
      public slots:
    
        /**
         * @brief Called when audio device changed the state
        */
        void handleStateChanged(QAudio::State newState);
    
      };
    

    My Implementation looks like this

    AudioOutputNewThread::AudioOutputNewThread(const std::string& strFileNameArg, QAudioDeviceInfo audioOutputDeviceArg, QObject* parent) :
        QThread(parent),
        strFileName(strFileNameArg),
        audioOutputDevice(audioOutputDeviceArg)
      {
        setTerminationEnabled(true);
        sourceFile.setFileName(QString::fromStdString(strFileName));
        if (!sourceFile.exists())
        {
          //throw some exception...
        }
    
      }
    
    
    
      AudioOutputNewThread::~AudioOutputNewThread()
      {
        if (nullptr != m_pAudio)
        {
          m_pAudio->stop();
        }
        exit(0);
        wait();
      }
    
    
    void AudioOutputNewThread::run()
      {
        //Thread starts here    
        qDebug() << "Starting task";
        if (!StartPlayFile())
        {
          emit error("Failed to play file");
        }
    
        connect(m_pAudio.get(), SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));
        const int iExcecRes = exec();
        if (0 != iExcecRes)
        {
          emit error("Failed to excecute thread. Exec Result " + iExcecRes);
        }
      }
    
    bool AudioOutputNewThread::StartPlayFile()
      {
    
        if (!sourceFile.open(QIODevice::ReadOnly))
        {
          qDebug() << "Failed to open file " << QString::fromStdString(strFileName);
          return false;
        }
    
        auto format = GetDefaultAudioFormat();
        
    
        qDebug() << "Playing to output device" << audioOutputDevice.deviceName() << "with format " << format;
    
        QAudioDeviceInfo info(audioOutputDevice);
        if (!info.isFormatSupported(format)) {
          qWarning() << "Raw audio format not supported by backend, cannot play audio.";
          format = audioOutputDevice.preferredFormat();
          qDebug() << "new format " << format;
        }
    
        m_pAudio = std::make_unique<QAudioOutput>(format, nullptr);
    
    
        m_pAudio->start(&sourceFile);
        int res = exec();
        return (0 == res);
      }
    
    void AudioOutputNewThread::handleStateChanged(QAudio::State newState)
      {
        qDebug() << "Audio playback state changed: " << newState;
        switch (newState) {
        case QAudio::IdleState:
          qDebug() << "Finnished playing audio";
          exit(0);
          break;
    
        case QAudio::StoppedState:
          // Stopped for other reasons
          if (m_pAudio->error() != QAudio::NoError) {
            qWarning() << "Stopped device for other reasons...";
            exit(0);
          }
          break;
    
        default:
          qWarning() << "handle state changed default state...";
          break;
        }
      }
    
    

    now to make things easy this class is called from a GTest case. The actual test case is pretty straight forward

    
    class UnitTestAudio : public ::testing::Test
    {
    public:
    protected:
    
      const std::string strWavFileName = "test.wav";
    
      //this list contains all available outputs acquired via QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
      const QList<QAudioDeviceInfo> lsOutputDevice;
    
    
    };
    
    
    
    TEST_F(UnitTestAudio, PlayQtAudioOutputThreadNew)
    {
      for (auto it = lsOutputDevice.begin(); it != lsOutputDevice.end(); it++)
      {
        qDebug() << "UnitTest: Start playing audio on output device "<<it->deviceName();
        auto pThread = std::make_unique< AudioOutputNewThread>(strWavFileName, *it);
    
        pThread->start();
        qDebug() << "Sleeping for 5s....";
        const auto sleepTime = 5s;
        std::this_thread::sleep_for(sleepTime);
        qDebug() << "UnitTest: finnished Playing " + QString::fromStdString(strWavFileName);
      }
    }
    
    

    Now I facing 2 problems:

    • On Windows is stuck at the End of AudioOutputNewThread Destruktor. (Probably within wait())

    • Linux on the other hand tells me

    "UnitTest: Start playing audio on output device "pulse"
    Sleeping for 5s....
    Starting task
    Playing to output device "pulse" with format QAudioFormat(11025Hz, 8bit, channelCount=2, sampleType=UnSignedInt, byteOrder=LittleEndian, codec="audio/pcm")
    "UnitTest: finnished Playing test.wav"
    QObject::killTimer: Timers cannot be stopped from another thread
    "
    =>so AudioOutputNewThread Destructor seams to cause an exception on Linux

    From reading the manual i understood 2 requirements for using QThreads

    1. void run() override; is never supposed to terminate. =>that's why i called excec() at the end of run().

    2. Within the Destructor of AudioOutputNewThread exit(0) is supposed to tell exec() to terminate with result 0.
      The following wait() is simply to wait for thread termination

    Where are my assumptions wrong? Could you help me out here?
    Why is handleStateChanged() never executed? Did i mess up the signal connection?

    thx Greets Julian


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    @JuliusCaesar said in How to properly close QThread:

    StartPlayFile

    You call exec in that method and you connect your signal after calling StartPlayFile.


Log in to reply