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:
@@ -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()),
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
Reference in New Issue
Block a user