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 LinuxFrom reading the manual i understood 2 requirements for using QThreads
-
void run() override; is never supposed to terminate. =>that's why i called excec() at the end of run().
-
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
-
-
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.