From ee81a47f2e2f7bb58ab77044f6ebe8a70b383635 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 30 Sep 2021 21:52:47 -0700 Subject: [PATCH] begin switch to portaudio Qt's audio system has served us well, but it's a little limited moving forward. PortAudio is a mature and versatile library that will allow for better audio for users. --- CMakeLists.txt | 4 + app/audio/CMakeLists.txt | 4 - app/audio/audiomanager.cpp | 146 +++++++++------- app/audio/audiomanager.h | 30 ++-- app/audio/outputdeviceproxy.cpp | 73 -------- app/audio/outputdeviceproxy.h | 60 ------- app/audio/outputmanager.cpp | 156 ------------------ app/audio/outputmanager.h | 91 ---------- .../preferences/tabs/preferencesaudiotab.cpp | 11 +- app/render/previewaudiodevice.cpp | 35 ++-- app/render/previewaudiodevice.h | 23 ++- app/widget/audiomonitor/audiomonitor.cpp | 44 ++--- app/widget/audiomonitor/audiomonitor.h | 31 +++- app/widget/viewer/viewer.cpp | 61 +++---- app/widget/viewer/viewer.h | 1 - 15 files changed, 224 insertions(+), 546 deletions(-) delete mode 100644 app/audio/outputdeviceproxy.cpp delete mode 100644 app/audio/outputdeviceproxy.h delete mode 100644 app/audio/outputmanager.cpp delete mode 100644 app/audio/outputmanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b73ca1160..81e962b2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,10 @@ list(APPEND OLIVE_LIBRARIES FFMPEG::swresample ) +# Link PortAudio +find_package(PortAudio REQUIRED) +list(APPEND OLIVE_LIBRARIES PortAudio) + # Optional: Link OpenTimelineIO find_package(OpenTimelineIO) if (OpenTimelineIO_FOUND) diff --git a/app/audio/CMakeLists.txt b/app/audio/CMakeLists.txt index a03a4d88e..6efe27768 100644 --- a/app/audio/CMakeLists.txt +++ b/app/audio/CMakeLists.txt @@ -20,10 +20,6 @@ set(OLIVE_SOURCES audio/audiomanager.h audio/audiovisualwaveform.cpp audio/audiovisualwaveform.h - audio/outputdeviceproxy.cpp - audio/outputdeviceproxy.h - audio/outputmanager.cpp - audio/outputmanager.h audio/packedprocessor.cpp audio/packedprocessor.h audio/tempoprocessor.cpp diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 4cb9358fa..63f93da13 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -89,35 +89,68 @@ bool AudioManager::IsRefreshingInputs() void AudioManager::PushToOutput(const QByteArray &samples) { - output_manager_->Push(samples); + if (!output_stream_) { + // Start output with no callback so it'll be in "push" mode + StartOutputStream(); + } - emit OutputPushed(samples); + Pa_WriteStream(output_stream_, samples.constData(), output_params_.bytes_to_samples(samples.size())); } -void AudioManager::StartOutput(std::shared_ptr device) +static PaSampleFormat GetPortAudioSampleFormat(AudioParams::Format fmt) { - // Move to output manager's thread - device->moveToThread(&output_thread_); + switch (fmt) { + case AudioParams::kFormatUnsigned8: + return paUInt8; + case AudioParams::kFormatSigned16: + return paInt16; + case AudioParams::kFormatSigned32: + return paInt32; + case AudioParams::kFormatFloat32: + return paFloat32; + case AudioParams::kFormatSigned64: + case AudioParams::kFormatFloat64: + case AudioParams::kFormatInvalid: + case AudioParams::kFormatCount: + break; + } - // Queue to output manager in other thread - QMetaObject::invokeMethod(output_manager_, - "PullFromDevice", - Qt::QueuedConnection, - Q_ARG(std::shared_ptr, device)); + return 0; +} + +int OutputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) +{ + PreviewAudioDevice *device = static_cast(userData); + + device->read(reinterpret_cast(output), frameCount * device->bytes_per_frame()); + + return paContinue; +} + +void AudioManager::StartOutput(std::shared_ptr device) +{ + // First stop any current device + StopOutputStream(); + + // Store device + output_device_ = device; + + // Start output stream that pulls from this device + StartOutputStream(OutputCallback); } void AudioManager::StopOutput() { - QMetaObject::invokeMethod(output_manager_, - "ResetToPushMode", - Qt::QueuedConnection); + // Stop playback first so the callback won't get called again + StopOutputStream(); - emit Stopped(); + // Then clear our reference to the output devicec + output_device_ = nullptr; } void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) { - qInfo() << "Setting output audio device to" << info.deviceName(); + /*qInfo() << "Setting output audio device to" << info.deviceName(); StopOutput(); @@ -142,24 +175,18 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) } else { qWarning() << "Output format not supported by device"; } - } + }*/ } void AudioManager::SetOutputParams(const AudioParams ¶ms) { if (output_params_ != params) { + // If an output stream is running, stop it now + StopOutputStream(); + + // Update parameters output_params_ = params; - - QMetaObject::invokeMethod(output_manager_, - "SetParameters", - Qt::QueuedConnection, - OLIVE_NS_ARG(AudioParams, params)); - - // Refresh output device - SetOutputDevice(output_device_info_); } - - emit AudioParamsChanged(output_params_); } void AudioManager::SetInputDevice(const QAudioDeviceInfo &info) @@ -178,50 +205,57 @@ const QList &AudioManager::ListOutputDevices() return output_devices_; } -void AudioManager::ReverseBuffer(char *buffer, int buffer_size, int sample_size) -{ - int half_buffer_sz = buffer_size / 2; - char* temp_buffer = new char[sample_size]; - - for (int src_index=0;src_index(sample_size)); - memcpy(src_ptr, dst_ptr, static_cast(sample_size)); - memcpy(dst_ptr, temp_buffer, static_cast(sample_size)); - } - - delete [] temp_buffer; -} - AudioManager::AudioManager() : is_refreshing_inputs_(false), is_refreshing_outputs_(false), - output_is_set_(false), + output_stream_(nullptr), input_(nullptr), input_file_(nullptr) { - RefreshDevices(); + //RefreshDevices(); - output_thread_.start(QThread::TimeCriticalPriority); - output_manager_ = new AudioOutputManager(); - output_manager_->moveToThread(&output_thread_); + Pa_Initialize(); - connect(output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified); + output_ = Pa_GetDefaultOutputDevice(); } AudioManager::~AudioManager() { - QMetaObject::invokeMethod(output_manager_, "deleteLater", Qt::BlockingQueuedConnection); - output_thread_.quit(); - output_thread_.wait(); + StopOutputStream(); + + Pa_Terminate(); +} + +void AudioManager::StartOutputStream(PaStreamCallback *streamCallback) +{ + if (output_stream_) { + StopOutputStream(); + } + + PaStreamParameters p; + + p.channelCount = output_params_.channel_count(); + p.device = output_; + p.hostApiSpecificStreamInfo = nullptr; + p.sampleFormat = GetPortAudioSampleFormat(output_params_.format()); + p.suggestedLatency = Pa_GetDeviceInfo(output_)->defaultLowOutputLatency; + + Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, streamCallback, output_device_.get()); + Pa_StartStream(output_stream_); +} + +void AudioManager::StopOutputStream() +{ + if (output_stream_) { + Pa_AbortStream(output_stream_); + Pa_CloseStream(output_stream_); + output_stream_ = nullptr; + } } void AudioManager::OutputDevicesRefreshed() { - QFutureWatcher< QList >* watcher = static_cast >*>(sender()); + /*QFutureWatcher< QList >* watcher = static_cast >*>(sender()); output_devices_ = watcher->result(); watcher->deleteLater(); @@ -229,7 +263,7 @@ void AudioManager::OutputDevicesRefreshed() QString preferred_audio_output = Config::Current()["AudioOutput"].toString(); - if (!output_is_set_ + if (output_ == paNoDevice || (!preferred_audio_output.isEmpty() && output_device_info_.deviceName() != preferred_audio_output)) { if (preferred_audio_output.isEmpty()) { SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice()); @@ -243,7 +277,7 @@ void AudioManager::OutputDevicesRefreshed() } } - emit OutputListReady(); + emit OutputListReady();*/ } void AudioManager::InputDevicesRefreshed() diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index a9ba75670..1a8174bc1 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -23,15 +23,15 @@ #include #include -#include #include #include +#include #include "audiovisualwaveform.h" #include "common/define.h" -#include "outputmanager.h" #include "render/audioparams.h" #include "render/audioplaybackcache.h" +#include "render/previewaudiodevice.h" namespace olive { @@ -68,7 +68,7 @@ public: /** * @brief Start playing audio from AudioPlaybackCache */ - void StartOutput(std::shared_ptr device); + void StartOutput(std::shared_ptr device); /** * @brief Stop audio output immediately @@ -84,28 +84,20 @@ public: const QList& ListInputDevices(); const QList& ListOutputDevices(); - static void ReverseBuffer(char* buffer, int size, int resolution); - signals: void OutputListReady(); void InputListReady(); - void OutputNotified(); - - void OutputWaveformStarted(const AudioVisualWaveform* waveform, const rational &start, int playback_speed); - - void AudioParamsChanged(const AudioParams& params); - - void OutputPushed(const QByteArray& data); - - void Stopped(); - private: AudioManager(); virtual ~AudioManager() override; + void StartOutputStream(PaStreamCallback *streamCallback = nullptr); + + void StopOutputStream(); + QList input_devices_; QList output_devices_; @@ -114,12 +106,10 @@ private: static AudioManager* instance_; - QThread output_thread_; - AudioOutputManager* output_manager_; - bool output_is_set_; - - QAudioDeviceInfo output_device_info_; + PaDeviceIndex output_; + PaStream *output_stream_; AudioParams output_params_; + std::shared_ptr output_device_; std::unique_ptr input_; QAudioDeviceInfo input_device_info_; diff --git a/app/audio/outputdeviceproxy.cpp b/app/audio/outputdeviceproxy.cpp deleted file mode 100644 index 924cc359c..000000000 --- a/app/audio/outputdeviceproxy.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "outputdeviceproxy.h" - -#include "audiomanager.h" - -namespace olive { - -AudioOutputDeviceProxy::AudioOutputDeviceProxy(QObject *parent) : - QIODevice(parent), - device_(nullptr) -{ -} - -void AudioOutputDeviceProxy::SetParameters(const AudioParams ¶ms) -{ - params_ = params; -} - -void AudioOutputDeviceProxy::SetDevice(std::shared_ptr device) -{ - device_ = device; - - if (!device_->open(QFile::ReadOnly)) { - qCritical() << "Failed to open IO device for audio playback"; - device_ = nullptr; - return; - } -} - -void AudioOutputDeviceProxy::close() -{ - QIODevice::close(); - - device_ = nullptr; -} - -qint64 AudioOutputDeviceProxy::readData(char *data, qint64 maxlen) -{ - if (!device_) { - return 0; - } - - return device_->read(data, maxlen); -} - -qint64 AudioOutputDeviceProxy::writeData(const char *data, qint64 maxSize) -{ - Q_UNUSED(data) - Q_UNUSED(maxSize) - - return -1; -} - -} diff --git a/app/audio/outputdeviceproxy.h b/app/audio/outputdeviceproxy.h deleted file mode 100644 index 092776a7c..000000000 --- a/app/audio/outputdeviceproxy.h +++ /dev/null @@ -1,60 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef AUDIOOUTPUTDEVICEPROXY_H -#define AUDIOOUTPUTDEVICEPROXY_H - -#include - -#include "common/define.h" -#include "tempoprocessor.h" - -namespace olive { - -/** - * @brief QIODevice wrapper that can adjust speed/reverse an audio file - */ -class AudioOutputDeviceProxy : public QIODevice -{ - Q_OBJECT -public: - AudioOutputDeviceProxy(QObject* parent = nullptr); - - void SetParameters(const AudioParams& params); - - void SetDevice(std::shared_ptr device); - - virtual void close() override; - -protected: - virtual qint64 readData(char *data, qint64 maxlen) override; - - virtual qint64 writeData(const char *data, qint64 maxSize) override; - -private: - std::shared_ptr device_; - - AudioParams params_; - -}; - -} - -#endif // AUDIOOUTPUTDEVICEPROXY_H diff --git a/app/audio/outputmanager.cpp b/app/audio/outputmanager.cpp deleted file mode 100644 index 9182c5040..000000000 --- a/app/audio/outputmanager.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "outputmanager.h" - -#include -#include - -#include - -namespace olive { - -AudioOutputManager::AudioOutputManager(QObject *parent) : - QObject(parent), - output_(nullptr), - push_device_(nullptr), - device_proxy_(this) -{ -} - -AudioOutputManager::~AudioOutputManager() -{ - Close(); -} - -void AudioOutputManager::Push(const QByteArray& samples) -{ - // 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 - push_samples_ = samples; - push_sample_index_ = 0; - - // If we had another device connected, disconnect it now - QMetaObject::invokeMethod(this, "ResetToPushMode", Qt::QueuedConnection); - - // Start pushing samples to the output - QMetaObject::invokeMethod(this, "PushMoreSamples", Qt::QueuedConnection); -} - -void AudioOutputManager::ResetToPushMode() -{ - // If we have a null push device, then we currently have the output in pull mode. We restore it to push mode here. - if (output_ && !push_device_) { - output_->stop(); - - device_proxy_.close(); - - // Put QAudioOutput back into push mode - push_device_ = output_->start(); - } -} - -void AudioOutputManager::SetParameters(AudioParams params) -{ - device_proxy_.SetParameters(params); -} - -void AudioOutputManager::Close() -{ - if (output_) { - output_->stop(); - - push_device_ = nullptr; - - if (device_proxy_.isOpen()) { - device_proxy_.close(); - } - - delete output_; - output_ = nullptr; - } -} - -void AudioOutputManager::PullFromDevice(std::shared_ptr device) -{ - if (!output_) { - return; - } - - // Stop any current output and disable push mode - output_->stop(); - push_device_ = nullptr; - push_samples_.clear(); - - // Pull from the device - device_proxy_.SetDevice(device); - device_proxy_.open(QIODevice::ReadOnly); - output_->start(&device_proxy_); -} - -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_ || push_samples_.isEmpty()) { - return; - } - - 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, - push_samples_.size() - push_sample_index_); - - // Increment sample buffer index (faster than shift the bytes up) - push_sample_index_ += static_cast(write_count); - - // If we've pushed all samples, we can clear this array - 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 - Close(); - - // Create a new output device and start it in push mode - output_ = new QAudioOutput(info, format, this); - output_->setBufferSize(16384); - output_->setNotifyInterval(1); - push_device_ = output_->start(); - connect(output_, &QAudioOutput::notify, this, &AudioOutputManager::PushMoreSamples); - connect(output_, &QAudioOutput::notify, this, &AudioOutputManager::OutputNotified); - - // Un-comment this to get debug information about what the audio output is doing - //connect(output_, &QAudioOutput::stateChanged, this, &AudioOutputManager::OutputStateChanged); -} - -void AudioOutputManager::OutputStateChanged(QAudio::State state) -{ - qDebug() << state << output_->error(); -} - -} diff --git a/app/audio/outputmanager.h b/app/audio/outputmanager.h deleted file mode 100644 index cea14474e..000000000 --- a/app/audio/outputmanager.h +++ /dev/null @@ -1,91 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef AUDIOHYBRIDDEVICE_H -#define AUDIOHYBRIDDEVICE_H - -#include -#include -#include -#include -#include -#include - -#include "outputdeviceproxy.h" - -namespace olive { - -class AudioOutputManager : public QObject -{ - Q_OBJECT -public: - AudioOutputManager(QObject* parent = nullptr); - - virtual ~AudioOutputManager() override; - - // 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 - * - * This will clear any pushed samples or QIODevices currently being read and will start reading from this next time - * the audio output requests data. - */ - void PullFromDevice(std::shared_ptr device); - - // Queued - void ResetToPushMode(); - - // Queued - void SetParameters(olive::AudioParams params); - - // Queued - void Close(); - -signals: - void OutputNotified(); - -private: - QAudioOutput* output_; - QIODevice* push_device_; - - QMutex push_sample_lock_; - QByteArray push_samples_; - int push_sample_index_; - - AudioOutputDeviceProxy device_proxy_; - -private slots: - void PushMoreSamples(); - - void OutputStateChanged(QAudio::State state); - -}; - -} - -Q_DECLARE_METATYPE(std::shared_ptr) - -#endif // AUDIOHYBRIDDEVICE_H diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index 0532cb26c..57c3027b5 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -33,7 +33,12 @@ PreferencesAudioTab::PreferencesAudioTab() { QVBoxLayout* audio_tab_layout = new QVBoxLayout(this); - { + QLabel *wip_lbl = new QLabel(tr("We just ported our audio backend to PortAudio so this section will need to be redone. Come back later...")); + wip_lbl->setWordWrap(true); + wip_lbl->setAlignment(Qt::AlignCenter); + audio_tab_layout->addWidget(wip_lbl); + + /*{ // Backend Layout QGridLayout* main_layout = new QGridLayout(); main_layout->setMargin(0); @@ -112,11 +117,12 @@ PreferencesAudioTab::PreferencesAudioTab() connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList); } - audio_tab_layout->addStretch(); + audio_tab_layout->addStretch();*/ } void PreferencesAudioTab::Accept(MultiUndoCommand *command) { + /* Q_UNUSED(command) // FIXME: Qt documentation states that QAudioDeviceInfo::deviceName() is a "unique identifiers", which would make them @@ -161,6 +167,7 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command) AudioManager::instance()->SetInputDevice(selected_input); } } + */ } void PreferencesAudioTab::RefreshDevices() diff --git a/app/render/previewaudiodevice.cpp b/app/render/previewaudiodevice.cpp index 6a4f770a8..b46fff7c0 100644 --- a/app/render/previewaudiodevice.cpp +++ b/app/render/previewaudiodevice.cpp @@ -22,7 +22,10 @@ namespace olive { -PreviewAudioDevice::PreviewAudioDevice(QObject *parent) +PreviewAudioDevice::PreviewAudioDevice(int bytes_per_frame, QObject *parent) : + bytes_per_frame_(bytes_per_frame), + notify_interval_(0), + bytes_read_(0) { // These pointers are always valid using_ = &internal_buffer_[0]; @@ -53,35 +56,41 @@ qint64 PreviewAudioDevice::readData(char *data, qint64 maxSize) qint64 copy_length = qMin(maxSize, qint64(using_->size())); if (copy_length) { + qint64 new_bytes_read = bytes_read_ + copy_length; + + if (notify_interval_ > 0) { + if ((bytes_read_ / notify_interval_) != (new_bytes_read / notify_interval_)) { + emit Notify(); + } + } + + bytes_read_ = new_bytes_read; + memcpy(data, using_->constData(), copy_length); *using_ = using_->mid(copy_length); + } - if (using_->isEmpty() && !SwapBuffers(kTryLock)) { - // Ask push function to swap if it can. If it can't, we'll catch it next read. - swap_requested_ = true; - } + if (using_->isEmpty() && !SwapBuffers(kTryLock)) { + // Ask push function to swap if it can. If it can't, we'll catch it next read. + swap_requested_ = true; } return copy_length; } -qint64 PreviewAudioDevice::writeData(const char *, qint64) -{ - // No writing to this device - return -1; -} - -void PreviewAudioDevice::Push(const QByteArray &b) +qint64 PreviewAudioDevice::writeData(const char *data, qint64 length) { // This function should NEVER touch the buffer in `using_` QMutexLocker locker(&lock_); - pushing_->append(b); + pushing_->append(data, length); // If swap requested, do this now if (swap_requested_) { SwapBuffers(kDontLock); swap_requested_ = false; } + + return length; } bool PreviewAudioDevice::SwapBuffers(LockMethod m) diff --git a/app/render/previewaudiodevice.h b/app/render/previewaudiodevice.h index 9b4179128..a285ad088 100644 --- a/app/render/previewaudiodevice.h +++ b/app/render/previewaudiodevice.h @@ -29,7 +29,7 @@ class PreviewAudioDevice : public QIODevice { Q_OBJECT public: - PreviewAudioDevice(QObject *parent = nullptr); + PreviewAudioDevice(int bytes_per_frame, QObject *parent = nullptr); virtual ~PreviewAudioDevice() override; @@ -39,9 +39,20 @@ public: virtual qint64 readData(char *data, qint64 maxSize) override; - virtual qint64 writeData(const char *, qint64) override; + virtual qint64 writeData(const char *data, qint64 length) override; - void Push(const QByteArray &b); + int bytes_per_frame() const + { + return bytes_per_frame_; + } + + void set_notify_interval(qint64 i) + { + notify_interval_ = i; + } + +signals: + void Notify(); private: enum LockMethod { @@ -61,6 +72,12 @@ private: QAtomicInt swap_requested_; + int bytes_per_frame_; + + qint64 notify_interval_; + + qint64 bytes_read_; + }; } diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index 6363465c5..d2285ecc8 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -33,18 +33,22 @@ const int kDecibelStep = 6; const int kDecibelMinimum = -200; const int kMaximumSmoothness = 8; +QVector AudioMonitor::instances_; + AudioMonitor::AudioMonitor(QWidget *parent) : QOpenGLWidget(parent), file_(nullptr), waveform_(nullptr), cached_channels_(0) { - values_.resize(kMaximumSmoothness); + instances_.append(this); - connect(AudioManager::instance(), &AudioManager::OutputWaveformStarted, this, &AudioMonitor::OutputAudioVisualWaveformSet); - connect(AudioManager::instance(), &AudioManager::OutputPushed, this, &AudioMonitor::OutputPushed); - connect(AudioManager::instance(), &AudioManager::AudioParamsChanged, this, &AudioMonitor::SetParams); - connect(AudioManager::instance(), &AudioManager::Stopped, this, &AudioMonitor::Stop); + values_.resize(kMaximumSmoothness); +} + +AudioMonitor::~AudioMonitor() +{ + instances_.removeOne(this); } void AudioMonitor::SetParams(const AudioParams ¶ms) @@ -64,32 +68,6 @@ void AudioMonitor::SetParams(const AudioParams ¶ms) } } -void AudioMonitor::OutputDeviceSet(AudioPlaybackCache *cache, qint64 offset, int playback_speed) -{ - Stop(); - - file_ = cache->CreatePlaybackDevice(this); - - if (!file_->open(QFile::ReadOnly)) { - qWarning() << "Failed to open IO device for AudioMonitor display"; - Stop(); - return; - } - - if (offset > file_->size()) { - Stop(); - return; - } - - file_->seek(offset); - - playback_speed_ = playback_speed; - - last_time_ = QDateTime::currentMSecsSinceEpoch(); - - SetUpdateLoop(true); -} - void AudioMonitor::Stop() { delete file_; @@ -100,7 +78,7 @@ void AudioMonitor::Stop() // loop will stop itself since file_ and waveform_ are null. } -void AudioMonitor::OutputPushed(const QByteArray &d) +void AudioMonitor::PushBytes(const QByteArray &d) { QVector v(params_.channel_count(), 0); @@ -111,7 +89,7 @@ void AudioMonitor::OutputPushed(const QByteArray &d) SetUpdateLoop(true); } -void AudioMonitor::OutputAudioVisualWaveformSet(const AudioVisualWaveform *waveform, const rational &start, int playback_speed) +void AudioMonitor::StartWaveform(const AudioVisualWaveform *waveform, const rational &start, int playback_speed) { Stop(); diff --git a/app/widget/audiomonitor/audiomonitor.h b/app/widget/audiomonitor/audiomonitor.h index 1570618bc..e9bdf05b3 100644 --- a/app/widget/audiomonitor/audiomonitor.h +++ b/app/widget/audiomonitor/audiomonitor.h @@ -38,21 +38,42 @@ class AudioMonitor : public QOpenGLWidget public: AudioMonitor(QWidget* parent = nullptr); + virtual ~AudioMonitor() override; + bool IsPlaying() const { return file_ || waveform_; } + static void StartWaveformOnAll(const AudioVisualWaveform *waveform, const rational& start, int playback_speed) + { + foreach (AudioMonitor *m, instances_) { + m->StartWaveform(waveform, start, playback_speed); + } + } + + static void StopOnAll() + { + foreach (AudioMonitor *m, instances_) { + m->Stop(); + } + } + + static void PushBytesOnAll(const QByteArray &d) + { + foreach (AudioMonitor *m, instances_) { + m->PushBytes(d); + } + } + public slots: void SetParams(const AudioParams& params); - void OutputDeviceSet(AudioPlaybackCache* cache, qint64 offset, int playback_speed); - void Stop(); - void OutputPushed(const QByteArray& d); + void PushBytes(const QByteArray& d); - void OutputAudioVisualWaveformSet(const AudioVisualWaveform *waveform, const rational& start, int playback_speed); + void StartWaveform(const AudioVisualWaveform *waveform, const rational& start, int playback_speed); protected: virtual void paintGL() override; @@ -88,6 +109,8 @@ private: QPixmap cached_background_; int cached_channels_; + static QVector instances_; + }; } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index c5a162ba4..2a2510a62 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -128,11 +128,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) : instances_.append(this); setAcceptDrops(true); - - audio_queue_next_timer_ = new QTimer(this); - audio_queue_next_timer_->setInterval(kAudioPlaybackInterval * 1000); - audio_queue_next_timer_->setSingleShot(true); - connect(audio_queue_next_timer_, &QTimer::timeout, this, &ViewerWidget::QueueNextAudioBuffer); } ViewerWidget::~ViewerWidget() @@ -412,15 +407,10 @@ void ViewerWidget::ClearVideoAutoCacherQueue() void ViewerWidget::StartAudioOutput() { - AudioParams params = GetConnectedNode()->GetAudioParams(); - - if (params.is_valid()) { - AudioManager::instance()->SetOutputParams(params); - AudioManager::instance()->StartOutput(audio_playback_device_); - - emit AudioManager::instance()->OutputWaveformStarted(&GetConnectedNode()->audio_playback_cache()->visual(), - GetTime(), playback_speed_); - } + AudioManager::instance()->SetOutputParams(GetConnectedNode()->GetAudioParams()); + AudioManager::instance()->StartOutput(audio_playback_device_); + AudioMonitor::StartWaveformOnAll(&GetConnectedNode()->audio_playback_cache()->visual(), + GetTime(), playback_speed_); } void ViewerWidget::QueueNextAudioBuffer() @@ -470,7 +460,7 @@ void ViewerWidget::ReceivedAudioBufferForPlayback() // TempoProcessor may have emptied the array if (!pack.isEmpty()) { - audio_playback_device_->Push(pack); + audio_playback_device_->write(pack); if (prequeuing_audio_) { prequeuing_audio_--; @@ -483,10 +473,6 @@ void ViewerWidget::ReceivedAudioBufferForPlayback() } } - // Do this in the loop so that clearing the array effectively prevents a queue - audio_queue_next_timer_->stop(); - audio_queue_next_timer_->start(); - delete watcher; } } @@ -508,8 +494,10 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing() samples->transform_volume_for_sample(samples->sample_count() - i - 1, amt); }*/ + QByteArray data = packed_processor_.Convert(samples); AudioManager::instance()->SetOutputParams(samples->audio_params()); - AudioManager::instance()->PushToOutput(packed_processor_.Convert(samples)); + AudioManager::instance()->PushToOutput(data); + AudioMonitor::PushBytesOnAll(data); } } @@ -674,14 +662,23 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) } } - if (std::abs(playback_speed_) > 1) { - tempo_processor_.Open(GetConnectedNode()->GetAudioParams(), std::abs(playback_speed_)); - } - audio_playback_device_ = std::make_shared(); - prequeuing_audio_ = 2; // Queue two buffers ahead of time - audio_playback_queue_time_ = GetTime(); - for (int i=0; iGetAudioParams(); + if (ap.is_valid()) { + audio_playback_device_ = std::make_shared(ap.bytes_per_sample_per_channel() * ap.channel_count()); + audio_playback_device_->set_notify_interval(ap.time_to_bytes(kAudioPlaybackInterval)); + connect(audio_playback_device_.get(), &PreviewAudioDevice::Notify, this, &ViewerWidget::QueueNextAudioBuffer, Qt::QueuedConnection); + + if (audio_playback_device_->open(QIODevice::ReadWrite)) { + if (std::abs(playback_speed_) > 1) { + tempo_processor_.Open(GetConnectedNode()->GetAudioParams(), std::abs(playback_speed_)); + } + + prequeuing_audio_ = 2; // Queue two buffers ahead of time + audio_playback_queue_time_ = GetTime(); + for (int i=0; iStopOutput(); + AudioMonitor::StopOnAll(); + playback_speed_ = 0; controls_->ShowPlayButton(); @@ -704,13 +703,13 @@ void ViewerWidget::PauseInternal() playback_queue_.clear(); playback_backup_timer_.stop(); + disconnect(audio_playback_device_.get(), &PreviewAudioDevice::Notify, this, &ViewerWidget::QueueNextAudioBuffer); audio_playback_device_ = nullptr; qDeleteAll(audio_playback_queue_); audio_playback_queue_.clear(); if (tempo_processor_.IsOpen()) { tempo_processor_.Close(); } - audio_queue_next_timer_->stop(); UpdateTextureFromNode(); } @@ -834,7 +833,9 @@ void ViewerWidget::FinishPlayPreprocess() int64_t playback_start_time = GetTimestamp(); - StartAudioOutput(); + if (audio_playback_device_) { + StartAudioOutput(); + } playback_timer_.Start(playback_start_time, playback_speed_, timebase_dbl()); display_widget_->ResetFPSTimer(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 0f071ea57..b9633b44f 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -262,7 +262,6 @@ private: PackedProcessor packed_processor_; TempoProcessor tempo_processor_; static const int kAudioPlaybackInterval; - QTimer *audio_queue_next_timer_; static QVector instances_;