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

QAudioOutput thread safety (on Android)



  • Is it safe to create QAudioOutput and QIODevice (as QFile) on one thread and call audioOutput.start(pDevice) and audioOutput.stop() on other thread?

    Now I have a code like this:

        class MySoundDevice : public QObject
        {
            Q_OBJECT
    
        public:
    
            MySoundDevice();
    
            void Start(QIODevice * p_device);
    
            void Stop();
    
            qreal GetVolume() const
            {
                return audioOutput.volume();
            }
    
            void SetVolume(qreal val)
            {
                audioOutput.setVolume(val);
            }
    
        signals:
    
            void done();
    
        public slots:
            void onStateChanged(QAudio::State);
            void onNotify();
    
        private:
    
            void SeekToStart()
            {
                pDevice->seek(44); // skip wav header
            }
    
            typedef std::lock_guard<std::recursive_mutex> Lock;
    
            std::recursive_mutex playMutex;
    
            QIODevice * pDevice = nullptr;
    
            QAudioOutput audioOutput;
    
            ...
        };
    
    MySoundDevice::MySoundDevice() : audioOutput(GetWavFormat(), this)
    {
        connect(&audioOutput, &QAudioOutput::notify, this, &QtSoundDevice::onNotify, Qt::DirectConnection);
        connect(&audioOutput, &QAudioOutput::stateChanged, this, &QtSoundDevice::onStateChanged, Qt::DirectConnection);
    }
    
    void MySoundDevice::Start(QIODevice * p_device)
    {
        Lock lock(playMutex);
    
        audioOutput.reset();
    
        pDevice = p_device;
    
        SeekToStart();
    
        ...
    
        audioOutput.start(pDevice);
    }
    
    void MySoundDevice::Stop()
    {
        Lock lock(playMutex);
    
        if (pDevice != nullptr)
        {
            audioOutput.stop();
    
            pDevice = nullptr;
        }
    }
    
    

    I use mutex so QFile and QAudioOutput are accessed from different threads, but the access is synchronized.

    Although the code does not crash in my test environment I am not sure it works correctly (on Android, for example) if MySoundDevice class constructor, start/stop and setVolume methods are called from different threads.


  • Moderators

    If Qt isn't giving you any warnings on the console (which it usually does if you are doing something wrong in regards to QObject and threads) then you are probably ok.

    Here is some guidelines on threading just in case:
    http://doc.qt.io/qt-5/threads-qobject.html

    From the sounds of it, you should be fine since you are mutexing the QObject.



  • I created a test that do this:

    class SoundTestThread : public QThread
    {
    protected:
    
        void run() override
        {
            //call MySoundDevice::Start(QIODevice * p_device) and Stop() in an infinite loop
        }
    };
    
    
        constexpr size_t thread_count = 4;
    
        for (size_t i = 0; i < thread_count; ++i)
        {
            auto thread = new SoundTestThread(context);
    
            thread->start();
            
            threads.push_back(thread);
        }
    
        //wait...
    
    

    This test crashes on Windows within 15 seconds with the following call stack:

     	qtaudio_windowsd.dll!QWindowsAudioOutput::freeBlocks(wavehdr_tag * blockArray) Line 154	C++
     	qtaudio_windowsd.dll!QWindowsAudioOutput::close() Line 304	C++
     	qtaudio_windowsd.dll!QWindowsAudioOutput::reset() Line 612	C++
     	Qt5Multimediad.dll!QAudioOutput::reset() Line 220	C++
    >	MyAppQt.exe!MyApp::QtSoundDevice::CommonStart(QIODevice * p_device, float duration, bool loop) Line 70	C++
     	MyAppQt.exe!MyApp::QtSound::CommonStart(float duration, bool loop) Line 46	C++
     	MyAppQt.exe!MyApp::QtSound::Start() Line 25	C++
     	MyAppQt.exe!ThreadFunc(const awl::testing::TestContext & context) Line 34	C++
     	MyAppQt.exe!SoundTestThread::run() Line 82	C++
     	Qt5Cored.dll!QThreadPrivate::start(void * arg) Line 378	C++
    

    where CommonStart is

    void QtSoundDevice::CommonStart(QIODevice * p_device, float duration, bool loop)
    {
        Lock lock(playMutex);
    
        //sound restarts without stop() call
        //audioOutput.stop();
    
        audioOutput.reset();
    
        pDevice = p_device;
    
        SeekToStart();
    
        ClearFlags();
    
        loopWithStateChanged = loop;
    
        //looks like it does not work on Android
        bool useOnNotify = false;
    
        if (useOnNotify)
        {
            if (loop)
            {
                if (std::isnan(duration))
                {
                    loopWithStateChanged = true;
                }
                else
                {
                    loopWithNotify = true;
    
                    audioOutput.setNotifyInterval((int)(duration * 1000));
                }
            }
    
            if (!loopWithNotify)
            {
                audioOutput.setNotifyInterval(100 * 1000);
            }
        }
    
        audioOutput.start(pDevice);
    }
    

    On Android it works a bit more stable, but also crashes sometimes.

    There is lpData == 0x0000000000000000 In function

    void QWindowsAudioOutput::freeBlocks(WAVEHDR* blockArray)
    {
        WAVEHDR* blocks = blockArray;
    
        int count = buffer_size/period_size;
    
        for(int i = 0; i < count; i++) {
            waveOutUnprepareHeader(hWaveOut,blocks, sizeof(WAVEHDR));
            blocks++;
        }
        HeapFree(GetProcessHeap(), 0, blockArray);
    }
    
    -		blocks	0x0000016bfbc2cdc0 {lpData=0x0000000000000000 <NULL> dwBufferLength=0 dwBytesRecorded=0 ...}	wavehdr_tag *
    +		lpData	0x0000000000000000 <NULL>	char *
    		dwBufferLength	0	unsigned long
    		dwBytesRecorded	0	unsigned long
    		dwUser	0	unsigned __int64
    		dwFlags	0	unsigned long
    		dwLoops	0	unsigned long
    +		lpNext	0x0000000000000000 <NULL>	wavehdr_tag *
    		reserved	0	unsigned __int64
    

    also my test periodically crashes on Windows with the following call stack:

     	ntdll.dll!00007ffd2283d979()	Unknown
     	ntdll.dll!00007ffd228383e7()	Unknown
     	ntdll.dll!00007ffd22838300()	Unknown
     	winmmbase.dll!00007ffd168a576a()	Unknown
    >	qtaudio_windowsd.dll!QWindowsAudioOutput::deviceReady() Line 529	C++
     	qtaudio_windowsd.dll!QWindowsAudioOutput::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) Line 75	C++
     	Qt5Cored.dll!QMetaCallEvent::placeMetaCall(QObject * object) Line 505	C++
     	Qt5Cored.dll!QObject::event(QEvent * e) Line 1247	C++
     	Qt5Cored.dll!QCoreApplicationPrivate::notify_helper(QObject * receiver, QEvent * event) Line 1200	C++
     	Qt5Cored.dll!doNotify(QObject * receiver, QEvent * event) Line 1140	C++
     	Qt5Cored.dll!QCoreApplication::notify(QObject * receiver, QEvent * event) Line 1127	C++
     	Qt5Guid.dll!QGuiApplication::notify(QObject * object, QEvent * event) Line 1697	C++
     	Qt5Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) Line 1050	C++
     	Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event) Line 234	C++
     	Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data) Line 1740	C++
     	Qt5Cored.dll!QEventDispatcherWin32::sendPostedEvents() Line 1085	C++
     	qwindowsd.dll!QWindowsGuiEventDispatcher::sendPostedEvents() Line 82	C++
     	Qt5Cored.dll!qt_internal_proc(HWND__ * hwnd, unsigned int message, unsigned __int64 wp, __int64 lp) Line 239	C++
     	[External Code]	
     	Qt5Cored.dll!QEventDispatcherWin32::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags) Line 630	C++
     	qwindowsd.dll!QWindowsGuiEventDispatcher::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags) Line 74	C++
     	Qt5Cored.dll!QEventLoop::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags) Line 135	C++
     	Qt5Cored.dll!QEventLoop::exec(QFlags<enum QEventLoop::ProcessEventsFlag> flags) Line 212	C++
     	Qt5Cored.dll!QCoreApplication::exec() Line 1338	C++
     	Qt5Guid.dll!QGuiApplication::exec() Line 1688	C++
    

    probably because I call QtSoundDevice::CommonStart from the main (UI) thread.

    all the calls to QT Sound interface and IODevice are protected with a std::mutex in my code.



  • @ambershark On Android I am getting the following warning in the log:

    W MyApp: (null):0 ((null)): Unable to initialize AudioPlayer
    W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
    E AudioTrack: AudioFlinger could not create track, status: -12
    E libOpenSLES: AudioTrack::initCheck status 4294967284
    W libOpenSLES: Leaving Object::Realize (SL_RESULT_CONTENT_UNSUPPORTED)
    W MyApp: (null):0 ((null)): Unable to initialize AudioPlayer
    W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
    W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
    W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
    W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
    W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
    W AudioTrack: AUDIO_OUTPUT_FLAG_FAST denied by client; transfer 1, track 44100 Hz, output 48000 Hz
    

    and within 10-15 minutes of extensive testing the app crashes outputting the following to the log:

    F libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x28 in tid 25162 (qtMainLoopThrea)
    E libOpenSLES: frameworks/wilhelm/src/itf/IObject.c:346: pthread 0x9cf1f930 (tid 25835) sees object 0x9f69ba00 was left unlocked in unexpected state by pthread 0xa3d78930 (tid 25162) at frameworks/wilhelm/src/itf/IBufferQueue.c:130
    F         : assertion "false" failed: file "frameworks/wilhelm/src/locks.c", line 114, function "void object_lock_exclusive_(IObject*, const char*, int)"
    F libc    : Fatal signal 6 (SIGABRT), code -6 in tid 25835 (QtThread)
    I libc    : Another thread contacted debuggerd first; not contacting debuggerd.
    

    I cannot reproduce this on Emulator, but only on my real device so I do not have the stack trace, because I cannot access /data/tombsotes/ directory on the device.


  • Moderators

    This looks like you have a thread synchronization issue. I would look into making sure anywhere that writes (or could potentially write) to an object that is shared that it is protected by a mutex.

    Usually that is why you see intermittent failures like that is bad thread syncing.

    If possible try taking the threads out of it and see if it crashes, my guess is that it won't.



  • @ambershark I tried to create a console app that reproduces the crash on Windows, but I was unable to create QAudioOutput on the main thread and play it on a different thread (as my Android app does). See https://github.com/elephantbug/SoundTest/blob/master/main.cpp. So probably QAudioOutput should not be used in this way?

    If I create QAudioOutput in SoundTestThread::run() the sound plays fine on 4 threads concurrently.

    Run the app with -r and -m options to test this code.

    SoundTest.exe -r -m soundfile.wav
    

Log in to reply