From 8ec63abc74dbb17ad9208f924611a7e5896f1e31 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 3 Jan 2020 00:32:23 +1100 Subject: [PATCH] moved audio output manager and device to separate thread --- app/audio/audiohybriddevice.cpp | 57 ++++++++++++++++++++++++++++++-- app/audio/audiohybriddevice.h | 58 +++++++++++++++++++-------------- app/audio/audiomanager.cpp | 52 +++++++++++++---------------- app/audio/audiomanager.h | 5 --- 4 files changed, 111 insertions(+), 61 deletions(-) diff --git a/app/audio/audiohybriddevice.cpp b/app/audio/audiohybriddevice.cpp index 13a207d1b..85d981143 100644 --- a/app/audio/audiohybriddevice.cpp +++ b/app/audio/audiohybriddevice.cpp @@ -25,6 +25,7 @@ AudioHybridDevice::AudioHybridDevice(QObject *parent) : QIODevice(parent), + output_(nullptr), device_(nullptr), sample_index_(0), enable_sending_samples_(false) @@ -32,20 +33,37 @@ AudioHybridDevice::AudioHybridDevice(QObject *parent) : } +bool AudioHybridDevice::OutputIsSet() +{ + qDebug() << "OIS"; + + output_set_lock_.lock(); + + bool output_is_set = (output_.get()); + + output_set_lock_.unlock(); + + return output_is_set; +} + void AudioHybridDevice::Push(const QByteArray& samples) { + qDebug() << "push"; + Stop(); pushed_samples_ = samples; sample_index_ = 0; if (!pushed_samples_.isEmpty()) { - emit HasSamples(); + WakeOutputDevice(); } } void AudioHybridDevice::Stop() { + qDebug() << "Stop"; + // Whatever is happening, stop it pushed_samples_.clear(); @@ -53,17 +71,27 @@ void AudioHybridDevice::Stop() device_->close(); device_ = nullptr; } + + if (output_) { + output_->stop(); + } } void AudioHybridDevice::ConnectDevice(QIODevice *device) { + qDebug() << "CD"; + + if (!output_) { + return; + } + // Clear any previous device or pushed sample Stop(); device_ = device; if (device_ != nullptr) { - emit HasSamples(); + WakeOutputDevice(); } } @@ -72,11 +100,36 @@ bool AudioHybridDevice::IsIdle() return device_ == nullptr && pushed_samples_.isEmpty(); } +void AudioHybridDevice::WakeOutputDevice() +{ + if (output_ != nullptr && output_->state() != QAudio::ActiveState) { + output_->start(this); + } +} + void AudioHybridDevice::SetEnableSendingSamples(bool e) { enable_sending_samples_ = e; } +void AudioHybridDevice::SetOutputDevice(QAudioDeviceInfo info, QAudioFormat format) +{ + output_set_lock_.lock(); + + output_ = std::unique_ptr(new QAudioOutput(info, format, this)); + + output_set_lock_.unlock(); + + connect(output_.get(), &QAudioOutput::notify, this, &AudioHybridDevice::OutputNotified); +} + +void AudioHybridDevice::OutputNotified() +{ + if (IsIdle()) { + static_cast(sender())->stop(); + } +} + qint64 AudioHybridDevice::readData(char *data, qint64 maxSize) { qint64 read_size = read_internal(data, maxSize); diff --git a/app/audio/audiohybriddevice.h b/app/audio/audiohybriddevice.h index 4a7c63dd1..56623394a 100644 --- a/app/audio/audiohybriddevice.h +++ b/app/audio/audiohybriddevice.h @@ -21,7 +21,9 @@ #ifndef AUDIOHYBRIDDEVICE_H #define AUDIOHYBRIDDEVICE_H +#include #include +#include /** * @brief A device that can be connected to QAudioOutput and provides both "push" and "pull" functionality @@ -44,6 +46,16 @@ class AudioHybridDevice : public QIODevice public: AudioHybridDevice(QObject* parent = nullptr); + bool OutputIsSet(); + + /** + * @brief If enabled, this will emit the SentSamples() signal whenever samples are sent to the output device + */ + void SetEnableSendingSamples(bool e); + +public slots: + void SetOutputDevice(QAudioDeviceInfo info, QAudioFormat format); + void Push(const QByteArray &samples); /** @@ -68,30 +80,7 @@ public: */ void ConnectDevice(QIODevice* device); - /** - * @brief Returns true if there are no more samples to be sent - * - * This is true if a device was connected or samples were pushed but we reached the end and no more data is available - * to be sent. - * - * In this state, this device will continue returning an empty buffer (all zeroes) to the device to workaround - * QAudioOutput's buffer underrun prevention with pushed samples. Therefore the QAudioOutput will never be put into - * QAudio::IdleState. Therefore only way to determine whether the output is no longer receiving usable audio is to - * check this function. - */ - bool IsIdle(); - - /** - * @brief If enabled, this will emit the SentSamples() signal whenever samples are sent to the output device - */ - void SetEnableSendingSamples(bool e); - signals: - /** - * @brief Signal emitted when this leaves "idle" state and has valid audio data that is ready to be sent - */ - void HasSamples(); - /** * @brief Signal emitted when samples are sent to the output device * @@ -130,14 +119,35 @@ protected: virtual qint64 writeData(const char *data, qint64 maxSize) override; private: + /** + * @brief Returns true if there are no more samples to be sent + * + * This is true if a device was connected or samples were pushed but we reached the end and no more data is available + * to be sent. + * + * In this state, this device will continue returning an empty buffer (all zeroes) to the device to workaround + * QAudioOutput's buffer underrun prevention with pushed samples. Therefore the QAudioOutput will never be put into + * QAudio::IdleState. Therefore only way to determine whether the output is no longer receiving usable audio is to + * check this function. + */ + bool IsIdle(); + + void WakeOutputDevice(); + qint64 read_internal(char *data, qint64 maxSize); + QMutex output_set_lock_; + std::unique_ptr output_; + QIODevice* device_; QByteArray pushed_samples_; qint64 sample_index_; - bool enable_sending_samples_; + QAtomicInt enable_sending_samples_; + +private slots: + void OutputNotified(); }; diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 721d99f3b..e37a42187 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -76,25 +76,25 @@ bool AudioManager::IsRefreshing() void AudioManager::PushToOutput(const QByteArray &samples) { - output_manager_.Push(samples); + QMetaObject::invokeMethod(&output_manager_, + "Push", + Qt::QueuedConnection, + Q_ARG(QByteArray, samples)); } void AudioManager::StartOutput(QIODevice *device) { - if (output_ == nullptr) { - return; - } - - output_manager_.ConnectDevice(device); + QMetaObject::invokeMethod(&output_manager_, + "ConnectDevice", + Qt::QueuedConnection, + Q_ARG(QIODevice*, device)); } void AudioManager::StopOutput() { - output_manager_.Stop(); - - if (output_ != nullptr) { - output_->stop(); - } + QMetaObject::invokeMethod(&output_manager_, + "Stop", + Qt::QueuedConnection); } void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) @@ -142,8 +142,11 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) abort(); } - output_ = std::unique_ptr(new QAudioOutput(info, format, this)); - connect(output_.get(), &QAudioOutput::notify, this, &AudioManager::OutputNotified); + QMetaObject::invokeMethod(&output_manager_, + "SetOutputDevice", + Qt::QueuedConnection, + Q_ARG(QAudioDeviceInfo, info), + Q_ARG(QAudioFormat, format)); } // Un-comment this to get debug information about what the audio output is doing @@ -177,18 +180,21 @@ const QList &AudioManager::ListOutputDevices() } AudioManager::AudioManager() : - output_(nullptr), input_(nullptr), input_file_(nullptr), refreshing_devices_(false) { RefreshDevices(); - connect(&output_manager_, &AudioHybridDevice::HasSamples, this, &AudioManager::OutputManagerHasSamples); connect(&output_manager_, &AudioHybridDevice::SentSamples, this, &AudioManager::SentSamples); output_manager_.SetEnableSendingSamples(true); output_manager_.open(AudioHybridDevice::ReadOnly); + + QThread* output_thread = new QThread(); + output_thread->start(); + + output_manager_.moveToThread(output_thread); } void AudioManager::RefreshThreadDone() @@ -202,7 +208,7 @@ void AudioManager::RefreshThreadDone() QString preferred_audio_output = Config::Current()["PreferredAudioOutput"].toString(); - if (output_ == nullptr + if (!output_manager_.OutputIsSet() || (!preferred_audio_output.isEmpty() && output_device_info_.deviceName() != preferred_audio_output)) { if (preferred_audio_output.isEmpty()) { SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice()); @@ -238,25 +244,11 @@ void AudioManager::RefreshThreadDone() emit DeviceListReady(); } -void AudioManager::OutputManagerHasSamples() -{ - if (output_ != nullptr && output_->state() != QAudio::ActiveState) { - output_->start(&output_manager_); - } -} - void AudioManager::OutputStateChanged(QAudio::State state) { qDebug() << state; } -void AudioManager::OutputNotified() -{ - if (output_manager_.IsIdle()) { - static_cast(sender())->stop(); - } -} - AudioRefreshDevicesObject::AudioRefreshDevicesObject() { } diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 1eb8fa3c4..f7712924c 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -111,7 +111,6 @@ private: AudioHybridDevice output_manager_; - std::unique_ptr output_; QAudioDeviceInfo output_device_info_; AudioRenderingParams output_params_; @@ -124,12 +123,8 @@ private: private slots: void RefreshThreadDone(); - void OutputManagerHasSamples(); - void OutputStateChanged(QAudio::State state); - void OutputNotified(); - }; #endif // AUDIOMANAGER_H