From b39565cff4525d8e9f7e0672b1a05ddd32882d5b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 30 Nov 2020 08:18:11 +1100 Subject: [PATCH] audiowaveformview: multithreaded waveform rendering --- app/widget/timeruler/seekablewidget.cpp | 10 +- app/widget/timeruler/seekablewidget.h | 10 +- app/widget/viewer/audiowaveformview.cpp | 171 ++++++++++++++++-------- app/widget/viewer/audiowaveformview.h | 37 ++++- 4 files changed, 158 insertions(+), 70 deletions(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index dd13e1d90..b80d332ff 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -129,27 +129,27 @@ void SeekableWidget::SetScroll(int s) update(); } -double SeekableWidget::ScreenToUnitFloat(int screen) +double SeekableWidget::ScreenToUnitFloat(int screen) const { return (screen + scroll_) / GetScale() / timebase_dbl(); } -int64_t SeekableWidget::ScreenToUnit(int screen) +int64_t SeekableWidget::ScreenToUnit(int screen) const { return qFloor(ScreenToUnitFloat(screen)); } -int64_t SeekableWidget::ScreenToUnitRounded(int screen) +int64_t SeekableWidget::ScreenToUnitRounded(int screen) const { return qRound64(ScreenToUnitFloat(screen)); } -int SeekableWidget::UnitToScreen(int64_t unit) +int SeekableWidget::UnitToScreen(int64_t unit) const { return qFloor(static_cast(unit) * GetScale() * timebase_dbl()) - scroll_; } -int SeekableWidget::TimeToScreen(const rational &time) +int SeekableWidget::TimeToScreen(const rational &time) const { return qFloor(time.toDouble() * GetScale()) - scroll_; } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index d34b47bac..b508e688c 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -60,14 +60,14 @@ protected: TimelinePoints* timeline_points() const; - double ScreenToUnitFloat(int screen); + double ScreenToUnitFloat(int screen) const; - int64_t ScreenToUnit(int screen); - int64_t ScreenToUnitRounded(int screen); + int64_t ScreenToUnit(int screen) const; + int64_t ScreenToUnitRounded(int screen) const; - int UnitToScreen(int64_t unit); + int UnitToScreen(int64_t unit) const; - int TimeToScreen(const rational& time); + int TimeToScreen(const rational& time) const; void DrawPlayhead(QPainter* p, int x, int y); diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index 2dce75fbd..3eb1bd549 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -36,6 +36,8 @@ AudioWaveformView::AudioWaveformView(QWidget *parent) : { setAutoFillBackground(true); setBackgroundRole(QPalette::Base); + + cached_waveform_.resize(QThread::idealThreadCount()); } void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) @@ -73,66 +75,44 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) return; } - if (cached_size_ != size() - || cached_scale_ != GetScale() - || cached_scroll_ != GetScroll()) { - - cached_waveform_ = QPixmap(size()); - cached_waveform_.fill(Qt::transparent); - - QIODevice* fs = playback_->CreatePlaybackDevice(); - - if (fs->open(QFile::ReadOnly)) { - - QPainter wave_painter(&cached_waveform_); - - // FIXME: Hardcoded color - wave_painter.setPen(QColor(64, 255, 160)); - - int drew = 0; - - fs->seek(params.samples_to_bytes(ScreenToUnitRounded(0))); - - for (int x=0; xatEnd(); 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 samples = AudioVisualWaveform::SumSamples(reinterpret_cast(read_buffer.constData()), - samples_len, - params.channel_count()); - - for (int i=0;iclose(); - - } - - delete fs; - } - QPainter p(this); // Draw in/out points DrawTimelinePoints(&p); - // Draw cached waveform pixmap - p.drawPixmap(0, 0, cached_waveform_); + CachedWaveformInfo wanted_info = {size(), GetScale(), GetScroll(), params}; + + for (int i=0; i(); + connect(cache.watcher, &QFutureWatcher::finished, this, &AudioWaveformView::BackgroundCacheFinished); + cache.watcher->setFuture(QtConcurrent::run(this, + &AudioWaveformView::DrawWaveform, + playback_->CreatePlaybackDevice(), + wanted_info, + slice_start, + slice_end)); + + } + } // Draw playhead p.setPen(PLAYHEAD_COLOR); @@ -141,6 +121,53 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) p.drawLine(playhead_x, 0, playhead_x, height()); } +QPixmap AudioWaveformView::DrawWaveform(QIODevice* fs, CachedWaveformInfo info, int slice_start, int slice_end) const +{ + QPixmap pixmap(slice_end - slice_start, info.size.height()); + pixmap.fill(Qt::transparent); + + if (fs->open(QFile::ReadOnly)) { + + QPainter wave_painter(&pixmap); + + // FIXME: Hardcoded color + wave_painter.setPen(QColor(64, 255, 160)); + + int drew = 0; + + fs->seek(info.params.samples_to_bytes(ScreenToUnitRounded(slice_start))); + + for (int x=slice_start; xatEnd(); x++) { + int samples_len = ScreenToUnitRounded(x+1) - ScreenToUnitRounded(x); + int max_read_size = info.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 = info.params.bytes_to_samples(read_buffer.size()); + } + + QVector samples = AudioVisualWaveform::SumSamples(reinterpret_cast(read_buffer.constData()), + samples_len, + info.params.channel_count()); + + for (int i=0;iclose(); + + } + + delete fs; + + return pixmap; +} + void AudioWaveformView::BackendParamsChanged() { SetTimebase(playback_->GetParameters().time_base()); @@ -148,8 +175,40 @@ void AudioWaveformView::BackendParamsChanged() void AudioWaveformView::ForceUpdate() { - cached_size_ = QSize(); + // Forces the cache to invalidate + for (int i=0; i* watcher = static_cast*>(sender()); + + // Determine index + int index = -1; + for (int i=0; i -1) { + // Store generated pixmap + cached_waveform_[index].info = cached_waveform_[index].caching_info; + cached_waveform_[index].pixmap = watcher->result(); + cached_waveform_[index].watcher = nullptr; + + // Update with new pixmap + update(); + } + + // Clean up + delete watcher; +} + } diff --git a/app/widget/viewer/audiowaveformview.h b/app/widget/viewer/audiowaveformview.h index 94ab95ab5..e1d71ea9a 100644 --- a/app/widget/viewer/audiowaveformview.h +++ b/app/widget/viewer/audiowaveformview.h @@ -21,6 +21,7 @@ #ifndef AUDIOWAVEFORMVIEW_H #define AUDIOWAVEFORMVIEW_H +#include #include #include "audio/audiovisualwaveform.h" @@ -44,18 +45,46 @@ protected: virtual void paintEvent(QPaintEvent* event) override; private: + struct CachedWaveformInfo { + QSize size; + double scale; + int scroll; + AudioParams params; + + bool operator==(const CachedWaveformInfo& rhs) const + { + return size == rhs.size + && qFuzzyCompare(scale, rhs.scale) + && scroll == rhs.scroll + && params == rhs.params; + } + + bool operator!=(const CachedWaveformInfo& rhs) const + { + return !(*this == rhs); + } + }; + + struct ActiveCache { + QPixmap pixmap; + CachedWaveformInfo info; + CachedWaveformInfo caching_info; + QFutureWatcher* watcher = nullptr; + }; + + QPixmap DrawWaveform(QIODevice *fs, CachedWaveformInfo info, int slice_start, int slice_end) const; + AudioPlaybackCache *playback_; - QPixmap cached_waveform_; - QSize cached_size_; - double cached_scale_; - int cached_scroll_; + QVector cached_waveform_; private slots: void BackendParamsChanged(); void ForceUpdate(); + void BackgroundCacheFinished(); + }; }