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.
This commit is contained in:
itsmattkc
2021-10-01 15:41:24 -07:00
parent 06066c279b
commit ee81a47f2e
15 changed files with 224 additions and 546 deletions
+4
View File
@@ -122,6 +122,10 @@ list(APPEND OLIVE_LIBRARIES
FFMPEG::swresample FFMPEG::swresample
) )
# Link PortAudio
find_package(PortAudio REQUIRED)
list(APPEND OLIVE_LIBRARIES PortAudio)
# Optional: Link OpenTimelineIO # Optional: Link OpenTimelineIO
find_package(OpenTimelineIO) find_package(OpenTimelineIO)
if (OpenTimelineIO_FOUND) if (OpenTimelineIO_FOUND)
-4
View File
@@ -20,10 +20,6 @@ set(OLIVE_SOURCES
audio/audiomanager.h audio/audiomanager.h
audio/audiovisualwaveform.cpp audio/audiovisualwaveform.cpp
audio/audiovisualwaveform.h audio/audiovisualwaveform.h
audio/outputdeviceproxy.cpp
audio/outputdeviceproxy.h
audio/outputmanager.cpp
audio/outputmanager.h
audio/packedprocessor.cpp audio/packedprocessor.cpp
audio/packedprocessor.h audio/packedprocessor.h
audio/tempoprocessor.cpp audio/tempoprocessor.cpp
+90 -56
View File
@@ -89,35 +89,68 @@ bool AudioManager::IsRefreshingInputs()
void AudioManager::PushToOutput(const QByteArray &samples) 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<QIODevice> device) static PaSampleFormat GetPortAudioSampleFormat(AudioParams::Format fmt)
{ {
// Move to output manager's thread switch (fmt) {
device->moveToThread(&output_thread_); 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 return 0;
QMetaObject::invokeMethod(output_manager_, }
"PullFromDevice",
Qt::QueuedConnection, int OutputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
Q_ARG(std::shared_ptr<QIODevice>, device)); {
PreviewAudioDevice *device = static_cast<PreviewAudioDevice*>(userData);
device->read(reinterpret_cast<char*>(output), frameCount * device->bytes_per_frame());
return paContinue;
}
void AudioManager::StartOutput(std::shared_ptr<PreviewAudioDevice> 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() void AudioManager::StopOutput()
{ {
QMetaObject::invokeMethod(output_manager_, // Stop playback first so the callback won't get called again
"ResetToPushMode", StopOutputStream();
Qt::QueuedConnection);
emit Stopped(); // Then clear our reference to the output devicec
output_device_ = nullptr;
} }
void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info)
{ {
qInfo() << "Setting output audio device to" << info.deviceName(); /*qInfo() << "Setting output audio device to" << info.deviceName();
StopOutput(); StopOutput();
@@ -142,24 +175,18 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info)
} else { } else {
qWarning() << "Output format not supported by device"; qWarning() << "Output format not supported by device";
} }
} }*/
} }
void AudioManager::SetOutputParams(const AudioParams &params) void AudioManager::SetOutputParams(const AudioParams &params)
{ {
if (output_params_ != params) { if (output_params_ != params) {
// If an output stream is running, stop it now
StopOutputStream();
// Update parameters
output_params_ = params; 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) void AudioManager::SetInputDevice(const QAudioDeviceInfo &info)
@@ -178,50 +205,57 @@ const QList<QAudioDeviceInfo> &AudioManager::ListOutputDevices()
return output_devices_; 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<half_buffer_sz;src_index+=sample_size) {
char* src_ptr = buffer + src_index;
char* dst_ptr = buffer + buffer_size - sample_size - src_index;
// Simple swap
memcpy(temp_buffer, src_ptr, static_cast<size_t>(sample_size));
memcpy(src_ptr, dst_ptr, static_cast<size_t>(sample_size));
memcpy(dst_ptr, temp_buffer, static_cast<size_t>(sample_size));
}
delete [] temp_buffer;
}
AudioManager::AudioManager() : AudioManager::AudioManager() :
is_refreshing_inputs_(false), is_refreshing_inputs_(false),
is_refreshing_outputs_(false), is_refreshing_outputs_(false),
output_is_set_(false), output_stream_(nullptr),
input_(nullptr), input_(nullptr),
input_file_(nullptr) input_file_(nullptr)
{ {
RefreshDevices(); //RefreshDevices();
output_thread_.start(QThread::TimeCriticalPriority); Pa_Initialize();
output_manager_ = new AudioOutputManager();
output_manager_->moveToThread(&output_thread_);
connect(output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified); output_ = Pa_GetDefaultOutputDevice();
} }
AudioManager::~AudioManager() AudioManager::~AudioManager()
{ {
QMetaObject::invokeMethod(output_manager_, "deleteLater", Qt::BlockingQueuedConnection); StopOutputStream();
output_thread_.quit();
output_thread_.wait(); 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() void AudioManager::OutputDevicesRefreshed()
{ {
QFutureWatcher< QList<QAudioDeviceInfo> >* watcher = static_cast<QFutureWatcher< QList<QAudioDeviceInfo> >*>(sender()); /*QFutureWatcher< QList<QAudioDeviceInfo> >* watcher = static_cast<QFutureWatcher< QList<QAudioDeviceInfo> >*>(sender());
output_devices_ = watcher->result(); output_devices_ = watcher->result();
watcher->deleteLater(); watcher->deleteLater();
@@ -229,7 +263,7 @@ void AudioManager::OutputDevicesRefreshed()
QString preferred_audio_output = Config::Current()["AudioOutput"].toString(); 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)) { || (!preferred_audio_output.isEmpty() && output_device_info_.deviceName() != preferred_audio_output)) {
if (preferred_audio_output.isEmpty()) { if (preferred_audio_output.isEmpty()) {
SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice()); SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice());
@@ -243,7 +277,7 @@ void AudioManager::OutputDevicesRefreshed()
} }
} }
emit OutputListReady(); emit OutputListReady();*/
} }
void AudioManager::InputDevicesRefreshed() void AudioManager::InputDevicesRefreshed()
+10 -20
View File
@@ -23,15 +23,15 @@
#include <memory> #include <memory>
#include <QAudioInput> #include <QAudioInput>
#include <QAudioOutput>
#include <QtConcurrent/QtConcurrent> #include <QtConcurrent/QtConcurrent>
#include <QThread> #include <QThread>
#include <portaudio.h>
#include "audiovisualwaveform.h" #include "audiovisualwaveform.h"
#include "common/define.h" #include "common/define.h"
#include "outputmanager.h"
#include "render/audioparams.h" #include "render/audioparams.h"
#include "render/audioplaybackcache.h" #include "render/audioplaybackcache.h"
#include "render/previewaudiodevice.h"
namespace olive { namespace olive {
@@ -68,7 +68,7 @@ public:
/** /**
* @brief Start playing audio from AudioPlaybackCache * @brief Start playing audio from AudioPlaybackCache
*/ */
void StartOutput(std::shared_ptr<QIODevice> device); void StartOutput(std::shared_ptr<PreviewAudioDevice> device);
/** /**
* @brief Stop audio output immediately * @brief Stop audio output immediately
@@ -84,28 +84,20 @@ public:
const QList<QAudioDeviceInfo>& ListInputDevices(); const QList<QAudioDeviceInfo>& ListInputDevices();
const QList<QAudioDeviceInfo>& ListOutputDevices(); const QList<QAudioDeviceInfo>& ListOutputDevices();
static void ReverseBuffer(char* buffer, int size, int resolution);
signals: signals:
void OutputListReady(); void OutputListReady();
void InputListReady(); 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: private:
AudioManager(); AudioManager();
virtual ~AudioManager() override; virtual ~AudioManager() override;
void StartOutputStream(PaStreamCallback *streamCallback = nullptr);
void StopOutputStream();
QList<QAudioDeviceInfo> input_devices_; QList<QAudioDeviceInfo> input_devices_;
QList<QAudioDeviceInfo> output_devices_; QList<QAudioDeviceInfo> output_devices_;
@@ -114,12 +106,10 @@ private:
static AudioManager* instance_; static AudioManager* instance_;
QThread output_thread_; PaDeviceIndex output_;
AudioOutputManager* output_manager_; PaStream *output_stream_;
bool output_is_set_;
QAudioDeviceInfo output_device_info_;
AudioParams output_params_; AudioParams output_params_;
std::shared_ptr<QIODevice> output_device_;
std::unique_ptr<QAudioInput> input_; std::unique_ptr<QAudioInput> input_;
QAudioDeviceInfo input_device_info_; QAudioDeviceInfo input_device_info_;
-73
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "outputdeviceproxy.h"
#include "audiomanager.h"
namespace olive {
AudioOutputDeviceProxy::AudioOutputDeviceProxy(QObject *parent) :
QIODevice(parent),
device_(nullptr)
{
}
void AudioOutputDeviceProxy::SetParameters(const AudioParams &params)
{
params_ = params;
}
void AudioOutputDeviceProxy::SetDevice(std::shared_ptr<QIODevice> 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;
}
}
-60
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef AUDIOOUTPUTDEVICEPROXY_H
#define AUDIOOUTPUTDEVICEPROXY_H
#include <QFile>
#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<QIODevice> 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<QIODevice> device_;
AudioParams params_;
};
}
#endif // AUDIOOUTPUTDEVICEPROXY_H
-156
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "outputmanager.h"
#include <QDebug>
#include <QtMath>
#include <QFile>
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<QIODevice> 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<int>(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();
}
}
-91
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef AUDIOHYBRIDDEVICE_H
#define AUDIOHYBRIDDEVICE_H
#include <memory>
#include <QAudioOutput>
#include <QBuffer>
#include <QIODevice>
#include <QMutex>
#include <QThread>
#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<QIODevice> 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<QIODevice>)
#endif // AUDIOHYBRIDDEVICE_H
@@ -33,7 +33,12 @@ PreferencesAudioTab::PreferencesAudioTab()
{ {
QVBoxLayout* audio_tab_layout = new QVBoxLayout(this); 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 // Backend Layout
QGridLayout* main_layout = new QGridLayout(); QGridLayout* main_layout = new QGridLayout();
main_layout->setMargin(0); main_layout->setMargin(0);
@@ -112,11 +117,12 @@ PreferencesAudioTab::PreferencesAudioTab()
connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList); connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList);
} }
audio_tab_layout->addStretch(); audio_tab_layout->addStretch();*/
} }
void PreferencesAudioTab::Accept(MultiUndoCommand *command) void PreferencesAudioTab::Accept(MultiUndoCommand *command)
{ {
/*
Q_UNUSED(command) Q_UNUSED(command)
// FIXME: Qt documentation states that QAudioDeviceInfo::deviceName() is a "unique identifiers", which would make them // 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); AudioManager::instance()->SetInputDevice(selected_input);
} }
} }
*/
} }
void PreferencesAudioTab::RefreshDevices() void PreferencesAudioTab::RefreshDevices()
+22 -13
View File
@@ -22,7 +22,10 @@
namespace olive { 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 // These pointers are always valid
using_ = &internal_buffer_[0]; using_ = &internal_buffer_[0];
@@ -53,35 +56,41 @@ qint64 PreviewAudioDevice::readData(char *data, qint64 maxSize)
qint64 copy_length = qMin(maxSize, qint64(using_->size())); qint64 copy_length = qMin(maxSize, qint64(using_->size()));
if (copy_length) { 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); memcpy(data, using_->constData(), copy_length);
*using_ = using_->mid(copy_length); *using_ = using_->mid(copy_length);
}
if (using_->isEmpty() && !SwapBuffers(kTryLock)) { if (using_->isEmpty() && !SwapBuffers(kTryLock)) {
// Ask push function to swap if it can. If it can't, we'll catch it next read. // Ask push function to swap if it can. If it can't, we'll catch it next read.
swap_requested_ = true; swap_requested_ = true;
}
} }
return copy_length; return copy_length;
} }
qint64 PreviewAudioDevice::writeData(const char *, qint64) qint64 PreviewAudioDevice::writeData(const char *data, qint64 length)
{
// No writing to this device
return -1;
}
void PreviewAudioDevice::Push(const QByteArray &b)
{ {
// This function should NEVER touch the buffer in `using_` // This function should NEVER touch the buffer in `using_`
QMutexLocker locker(&lock_); QMutexLocker locker(&lock_);
pushing_->append(b); pushing_->append(data, length);
// If swap requested, do this now // If swap requested, do this now
if (swap_requested_) { if (swap_requested_) {
SwapBuffers(kDontLock); SwapBuffers(kDontLock);
swap_requested_ = false; swap_requested_ = false;
} }
return length;
} }
bool PreviewAudioDevice::SwapBuffers(LockMethod m) bool PreviewAudioDevice::SwapBuffers(LockMethod m)
+20 -3
View File
@@ -29,7 +29,7 @@ class PreviewAudioDevice : public QIODevice
{ {
Q_OBJECT Q_OBJECT
public: public:
PreviewAudioDevice(QObject *parent = nullptr); PreviewAudioDevice(int bytes_per_frame, QObject *parent = nullptr);
virtual ~PreviewAudioDevice() override; virtual ~PreviewAudioDevice() override;
@@ -39,9 +39,20 @@ public:
virtual qint64 readData(char *data, qint64 maxSize) override; 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: private:
enum LockMethod { enum LockMethod {
@@ -61,6 +72,12 @@ private:
QAtomicInt swap_requested_; QAtomicInt swap_requested_;
int bytes_per_frame_;
qint64 notify_interval_;
qint64 bytes_read_;
}; };
} }
+11 -33
View File
@@ -33,18 +33,22 @@ const int kDecibelStep = 6;
const int kDecibelMinimum = -200; const int kDecibelMinimum = -200;
const int kMaximumSmoothness = 8; const int kMaximumSmoothness = 8;
QVector<AudioMonitor*> AudioMonitor::instances_;
AudioMonitor::AudioMonitor(QWidget *parent) : AudioMonitor::AudioMonitor(QWidget *parent) :
QOpenGLWidget(parent), QOpenGLWidget(parent),
file_(nullptr), file_(nullptr),
waveform_(nullptr), waveform_(nullptr),
cached_channels_(0) cached_channels_(0)
{ {
values_.resize(kMaximumSmoothness); instances_.append(this);
connect(AudioManager::instance(), &AudioManager::OutputWaveformStarted, this, &AudioMonitor::OutputAudioVisualWaveformSet); values_.resize(kMaximumSmoothness);
connect(AudioManager::instance(), &AudioManager::OutputPushed, this, &AudioMonitor::OutputPushed); }
connect(AudioManager::instance(), &AudioManager::AudioParamsChanged, this, &AudioMonitor::SetParams);
connect(AudioManager::instance(), &AudioManager::Stopped, this, &AudioMonitor::Stop); AudioMonitor::~AudioMonitor()
{
instances_.removeOne(this);
} }
void AudioMonitor::SetParams(const AudioParams &params) void AudioMonitor::SetParams(const AudioParams &params)
@@ -64,32 +68,6 @@ void AudioMonitor::SetParams(const AudioParams &params)
} }
} }
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() void AudioMonitor::Stop()
{ {
delete file_; delete file_;
@@ -100,7 +78,7 @@ void AudioMonitor::Stop()
// loop will stop itself since file_ and waveform_ are null. // loop will stop itself since file_ and waveform_ are null.
} }
void AudioMonitor::OutputPushed(const QByteArray &d) void AudioMonitor::PushBytes(const QByteArray &d)
{ {
QVector<double> v(params_.channel_count(), 0); QVector<double> v(params_.channel_count(), 0);
@@ -111,7 +89,7 @@ void AudioMonitor::OutputPushed(const QByteArray &d)
SetUpdateLoop(true); 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(); Stop();
+27 -4
View File
@@ -38,21 +38,42 @@ class AudioMonitor : public QOpenGLWidget
public: public:
AudioMonitor(QWidget* parent = nullptr); AudioMonitor(QWidget* parent = nullptr);
virtual ~AudioMonitor() override;
bool IsPlaying() const bool IsPlaying() const
{ {
return file_ || waveform_; 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: public slots:
void SetParams(const AudioParams& params); void SetParams(const AudioParams& params);
void OutputDeviceSet(AudioPlaybackCache* cache, qint64 offset, int playback_speed);
void Stop(); 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: protected:
virtual void paintGL() override; virtual void paintGL() override;
@@ -88,6 +109,8 @@ private:
QPixmap cached_background_; QPixmap cached_background_;
int cached_channels_; int cached_channels_;
static QVector<AudioMonitor*> instances_;
}; };
} }
+31 -30
View File
@@ -128,11 +128,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
instances_.append(this); instances_.append(this);
setAcceptDrops(true); 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() ViewerWidget::~ViewerWidget()
@@ -412,15 +407,10 @@ void ViewerWidget::ClearVideoAutoCacherQueue()
void ViewerWidget::StartAudioOutput() void ViewerWidget::StartAudioOutput()
{ {
AudioParams params = GetConnectedNode()->GetAudioParams(); AudioManager::instance()->SetOutputParams(GetConnectedNode()->GetAudioParams());
AudioManager::instance()->StartOutput(audio_playback_device_);
if (params.is_valid()) { AudioMonitor::StartWaveformOnAll(&GetConnectedNode()->audio_playback_cache()->visual(),
AudioManager::instance()->SetOutputParams(params); GetTime(), playback_speed_);
AudioManager::instance()->StartOutput(audio_playback_device_);
emit AudioManager::instance()->OutputWaveformStarted(&GetConnectedNode()->audio_playback_cache()->visual(),
GetTime(), playback_speed_);
}
} }
void ViewerWidget::QueueNextAudioBuffer() void ViewerWidget::QueueNextAudioBuffer()
@@ -470,7 +460,7 @@ void ViewerWidget::ReceivedAudioBufferForPlayback()
// TempoProcessor may have emptied the array // TempoProcessor may have emptied the array
if (!pack.isEmpty()) { if (!pack.isEmpty()) {
audio_playback_device_->Push(pack); audio_playback_device_->write(pack);
if (prequeuing_audio_) { if (prequeuing_audio_) {
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; delete watcher;
} }
} }
@@ -508,8 +494,10 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing()
samples->transform_volume_for_sample(samples->sample_count() - i - 1, amt); 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()->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) { AudioParams ap = GetConnectedNode()->GetAudioParams();
tempo_processor_.Open(GetConnectedNode()->GetAudioParams(), std::abs(playback_speed_)); if (ap.is_valid()) {
} audio_playback_device_ = std::make_shared<PreviewAudioDevice>(ap.bytes_per_sample_per_channel() * ap.channel_count());
audio_playback_device_ = std::make_shared<PreviewAudioDevice>(); audio_playback_device_->set_notify_interval(ap.time_to_bytes(kAudioPlaybackInterval));
prequeuing_audio_ = 2; // Queue two buffers ahead of time connect(audio_playback_device_.get(), &PreviewAudioDevice::Notify, this, &ViewerWidget::QueueNextAudioBuffer, Qt::QueuedConnection);
audio_playback_queue_time_ = GetTime();
for (int i=0; i<prequeuing_audio_; i++) { if (audio_playback_device_->open(QIODevice::ReadWrite)) {
QueueNextAudioBuffer(); 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; i<prequeuing_audio_; i++) {
QueueNextAudioBuffer();
}
}
} }
} }
@@ -689,6 +686,8 @@ void ViewerWidget::PauseInternal()
{ {
if (IsPlaying()) { if (IsPlaying()) {
AudioManager::instance()->StopOutput(); AudioManager::instance()->StopOutput();
AudioMonitor::StopOnAll();
playback_speed_ = 0; playback_speed_ = 0;
controls_->ShowPlayButton(); controls_->ShowPlayButton();
@@ -704,13 +703,13 @@ void ViewerWidget::PauseInternal()
playback_queue_.clear(); playback_queue_.clear();
playback_backup_timer_.stop(); playback_backup_timer_.stop();
disconnect(audio_playback_device_.get(), &PreviewAudioDevice::Notify, this, &ViewerWidget::QueueNextAudioBuffer);
audio_playback_device_ = nullptr; audio_playback_device_ = nullptr;
qDeleteAll(audio_playback_queue_); qDeleteAll(audio_playback_queue_);
audio_playback_queue_.clear(); audio_playback_queue_.clear();
if (tempo_processor_.IsOpen()) { if (tempo_processor_.IsOpen()) {
tempo_processor_.Close(); tempo_processor_.Close();
} }
audio_queue_next_timer_->stop();
UpdateTextureFromNode(); UpdateTextureFromNode();
} }
@@ -834,7 +833,9 @@ void ViewerWidget::FinishPlayPreprocess()
int64_t playback_start_time = GetTimestamp(); int64_t playback_start_time = GetTimestamp();
StartAudioOutput(); if (audio_playback_device_) {
StartAudioOutput();
}
playback_timer_.Start(playback_start_time, playback_speed_, timebase_dbl()); playback_timer_.Start(playback_start_time, playback_speed_, timebase_dbl());
display_widget_->ResetFPSTimer(); display_widget_->ResetFPSTimer();
-1
View File
@@ -262,7 +262,6 @@ private:
PackedProcessor packed_processor_; PackedProcessor packed_processor_;
TempoProcessor tempo_processor_; TempoProcessor tempo_processor_;
static const int kAudioPlaybackInterval; static const int kAudioPlaybackInterval;
QTimer *audio_queue_next_timer_;
static QVector<ViewerWidget*> instances_; static QVector<ViewerWidget*> instances_;