Files
oak-editor/app/render/backend/audiorenderbackend.cpp
T
itsmattkc c61adbf2a2 videorenderer: if an InvalidateVisible() signal comes through, cancel the queue
Optimization that frees up the GPU as quickly as possible when the user starts
making changes that may require quick feedback.
2020-05-07 00:56:16 +10:00

276 lines
7.6 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "audiorenderbackend.h"
#include <QDir>
#include <QtMath>
#include "audiorenderworker.h"
#include "common/filefunctions.h"
#include "task/conform/conform.h"
#include "task/taskmanager.h"
OLIVE_NAMESPACE_ENTER
AudioRenderBackend::AudioRenderBackend(QObject *parent) :
RenderBackend(parent),
ic_from_conform_(false)
{
connect(this, &AudioRenderBackend::QueueComplete, this, &AudioRenderBackend::FilterQueueCompleteSignal);
}
void AudioRenderBackend::SetParameters(const AudioRenderingParams &params)
{
CancelQueue();
// Set new parameters
params_ = params;
// Set params on all processors
foreach (RenderWorker* worker, processors_) {
static_cast<AudioRenderWorker*>(worker)->SetParameters(params_);
}
// Regenerate the cache ID
RegenerateCacheID();
emit ParamsChanged();
}
void AudioRenderBackend::ConnectViewer(ViewerOutput *node)
{
connect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
connect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
}
void AudioRenderBackend::DisconnectViewer(ViewerOutput *node)
{
disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
conform_wait_info_.clear();
}
bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash)
{
if (!params_.is_valid()) {
return false;
}
// Generate an ID that is more or less guaranteed to be unique to this Sequence
hash.addData(QString::number(params_.sample_rate()).toUtf8());
hash.addData(QString::number(params_.channel_layout()).toUtf8());
hash.addData(QString::number(params_.format()).toUtf8());
return true;
}
const AudioRenderingParams &AudioRenderBackend::params() const
{
return params_;
}
NodeInput *AudioRenderBackend::GetDependentInput()
{
return viewer_node()->samples_input();
}
QString AudioRenderBackend::CachePathName() const
{
QString cache_fn = cache_id();
cache_fn.append(".pcm");
return QDir(FileFunctions::GetMediaCacheLocation()).filePath(cache_fn);
}
bool AudioRenderBackend::CanRender()
{
return params_.is_valid();
}
void AudioRenderBackend::ConnectWorkerToThis(RenderWorker *worker)
{
AudioRenderWorker* arw = static_cast<AudioRenderWorker*>(worker);
connect(arw, &AudioRenderWorker::ConformUnavailable, this, &AudioRenderBackend::ConformUnavailable, Qt::QueuedConnection);
}
TimeRange AudioRenderBackend::PopNextFrameFromQueue()
{
TimeRange range = cache_queue_.first();
// Limit range per worker to 2 seconds (FIXME: arbitrary, should be tweaked, maybe even in config?)
range.set_out(qMin(range.out(), range.in() + rational(2)));
cache_queue_.RemoveTimeRange(range);
return range;
}
void AudioRenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range, bool only_visible)
{
if (!ic_from_conform_) {
// Cancel any ranges waiting on a conform here since obviously the contents have changed
TimeRange range(start_range, end_range);
for (int i=0;i<conform_wait_info_.size();i++) {
ConformWaitInfo& info = conform_wait_info_[i];
// FIXME: Code copied from TimeRangeList::RemoveTimeRange()
if (range.Contains(info.affected_range)) {
conform_wait_info_.removeAt(i);
i--;
} else if (info.affected_range.Contains(range, false, false)) {
ConformWaitInfo copy = info;
info.affected_range.set_out(start_range);
copy.affected_range.set_in(end_range);
conform_wait_info_.append(copy);
} else if (info.affected_range.in() < start_range && info.affected_range.out() > start_range) {
info.affected_range.set_out(start_range);
} else if (info.affected_range.in() < end_range && info.affected_range.out() > end_range) {
info.affected_range.set_in(end_range);
}
}
}
RenderBackend::InvalidateCacheInternal(start_range, end_range, only_visible);
}
void AudioRenderBackend::ListenForConformSignal(AudioStreamPtr s)
{
foreach (const ConformWaitInfo& info, conform_wait_info_) {
if (info.stream == s) {
// We've probably already connected to this one
return;
}
}
connect(s.get(), &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated);
}
void AudioRenderBackend::StopListeningForConformSignal(AudioStream* s)
{
foreach (const ConformWaitInfo& info, conform_wait_info_) {
if (info.stream.get() == s) {
// There are still conforms we're waiting for, don't disconnect
return;
}
}
disconnect(s, &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated);
}
void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params)
{
ConformWaitInfo info = {stream, params, range, stream_time};
if (conform_wait_info_.contains(info)) {
return;
}
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream);
if (audio_stream->try_start_conforming(params)) {
// Start indexing process
ListenForConformSignal(audio_stream);
conform_wait_info_.append(info);
ConformTask* conform_task = new ConformTask(audio_stream, params);
TaskManager::instance()->AddTask(conform_task);
} else if (audio_stream->has_conformed_version(params)) {
// Conform JUST finished, requeue this time
ic_from_conform_ = true;
InvalidateCache(range, nullptr);
ic_from_conform_ = false;
} else {
// A conform task is already running, so we'll just wait for it
ListenForConformSignal(audio_stream);
conform_wait_info_.append(info);
}
}
void AudioRenderBackend::ConformUpdated(AudioRenderingParams params)
{
AudioStream *stream = static_cast<AudioStream*>(sender());
for (int i=0;i<conform_wait_info_.size();i++) {
const ConformWaitInfo& info = conform_wait_info_.at(i);
if (info.stream.get() == stream
&& info.params == params) {
// Make a copy so the values we use aren't corrupt
ConformWaitInfo copy = info;
// Remove this entry from the list
conform_wait_info_.removeAt(i);
i--;
// Send invalidate cache signal
ic_from_conform_ = true;
InvalidateCache(copy.affected_range, nullptr);
ic_from_conform_ = false;
}
}
StopListeningForConformSignal(stream);
}
void AudioRenderBackend::TruncateCache(const rational &r)
{
int seq_length = params_.time_to_bytes(r);
QFile cache_pcm(CachePathName());
if (cache_pcm.size() > seq_length) {
cache_pcm.resize(seq_length);
}
}
void AudioRenderBackend::FilterQueueCompleteSignal()
{
if (conform_wait_info_.isEmpty()) {
emit AudioComplete();
}
}
bool AudioRenderBackend::ConformWaitInfo::operator==(const AudioRenderBackend::ConformWaitInfo &rhs) const
{
return rhs.params == params
&& rhs.stream == stream
&& rhs.stream_time == stream_time
&& rhs.affected_range == affected_range;
}
OLIVE_NAMESPACE_EXIT