diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 12c314c63..9186b9a83 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -48,31 +48,18 @@ AudioManager *AudioManager::instance() void AudioManager::RefreshDevices() { - if (refresh_thread_) { - return; - } - - input_devices_.clear(); - output_devices_.clear(); - - // Refreshing devices can take some time, so we do it in a separate thread - refresh_thread_ = new QThread(this); - connect(refresh_thread_, &QThread::finished, refresh_thread_, &QThread::deleteLater); - - refresh_thread_->start(QThread::IdlePriority); - - AudioRefreshDevicesObject* refresher = new AudioRefreshDevicesObject(); - connect(refresher, &AudioRefreshDevicesObject::ListsReady, this, &AudioManager::RefreshThreadDone, Qt::QueuedConnection); - refresher->moveToThread(refresh_thread_); - - QMetaObject::invokeMethod(refresher, - "Refresh", - Qt::QueuedConnection); + output_watcher_.setFuture(QtConcurrent::run(QAudioDeviceInfo::availableDevices, QAudio::AudioOutput)); + input_watcher_.setFuture(QtConcurrent::run(QAudioDeviceInfo::availableDevices, QAudio::AudioInput)); } -bool AudioManager::IsRefreshing() +bool AudioManager::IsRefreshingOutputs() { - return refresh_thread_; + return output_watcher_.isRunning(); +} + +bool AudioManager::IsRefreshingInputs() +{ + return input_watcher_.isRunning(); } void AudioManager::PushToOutput(const QByteArray &samples) @@ -82,12 +69,19 @@ void AudioManager::PushToOutput(const QByteArray &samples) void AudioManager::StartOutput(const QString &filename, qint64 offset, int playback_speed) { - output_manager_.PullFromDevice(filename, offset, playback_speed); + QMetaObject::invokeMethod(&output_manager_, + "PullFromDevice", + Qt::QueuedConnection, + Q_ARG(const QString&, filename), + Q_ARG(qint64, offset), + Q_ARG(int, playback_speed)); } void AudioManager::StopOutput() { - output_manager_.ResetToPushMode(); + QMetaObject::invokeMethod(&output_manager_, + "ResetToPushMode", + Qt::QueuedConnection); } void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) @@ -136,7 +130,12 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) } if (info.isFormatSupported(format)) { - output_manager_.SetOutputDevice(info, format); + QMetaObject::invokeMethod(&output_manager_, + "SetOutputDevice", + Qt::QueuedConnection, + Q_ARG(const QAudioDeviceInfo&, info), + Q_ARG(const QAudioFormat&, format)); + output_is_set_ = true; } else { qWarning() << "Output format not supported by device"; } @@ -148,7 +147,10 @@ void AudioManager::SetOutputParams(const AudioRenderingParams ¶ms) if (output_params_ != params) { output_params_ = params; - output_manager_.SetParameters(params); + QMetaObject::invokeMethod(&output_manager_, + "SetParameters", + Qt::QueuedConnection, + OLIVE_NS_ARG(AudioRenderingParams, params)); // Refresh output device SetOutputDevice(output_device_info_); @@ -190,35 +192,35 @@ void AudioManager::ReverseBuffer(char *buffer, int buffer_size, int sample_size) } AudioManager::AudioManager() : + output_is_set_(false), input_(nullptr), - input_file_(nullptr), - refresh_thread_(nullptr) + input_file_(nullptr) { RefreshDevices(); + output_thread_.start(QThread::TimeCriticalPriority); + output_manager_.moveToThread(&output_thread_); + connect(&output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified); + + connect(&output_watcher_, &QFutureWatcher< QList >::finished, this, &AudioManager::OutputDevicesRefreshed); + connect(&input_watcher_, &QFutureWatcher< QList >::finished, this, &AudioManager::InputDevicesRefreshed); } AudioManager::~AudioManager() { - if (refresh_thread_) { - refresh_thread_->quit(); - refresh_thread_->wait(); - } + QMetaObject::invokeMethod(&output_manager_, "Close", Qt::QueuedConnection); + output_thread_.quit(); + output_thread_.wait(); } -void AudioManager::RefreshThreadDone() +void AudioManager::OutputDevicesRefreshed() { - AudioRefreshDevicesObject* refresher = static_cast(sender()); - - output_devices_ = refresher->output_devices(); - input_devices_ = refresher->input_devices(); - - + output_devices_ = output_watcher_.result(); QString preferred_audio_output = Config::Current()["PreferredAudioOutput"].toString(); - if (!output_manager_.OutputIsSet() + if (!output_is_set_ || (!preferred_audio_output.isEmpty() && output_device_info_.deviceName() != preferred_audio_output)) { if (preferred_audio_output.isEmpty()) { SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice()); @@ -232,6 +234,13 @@ void AudioManager::RefreshThreadDone() } } + emit OutputListReady(); +} + +void AudioManager::InputDevicesRefreshed() +{ + input_devices_ = input_watcher_.result(); + QString preferred_audio_input = Config::Current()["PreferredAudioInput"].toString(); if (input_ == nullptr @@ -248,34 +257,7 @@ void AudioManager::RefreshThreadDone() } } - // Clean up refresher object - refresh_thread_->quit(); - refresh_thread_ = nullptr; - refresher->deleteLater(); - - emit DeviceListReady(); -} - -AudioRefreshDevicesObject::AudioRefreshDevicesObject() -{ -} - -const QList& AudioRefreshDevicesObject::input_devices() -{ - return input_devices_; -} - -const QList& AudioRefreshDevicesObject::output_devices() -{ - return output_devices_; -} - -void AudioRefreshDevicesObject::Refresh() -{ - output_devices_ = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - input_devices_ = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); - - emit ListsReady(); + emit InputListReady(); } OLIVE_NAMESPACE_EXIT diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index a90c3ecf6..0d5b1cb03 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "common/define.h" @@ -32,30 +33,6 @@ OLIVE_NAMESPACE_ENTER -/** - * @brief A thread for refreshing the total list of devices on the system - * - * Refreshing devices causes a noticeable pause in execution. Doing it another thread is intended to avoid this. - */ -class AudioRefreshDevicesObject : public QObject { - Q_OBJECT -public: - AudioRefreshDevicesObject(); - - const QList& input_devices(); - const QList& output_devices(); - -public slots: - void Refresh(); - -signals: - void ListsReady(); - -private: - QList input_devices_; - QList output_devices_; -}; - /** * @brief Audio input and output management class * @@ -73,7 +50,9 @@ public: void RefreshDevices(); - bool IsRefreshing(); + bool IsRefreshingOutputs(); + + bool IsRefreshingInputs(); void PushToOutput(const QByteArray& samples); @@ -101,7 +80,9 @@ public: static void ReverseBuffer(char* buffer, int size, int resolution); signals: - void DeviceListReady(); + void OutputListReady(); + + void InputListReady(); void OutputNotified(); @@ -111,12 +92,16 @@ private: virtual ~AudioManager() override; QList input_devices_; - QList output_devices_; + QFutureWatcher< QList > input_watcher_; + QFutureWatcher< QList > output_watcher_; + static AudioManager* instance_; + QThread output_thread_; AudioOutputManager output_manager_; + bool output_is_set_; QAudioDeviceInfo output_device_info_; AudioRenderingParams output_params_; @@ -125,10 +110,10 @@ private: QAudioDeviceInfo input_device_info_; QIODevice* input_file_; - QThread* refresh_thread_; - private slots: - void RefreshThreadDone(); + void OutputDevicesRefreshed(); + + void InputDevicesRefreshed(); }; diff --git a/app/audio/outputdeviceproxy.cpp b/app/audio/outputdeviceproxy.cpp index 633e7e065..7c0eb566f 100644 --- a/app/audio/outputdeviceproxy.cpp +++ b/app/audio/outputdeviceproxy.cpp @@ -25,10 +25,6 @@ OLIVE_NAMESPACE_ENTER -AudioOutputDeviceProxy::AudioOutputDeviceProxy() -{ -} - AudioOutputDeviceProxy::~AudioOutputDeviceProxy() { if (file_.isOpen()) { @@ -76,31 +72,30 @@ void AudioOutputDeviceProxy::close() qint64 AudioOutputDeviceProxy::readData(char *data, qint64 maxlen) { - if (file_.isOpen()) { - - qint64 read_count; - - if (tempo_processor_.IsOpen()) { - - while ((read_count = tempo_processor_.Pull(data, static_cast(maxlen))) == 0) { - int dev_read = static_cast(ReverseAwareRead(data, maxlen)); - - if (!dev_read) { - break; - } - - tempo_processor_.Push(data, dev_read); - } - - } else { - // If we aren't doing any tempo processing, simply passthrough the read signal - read_count = ReverseAwareRead(data, maxlen); - } - - return read_count; + if (!file_.isOpen()) { + return 0; } - return 0; + qint64 read_count; + + if (tempo_processor_.IsOpen()) { + + while ((read_count = tempo_processor_.Pull(data, static_cast(maxlen))) == 0) { + int dev_read = static_cast(ReverseAwareRead(data, maxlen)); + + if (!dev_read) { + break; + } + + tempo_processor_.Push(data, dev_read); + } + + } else { + // If we aren't doing any tempo processing, simply passthrough the read signal + read_count = ReverseAwareRead(data, maxlen); + } + + return read_count; } qint64 AudioOutputDeviceProxy::writeData(const char *data, qint64 maxSize) diff --git a/app/audio/outputdeviceproxy.h b/app/audio/outputdeviceproxy.h index 52c6a8ca9..12116e0dd 100644 --- a/app/audio/outputdeviceproxy.h +++ b/app/audio/outputdeviceproxy.h @@ -35,7 +35,7 @@ class AudioOutputDeviceProxy : public QIODevice { Q_OBJECT public: - AudioOutputDeviceProxy(); + AudioOutputDeviceProxy() = default; virtual ~AudioOutputDeviceProxy() override; diff --git a/app/audio/outputmanager.cpp b/app/audio/outputmanager.cpp index 1b4e20c57..f68feb33e 100644 --- a/app/audio/outputmanager.cpp +++ b/app/audio/outputmanager.cpp @@ -38,30 +38,23 @@ AudioOutputManager::AudioOutputManager(QObject *parent) : AudioOutputManager::~AudioOutputManager() { - delete output_; -} - -bool AudioOutputManager::OutputIsSet() -{ - return output_; + Close(); } void AudioOutputManager::Push(const QByteArray& samples) { - // If no output device, nothing to be done - if (!output_) { - return; - } + // This function is not queued and is intended to be called from the caller's thread + QMutexLocker lock(&push_sample_lock_); // Replace sample buffer with this one - pushed_samples_ = samples; - pushed_sample_index_ = 0; + push_samples_ = samples; + push_sample_index_ = 0; // If we had another device connected, disconnect it now - ResetToPushMode(); + QMetaObject::invokeMethod(this, "ResetToPushMode", Qt::QueuedConnection); // Start pushing samples to the output - PushMoreSamples(); + QMetaObject::invokeMethod(this, "PushMoreSamples", Qt::QueuedConnection); } void AudioOutputManager::ResetToPushMode() @@ -77,11 +70,25 @@ void AudioOutputManager::ResetToPushMode() } } -void AudioOutputManager::SetParameters(const AudioRenderingParams ¶ms) +void AudioOutputManager::SetParameters(AudioRenderingParams params) { device_proxy_.SetParameters(params); } +void AudioOutputManager::Close() +{ + if (output_) { + output_->stop(); + + if (device_proxy_.isOpen()) { + device_proxy_.close(); + } + + delete output_; + output_ = nullptr; + } +} + void AudioOutputManager::PullFromDevice(const QString &filename, qint64 offset, int playback_speed) { if (!output_) { @@ -91,7 +98,7 @@ void AudioOutputManager::PullFromDevice(const QString &filename, qint64 offset, // Stop any current output and disable push mode output_->stop(); push_device_ = nullptr; - pushed_samples_.clear(); + push_samples_.clear(); // Pull from the device device_proxy_.SetDevice(filename, offset, playback_speed); @@ -101,38 +108,32 @@ void AudioOutputManager::PullFromDevice(const QString &filename, qint64 offset, void AudioOutputManager::PushMoreSamples() { + QMutexLocker lock(&push_sample_lock_); + // Check if we're currently in push mode and if we have samples to push - if (!push_device_ || pushed_samples_.isEmpty()) { + if (!push_device_ || push_samples_.isEmpty()) { return; } - const char* read_ptr = pushed_samples_.constData() + pushed_sample_index_; + const char* read_ptr = push_samples_.constData() + push_sample_index_; // Push the bytes we have to the audio output qint64 write_count = push_device_->write(read_ptr, - pushed_samples_.size() - pushed_sample_index_); + push_samples_.size() - push_sample_index_); // Increment sample buffer index (faster than shift the bytes up) - pushed_sample_index_ += static_cast(write_count); + push_sample_index_ += static_cast(write_count); // If we've pushed all samples, we can clear this array - if (pushed_sample_index_ == pushed_samples_.size()) { - pushed_samples_.clear(); + if (push_sample_index_ == push_samples_.size()) { + push_samples_.clear(); } } void AudioOutputManager::SetOutputDevice(QAudioDeviceInfo info, QAudioFormat format) { // Whatever the output is doing right now, stop it - if (output_) { - output_->stop(); - - if (device_proxy_.isOpen()) { - device_proxy_.close(); - } - - delete output_; - } + Close(); // Create a new output device and start it in push mode output_ = new QAudioOutput(info, format, this); diff --git a/app/audio/outputmanager.h b/app/audio/outputmanager.h index e845b02f9..0f56eb5d5 100644 --- a/app/audio/outputmanager.h +++ b/app/audio/outputmanager.h @@ -40,12 +40,13 @@ public: virtual ~AudioOutputManager() override; - bool OutputIsSet(); - - void SetOutputDevice(QAudioDeviceInfo info, QAudioFormat format); - + // Thread-safe void Push(const QByteArray &samples); +public slots: + // Queued + void SetOutputDevice(QAudioDeviceInfo info, QAudioFormat format); + /** * @brief Connect a QIODevice (e.g. QFile) to start sending to the audio output * @@ -54,12 +55,14 @@ public: */ void PullFromDevice(const QString &filename, qint64 offset, int playback_speed); + // Queued void ResetToPushMode(); - void SetParameters(const AudioRenderingParams& params); - -public slots: + // Queued + void SetParameters(OLIVE_NAMESPACE::AudioRenderingParams params); + // Queued + void Close(); signals: void OutputNotified(); @@ -68,8 +71,9 @@ private: QAudioOutput* output_; QIODevice* push_device_; - QByteArray pushed_samples_; - int pushed_sample_index_; + QMutex push_sample_lock_; + QByteArray push_samples_; + int push_sample_index_; AudioOutputDeviceProxy device_proxy_;