audiowaveformview: optimizes waveform to only redraw when necessary

Draws to a pixmap only when the waveform changes so it doesn't have to be
regenerated on every draw. Also renames WaveformView to AudioWaveformView
This commit is contained in:
itsmattkc
2020-03-18 14:44:05 +11:00
parent 5bdee74205
commit fed17e35fa
7 changed files with 207 additions and 184 deletions
@@ -32,7 +32,7 @@
#include "common/qtutils.h"
#include "config/config.h"
#include "node/block/transition/transition.h"
#include "widget/viewer/waveformview.h"
#include "widget/viewer/audiowaveformview.h"
TimelineViewBlockItem::TimelineViewBlockItem(Block *block, QGraphicsItem* parent) :
TimelineViewRect(parent),
@@ -93,7 +93,7 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI
QByteArray w = wave_file.readAll();
// FIXME: Hardcoded channel count
WaveformView::DrawWaveform(painter,
AudioWaveformView::DrawWaveform(painter,
rect().toRect(),
this->GetScale(),
reinterpret_cast<const SampleSummer::Sum*>(w.constData()),
+2 -2
View File
@@ -16,6 +16,8 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/viewer/audiowaveformview.h
widget/viewer/audiowaveformview.cpp
widget/viewer/footageviewer.h
widget/viewer/footageviewer.cpp
widget/viewer/viewer.h
@@ -24,7 +26,5 @@ set(OLIVE_SOURCES
widget/viewer/viewerglwidget.cpp
widget/viewer/viewersizer.h
widget/viewer/viewersizer.cpp
widget/viewer/waveformview.h
widget/viewer/waveformview.cpp
PARENT_SCOPE
)
+189
View File
@@ -0,0 +1,189 @@
#include "audiowaveformview.h"
#include <QFile>
#include <QPainter>
#include <QtMath>
#include "common/clamp.h"
#include "config/config.h"
AudioWaveformView::AudioWaveformView(QWidget *parent) :
SeekableWidget(parent),
backend_(nullptr)
{
setAutoFillBackground(true);
setBackgroundRole(QPalette::Base);
}
void AudioWaveformView::SetBackend(AudioRenderBackend *backend)
{
if (backend_) {
disconnect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast<void (AudioWaveformView::*)()>(&AudioWaveformView::update));
disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
SetTimebase(0);
}
backend_ = backend;
if (backend_) {
connect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast<void (AudioWaveformView::*)()>(&AudioWaveformView::update));
connect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
SetTimebase(backend_->params().time_base());
}
update();
}
void AudioWaveformView::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const SampleSummer::Sum* samples, int nb_samples, int channels)
{
int sample_index, next_sample_index = 0;
QVector<SampleSummer::Sum> summary;
int summary_index = -1;
int channel_height = rect.height() / channels;
int channel_half_height = channel_height / 2;
for (int i=0;i<rect.width();i++) {
sample_index = next_sample_index;
if (sample_index == nb_samples) {
break;
}
next_sample_index = qMin(nb_samples,
qFloor(static_cast<double>(SampleSummer::kSumSampleRate) * static_cast<double>(i+1) / scale) * channels);
if (summary_index != sample_index) {
summary = SampleSummer::ReSumSamples(&samples[sample_index],
qMax(channels, next_sample_index - sample_index),
channels);
summary_index = sample_index;
}
int line_x = i + rect.x();
for (int j=0;j<summary.size();j++) {
if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) {
int channel_bottom = rect.y() + channel_height * (j + 1);
int diff = qRound((summary.at(j).max - summary.at(j).min) * channel_half_height);
painter->drawLine(line_x,
channel_bottom - diff,
line_x,
channel_bottom);
} else{
int channel_mid = rect.y() + channel_height * j + channel_half_height;
painter->drawLine(line_x,
channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height),
line_x,
channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height));
}
}
}
}
void AudioWaveformView::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) {
return;
}
const AudioRenderingParams& params = backend_->params();
if (cached_size_ != size()
|| cached_scale_ != GetScale()
|| cached_scroll_ != GetScroll()) {
cached_waveform_ = QPixmap(size());
cached_waveform_.fill(Qt::transparent);
QFile fs(backend_->CachePathName());
if (fs.open(QFile::ReadOnly)) {
QPainter wave_painter(&cached_waveform_);
// FIXME: Hardcoded color
wave_painter.setPen(Qt::green);
int channel_height = height() / params.channel_count();
int channel_half_height = channel_height / 2;
int drew = 0;
fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0)));
for (int x=0; x<width() && !fs.atEnd(); x++) {
int samples_len = ScreenToUnitRounded(x+1) - ScreenToUnitRounded(x);
int max_read_size = params.samples_to_bytes(samples_len);
QByteArray read_buffer = fs.read(max_read_size);
// Detect whether we've reached EOF and recalculate sample count if so
if (read_buffer.size() < max_read_size) {
samples_len = params.bytes_to_samples(read_buffer.size());
}
QVector<SampleSummer::Sum> samples = SampleSummer::SumSamples(reinterpret_cast<const float*>(read_buffer.constData()),
samples_len,
params.channel_count());
for (int i=0;i<params.channel_count();i++) {
if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) {
int channel_bottom = channel_height * (i + 1);
int diff = qRound((samples.at(i).max - samples.at(i).min) * channel_half_height);
wave_painter.drawLine(x,
channel_bottom - diff,
x,
channel_bottom);
} else {
int channel_mid = channel_height * i + channel_half_height;
wave_painter.drawLine(x,
channel_mid + samples.at(i).min * static_cast<float>(channel_half_height),
x,
channel_mid + samples.at(i).max * static_cast<float>(channel_half_height));
}
drew++;
}
}
cached_size_ = size();
cached_scale_ = GetScale();
cached_scroll_ = GetScroll();
fs.close();
}
}
QPainter p(this);
// Draw in/out points
DrawTimelinePoints(&p);
// Draw cached waveform pixmap
p.drawPixmap(0, 0, cached_waveform_);
// Draw playhead
p.setPen(GetPlayheadColor());
int playhead_x = UnitToScreen(GetTime());
p.drawLine(playhead_x, 0, playhead_x, height());
}
void AudioWaveformView::BackendParamsChanged()
{
SetTimebase(backend_->params().time_base());
}
@@ -8,11 +8,11 @@
#include "render/backend/audiorenderbackend.h"
#include "widget/timeruler/seekablewidget.h"
class WaveformView : public SeekableWidget
class AudioWaveformView : public SeekableWidget
{
Q_OBJECT
public:
WaveformView(QWidget* parent = nullptr);
AudioWaveformView(QWidget* parent = nullptr);
//void SetData(const QString& file, const AudioRenderingParams& params);
@@ -26,6 +26,11 @@ protected:
private:
AudioRenderBackend* backend_;
QPixmap cached_waveform_;
QSize cached_size_;
double cached_scale_;
int cached_scroll_;
private slots:
void BackendParamsChanged();
+5 -5
View File
@@ -61,7 +61,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
sizer_->SetWidget(gl_widget_);
// Create waveform view when audio is connected and video isn't
waveform_view_ = new WaveformView();
waveform_view_ = new AudioWaveformView();
stack_->addWidget(waveform_view_);
// Create time ruler
@@ -70,7 +70,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
// Create scrollbar
layout->addWidget(scrollbar());
connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
connect(scrollbar(), &QScrollBar::valueChanged, waveform_view_, &WaveformView::SetScroll);
connect(scrollbar(), &QScrollBar::valueChanged, waveform_view_, &AudioWaveformView::SetScroll);
// Create lower controls
controls_ = new PlaybackControls();
@@ -96,7 +96,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
audio_renderer_ = new AudioBackend(this);
waveform_view_->SetBackend(audio_renderer_);
connect(waveform_view_, &WaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal);
connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal);
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters);
@@ -306,8 +306,8 @@ void ViewerWidget::PushScrubbedAudio()
QIODevice* audio_src = audio_renderer_->GetAudioPullDevice();
if (audio_src && audio_src->open(QFile::ReadOnly)) {
// Try to get one "frame" of audio
int size_of_sample = audio_renderer_->params().time_to_bytes(timebase());
// FIXME: Hardcoded scrubbing interval (20ms)
int size_of_sample = audio_renderer_->params().time_to_bytes(rational(20, 1000));
// Push audio
audio_src->seek(audio_renderer_->params().time_to_bytes(GetTime()));
+2 -2
View File
@@ -28,6 +28,7 @@
#include <QTimer>
#include <QWidget>
#include "audiowaveformview.h"
#include "common/rational.h"
#include "node/output/viewer/viewer.h"
#include "render/backend/opengl/openglbackend.h"
@@ -35,7 +36,6 @@
#include "render/backend/audio/audiobackend.h"
#include "viewerglwidget.h"
#include "viewersizer.h"
#include "waveformview.h"
#include "widget/playbackcontrols/playbackcontrols.h"
#include "widget/timebased/timebased.h"
@@ -159,7 +159,7 @@ private:
bool time_changed_from_timer_;
WaveformView* waveform_view_;
AudioWaveformView* waveform_view_;
private slots:
void PlaybackTimerUpdate();
-171
View File
@@ -1,171 +0,0 @@
#include "waveformview.h"
#include <QFile>
#include <QPainter>
#include <QtMath>
#include "common/clamp.h"
#include "config/config.h"
WaveformView::WaveformView(QWidget *parent) :
SeekableWidget(parent),
backend_(nullptr)
{
setAutoFillBackground(true);
setBackgroundRole(QPalette::Base);
}
void WaveformView::SetBackend(AudioRenderBackend *backend)
{
if (backend_) {
disconnect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast<void (WaveformView::*)()>(&WaveformView::update));
disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &WaveformView::BackendParamsChanged);
SetTimebase(0);
}
backend_ = backend;
if (backend_) {
connect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast<void (WaveformView::*)()>(&WaveformView::update));
connect(backend_, &AudioRenderBackend::ParamsChanged, this, &WaveformView::BackendParamsChanged);
SetTimebase(backend_->params().time_base());
}
update();
}
void WaveformView::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const SampleSummer::Sum* samples, int nb_samples, int channels)
{
int sample_index, next_sample_index = 0;
QVector<SampleSummer::Sum> summary;
int summary_index = -1;
int channel_height = rect.height() / channels;
int channel_half_height = channel_height / 2;
for (int i=0;i<rect.width();i++) {
sample_index = next_sample_index;
if (sample_index == nb_samples) {
break;
}
next_sample_index = qMin(nb_samples,
qFloor(static_cast<double>(SampleSummer::kSumSampleRate) * static_cast<double>(i+1) / scale) * channels);
if (summary_index != sample_index) {
summary = SampleSummer::ReSumSamples(&samples[sample_index],
qMax(channels, next_sample_index - sample_index),
channels);
summary_index = sample_index;
}
int line_x = i + rect.x();
for (int j=0;j<summary.size();j++) {
if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) {
int channel_bottom = rect.y() + channel_height * (j + 1);
int diff = qRound((summary.at(j).max - summary.at(j).min) * channel_half_height);
painter->drawLine(line_x,
channel_bottom - diff,
line_x,
channel_bottom);
} else{
int channel_mid = rect.y() + channel_height * j + channel_half_height;
painter->drawLine(line_x,
channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height),
line_x,
channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height));
}
}
}
}
void WaveformView::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) {
return;
}
const AudioRenderingParams& params = backend_->params();
QFile fs(backend_->CachePathName());
if (fs.open(QFile::ReadOnly)) {
QPainter p(this);
DrawTimelinePoints(&p);
// FIXME: Hardcoded color
p.setPen(Qt::green);
int channel_height = height() / params.channel_count();
int channel_half_height = channel_height / 2;
int drew = 0;
fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0)));
for (int x=0; x<width() && !fs.atEnd(); x++) {
int samples_len = ScreenToUnitRounded(x+1) - ScreenToUnitRounded(x);
int max_read_size = params.samples_to_bytes(samples_len);
QByteArray read_buffer = fs.read(max_read_size);
// Detect whether we've reached EOF and recalculate sample count if so
if (read_buffer.size() < max_read_size) {
samples_len = params.bytes_to_samples(read_buffer.size());
}
QVector<SampleSummer::Sum> samples = SampleSummer::SumSamples(reinterpret_cast<const float*>(read_buffer.constData()),
samples_len,
params.channel_count());
for (int i=0;i<params.channel_count();i++) {
if (Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool()) {
int channel_bottom = channel_height * (i + 1);
int diff = qRound((samples.at(i).max - samples.at(i).min) * channel_half_height);
p.drawLine(x,
channel_bottom - diff,
x,
channel_bottom);
} else {
int channel_mid = channel_height * i + channel_half_height;
p.drawLine(x,
channel_mid + samples.at(i).min * static_cast<float>(channel_half_height),
x,
channel_mid + samples.at(i).max * static_cast<float>(channel_half_height));
}
drew++;
}
}
fs.close();
// Draw playhead
p.setPen(GetPlayheadColor());
int playhead_x = UnitToScreen(GetTime());
p.drawLine(playhead_x, 0, playhead_x, height());
}
}
void WaveformView::BackendParamsChanged()
{
SetTimebase(rational(1, backend_->params().sample_rate()));
}