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:
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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 ¶ms)
|
void AudioManager::SetOutputParams(const AudioParams ¶ms)
|
||||||
{
|
{
|
||||||
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
@@ -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_;
|
||||||
|
|||||||
@@ -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 ¶ms)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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,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)
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ¶ms)
|
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()
|
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();
|
||||||
|
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user