renderer: move waveform generation into worker and mutex the waveform
Addresses scheduling issues where a backend might be closed (or even destroyed) before it processes a waveform signal from a worker. Requires extra multithreading code.
This commit is contained in:
@@ -199,6 +199,11 @@ public:
|
||||
return waveform_;
|
||||
}
|
||||
|
||||
QMutex* waveform_lock()
|
||||
{
|
||||
return &waveform_lock_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void SetTrackName(const QString& name);
|
||||
|
||||
@@ -276,6 +281,7 @@ private:
|
||||
rational queued_length_;
|
||||
|
||||
AudioVisualWaveform waveform_;
|
||||
QMutex waveform_lock_;
|
||||
|
||||
private slots:
|
||||
void BlockConnected(NodeEdgePtr edge);
|
||||
|
||||
@@ -34,7 +34,8 @@ OLIVE_NAMESPACE_ENTER
|
||||
RenderBackend::RenderBackend(QObject *parent) :
|
||||
QObject(parent),
|
||||
viewer_node_(nullptr),
|
||||
update_with_graph_(false)
|
||||
update_with_graph_(false),
|
||||
preview_job_time_(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -270,7 +271,6 @@ void RenderBackend::RunNextJob()
|
||||
RenderWorker* worker = CreateNewWorker();
|
||||
|
||||
connect(worker, &RenderWorker::FinishedJob, this, &RenderBackend::WorkerFinished);
|
||||
connect(worker, &RenderWorker::WaveformGenerated, this, &RenderBackend::WorkerGeneratedWaveform);
|
||||
|
||||
workers_.replace(i, {worker, false});
|
||||
}
|
||||
@@ -288,7 +288,9 @@ void RenderBackend::RunNextJob()
|
||||
worker->SetVideoParams(video_params_);
|
||||
worker->SetAudioParams(audio_params_);
|
||||
worker->SetVideoDownloadMatrix(video_download_matrix_);
|
||||
worker->SetGeneratePreviews(generate_previews_);
|
||||
if (preview_job_time_) {
|
||||
worker->EnablePreviewGeneration(viewer_node_->audio_playback_cache(), preview_job_time_);
|
||||
}
|
||||
worker->SetCopyMap(©_map_);
|
||||
|
||||
RenderTicketPtr ticket = render_queue_.front();
|
||||
@@ -351,37 +353,6 @@ void RenderBackend::WorkerFinished()
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::WorkerGeneratedWaveform(const TrackOutput *copied_track, const AudioVisualWaveform& samples, const rational &r)
|
||||
{
|
||||
if (!viewer_node_) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackOutput* track = nullptr;
|
||||
|
||||
/*
|
||||
if (!viewer_node_->audio_playback_cache()->JobIsCurrent(r)) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
QHash<Node*, Node*>::const_iterator i;
|
||||
for (i=copy_map_.constBegin(); i!=copy_map_.constEnd(); i++) {
|
||||
if (i.value() == copied_track) {
|
||||
track = static_cast<TrackOutput*>(i.key());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (track) {
|
||||
track->waveform().set_channel_count(audio_params_.channel_count());
|
||||
track->waveform().OverwriteSums(samples, r);
|
||||
emit track->PreviewChanged();
|
||||
} else {
|
||||
qDebug() << "Failed to find track";
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::CopyNodeInputValue(NodeInput *input)
|
||||
{
|
||||
// Find our copy of this parameter
|
||||
|
||||
@@ -50,9 +50,9 @@ public:
|
||||
update_with_graph_ = e;
|
||||
}
|
||||
|
||||
void SetGeneratePreviews(bool e)
|
||||
void EnablePreviewGeneration(qint64 job_time)
|
||||
{
|
||||
generate_previews_ = e;
|
||||
preview_job_time_ = job_time;
|
||||
}
|
||||
|
||||
void ClearVideoQueue();
|
||||
@@ -121,13 +121,11 @@ private:
|
||||
|
||||
bool update_with_graph_;
|
||||
|
||||
bool generate_previews_;
|
||||
qint64 preview_job_time_;
|
||||
|
||||
private slots:
|
||||
void WorkerFinished();
|
||||
|
||||
void WorkerGeneratedWaveform(const OLIVE_NAMESPACE::TrackOutput* copied_track, const OLIVE_NAMESPACE::AudioVisualWaveform& samples, const OLIVE_NAMESPACE::rational& start);
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -35,7 +35,7 @@ RenderWorker::RenderWorker(RenderBackend* parent) :
|
||||
parent_(parent),
|
||||
available_(true),
|
||||
audio_mode_is_preview_(false),
|
||||
generate_previews_(false)
|
||||
preview_cache_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -127,12 +127,41 @@ NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const
|
||||
NodeValueTable::Merge({merged_table, table});
|
||||
}
|
||||
|
||||
if (generate_previews_) {
|
||||
// Generate visual waveform in this background thread
|
||||
AudioVisualWaveform visual_waveform;
|
||||
visual_waveform.set_channel_count(audio_params_.channel_count());
|
||||
visual_waveform.AddSamples(block_range_buffer, audio_params_.sample_rate());
|
||||
emit WaveformGenerated(track, visual_waveform, range.in());
|
||||
if (preview_cache_) {
|
||||
// Find original track object
|
||||
TrackOutput* original_track = nullptr;
|
||||
|
||||
QList<TimeRange> valid_ranges = preview_cache_->GetValidRanges(range, preview_job_time_);
|
||||
if (!valid_ranges.isEmpty()) {
|
||||
qDebug() << "Worker generated valid waveform for" << range << preview_cache_;
|
||||
|
||||
QHash<Node*, Node*>::const_iterator i;
|
||||
for (i=copy_map_->constBegin(); i!=copy_map_->constEnd(); i++) {
|
||||
if (i.value() == track) {
|
||||
original_track = static_cast<TrackOutput*>(i.key());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate visual waveform in this background thread
|
||||
if (original_track) {
|
||||
AudioVisualWaveform visual_waveform;
|
||||
visual_waveform.set_channel_count(audio_params_.channel_count());
|
||||
visual_waveform.AddSamples(block_range_buffer, audio_params_.sample_rate());
|
||||
|
||||
original_track->waveform_lock()->lock();
|
||||
|
||||
original_track->waveform().set_channel_count(audio_params_.channel_count());
|
||||
|
||||
foreach (const TimeRange& r, valid_ranges) {
|
||||
original_track->waveform().OverwriteSums(visual_waveform, r.in(), r.in() - range.in(), r.length());
|
||||
}
|
||||
|
||||
original_track->waveform_lock()->unlock();
|
||||
|
||||
emit original_track->PreviewChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer));
|
||||
|
||||
@@ -73,9 +73,10 @@ public:
|
||||
copy_map_ = copy_map;
|
||||
}
|
||||
|
||||
void SetGeneratePreviews(bool e)
|
||||
void EnablePreviewGeneration(AudioPlaybackCache* cache, qint64 job_time)
|
||||
{
|
||||
generate_previews_ = e;
|
||||
preview_cache_ = cache;
|
||||
preview_job_time_ = job_time;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +133,7 @@ signals:
|
||||
|
||||
void FinishedJob();
|
||||
|
||||
void WaveformGenerated(const OLIVE_NAMESPACE::TrackOutput* track, const OLIVE_NAMESPACE::AudioVisualWaveform& samples, const OLIVE_NAMESPACE::rational& start);
|
||||
void WaveformGenerated(OLIVE_NAMESPACE::TrackOutput* track, OLIVE_NAMESPACE::AudioVisualWaveform samples, OLIVE_NAMESPACE::TimeRange start);
|
||||
|
||||
private:
|
||||
NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time);
|
||||
@@ -164,7 +165,8 @@ private:
|
||||
|
||||
bool audio_mode_is_preview_;
|
||||
|
||||
bool generate_previews_;
|
||||
AudioPlaybackCache* preview_cache_;
|
||||
qint64 preview_job_time_;
|
||||
|
||||
QHash<Node*, Node*>* copy_map_;
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -33,7 +33,7 @@ CacheTask::CacheTask(ViewerOutput* viewer, const VideoRenderingParams& vparams,
|
||||
{
|
||||
SetTitle(tr("Caching \"%1\"").arg(viewer->media_name()));
|
||||
|
||||
backend()->SetGeneratePreviews(true);
|
||||
backend()->EnablePreviewGeneration(job_time());
|
||||
}
|
||||
|
||||
bool CacheTask::Run()
|
||||
|
||||
@@ -99,6 +99,8 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI
|
||||
painter->setPen(QColor(64, 64, 64));
|
||||
TrackOutput* track = TrackOutput::TrackFromBlock(block_);
|
||||
if (track) {
|
||||
QMutexLocker locker(track->waveform_lock());
|
||||
|
||||
AudioVisualWaveform::DrawWaveform(painter,
|
||||
rect().toRect(),
|
||||
this->GetScale(),
|
||||
|
||||
Reference in New Issue
Block a user