change: change code style to Linux style except indent.

This commit is contained in:
Mike Solar
2025-08-03 03:09:40 +08:00
parent 65ab76edc8
commit 74f73ab3be
789 changed files with 77113 additions and 68888 deletions
+200 -171
View File
@@ -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 &params, const QByteArray &samples, QString *error)
bool AudioManager::PushToOutput(const AudioParams &params,
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 &params, QString *error_str)
bool AudioManager::StartRecording(const EncodingParams &params,
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 &params, PaDeviceIndex device)
PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams &params,
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
View File
@@ -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 &params, const QByteArray& samples, QString *error = nullptr);
bool PushToOutput(const AudioParams &params, 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 &params, QString *error_str = nullptr);
bool StartRecording(const EncodingParams &params,
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
View File
@@ -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
View File
@@ -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
View File
@@ -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());
}
}
+67 -48
View File
@@ -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_;
};
}