change: change code style to Linux style except indent.
This commit is contained in:
+200
-171
@@ -28,290 +28,319 @@
|
||||
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive {
|
||||
namespace olive
|
||||
{
|
||||
|
||||
AudioManager* AudioManager::instance_ = nullptr;
|
||||
AudioManager *AudioManager::instance_ = nullptr;
|
||||
|
||||
void AudioManager::CreateInstance()
|
||||
{
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new AudioManager();
|
||||
}
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new AudioManager();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::DestroyInstance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
AudioManager *AudioManager::instance()
|
||||
{
|
||||
return instance_;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void AudioManager::SetOutputNotifyInterval(int n)
|
||||
{
|
||||
output_buffer_->set_notify_interval(n);
|
||||
output_buffer_->set_notify_interval(n);
|
||||
}
|
||||
|
||||
int OutputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
|
||||
int OutputCallback(const void *input, void *output, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo *timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData)
|
||||
{
|
||||
PreviewAudioDevice *device = static_cast<PreviewAudioDevice*>(userData);
|
||||
PreviewAudioDevice *device = static_cast<PreviewAudioDevice *>(userData);
|
||||
|
||||
qint64 max_read = frameCount * device->bytes_per_frame();
|
||||
qint64 read_count = device->read(reinterpret_cast<char*>(output), max_read);
|
||||
if (read_count < max_read) {
|
||||
memset(reinterpret_cast<uint8_t*>(output) + read_count, 0, max_read - read_count);
|
||||
}
|
||||
qint64 max_read = frameCount * device->bytes_per_frame();
|
||||
qint64 read_count =
|
||||
device->read(reinterpret_cast<char *>(output), max_read);
|
||||
if (read_count < max_read) {
|
||||
memset(reinterpret_cast<uint8_t *>(output) + read_count, 0,
|
||||
max_read - read_count);
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
int InputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
|
||||
int InputCallback(const void *input, void *output, unsigned long frameCount,
|
||||
const PaStreamCallbackTimeInfo *timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData)
|
||||
{
|
||||
FFmpegEncoder *f = static_cast<FFmpegEncoder*>(userData);
|
||||
FFmpegEncoder *f = static_cast<FFmpegEncoder *>(userData);
|
||||
|
||||
AudioParams our_params = f->params().audio_params();
|
||||
our_params.set_format(f->params().audio_params().format().to_packed_equivalent());
|
||||
AudioParams our_params = f->params().audio_params();
|
||||
our_params.set_format(
|
||||
f->params().audio_params().format().to_packed_equivalent());
|
||||
|
||||
f->WriteAudioData(our_params, reinterpret_cast<const uint8_t**>(&input), frameCount);
|
||||
f->WriteAudioData(our_params, reinterpret_cast<const uint8_t **>(&input),
|
||||
frameCount);
|
||||
|
||||
return paContinue;
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
bool AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples, QString *error)
|
||||
bool AudioManager::PushToOutput(const AudioParams ¶ms,
|
||||
const QByteArray &samples, QString *error)
|
||||
{
|
||||
if (output_device_ == paNoDevice) {
|
||||
if (error) *error = tr("No output device is set");
|
||||
return false;
|
||||
}
|
||||
if (output_device_ == paNoDevice) {
|
||||
if (error)
|
||||
*error = tr("No output device is set");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output_params_ != params || output_stream_ == nullptr) {
|
||||
output_params_ = params;
|
||||
if (output_params_ != params || output_stream_ == nullptr) {
|
||||
output_params_ = params;
|
||||
|
||||
CloseOutputStream();
|
||||
CloseOutputStream();
|
||||
|
||||
PaStreamParameters p = GetPortAudioParams(params, output_device_);
|
||||
PaStreamParameters p = GetPortAudioParams(params, output_device_);
|
||||
|
||||
PaError r = Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
|
||||
if (r != paNoError) {
|
||||
// Unhandled error
|
||||
//qCritical() << "Failed to open output stream:" << Pa_GetErrorText(r);
|
||||
if (error) *error = Pa_GetErrorText(r);
|
||||
return false;
|
||||
}
|
||||
PaError r = Pa_OpenStream(&output_stream_, nullptr, &p,
|
||||
output_params_.sample_rate(),
|
||||
paFramesPerBufferUnspecified, paNoFlag,
|
||||
OutputCallback, output_buffer_);
|
||||
if (r != paNoError) {
|
||||
// Unhandled error
|
||||
//qCritical() << "Failed to open output stream:" << Pa_GetErrorText(r);
|
||||
if (error)
|
||||
*error = Pa_GetErrorText(r);
|
||||
return false;
|
||||
}
|
||||
|
||||
output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1));
|
||||
}
|
||||
output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1));
|
||||
}
|
||||
|
||||
output_buffer_->write(samples);
|
||||
output_buffer_->write(samples);
|
||||
|
||||
if (!Pa_IsStreamActive(output_stream_)) {
|
||||
Pa_StartStream(output_stream_);
|
||||
}
|
||||
if (!Pa_IsStreamActive(output_stream_)) {
|
||||
Pa_StartStream(output_stream_);
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioManager::ClearBufferedOutput()
|
||||
{
|
||||
output_buffer_->clear();
|
||||
output_buffer_->clear();
|
||||
}
|
||||
|
||||
PaSampleFormat AudioManager::GetPortAudioSampleFormat(SampleFormat fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
case SampleFormat::U8:
|
||||
case SampleFormat::U8P:
|
||||
return paUInt8;
|
||||
case SampleFormat::S16:
|
||||
case SampleFormat::S16P:
|
||||
return paInt16;
|
||||
case SampleFormat::S32:
|
||||
case SampleFormat::S32P:
|
||||
return paInt32;
|
||||
case SampleFormat::F32:
|
||||
case SampleFormat::F32P:
|
||||
return paFloat32;
|
||||
case SampleFormat::S64:
|
||||
case SampleFormat::S64P:
|
||||
case SampleFormat::F64:
|
||||
case SampleFormat::F64P:
|
||||
case SampleFormat::INVALID:
|
||||
case SampleFormat::COUNT:
|
||||
break;
|
||||
}
|
||||
switch (fmt) {
|
||||
case SampleFormat::U8:
|
||||
case SampleFormat::U8P:
|
||||
return paUInt8;
|
||||
case SampleFormat::S16:
|
||||
case SampleFormat::S16P:
|
||||
return paInt16;
|
||||
case SampleFormat::S32:
|
||||
case SampleFormat::S32P:
|
||||
return paInt32;
|
||||
case SampleFormat::F32:
|
||||
case SampleFormat::F32P:
|
||||
return paFloat32;
|
||||
case SampleFormat::S64:
|
||||
case SampleFormat::S64P:
|
||||
case SampleFormat::F64:
|
||||
case SampleFormat::F64P:
|
||||
case SampleFormat::INVALID:
|
||||
case SampleFormat::COUNT:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioManager::CloseOutputStream()
|
||||
{
|
||||
if (output_stream_) {
|
||||
if (Pa_IsStreamActive(output_stream_)) {
|
||||
StopOutput();
|
||||
}
|
||||
Pa_CloseStream(output_stream_);
|
||||
output_stream_ = nullptr;
|
||||
}
|
||||
if (output_stream_) {
|
||||
if (Pa_IsStreamActive(output_stream_)) {
|
||||
StopOutput();
|
||||
}
|
||||
Pa_CloseStream(output_stream_);
|
||||
output_stream_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::StopOutput()
|
||||
{
|
||||
// Abort the stream so playback stops immediately
|
||||
if (output_stream_) {
|
||||
Pa_AbortStream(output_stream_);
|
||||
ClearBufferedOutput();
|
||||
}
|
||||
// Abort the stream so playback stops immediately
|
||||
if (output_stream_) {
|
||||
Pa_AbortStream(output_stream_);
|
||||
ClearBufferedOutput();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManager::SetOutputDevice(PaDeviceIndex device)
|
||||
{
|
||||
if (device == paNoDevice) {
|
||||
qInfo() << "No output device found";
|
||||
} else {
|
||||
qInfo() << "Setting output audio device to" << Pa_GetDeviceInfo(device)->name;
|
||||
}
|
||||
if (device == paNoDevice) {
|
||||
qInfo() << "No output device found";
|
||||
} else {
|
||||
qInfo() << "Setting output audio device to"
|
||||
<< Pa_GetDeviceInfo(device)->name;
|
||||
}
|
||||
|
||||
output_device_ = device;
|
||||
output_device_ = device;
|
||||
|
||||
CloseOutputStream();
|
||||
CloseOutputStream();
|
||||
}
|
||||
|
||||
void AudioManager::SetInputDevice(PaDeviceIndex device)
|
||||
{
|
||||
if (device == paNoDevice) {
|
||||
qInfo() << "No input device found";
|
||||
} else {
|
||||
qInfo() << "Setting input audio device to" << Pa_GetDeviceInfo(device)->name;
|
||||
}
|
||||
if (device == paNoDevice) {
|
||||
qInfo() << "No input device found";
|
||||
} else {
|
||||
qInfo() << "Setting input audio device to"
|
||||
<< Pa_GetDeviceInfo(device)->name;
|
||||
}
|
||||
|
||||
input_device_ = device;
|
||||
input_device_ = device;
|
||||
}
|
||||
|
||||
void AudioManager::HardReset()
|
||||
{
|
||||
CloseOutputStream();
|
||||
Pa_Terminate();
|
||||
Pa_Initialize();
|
||||
CloseOutputStream();
|
||||
Pa_Terminate();
|
||||
Pa_Initialize();
|
||||
}
|
||||
|
||||
bool AudioManager::StartRecording(const EncodingParams ¶ms, QString *error_str)
|
||||
bool AudioManager::StartRecording(const EncodingParams ¶ms,
|
||||
QString *error_str)
|
||||
{
|
||||
if (input_device_ == paNoDevice) {
|
||||
return false;
|
||||
}
|
||||
if (input_device_ == paNoDevice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input_encoder_ = new FFmpegEncoder(params);
|
||||
if (!input_encoder_->Open()) {
|
||||
qCritical() << "Failed to open encoder for recording";
|
||||
return false;
|
||||
}
|
||||
input_encoder_ = new FFmpegEncoder(params);
|
||||
if (!input_encoder_->Open()) {
|
||||
qCritical() << "Failed to open encoder for recording";
|
||||
return false;
|
||||
}
|
||||
|
||||
PaStreamParameters p = GetPortAudioParams(params.audio_params(), input_device_);
|
||||
PaStreamParameters p =
|
||||
GetPortAudioParams(params.audio_params(), input_device_);
|
||||
|
||||
PaError r = Pa_OpenStream(&input_stream_, &p, nullptr, params.audio_params().sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_);
|
||||
if (r == paNoError) {
|
||||
//const PaStreamInfo* info = Pa_GetStreamInfo(input_stream_);
|
||||
r = Pa_StartStream(input_stream_);
|
||||
if (r == paNoError) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
PaError r = Pa_OpenStream(&input_stream_, &p, nullptr,
|
||||
params.audio_params().sample_rate(),
|
||||
paFramesPerBufferUnspecified, paNoFlag,
|
||||
InputCallback, input_encoder_);
|
||||
if (r == paNoError) {
|
||||
//const PaStreamInfo* info = Pa_GetStreamInfo(input_stream_);
|
||||
r = Pa_StartStream(input_stream_);
|
||||
if (r == paNoError) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (error_str) {
|
||||
*error_str = Pa_GetErrorText(r);
|
||||
}
|
||||
if (error_str) {
|
||||
*error_str = Pa_GetErrorText(r);
|
||||
}
|
||||
|
||||
StopRecording();
|
||||
return false;
|
||||
StopRecording();
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioManager::StopRecording()
|
||||
{
|
||||
if (input_stream_) {
|
||||
if (Pa_IsStreamActive(input_stream_)) {
|
||||
Pa_StopStream(input_stream_);
|
||||
}
|
||||
Pa_CloseStream(input_stream_);
|
||||
if (input_stream_) {
|
||||
if (Pa_IsStreamActive(input_stream_)) {
|
||||
Pa_StopStream(input_stream_);
|
||||
}
|
||||
Pa_CloseStream(input_stream_);
|
||||
|
||||
input_stream_ = nullptr;
|
||||
}
|
||||
input_stream_ = nullptr;
|
||||
}
|
||||
|
||||
if (input_encoder_) {
|
||||
input_encoder_->Close();
|
||||
delete input_encoder_;
|
||||
input_encoder_ = nullptr;
|
||||
}
|
||||
if (input_encoder_) {
|
||||
input_encoder_->Close();
|
||||
delete input_encoder_;
|
||||
input_encoder_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
|
||||
{
|
||||
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
|
||||
QString entry = is_output_device ? QStringLiteral("AudioOutput") :
|
||||
QStringLiteral("AudioInput");
|
||||
|
||||
return FindDeviceByName(OLIVE_CONFIG_STR(entry).toString(), is_output_device);
|
||||
return FindDeviceByName(OLIVE_CONFIG_STR(entry).toString(),
|
||||
is_output_device);
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_device)
|
||||
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s,
|
||||
bool is_output_device)
|
||||
{
|
||||
if (!s.isEmpty()) {
|
||||
for (PaDeviceIndex i=0, end=Pa_GetDeviceCount(); i<end; i++) {
|
||||
const PaDeviceInfo *device = Pa_GetDeviceInfo(i);
|
||||
if (!s.isEmpty()) {
|
||||
for (PaDeviceIndex i = 0, end = Pa_GetDeviceCount(); i < end; i++) {
|
||||
const PaDeviceInfo *device = Pa_GetDeviceInfo(i);
|
||||
|
||||
if (((is_output_device && device->maxOutputChannels) || (!is_output_device && device->maxInputChannels))
|
||||
&& !s.compare(device->name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (((is_output_device && device->maxOutputChannels) ||
|
||||
(!is_output_device && device->maxInputChannels)) &&
|
||||
!s.compare(device->name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice();
|
||||
return is_output_device ? Pa_GetDefaultOutputDevice() :
|
||||
Pa_GetDefaultInputDevice();
|
||||
}
|
||||
|
||||
PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams ¶ms, PaDeviceIndex device)
|
||||
PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams ¶ms,
|
||||
PaDeviceIndex device)
|
||||
{
|
||||
PaStreamParameters p;
|
||||
PaStreamParameters p;
|
||||
|
||||
p.channelCount = params.channel_count();
|
||||
p.device = device;
|
||||
p.hostApiSpecificStreamInfo = nullptr;
|
||||
p.sampleFormat = GetPortAudioSampleFormat(params.format());
|
||||
p.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency;
|
||||
p.channelCount = params.channel_count();
|
||||
p.device = device;
|
||||
p.hostApiSpecificStreamInfo = nullptr;
|
||||
p.sampleFormat = GetPortAudioSampleFormat(params.format());
|
||||
p.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency;
|
||||
|
||||
return p;
|
||||
return p;
|
||||
}
|
||||
|
||||
AudioManager::AudioManager() :
|
||||
output_stream_(nullptr),
|
||||
input_stream_(nullptr),
|
||||
input_encoder_(nullptr)
|
||||
AudioManager::AudioManager()
|
||||
: output_stream_(nullptr)
|
||||
, input_stream_(nullptr)
|
||||
, input_encoder_(nullptr)
|
||||
{
|
||||
#ifdef PA_HAS_JACK
|
||||
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
|
||||
// a QString converted to UTF-8)
|
||||
PaJack_SetClientName("Olive");
|
||||
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
|
||||
// a QString converted to UTF-8)
|
||||
PaJack_SetClientName("Olive");
|
||||
#endif
|
||||
|
||||
Pa_Initialize();
|
||||
Pa_Initialize();
|
||||
|
||||
// Get device from config
|
||||
PaDeviceIndex output_device = FindConfigDeviceByName(true);
|
||||
PaDeviceIndex input_device = FindConfigDeviceByName(false);
|
||||
// Get device from config
|
||||
PaDeviceIndex output_device = FindConfigDeviceByName(true);
|
||||
PaDeviceIndex input_device = FindConfigDeviceByName(false);
|
||||
|
||||
SetOutputDevice(output_device);
|
||||
SetInputDevice(input_device);
|
||||
SetOutputDevice(output_device);
|
||||
SetInputDevice(input_device);
|
||||
|
||||
output_buffer_ = new PreviewAudioDevice(this);
|
||||
output_buffer_->open(PreviewAudioDevice::ReadWrite);
|
||||
connect(output_buffer_, &PreviewAudioDevice::Notify, this, &AudioManager::OutputNotify);
|
||||
output_buffer_ = new PreviewAudioDevice(this);
|
||||
output_buffer_->open(PreviewAudioDevice::ReadWrite);
|
||||
connect(output_buffer_, &PreviewAudioDevice::Notify, this,
|
||||
&AudioManager::OutputNotify);
|
||||
}
|
||||
|
||||
AudioManager::~AudioManager()
|
||||
{
|
||||
CloseOutputStream();
|
||||
CloseOutputStream();
|
||||
|
||||
Pa_Terminate();
|
||||
Pa_Terminate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+45
-42
@@ -33,7 +33,8 @@
|
||||
#include "render/audioplaybackcache.h"
|
||||
#include "render/previewaudiodevice.h"
|
||||
|
||||
namespace olive {
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Audio input and output management class
|
||||
@@ -41,74 +42,76 @@ namespace olive {
|
||||
* Wraps around a QAudioOutput and AudioHybridDevice, connecting them together and exposing audio functionality to
|
||||
* the rest of the system.
|
||||
*/
|
||||
class AudioManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
class AudioManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static void CreateInstance();
|
||||
static void DestroyInstance();
|
||||
static void CreateInstance();
|
||||
static void DestroyInstance();
|
||||
|
||||
static AudioManager* instance();
|
||||
static AudioManager *instance();
|
||||
|
||||
void SetOutputNotifyInterval(int n);
|
||||
void SetOutputNotifyInterval(int n);
|
||||
|
||||
bool PushToOutput(const AudioParams ¶ms, const QByteArray& samples, QString *error = nullptr);
|
||||
bool PushToOutput(const AudioParams ¶ms, const QByteArray &samples,
|
||||
QString *error = nullptr);
|
||||
|
||||
void ClearBufferedOutput();
|
||||
void ClearBufferedOutput();
|
||||
|
||||
void StopOutput();
|
||||
void StopOutput();
|
||||
|
||||
PaDeviceIndex GetOutputDevice() const
|
||||
{
|
||||
return output_device_;
|
||||
}
|
||||
PaDeviceIndex GetOutputDevice() const
|
||||
{
|
||||
return output_device_;
|
||||
}
|
||||
|
||||
PaDeviceIndex GetInputDevice() const
|
||||
{
|
||||
return input_device_;
|
||||
}
|
||||
PaDeviceIndex GetInputDevice() const
|
||||
{
|
||||
return input_device_;
|
||||
}
|
||||
|
||||
void SetOutputDevice(PaDeviceIndex device);
|
||||
void SetOutputDevice(PaDeviceIndex device);
|
||||
|
||||
void SetInputDevice(PaDeviceIndex device);
|
||||
void SetInputDevice(PaDeviceIndex device);
|
||||
|
||||
void HardReset();
|
||||
void HardReset();
|
||||
|
||||
bool StartRecording(const EncodingParams ¶ms, QString *error_str = nullptr);
|
||||
bool StartRecording(const EncodingParams ¶ms,
|
||||
QString *error_str = nullptr);
|
||||
|
||||
void StopRecording();
|
||||
void StopRecording();
|
||||
|
||||
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
|
||||
static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device);
|
||||
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
|
||||
static PaDeviceIndex FindDeviceByName(const QString &s,
|
||||
bool is_output_device);
|
||||
|
||||
static PaStreamParameters GetPortAudioParams(const AudioParams &p, PaDeviceIndex device);
|
||||
static PaStreamParameters GetPortAudioParams(const AudioParams &p,
|
||||
PaDeviceIndex device);
|
||||
|
||||
signals:
|
||||
void OutputNotify();
|
||||
void OutputNotify();
|
||||
|
||||
void OutputParamsChanged();
|
||||
void OutputParamsChanged();
|
||||
|
||||
private:
|
||||
AudioManager();
|
||||
AudioManager();
|
||||
|
||||
virtual ~AudioManager() override;
|
||||
virtual ~AudioManager() override;
|
||||
|
||||
static PaSampleFormat GetPortAudioSampleFormat(SampleFormat fmt);
|
||||
static PaSampleFormat GetPortAudioSampleFormat(SampleFormat fmt);
|
||||
|
||||
void CloseOutputStream();
|
||||
void CloseOutputStream();
|
||||
|
||||
static AudioManager* instance_;
|
||||
static AudioManager *instance_;
|
||||
|
||||
PaDeviceIndex output_device_;
|
||||
PaStream *output_stream_;
|
||||
AudioParams output_params_;
|
||||
PreviewAudioDevice *output_buffer_;
|
||||
PaDeviceIndex output_device_;
|
||||
PaStream *output_stream_;
|
||||
AudioParams output_params_;
|
||||
PreviewAudioDevice *output_buffer_;
|
||||
|
||||
PaDeviceIndex input_device_;
|
||||
PaStream *input_stream_;
|
||||
|
||||
FFmpegEncoder *input_encoder_;
|
||||
PaDeviceIndex input_device_;
|
||||
PaStream *input_stream_;
|
||||
|
||||
FFmpegEncoder *input_encoder_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+226
-207
@@ -29,279 +29,298 @@ extern "C" {
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
namespace olive
|
||||
{
|
||||
|
||||
AudioProcessor::AudioProcessor()
|
||||
{
|
||||
filter_graph_ = nullptr;
|
||||
in_frame_ = nullptr;
|
||||
out_frame_ = nullptr;
|
||||
filter_graph_ = nullptr;
|
||||
in_frame_ = nullptr;
|
||||
out_frame_ = nullptr;
|
||||
}
|
||||
|
||||
AudioProcessor::~AudioProcessor()
|
||||
{
|
||||
Close();
|
||||
Close();
|
||||
}
|
||||
|
||||
bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to, double tempo)
|
||||
bool AudioProcessor::Open(const AudioParams &from, const AudioParams &to,
|
||||
double tempo)
|
||||
{
|
||||
if (filter_graph_) {
|
||||
qWarning() << "Tried to open a processor that was already open";
|
||||
return false;
|
||||
}
|
||||
if (filter_graph_) {
|
||||
qWarning() << "Tried to open a processor that was already open";
|
||||
return false;
|
||||
}
|
||||
|
||||
filter_graph_ = avfilter_graph_alloc();
|
||||
if (!filter_graph_) {
|
||||
qCritical() << "Failed to allocate filter graph";
|
||||
return false;
|
||||
}
|
||||
filter_graph_ = avfilter_graph_alloc();
|
||||
if (!filter_graph_) {
|
||||
qCritical() << "Failed to allocate filter graph";
|
||||
return false;
|
||||
}
|
||||
|
||||
from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from.format());
|
||||
to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to.format());
|
||||
from_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(from.format());
|
||||
to_fmt_ = FFmpegUtils::GetFFmpegSampleFormat(to.format());
|
||||
|
||||
// Set up audio buffer args
|
||||
char filter_args[200];
|
||||
snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
|
||||
1,
|
||||
from.sample_rate(),
|
||||
from.sample_rate(),
|
||||
from_fmt_,
|
||||
from.channel_layout().u.mask);
|
||||
// Set up audio buffer args
|
||||
char filter_args[200];
|
||||
snprintf(
|
||||
filter_args, 200,
|
||||
"time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
|
||||
1, from.sample_rate(), from.sample_rate(), from_fmt_,
|
||||
from.channel_layout().u.mask);
|
||||
|
||||
int r;
|
||||
int r;
|
||||
|
||||
// Create buffersrc (input)
|
||||
r = avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create buffersrc:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
// Create buffersrc (input)
|
||||
r = avfilter_graph_create_filter(&buffersrc_ctx_,
|
||||
avfilter_get_by_name("abuffer"), "in",
|
||||
filter_args, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create buffersrc:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store "previous" filter for linking
|
||||
AVFilterContext *previous_filter = buffersrc_ctx_;
|
||||
// Store "previous" filter for linking
|
||||
AVFilterContext *previous_filter = buffersrc_ctx_;
|
||||
|
||||
// Create tempo
|
||||
bool create_tempo;
|
||||
if ((create_tempo = !qFuzzyCompare(tempo, 1.0))) {
|
||||
// Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside
|
||||
// those boundaries, we need to daisychain more than one together.
|
||||
double base = (tempo > 1.0) ? 2.0 : 0.5;
|
||||
double speed_log = log(tempo) / log(base);
|
||||
// Create tempo
|
||||
bool create_tempo;
|
||||
if ((create_tempo = !qFuzzyCompare(tempo, 1.0))) {
|
||||
// Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside
|
||||
// those boundaries, we need to daisychain more than one together.
|
||||
double base = (tempo > 1.0) ? 2.0 : 0.5;
|
||||
double speed_log = log(tempo) / log(base);
|
||||
|
||||
// This is the number of how many 0.5 or 2.0 tempos we need to daisychain
|
||||
int whole = std::floor(speed_log);
|
||||
// This is the number of how many 0.5 or 2.0 tempos we need to daisychain
|
||||
int whole = std::floor(speed_log);
|
||||
|
||||
// Set speed_log to the remainder
|
||||
speed_log -= whole;
|
||||
// Set speed_log to the remainder
|
||||
speed_log -= whole;
|
||||
|
||||
for (int i=0;i<=whole;i++) {
|
||||
double filter_tempo = (i == whole) ? std::pow(base, speed_log) : base;
|
||||
/*
|
||||
for (int i = 0; i <= whole; i++) {
|
||||
double filter_tempo = (i == whole) ? std::pow(base, speed_log) :
|
||||
base;
|
||||
/*
|
||||
if (qFuzzyCompare(filter_tempo, 1.0)) {
|
||||
// This filter would do nothing
|
||||
continue;
|
||||
}*/
|
||||
|
||||
previous_filter = CreateTempoFilter(filter_graph_,
|
||||
previous_filter,
|
||||
filter_tempo);
|
||||
previous_filter =
|
||||
CreateTempoFilter(filter_graph_, previous_filter, filter_tempo);
|
||||
|
||||
if (!previous_filter) {
|
||||
qCritical() << "Failed to create audio tempo filter";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!previous_filter) {
|
||||
qCritical() << "Failed to create audio tempo filter";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create conversion filter
|
||||
auto ch1=from.channel_layout();
|
||||
auto ch2=to.channel_layout();
|
||||
if (from.sample_rate() != to.sample_rate() || av_channel_layout_compare(&ch1, &ch2) || from.format() != to.format()
|
||||
|| (to.format().is_planar() && create_tempo)) { // Tempo processor automatically converts to packed,
|
||||
// so if the desired output is planar, it'll need
|
||||
// to be converted
|
||||
snprintf(filter_args, 200, "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64,
|
||||
av_get_sample_fmt_name(to_fmt_),
|
||||
to.sample_rate(),
|
||||
to.channel_layout().u.mask);
|
||||
// Create conversion filter
|
||||
auto ch1 = from.channel_layout();
|
||||
auto ch2 = to.channel_layout();
|
||||
if (from.sample_rate() != to.sample_rate() ||
|
||||
av_channel_layout_compare(&ch1, &ch2) || from.format() != to.format() ||
|
||||
(to.format().is_planar() &&
|
||||
create_tempo)) { // Tempo processor automatically converts to packed,
|
||||
// so if the desired output is planar, it'll need
|
||||
// to be converted
|
||||
snprintf(filter_args, 200,
|
||||
"sample_fmts=%s:sample_rates=%d:channel_layouts=0x%" PRIx64,
|
||||
av_get_sample_fmt_name(to_fmt_), to.sample_rate(),
|
||||
to.channel_layout().u.mask);
|
||||
|
||||
AVFilterContext *c;
|
||||
r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"), "fmt", filter_args, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create format conversion filter:" << r << filter_args;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
AVFilterContext *c;
|
||||
r = avfilter_graph_create_filter(&c, avfilter_get_by_name("aformat"),
|
||||
"fmt", filter_args, nullptr,
|
||||
filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create format conversion filter:" << r
|
||||
<< filter_args;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
r = avfilter_link(previous_filter, 0, c, 0);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to link filters:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
r = avfilter_link(previous_filter, 0, c, 0);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to link filters:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
previous_filter = c;
|
||||
}
|
||||
previous_filter = c;
|
||||
}
|
||||
|
||||
// Create buffersink (output)
|
||||
r = avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create buffersink:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
// Create buffersink (output)
|
||||
r = avfilter_graph_create_filter(&buffersink_ctx_,
|
||||
avfilter_get_by_name("abuffersink"), "out",
|
||||
nullptr, nullptr, filter_graph_);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to create buffersink:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
r = avfilter_link(previous_filter, 0, buffersink_ctx_, 0);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to link filters:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
char *dump = avfilter_graph_dump(filter_graph_, nullptr);
|
||||
qDebug() << dump;
|
||||
av_free(dump);
|
||||
r = avfilter_graph_config(filter_graph_, nullptr);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to configure graph:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
r = avfilter_link(previous_filter, 0, buffersink_ctx_, 0);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to link filters:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
char *dump = avfilter_graph_dump(filter_graph_, nullptr);
|
||||
qDebug() << dump;
|
||||
av_free(dump);
|
||||
r = avfilter_graph_config(filter_graph_, nullptr);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to configure graph:" << r;
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
in_frame_ = av_frame_alloc();
|
||||
if (in_frame_) {
|
||||
in_frame_->sample_rate = from.sample_rate();
|
||||
in_frame_->format = from_fmt_;
|
||||
in_frame_->ch_layout = from.channel_layout();
|
||||
in_frame_->pts = 0;
|
||||
} else {
|
||||
qCritical() << "Failed to allocate input frame";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
in_frame_ = av_frame_alloc();
|
||||
if (in_frame_) {
|
||||
in_frame_->sample_rate = from.sample_rate();
|
||||
in_frame_->format = from_fmt_;
|
||||
in_frame_->ch_layout = from.channel_layout();
|
||||
in_frame_->pts = 0;
|
||||
} else {
|
||||
qCritical() << "Failed to allocate input frame";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
out_frame_ = av_frame_alloc();
|
||||
if (!out_frame_) {
|
||||
qCritical() << "Failed to allocate output frame";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
out_frame_ = av_frame_alloc();
|
||||
if (!out_frame_) {
|
||||
qCritical() << "Failed to allocate output frame";
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
from_ = from;
|
||||
to_ = to;
|
||||
from_ = from;
|
||||
to_ = to;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioProcessor::Close()
|
||||
{
|
||||
if (filter_graph_) {
|
||||
avfilter_graph_free(&filter_graph_);
|
||||
filter_graph_ = nullptr;
|
||||
buffersrc_ctx_ = nullptr;
|
||||
buffersink_ctx_ = nullptr;
|
||||
}
|
||||
if (filter_graph_) {
|
||||
avfilter_graph_free(&filter_graph_);
|
||||
filter_graph_ = nullptr;
|
||||
buffersrc_ctx_ = nullptr;
|
||||
buffersink_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (in_frame_) {
|
||||
av_frame_free(&in_frame_);
|
||||
in_frame_ = nullptr;
|
||||
}
|
||||
if (in_frame_) {
|
||||
av_frame_free(&in_frame_);
|
||||
in_frame_ = nullptr;
|
||||
}
|
||||
|
||||
if (out_frame_) {
|
||||
av_frame_free(&out_frame_);
|
||||
out_frame_ = nullptr;
|
||||
}
|
||||
if (out_frame_) {
|
||||
av_frame_free(&out_frame_);
|
||||
out_frame_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int AudioProcessor::Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output)
|
||||
int AudioProcessor::Convert(float **in, int nb_in_samples,
|
||||
AudioProcessor::Buffer *output)
|
||||
{
|
||||
if (!IsOpen()) {
|
||||
qCritical() << "Tried to convert on closed processor";
|
||||
return -1;
|
||||
}
|
||||
if (!IsOpen()) {
|
||||
qCritical() << "Tried to convert on closed processor";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
int r = 0;
|
||||
|
||||
if (in && nb_in_samples) {
|
||||
// Set frame parameters
|
||||
in_frame_->nb_samples = nb_in_samples;
|
||||
for (int i=0; i<from_.channel_count(); i++) {
|
||||
in_frame_->data[i] = reinterpret_cast<uint8_t*>(in[i]);
|
||||
in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples);
|
||||
}
|
||||
if (in && nb_in_samples) {
|
||||
// Set frame parameters
|
||||
in_frame_->nb_samples = nb_in_samples;
|
||||
for (int i = 0; i < from_.channel_count(); i++) {
|
||||
in_frame_->data[i] = reinterpret_cast<uint8_t *>(in[i]);
|
||||
in_frame_->linesize[i] = from_.samples_to_bytes(nb_in_samples);
|
||||
}
|
||||
|
||||
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to add frame to buffersrc:" << r;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in_frame_,
|
||||
AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to add frame to buffersrc:" << r;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (output) {
|
||||
int nb_channels = to_.channel_count();
|
||||
if (output) {
|
||||
int nb_channels = to_.channel_count();
|
||||
|
||||
if (to_.format().is_packed()) {
|
||||
nb_channels = 1;
|
||||
}
|
||||
if (to_.format().is_packed()) {
|
||||
nb_channels = 1;
|
||||
}
|
||||
|
||||
AudioProcessor::Buffer &result = *output;
|
||||
result.resize(nb_channels);
|
||||
AudioProcessor::Buffer &result = *output;
|
||||
result.resize(nb_channels);
|
||||
|
||||
int byte_offset = 0;
|
||||
int byte_offset = 0;
|
||||
|
||||
while (true) {
|
||||
av_frame_unref(out_frame_);
|
||||
r = av_buffersink_get_frame(buffersink_ctx_, out_frame_);
|
||||
if (r < 0) {
|
||||
if (r == AVERROR(EAGAIN)) {
|
||||
r = 0;
|
||||
} else {
|
||||
// Handle unexpected error
|
||||
qCritical() << "Failed to pull from buffersink:" << r;
|
||||
}
|
||||
break;
|
||||
}
|
||||
while (true) {
|
||||
av_frame_unref(out_frame_);
|
||||
r = av_buffersink_get_frame(buffersink_ctx_, out_frame_);
|
||||
if (r < 0) {
|
||||
if (r == AVERROR(EAGAIN)) {
|
||||
r = 0;
|
||||
} else {
|
||||
// Handle unexpected error
|
||||
qCritical() << "Failed to pull from buffersink:" << r;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int nb_bytes = out_frame_->nb_samples * to_.bytes_per_sample_per_channel();
|
||||
if (to_.format().is_packed()) {
|
||||
nb_bytes *= to_.channel_count();
|
||||
}
|
||||
int nb_bytes =
|
||||
out_frame_->nb_samples * to_.bytes_per_sample_per_channel();
|
||||
if (to_.format().is_packed()) {
|
||||
nb_bytes *= to_.channel_count();
|
||||
}
|
||||
|
||||
for (int i=0; i<nb_channels; i++) {
|
||||
result[i].resize(byte_offset + nb_bytes);
|
||||
memcpy(result[i].data() + byte_offset, out_frame_->data[i], nb_bytes);
|
||||
}
|
||||
byte_offset += nb_bytes;
|
||||
}
|
||||
av_frame_unref(out_frame_);
|
||||
}
|
||||
for (int i = 0; i < nb_channels; i++) {
|
||||
result[i].resize(byte_offset + nb_bytes);
|
||||
memcpy(result[i].data() + byte_offset, out_frame_->data[i],
|
||||
nb_bytes);
|
||||
}
|
||||
byte_offset += nb_bytes;
|
||||
}
|
||||
av_frame_unref(out_frame_);
|
||||
}
|
||||
|
||||
return r;
|
||||
return r;
|
||||
}
|
||||
|
||||
void AudioProcessor::Flush()
|
||||
{
|
||||
int r = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to flush:" << r;
|
||||
}
|
||||
int r = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr,
|
||||
AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
qCritical() << "Failed to flush:" << r;
|
||||
}
|
||||
}
|
||||
|
||||
AVFilterContext *AudioProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo)
|
||||
AVFilterContext *AudioProcessor::CreateTempoFilter(AVFilterGraph *graph,
|
||||
AVFilterContext *link,
|
||||
const double &tempo)
|
||||
{
|
||||
// Set up tempo param, which is taken as a C string
|
||||
char speed_param[20];
|
||||
snprintf(speed_param, 20, "%f", tempo);
|
||||
// Set up tempo param, which is taken as a C string
|
||||
char speed_param[20];
|
||||
snprintf(speed_param, 20, "%f", tempo);
|
||||
|
||||
AVFilterContext* tempo_ctx = nullptr;
|
||||
AVFilterContext *tempo_ctx = nullptr;
|
||||
|
||||
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0
|
||||
&& avfilter_link(link, 0, tempo_ctx, 0) == 0) {
|
||||
return tempo_ctx;
|
||||
}
|
||||
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"),
|
||||
"atempo", speed_param, nullptr,
|
||||
graph) >= 0 &&
|
||||
avfilter_link(link, 0, tempo_ctx, 0) == 0) {
|
||||
return tempo_ctx;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+36
-25
@@ -31,52 +31,63 @@ extern "C" {
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using namespace core;
|
||||
|
||||
class AudioProcessor
|
||||
{
|
||||
class AudioProcessor {
|
||||
public:
|
||||
AudioProcessor();
|
||||
AudioProcessor();
|
||||
|
||||
~AudioProcessor();
|
||||
~AudioProcessor();
|
||||
|
||||
DISABLE_COPY_MOVE(AudioProcessor)
|
||||
DISABLE_COPY_MOVE(AudioProcessor)
|
||||
|
||||
bool Open(const AudioParams &from, const AudioParams &to, double tempo = 1.0);
|
||||
bool Open(const AudioParams &from, const AudioParams &to,
|
||||
double tempo = 1.0);
|
||||
|
||||
void Close();
|
||||
void Close();
|
||||
|
||||
bool IsOpen() const { return filter_graph_; }
|
||||
bool IsOpen() const
|
||||
{
|
||||
return filter_graph_;
|
||||
}
|
||||
|
||||
using Buffer = QVector<QByteArray>;
|
||||
int Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output);
|
||||
using Buffer = QVector<QByteArray>;
|
||||
int Convert(float **in, int nb_in_samples, AudioProcessor::Buffer *output);
|
||||
|
||||
void Flush();
|
||||
void Flush();
|
||||
|
||||
const AudioParams &from() const { return from_; }
|
||||
const AudioParams &to() const { return to_; }
|
||||
const AudioParams &from() const
|
||||
{
|
||||
return from_;
|
||||
}
|
||||
const AudioParams &to() const
|
||||
{
|
||||
return to_;
|
||||
}
|
||||
|
||||
private:
|
||||
static AVFilterContext* CreateTempoFilter(AVFilterGraph *graph, AVFilterContext *link, const double& tempo);
|
||||
static AVFilterContext *CreateTempoFilter(AVFilterGraph *graph,
|
||||
AVFilterContext *link,
|
||||
const double &tempo);
|
||||
|
||||
AVFilterGraph* filter_graph_;
|
||||
AVFilterGraph *filter_graph_;
|
||||
|
||||
AVFilterContext* buffersrc_ctx_;
|
||||
AVFilterContext *buffersrc_ctx_;
|
||||
|
||||
AVFilterContext* buffersink_ctx_;
|
||||
AVFilterContext *buffersink_ctx_;
|
||||
|
||||
AudioParams from_;
|
||||
AVSampleFormat from_fmt_;
|
||||
AudioParams from_;
|
||||
AVSampleFormat from_fmt_;
|
||||
|
||||
AudioParams to_;
|
||||
AVSampleFormat to_fmt_;
|
||||
AudioParams to_;
|
||||
AVSampleFormat to_fmt_;
|
||||
|
||||
AVFrame *in_frame_;
|
||||
|
||||
AVFrame *out_frame_;
|
||||
AVFrame *in_frame_;
|
||||
|
||||
AVFrame *out_frame_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+371
-315
@@ -25,480 +25,536 @@
|
||||
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive {
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const rational AudioVisualWaveform::kMinimumSampleRate = rational(1, 8);
|
||||
const rational AudioVisualWaveform::kMaximumSampleRate = 1024;
|
||||
|
||||
AudioVisualWaveform::AudioVisualWaveform() :
|
||||
channels_(0)
|
||||
AudioVisualWaveform::AudioVisualWaveform()
|
||||
: channels_(0)
|
||||
{
|
||||
for (rational i=kMinimumSampleRate; i<=kMaximumSampleRate; i*=2) {
|
||||
mipmapped_data_.insert({i, Sample()});
|
||||
}
|
||||
for (rational i = kMinimumSampleRate; i <= kMaximumSampleRate; i *= 2) {
|
||||
mipmapped_data_.insert({ i, Sample() });
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational &start, double target_rate, Sample& data, size_t &start_index, size_t &samples_length)
|
||||
void AudioVisualWaveform::OverwriteSamplesFromBuffer(
|
||||
const SampleBuffer &samples, int sample_rate, const rational &start,
|
||||
double target_rate, Sample &data, size_t &start_index,
|
||||
size_t &samples_length)
|
||||
{
|
||||
start_index = time_to_samples(start, target_rate);
|
||||
samples_length = time_to_samples(static_cast<double>(samples.sample_count()) / static_cast<double>(sample_rate), target_rate);
|
||||
start_index = time_to_samples(start, target_rate);
|
||||
samples_length =
|
||||
time_to_samples(static_cast<double>(samples.sample_count()) /
|
||||
static_cast<double>(sample_rate),
|
||||
target_rate);
|
||||
|
||||
size_t end_index = start_index + samples_length;
|
||||
if (data.size() < end_index) {
|
||||
data.resize(end_index);
|
||||
}
|
||||
size_t end_index = start_index + samples_length;
|
||||
if (data.size() < end_index) {
|
||||
data.resize(end_index);
|
||||
}
|
||||
|
||||
double chunk_size = double(sample_rate) / double(target_rate);
|
||||
double chunk_size = double(sample_rate) / double(target_rate);
|
||||
|
||||
for (size_t i=0; i<samples_length; i+=channels_) {
|
||||
size_t src_start = qRound((double(i) * chunk_size)) / channels_;
|
||||
size_t src_end = qMin(size_t(qRound64((double(i + channels_) * chunk_size))) / channels_, samples.sample_count());
|
||||
for (size_t i = 0; i < samples_length; i += channels_) {
|
||||
size_t src_start = qRound((double(i) * chunk_size)) / channels_;
|
||||
size_t src_end = qMin(
|
||||
size_t(qRound64((double(i + channels_) * chunk_size))) / channels_,
|
||||
samples.sample_count());
|
||||
|
||||
Sample summary = SumSamples(samples,
|
||||
src_start,
|
||||
src_end - src_start);
|
||||
Sample summary = SumSamples(samples, src_start, src_end - src_start);
|
||||
|
||||
memcpy(&data.data()[i + start_index],
|
||||
summary.data(),
|
||||
summary.size() * sizeof(SamplePerChannel));
|
||||
}
|
||||
memcpy(&data.data()[i + start_index], summary.data(),
|
||||
summary.size() * sizeof(SamplePerChannel));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform::Sample &input, double input_sample_rate, size_t &input_start, size_t &input_length, const rational &start, double output_rate, AudioVisualWaveform::Sample &output_data)
|
||||
void AudioVisualWaveform::OverwriteSamplesFromMipmap(
|
||||
const AudioVisualWaveform::Sample &input, double input_sample_rate,
|
||||
size_t &input_start, size_t &input_length, const rational &start,
|
||||
double output_rate, AudioVisualWaveform::Sample &output_data)
|
||||
{
|
||||
size_t start_index = time_to_samples(start, output_rate);
|
||||
size_t samples_length = time_to_samples(static_cast<double>(input_length / channels_) / input_sample_rate, output_rate);
|
||||
size_t start_index = time_to_samples(start, output_rate);
|
||||
size_t samples_length = time_to_samples(
|
||||
static_cast<double>(input_length / channels_) / input_sample_rate,
|
||||
output_rate);
|
||||
|
||||
size_t end_index = start_index + samples_length;
|
||||
if (output_data.size() < end_index) {
|
||||
output_data.resize(end_index);
|
||||
}
|
||||
size_t end_index = start_index + samples_length;
|
||||
if (output_data.size() < end_index) {
|
||||
output_data.resize(end_index);
|
||||
}
|
||||
|
||||
// We guarantee mipmaps are powers of two so integer division should be perfectly accurate here
|
||||
size_t chunk_size = input_sample_rate / output_rate;
|
||||
// We guarantee mipmaps are powers of two so integer division should be perfectly accurate here
|
||||
size_t chunk_size = input_sample_rate / output_rate;
|
||||
|
||||
for (size_t i=0; i<samples_length; i+=channels_) {
|
||||
Sample summary = ReSumSamples(&input.data()[input_start + (i*chunk_size)], chunk_size * channels_, channels_);
|
||||
for (size_t i = 0; i < samples_length; i += channels_) {
|
||||
Sample summary =
|
||||
ReSumSamples(&input.data()[input_start + (i * chunk_size)],
|
||||
chunk_size * channels_, channels_);
|
||||
|
||||
memcpy(&output_data.data()[i + start_index],
|
||||
summary.data(),
|
||||
summary.size() * sizeof(SamplePerChannel));
|
||||
}
|
||||
memcpy(&output_data.data()[i + start_index], summary.data(),
|
||||
summary.size() * sizeof(SamplePerChannel));
|
||||
}
|
||||
|
||||
input_start = start_index;
|
||||
input_length = samples_length;
|
||||
input_start = start_index;
|
||||
input_length = samples_length;
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::ValidateVirtualStart(const rational &new_start)
|
||||
{
|
||||
if (length_ == 0) {
|
||||
virtual_start_ = new_start;
|
||||
} else if (virtual_start_ > new_start) {
|
||||
TrimIn(new_start - virtual_start_);
|
||||
}
|
||||
if (length_ == 0) {
|
||||
virtual_start_ = new_start;
|
||||
} else if (virtual_start_ > new_start) {
|
||||
TrimIn(new_start - virtual_start_);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational &start)
|
||||
void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples,
|
||||
int sample_rate,
|
||||
const rational &start)
|
||||
{
|
||||
if (!channels_) {
|
||||
qWarning() << "Failed to write samples - channel count is zero";
|
||||
return;
|
||||
}
|
||||
if (!channels_) {
|
||||
qWarning() << "Failed to write samples - channel count is zero";
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateVirtualStart(start);
|
||||
ValidateVirtualStart(start);
|
||||
|
||||
// Process the largest mipmap directly for the samples
|
||||
auto current_mipmap = mipmapped_data_.rbegin();
|
||||
size_t input_start, input_length;
|
||||
OverwriteSamplesFromBuffer(samples, sample_rate, start - virtual_start_, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length);
|
||||
// Process the largest mipmap directly for the samples
|
||||
auto current_mipmap = mipmapped_data_.rbegin();
|
||||
size_t input_start, input_length;
|
||||
OverwriteSamplesFromBuffer(samples, sample_rate, start - virtual_start_,
|
||||
current_mipmap->first.toDouble(),
|
||||
current_mipmap->second, input_start,
|
||||
input_length);
|
||||
|
||||
while (true) {
|
||||
// For each smaller mipmap, we just process from the mipmap before it, making each one
|
||||
// exponentially faster to create
|
||||
auto previous_mipmap = current_mipmap;
|
||||
current_mipmap++;
|
||||
if (current_mipmap == mipmapped_data_.rend()) {
|
||||
break;
|
||||
}
|
||||
while (true) {
|
||||
// For each smaller mipmap, we just process from the mipmap before it, making each one
|
||||
// exponentially faster to create
|
||||
auto previous_mipmap = current_mipmap;
|
||||
current_mipmap++;
|
||||
if (current_mipmap == mipmapped_data_.rend()) {
|
||||
break;
|
||||
}
|
||||
|
||||
OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(),
|
||||
input_start, input_length, start - virtual_start_, current_mipmap->first.toDouble(),
|
||||
current_mipmap->second);
|
||||
}
|
||||
OverwriteSamplesFromMipmap(
|
||||
previous_mipmap->second, previous_mipmap->first.toDouble(),
|
||||
input_start, input_length, start - virtual_start_,
|
||||
current_mipmap->first.toDouble(), current_mipmap->second);
|
||||
}
|
||||
|
||||
rational sample_length(samples.sample_count(), sample_rate);
|
||||
length_ = qMax(length_, start + sample_length);
|
||||
rational sample_length(samples.sample_count(), sample_rate);
|
||||
length_ = qMax(length_, start + sample_length);
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length)
|
||||
void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums,
|
||||
const rational &dest,
|
||||
const rational &offset,
|
||||
const rational &length)
|
||||
{
|
||||
ValidateVirtualStart(dest);
|
||||
ValidateVirtualStart(dest);
|
||||
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
for (auto it = mipmapped_data_.begin(); it != mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
|
||||
Sample& our_arr = it->second;
|
||||
const Sample& their_arr = sums.mipmapped_data_.at(rate);
|
||||
Sample &our_arr = it->second;
|
||||
const Sample &their_arr = sums.mipmapped_data_.at(rate);
|
||||
|
||||
double rate_dbl = rate.toDouble();
|
||||
double rate_dbl = rate.toDouble();
|
||||
|
||||
// Get our destination sample
|
||||
size_t our_start_index = time_to_samples(dest - virtual_start_, rate_dbl);
|
||||
// Get our destination sample
|
||||
size_t our_start_index =
|
||||
time_to_samples(dest - virtual_start_, rate_dbl);
|
||||
|
||||
// Get our source sample
|
||||
size_t their_start_index = time_to_samples(offset, rate_dbl);
|
||||
if (their_start_index >= their_arr.size()) {
|
||||
continue;
|
||||
}
|
||||
// Get our source sample
|
||||
size_t their_start_index = time_to_samples(offset, rate_dbl);
|
||||
if (their_start_index >= their_arr.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine how much we're copying
|
||||
size_t copy_len = their_arr.size() - their_start_index;
|
||||
if (!length.isNull()) {
|
||||
copy_len = qMin(copy_len, time_to_samples(length, rate_dbl));
|
||||
if (copy_len == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Determine how much we're copying
|
||||
size_t copy_len = their_arr.size() - their_start_index;
|
||||
if (!length.isNull()) {
|
||||
copy_len = qMin(copy_len, time_to_samples(length, rate_dbl));
|
||||
if (copy_len == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine end index of our array
|
||||
size_t end_index = our_start_index + copy_len;
|
||||
if (our_arr.size() < end_index) {
|
||||
our_arr.resize(end_index);
|
||||
}
|
||||
// Determine end index of our array
|
||||
size_t end_index = our_start_index + copy_len;
|
||||
if (our_arr.size() < end_index) {
|
||||
our_arr.resize(end_index);
|
||||
}
|
||||
|
||||
memcpy(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel),
|
||||
reinterpret_cast<const char*>(their_arr.data()) + their_start_index * sizeof(SamplePerChannel),
|
||||
copy_len * sizeof(SamplePerChannel));
|
||||
}
|
||||
memcpy(reinterpret_cast<char *>(our_arr.data()) +
|
||||
our_start_index * sizeof(SamplePerChannel),
|
||||
reinterpret_cast<const char *>(their_arr.data()) +
|
||||
their_start_index * sizeof(SamplePerChannel),
|
||||
copy_len * sizeof(SamplePerChannel));
|
||||
}
|
||||
|
||||
length_ = qMax(length_, dest + ((length.isNull()) ? sums.length() - offset : length));
|
||||
length_ = qMax(length_, dest + ((length.isNull()) ? sums.length() - offset :
|
||||
length));
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length)
|
||||
void AudioVisualWaveform::OverwriteSilence(const rational &start,
|
||||
const rational &length)
|
||||
{
|
||||
ValidateVirtualStart(start);
|
||||
ValidateVirtualStart(start);
|
||||
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
for (auto it = mipmapped_data_.begin(); it != mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
|
||||
Sample& our_arr = it->second;
|
||||
Sample &our_arr = it->second;
|
||||
|
||||
double rate_dbl = rate.toDouble();
|
||||
double rate_dbl = rate.toDouble();
|
||||
|
||||
// Get our destination sample
|
||||
size_t our_start_index = time_to_samples(start - virtual_start_, rate_dbl);
|
||||
size_t our_length_index = time_to_samples(length, rate_dbl);
|
||||
size_t our_end_index = our_start_index + our_length_index;
|
||||
// Get our destination sample
|
||||
size_t our_start_index =
|
||||
time_to_samples(start - virtual_start_, rate_dbl);
|
||||
size_t our_length_index = time_to_samples(length, rate_dbl);
|
||||
size_t our_end_index = our_start_index + our_length_index;
|
||||
|
||||
if (our_arr.size() < our_end_index) {
|
||||
our_arr.resize(our_end_index);
|
||||
}
|
||||
if (our_arr.size() < our_end_index) {
|
||||
our_arr.resize(our_end_index);
|
||||
}
|
||||
|
||||
memset(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), 0, our_length_index * sizeof(SamplePerChannel));
|
||||
}
|
||||
memset(reinterpret_cast<char *>(our_arr.data()) +
|
||||
our_start_index * sizeof(SamplePerChannel),
|
||||
0, our_length_index * sizeof(SamplePerChannel));
|
||||
}
|
||||
|
||||
length_ = qMax(length_, start + length);
|
||||
length_ = qMax(length_, start + length);
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::TrimIn(rational length)
|
||||
{
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
virtual_start_ += length;
|
||||
virtual_start_ += length;
|
||||
|
||||
bool negative = (length < 0);
|
||||
if (negative) {
|
||||
length = -length;
|
||||
}
|
||||
bool negative = (length < 0);
|
||||
if (negative) {
|
||||
length = -length;
|
||||
}
|
||||
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
Sample& data = it->second;
|
||||
for (auto it = mipmapped_data_.begin(); it != mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
Sample &data = it->second;
|
||||
|
||||
size_t chop_length = time_to_samples(length, rate_dbl);
|
||||
if (chop_length == 0) {
|
||||
continue;
|
||||
}
|
||||
size_t chop_length = time_to_samples(length, rate_dbl);
|
||||
if (chop_length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!negative) {
|
||||
data = Sample(data.begin() + chop_length, data.end());
|
||||
} else {
|
||||
data.insert(data.begin(), chop_length, SamplePerChannel());
|
||||
}
|
||||
}
|
||||
if (!negative) {
|
||||
data = Sample(data.begin() + chop_length, data.end());
|
||||
} else {
|
||||
data.insert(data.begin(), chop_length, SamplePerChannel());
|
||||
}
|
||||
}
|
||||
|
||||
length_ = qMax(rational(0), length_ - length);
|
||||
length_ = qMax(rational(0), length_ - length);
|
||||
}
|
||||
|
||||
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const
|
||||
{
|
||||
AudioVisualWaveform mid = *this;
|
||||
AudioVisualWaveform mid = *this;
|
||||
|
||||
mid.TrimIn(offset - virtual_start_);
|
||||
mid.TrimIn(offset - virtual_start_);
|
||||
|
||||
return mid;
|
||||
return mid;
|
||||
}
|
||||
|
||||
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset, const rational &length) const
|
||||
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset,
|
||||
const rational &length) const
|
||||
{
|
||||
AudioVisualWaveform mid = *this;
|
||||
AudioVisualWaveform mid = *this;
|
||||
|
||||
mid.TrimRange(offset - virtual_start_, length);
|
||||
mid.TrimRange(offset - virtual_start_, length);
|
||||
|
||||
return mid;
|
||||
return mid;
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::Resize(const rational &length)
|
||||
{
|
||||
if (length_ == length) {
|
||||
return;
|
||||
}
|
||||
if (length_ == length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
Sample& data = it->second;
|
||||
for (auto it = mipmapped_data_.begin(); it != mipmapped_data_.end(); it++) {
|
||||
rational rate = it->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
Sample &data = it->second;
|
||||
|
||||
size_t chop_length = time_to_samples(length, rate_dbl);
|
||||
size_t chop_length = time_to_samples(length, rate_dbl);
|
||||
|
||||
data.resize(chop_length);
|
||||
}
|
||||
data.resize(chop_length);
|
||||
}
|
||||
|
||||
length_ = length;
|
||||
length_ = length;
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::TrimRange(const rational &in, const rational &length)
|
||||
{
|
||||
TrimIn(in);
|
||||
Resize(length);
|
||||
TrimIn(in);
|
||||
Resize(length);
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const rational &start, const rational &length) const
|
||||
AudioVisualWaveform::Sample
|
||||
AudioVisualWaveform::GetSummaryFromTime(const rational &start,
|
||||
const rational &length) const
|
||||
{
|
||||
// Find mipmap that requires
|
||||
auto using_mipmap = GetMipmapForScale(length.flipped().toDouble());
|
||||
// Find mipmap that requires
|
||||
auto using_mipmap = GetMipmapForScale(length.flipped().toDouble());
|
||||
|
||||
double rate_dbl = using_mipmap->first.toDouble();
|
||||
double rate_dbl = using_mipmap->first.toDouble();
|
||||
|
||||
size_t start_sample = time_to_samples(start - virtual_start_, rate_dbl);
|
||||
size_t sample_length = time_to_samples(length, rate_dbl);
|
||||
size_t start_sample = time_to_samples(start - virtual_start_, rate_dbl);
|
||||
size_t sample_length = time_to_samples(length, rate_dbl);
|
||||
|
||||
const Sample &mipmap_data = using_mipmap->second;
|
||||
const Sample &mipmap_data = using_mipmap->second;
|
||||
|
||||
// Determine if the array actually has this sample
|
||||
sample_length = qMin(sample_length, mipmap_data.size() - start_sample);
|
||||
// Determine if the array actually has this sample
|
||||
sample_length = qMin(sample_length, mipmap_data.size() - start_sample);
|
||||
|
||||
// Based on the above `min`, if sample length <= 0, that means start_sample >= the size of the
|
||||
// array and nothing can be returned.
|
||||
if (sample_length > 0) {
|
||||
return ReSumSamples(&mipmap_data.data()[start_sample], sample_length, channels_);
|
||||
}
|
||||
// Based on the above `min`, if sample length <= 0, that means start_sample >= the size of the
|
||||
// array and nothing can be returned.
|
||||
if (sample_length > 0) {
|
||||
return ReSumSamples(&mipmap_data.data()[start_sample], sample_length,
|
||||
channels_);
|
||||
}
|
||||
|
||||
// Return null samples
|
||||
return AudioVisualWaveform::Sample(channel_count(), {0, 0});
|
||||
// Return null samples
|
||||
return AudioVisualWaveform::Sample(channel_count(), { 0, 0 });
|
||||
}
|
||||
|
||||
void ExpandMinMaxChannel(const float *a, size_t length, float &min_val, float &max_val)
|
||||
void ExpandMinMaxChannel(const float *a, size_t length, float &min_val,
|
||||
float &max_val)
|
||||
{
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
// SSE optimized
|
||||
// SSE optimized
|
||||
|
||||
// load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits)
|
||||
__m128 max = _mm_loadu_ps(a);
|
||||
__m128 min = _mm_loadu_ps(a);
|
||||
// load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits)
|
||||
__m128 max = _mm_loadu_ps(a);
|
||||
__m128 min = _mm_loadu_ps(a);
|
||||
|
||||
// loop over 'a' and compare current elements with min and max 4 by 4.
|
||||
// we need to make sure we don't read out of boundaries should 'a' length be not mod. 4
|
||||
for(size_t i = 4; i < length-4; i+=4) {
|
||||
__m128 cur = _mm_loadu_ps(a + i);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
}
|
||||
// so we read the last 4 (or less) elements in a safe manner.
|
||||
__m128 cur = _mm_loadu_ps(a + length - 4);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
// this potentially overlaps up to the last 3 elements but it's not an issue.
|
||||
// loop over 'a' and compare current elements with min and max 4 by 4.
|
||||
// we need to make sure we don't read out of boundaries should 'a' length be not mod. 4
|
||||
for (size_t i = 4; i < length - 4; i += 4) {
|
||||
__m128 cur = _mm_loadu_ps(a + i);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
}
|
||||
// so we read the last 4 (or less) elements in a safe manner.
|
||||
__m128 cur = _mm_loadu_ps(a + length - 4);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
// this potentially overlaps up to the last 3 elements but it's not an issue.
|
||||
|
||||
// min and max will contain 4 min and max. To get the absolute min and max
|
||||
// we need to compare the 4 values over themselves by shuffling each time.
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93));
|
||||
min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93));
|
||||
}
|
||||
// now min and max contain 4 identical items each representing min and max value respectively.
|
||||
// min and max will contain 4 min and max. To get the absolute min and max
|
||||
// we need to compare the 4 values over themselves by shuffling each time.
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93));
|
||||
min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93));
|
||||
}
|
||||
// now min and max contain 4 identical items each representing min and max value respectively.
|
||||
|
||||
// and we store the first one into a float variable.
|
||||
_mm_store_ss(&max_val, max);
|
||||
_mm_store_ss(&min_val, min);
|
||||
// I bet you don't find annotated low level code very often.
|
||||
// and we store the first one into a float variable.
|
||||
_mm_store_ss(&max_val, max);
|
||||
_mm_store_ss(&min_val, min);
|
||||
// I bet you don't find annotated low level code very often.
|
||||
#else
|
||||
// Standard unoptimized function
|
||||
for (size_t i=0; i<length; i++) {
|
||||
min_val = std::min(min_val, a[i]);
|
||||
max_val = std::max(max_val, a[i]);
|
||||
}
|
||||
// Standard unoptimized function
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
min_val = std::min(min_val, a[i]);
|
||||
max_val = std::max(max_val, a[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const SampleBuffer &samples, size_t start_index, size_t length)
|
||||
AudioVisualWaveform::Sample
|
||||
AudioVisualWaveform::SumSamples(const SampleBuffer &samples, size_t start_index,
|
||||
size_t length)
|
||||
{
|
||||
int channels = samples.audio_params().channel_count();
|
||||
AudioVisualWaveform::Sample summed_samples(channels);
|
||||
int channels = samples.audio_params().channel_count();
|
||||
AudioVisualWaveform::Sample summed_samples(channels);
|
||||
|
||||
for (int channel=0; channel<samples.audio_params().channel_count(); channel++) {
|
||||
ExpandMinMaxChannel(samples.data(channel) + start_index, length, summed_samples[channel].min, summed_samples[channel].max);
|
||||
}
|
||||
for (int channel = 0; channel < samples.audio_params().channel_count();
|
||||
channel++) {
|
||||
ExpandMinMaxChannel(samples.data(channel) + start_index, length,
|
||||
summed_samples[channel].min,
|
||||
summed_samples[channel].max);
|
||||
}
|
||||
|
||||
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
|
||||
// for (size_t i=start_index; i<end_index; i++) {
|
||||
// ExpandMinMax(summed_samples[i%channels], samples->data(i%channels)[i]);
|
||||
// }
|
||||
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
|
||||
// for (size_t i=start_index; i<end_index; i++) {
|
||||
// ExpandMinMax(summed_samples[i%channels], samples->data(i%channels)[i]);
|
||||
// }
|
||||
|
||||
return summed_samples;
|
||||
return summed_samples;
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::ReSumSamples(const SamplePerChannel* samples,
|
||||
size_t nb_samples,
|
||||
int nb_channels)
|
||||
AudioVisualWaveform::Sample
|
||||
AudioVisualWaveform::ReSumSamples(const SamplePerChannel *samples,
|
||||
size_t nb_samples, int nb_channels)
|
||||
{
|
||||
AudioVisualWaveform::Sample summed_samples(nb_channels);
|
||||
AudioVisualWaveform::Sample summed_samples(nb_channels);
|
||||
|
||||
for (size_t i=0;i<nb_samples;i+=nb_channels) {
|
||||
for (int j=0;j<nb_channels;j++) {
|
||||
const AudioVisualWaveform::SamplePerChannel& sample = samples[i + j];
|
||||
for (size_t i = 0; i < nb_samples; i += nb_channels) {
|
||||
for (int j = 0; j < nb_channels; j++) {
|
||||
const AudioVisualWaveform::SamplePerChannel &sample =
|
||||
samples[i + j];
|
||||
|
||||
if (sample.min < summed_samples[j].min) {
|
||||
summed_samples[j].min = sample.min;
|
||||
}
|
||||
if (sample.min < summed_samples[j].min) {
|
||||
summed_samples[j].min = sample.min;
|
||||
}
|
||||
|
||||
if (sample.max > summed_samples[j].max) {
|
||||
summed_samples[j].max = sample.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sample.max > summed_samples[j].max) {
|
||||
summed_samples[j].max = sample.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return summed_samples;
|
||||
return summed_samples;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int round_away_from_zero(T t)
|
||||
template <typename T> inline int round_away_from_zero(T t)
|
||||
{
|
||||
return (t < 0) ? std::floor(t) : std::ceil(t);
|
||||
return (t < 0) ? std::floor(t) : std::ceil(t);
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::DrawSample(QPainter *painter, const Sample& sample, int x, int y, int height, bool rectified)
|
||||
void AudioVisualWaveform::DrawSample(QPainter *painter, const Sample &sample,
|
||||
int x, int y, int height, bool rectified)
|
||||
{
|
||||
if (sample.empty()) {
|
||||
return;
|
||||
}
|
||||
if (sample.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int channel_height = height / sample.size();
|
||||
int channel_half_height = channel_height / 2;
|
||||
int channel_height = height / sample.size();
|
||||
int channel_half_height = channel_height / 2;
|
||||
|
||||
for (size_t i=0;i<sample.size();i++) {
|
||||
float max = qMin(sample.at(i).max, 1.0f);
|
||||
float min = qMax(sample.at(i).min, -1.0f);
|
||||
for (size_t i = 0; i < sample.size(); i++) {
|
||||
float max = qMin(sample.at(i).max, 1.0f);
|
||||
float min = qMax(sample.at(i).min, -1.0f);
|
||||
|
||||
if (rectified) {
|
||||
int channel_bottom = y + channel_height * (i + 1);
|
||||
if (rectified) {
|
||||
int channel_bottom = y + channel_height * (i + 1);
|
||||
|
||||
int diff = round_away_from_zero((max - min) * channel_half_height);
|
||||
int diff = round_away_from_zero((max - min) * channel_half_height);
|
||||
|
||||
painter->drawLine(x,
|
||||
channel_bottom - diff,
|
||||
x,
|
||||
channel_bottom);
|
||||
} else {
|
||||
int channel_mid = y + channel_height * i + channel_half_height;
|
||||
painter->drawLine(x, channel_bottom - diff, x, channel_bottom);
|
||||
} else {
|
||||
int channel_mid = y + channel_height * i + channel_half_height;
|
||||
|
||||
// We subtract the sample so that positive Y values go up on the screen rather than down,
|
||||
// which is how waveforms are usually rendered
|
||||
painter->drawLine(x,
|
||||
channel_mid - round_away_from_zero(min * static_cast<float>(channel_half_height)),
|
||||
x,
|
||||
channel_mid - round_away_from_zero(max * static_cast<float>(channel_half_height)));
|
||||
}
|
||||
}
|
||||
// We subtract the sample so that positive Y values go up on the screen rather than down,
|
||||
// which is how waveforms are usually rendered
|
||||
painter->drawLine(
|
||||
x,
|
||||
channel_mid -
|
||||
round_away_from_zero(
|
||||
min * static_cast<float>(channel_half_height)),
|
||||
x,
|
||||
channel_mid -
|
||||
round_away_from_zero(
|
||||
max * static_cast<float>(channel_half_height)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const AudioVisualWaveform &samples, const rational& start_time)
|
||||
void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect &rect,
|
||||
const double &scale,
|
||||
const AudioVisualWaveform &samples,
|
||||
const rational &start_time)
|
||||
{
|
||||
if (samples.mipmapped_data_.empty()) {
|
||||
return;
|
||||
}
|
||||
if (samples.mipmapped_data_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto using_mipmap = samples.GetMipmapForScale(scale);
|
||||
auto using_mipmap = samples.GetMipmapForScale(scale);
|
||||
|
||||
rational rate = using_mipmap->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
const Sample& arr = using_mipmap->second;
|
||||
rational rate = using_mipmap->first;
|
||||
double rate_dbl = rate.toDouble();
|
||||
const Sample &arr = using_mipmap->second;
|
||||
|
||||
size_t start_sample_index = samples.time_to_samples(start_time - samples.virtual_start_, rate_dbl);
|
||||
size_t start_sample_index =
|
||||
samples.time_to_samples(start_time - samples.virtual_start_, rate_dbl);
|
||||
|
||||
if (start_sample_index >= arr.size()) {
|
||||
return;
|
||||
}
|
||||
if (start_sample_index >= arr.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t next_sample_index = start_sample_index;
|
||||
size_t sample_index;
|
||||
size_t next_sample_index = start_sample_index;
|
||||
size_t sample_index;
|
||||
|
||||
Sample summary;
|
||||
size_t summary_index = -1;
|
||||
Sample summary;
|
||||
size_t summary_index = -1;
|
||||
|
||||
const QRect& viewport = painter->viewport();
|
||||
QPoint top_left = painter->transform().map(viewport.topLeft());
|
||||
const QRect &viewport = painter->viewport();
|
||||
QPoint top_left = painter->transform().map(viewport.topLeft());
|
||||
|
||||
size_t start = qMax(rect.x(), -top_left.x());
|
||||
size_t end = qMin(rect.right(), -top_left.x() + viewport.width());
|
||||
size_t start = qMax(rect.x(), -top_left.x());
|
||||
size_t end = qMin(rect.right(), -top_left.x() + viewport.width());
|
||||
|
||||
bool rectified = OLIVE_CONFIG("RectifiedWaveforms").toBool();
|
||||
bool rectified = OLIVE_CONFIG("RectifiedWaveforms").toBool();
|
||||
|
||||
for (size_t i=start;i<end;i++) {
|
||||
sample_index = next_sample_index;
|
||||
for (size_t i = start; i < end; i++) {
|
||||
sample_index = next_sample_index;
|
||||
|
||||
if (sample_index == arr.size()) {
|
||||
break;
|
||||
}
|
||||
if (sample_index == arr.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
next_sample_index = std::min(arr.size(),
|
||||
size_t(start_sample_index + std::floor(rate_dbl * static_cast<double>(i - rect.x() + 1) / scale) * samples.channel_count()));
|
||||
next_sample_index = std::min(
|
||||
arr.size(),
|
||||
size_t(start_sample_index +
|
||||
std::floor(rate_dbl * static_cast<double>(i - rect.x() + 1) /
|
||||
scale) *
|
||||
samples.channel_count()));
|
||||
|
||||
if (summary_index != sample_index) {
|
||||
summary = AudioVisualWaveform::ReSumSamples(&arr.at(sample_index),
|
||||
qMax(size_t(samples.channel_count()), next_sample_index - sample_index),
|
||||
samples.channel_count());
|
||||
summary_index = sample_index;
|
||||
}
|
||||
if (summary_index != sample_index) {
|
||||
summary = AudioVisualWaveform::ReSumSamples(
|
||||
&arr.at(sample_index),
|
||||
qMax(size_t(samples.channel_count()),
|
||||
next_sample_index - sample_index),
|
||||
samples.channel_count());
|
||||
summary_index = sample_index;
|
||||
}
|
||||
|
||||
DrawSample(painter, summary, i, rect.y(), rect.height(), rectified);
|
||||
}
|
||||
DrawSample(painter, summary, i, rect.y(), rect.height(), rectified);
|
||||
}
|
||||
}
|
||||
|
||||
size_t AudioVisualWaveform::time_to_samples(const rational &time, double sample_rate) const
|
||||
size_t AudioVisualWaveform::time_to_samples(const rational &time,
|
||||
double sample_rate) const
|
||||
{
|
||||
return time_to_samples(time.toDouble(), sample_rate);
|
||||
return time_to_samples(time.toDouble(), sample_rate);
|
||||
}
|
||||
|
||||
size_t AudioVisualWaveform::time_to_samples(const double &time, double sample_rate) const
|
||||
size_t AudioVisualWaveform::time_to_samples(const double &time,
|
||||
double sample_rate) const
|
||||
{
|
||||
return std::floor(time * sample_rate) * channels_;
|
||||
return std::floor(time * sample_rate) * channels_;
|
||||
}
|
||||
|
||||
std::map<rational, AudioVisualWaveform::Sample>::const_iterator AudioVisualWaveform::GetMipmapForScale(double scale) const
|
||||
std::map<rational, AudioVisualWaveform::Sample>::const_iterator
|
||||
AudioVisualWaveform::GetMipmapForScale(double scale) const
|
||||
{
|
||||
// Find largest mipmap for this scale (or the largest if we don't find one sufficient)
|
||||
for (auto it=mipmapped_data_.cbegin(); it!=mipmapped_data_.cend(); it++) {
|
||||
if (it->first.toDouble() >= scale) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
// Find largest mipmap for this scale (or the largest if we don't find one sufficient)
|
||||
for (auto it = mipmapped_data_.cbegin(); it != mipmapped_data_.cend();
|
||||
it++) {
|
||||
if (it->first.toDouble() >= scale) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have a mipmap large enough for this scale, so just return the largest we have
|
||||
return std::prev(mipmapped_data_.cend());
|
||||
// We don't have a mipmap large enough for this scale, so just return the largest we have
|
||||
return std::prev(mipmapped_data_.cend());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
#include <QPainter>
|
||||
#include <QVector>
|
||||
|
||||
namespace olive {
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using namespace core;
|
||||
|
||||
@@ -37,38 +38,39 @@ using namespace core;
|
||||
*/
|
||||
class AudioVisualWaveform {
|
||||
public:
|
||||
AudioVisualWaveform();
|
||||
AudioVisualWaveform();
|
||||
|
||||
struct SamplePerChannel {
|
||||
float min;
|
||||
float max;
|
||||
};
|
||||
struct SamplePerChannel {
|
||||
float min;
|
||||
float max;
|
||||
};
|
||||
|
||||
using Sample = std::vector<SamplePerChannel>;
|
||||
using Sample = std::vector<SamplePerChannel>;
|
||||
|
||||
int channel_count() const
|
||||
{
|
||||
return channels_;
|
||||
}
|
||||
int channel_count() const
|
||||
{
|
||||
return channels_;
|
||||
}
|
||||
|
||||
void set_channel_count(int channels)
|
||||
{
|
||||
channels_ = channels;
|
||||
}
|
||||
void set_channel_count(int channels)
|
||||
{
|
||||
channels_ = channels;
|
||||
}
|
||||
|
||||
const rational& length() const
|
||||
{
|
||||
return length_;
|
||||
}
|
||||
const rational &length() const
|
||||
{
|
||||
return length_;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Writes samples into the visual waveform buffer
|
||||
*
|
||||
* Starting at `start`, writes samples over anything in the buffer, expanding it if necessary.
|
||||
*/
|
||||
void OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational& start = 0);
|
||||
void OverwriteSamples(const SampleBuffer &samples, int sample_rate,
|
||||
const rational &start = 0);
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Replaces sums at a certain range in this visual waveform
|
||||
*
|
||||
* @param sums
|
||||
@@ -87,53 +89,70 @@ public:
|
||||
*
|
||||
* Maximum length of `sums` to overwrite with.
|
||||
*/
|
||||
void OverwriteSums(const AudioVisualWaveform& sums, const rational& dest, const rational& offset = 0, const rational &length = 0);
|
||||
void OverwriteSums(const AudioVisualWaveform &sums, const rational &dest,
|
||||
const rational &offset = 0, const rational &length = 0);
|
||||
|
||||
void OverwriteSilence(const rational &start, const rational &length);
|
||||
void OverwriteSilence(const rational &start, const rational &length);
|
||||
|
||||
void TrimIn(rational length);
|
||||
void TrimIn(rational length);
|
||||
|
||||
AudioVisualWaveform Mid(const rational &offset) const;
|
||||
AudioVisualWaveform Mid(const rational &offset, const rational &length) const;
|
||||
AudioVisualWaveform Mid(const rational &offset) const;
|
||||
AudioVisualWaveform Mid(const rational &offset,
|
||||
const rational &length) const;
|
||||
|
||||
void Resize(const rational &length);
|
||||
void Resize(const rational &length);
|
||||
|
||||
void TrimRange(const rational &in, const rational &length);
|
||||
void TrimRange(const rational &in, const rational &length);
|
||||
|
||||
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
|
||||
Sample GetSummaryFromTime(const rational &start,
|
||||
const rational &length) const;
|
||||
|
||||
static Sample SumSamples(const SampleBuffer &samples, size_t start_index, size_t length);
|
||||
static Sample SumSamples(const SampleBuffer &samples, size_t start_index,
|
||||
size_t length);
|
||||
|
||||
static Sample ReSumSamples(const SamplePerChannel *samples, size_t nb_samples, int nb_channels);
|
||||
static Sample ReSumSamples(const SamplePerChannel *samples,
|
||||
size_t nb_samples, int nb_channels);
|
||||
|
||||
static void DrawSample(QPainter* painter, const Sample &sample, int x, int y, int height, bool rectified);
|
||||
static void DrawSample(QPainter *painter, const Sample &sample, int x,
|
||||
int y, int height, bool rectified);
|
||||
|
||||
static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const AudioVisualWaveform& samples, const rational &start_time);
|
||||
static void DrawWaveform(QPainter *painter, const QRect &rect,
|
||||
const double &scale,
|
||||
const AudioVisualWaveform &samples,
|
||||
const rational &start_time);
|
||||
|
||||
// Must be a power of 2
|
||||
static const rational kMinimumSampleRate;
|
||||
static const rational kMaximumSampleRate;
|
||||
// Must be a power of 2
|
||||
static const rational kMinimumSampleRate;
|
||||
static const rational kMaximumSampleRate;
|
||||
|
||||
private:
|
||||
void OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational& start, double target_rate, Sample &data, size_t &start_index, size_t &samples_length);
|
||||
void OverwriteSamplesFromBuffer(const SampleBuffer &samples,
|
||||
int sample_rate, const rational &start,
|
||||
double target_rate, Sample &data,
|
||||
size_t &start_index,
|
||||
size_t &samples_length);
|
||||
|
||||
void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, size_t &input_start, size_t &input_length, const rational& start, double output_rate, Sample &output_data);
|
||||
void OverwriteSamplesFromMipmap(const Sample &input,
|
||||
double input_sample_rate,
|
||||
size_t &input_start, size_t &input_length,
|
||||
const rational &start, double output_rate,
|
||||
Sample &output_data);
|
||||
|
||||
size_t time_to_samples(const rational& time, double sample_rate) const;
|
||||
size_t time_to_samples(const double& time, double sample_rate) const;
|
||||
size_t time_to_samples(const rational &time, double sample_rate) const;
|
||||
size_t time_to_samples(const double &time, double sample_rate) const;
|
||||
|
||||
std::map<rational, Sample>::const_iterator GetMipmapForScale(double scale) const;
|
||||
std::map<rational, Sample>::const_iterator
|
||||
GetMipmapForScale(double scale) const;
|
||||
|
||||
void ValidateVirtualStart(const rational &new_start);
|
||||
void ValidateVirtualStart(const rational &new_start);
|
||||
|
||||
rational virtual_start_;
|
||||
rational virtual_start_;
|
||||
|
||||
int channels_;
|
||||
int channels_;
|
||||
|
||||
std::map<rational, Sample> mipmapped_data_;
|
||||
|
||||
rational length_;
|
||||
std::map<rational, Sample> mipmapped_data_;
|
||||
|
||||
rational length_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user