/*** 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 . ***/ #include "audiorenderbackend.h" #include #include #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 ¶ms) { CancelQueue(); // Set new parameters params_ = params; // Set params on all processors foreach (RenderWorker* worker, processors_) { static_cast(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(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 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(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(sender()); for (int i=0;i 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