a first attempt at a new render system

This commit is contained in:
itsmattkc
2020-05-14 14:43:03 +10:00
parent c5dfddff0d
commit 863ae1b913
60 changed files with 927 additions and 3164 deletions
+3 -3
View File
@@ -42,7 +42,7 @@ extern "C" {
#include "common/functiontimer.h"
#include "common/timecodefunctions.h"
#include "ffmpegcommon.h"
#include "render/backend/videorenderframecache.h"
#include "render/framehashcache.h"
#include "render/diskmanager.h"
#include "render/pixelformat.h"
@@ -662,7 +662,7 @@ void SaveCacheFrame(FFmpegDecoder* decoder,
&converted_linesize);
scaler_lock.unlock();
if (!VideoRenderFrameCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params)) {
if (!FrameHashCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params)) {
qCritical() <<" Failed to save cache frame" << dst_fn;
}
@@ -1286,7 +1286,7 @@ QString FFmpegDecoder::GetProxyFrameFilename(const int64_t &timestamp, const int
{
QString dst_fn = GetProxyFilename(divider);
dst_fn.append(QString::number(timestamp));
dst_fn.append(VideoRenderFrameCache::GetFormatExtension(native_pix_fmt_));
dst_fn.append(FrameHashCache::GetFormatExtension(native_pix_fmt_));
return dst_fn;
}
+1 -2
View File
@@ -379,7 +379,7 @@ void Core::DialogExportShow()
TimeBasedPanel* latest_time_based = PanelManager::instance()->MostRecentlyFocused<TimeBasedPanel>();
if (latest_time_based && latest_time_based->GetConnectedViewer()) {
if (latest_time_based->GetConnectedViewer()->Length() == 0) {
if (latest_time_based->GetConnectedViewer()->GetLength() == 0) {
QMessageBox::critical(main_window_,
tr("Error"),
tr("This Sequence is empty. There is nothing to export."),
@@ -525,7 +525,6 @@ void Core::ProjectWasModified(bool e)
void Core::DeclareTypesForQt()
{
qRegisterMetaType<NodeDependency>();
qRegisterMetaType<rational>();
qRegisterMetaType<OpenGLTexturePtr>();
qRegisterMetaType<OpenGLTextureCache::ReferencePtr>();
+1 -6
View File
@@ -228,11 +228,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
preview_viewer_->SetColorMenuEnabled(false);
preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace());
// Update renderer
// FIXME: This is going to be VERY slow since it will need to hash every single frame. It would be better to have a
// the renderer save the map as some sort of file that this can load.
preview_viewer_->video_renderer()->InvalidateCache(TimeRange(0, viewer_node_->Length()), nullptr);
progress_timer_.setInterval(1000);
connect(&progress_timer_, &QTimer::timeout, this, &ExportDialog::UpdateTimeLabels);
}
@@ -585,7 +580,7 @@ ExportParams ExportDialog::GenerateParams() const
ExportParams params;
params.SetFilename(filename_edit_->text());
params.SetExportLength(viewer_node_->Length());
params.SetExportLength(viewer_node_->GetLength());
if (video_tab_->scaling_method_combobox()->isEnabled()) {
params.set_video_scaling_method(static_cast<ExportParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()));
-2
View File
@@ -24,8 +24,6 @@ add_subdirectory(output)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/dependency.h
node/dependency.cpp
node/edge.h
node/edge.cpp
node/factory.h
-64
View File
@@ -1,64 +0,0 @@
/***
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 "dependency.h"
#include "node.h"
OLIVE_NAMESPACE_ENTER
NodeDependency::NodeDependency() :
node_(nullptr)
{
}
NodeDependency::NodeDependency(const Node *node, const TimeRange &range) :
node_(node),
range_(range)
{
}
NodeDependency::NodeDependency(const Node *node, const rational &in, const rational &out) :
node_(node),
range_(in, out)
{
}
const Node *NodeDependency::node() const
{
return node_;
}
const rational& NodeDependency::in() const
{
return range_.in();
}
const rational &NodeDependency::out() const
{
return range_.out();
}
const TimeRange &NodeDependency::range() const
{
return range_;
}
OLIVE_NAMESPACE_EXIT
+1 -1
View File
@@ -55,7 +55,7 @@ void TrackOutput::set_track_type(const Timeline::TrackType &track_type)
track_type_ = track_type;
}
const Timeline::TrackType& TrackOutput::track_type()
const Timeline::TrackType& TrackOutput::track_type() const
{
return track_type_;
}
+1 -1
View File
@@ -35,7 +35,7 @@ class TrackOutput : public Block
public:
TrackOutput();
const Timeline::TrackType& track_type();
const Timeline::TrackType& track_type() const;
void set_track_type(const Timeline::TrackType& track_type);
virtual Type type() const override;
+13 -50
View File
@@ -55,6 +55,8 @@ ViewerOutput::ViewerOutput()
// Create UUID for this node
uuid_ = QUuid::createUuid();
connect(this, &ViewerOutput::LengthChanged, &video_frame_cache_, &FrameHashCache::SetLength);
}
Node *ViewerOutput::copy() const
@@ -82,22 +84,16 @@ QString ViewerOutput::Description() const
return tr("Interface between a Viewer panel and the node system.");
}
NodeInput *ViewerOutput::texture_input() const
{
return texture_input_;
}
NodeInput *ViewerOutput::samples_input() const
{
return samples_input_;
}
void ViewerOutput::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source)
{
if (from == texture_input()) {
emit VideoChangedBetween(range, source);
emit GraphChangedFrom(from, source);
video_frame_cache_.Invalidate(range);
} else if (from == samples_input()) {
emit AudioChangedBetween(range, source);
emit GraphChangedFrom(from, source);
audio_playback_cache_.Invalidate(range);
}
Node::InvalidateCache(range, from, source);
@@ -112,16 +108,6 @@ void ViewerOutput::InvalidateVisible(NodeInput* from, NodeInput *source)
Node::InvalidateVisible(from, source);
}
const VideoParams &ViewerOutput::video_params() const
{
return video_params_;
}
const AudioParams &ViewerOutput::audio_params() const
{
return audio_params_;
}
void ViewerOutput::set_video_params(const VideoParams &video)
{
video_params_ = video;
@@ -134,34 +120,31 @@ void ViewerOutput::set_video_params(const VideoParams &video)
void ViewerOutput::set_audio_params(const AudioParams &audio)
{
audio_params_ = audio;
emit AudioParamsChanged();
}
rational ViewerOutput::Length()
rational ViewerOutput::GetLength()
{
NodeTraverser traverser;
rational video_length;
if (texture_input_->IsConnected()) {
NodeValueTable t = traverser.ProcessNode(NodeDependency(texture_input_->get_connected_node(), 0, 0));
NodeValueTable t = traverser.GenerateTable(texture_input_->get_connected_node(), 0, 0);
video_length = t.Get(NodeParam::kNumber, "length").value<rational>();
}
rational audio_length;
if (samples_input_->IsConnected()) {
NodeValueTable t = traverser.ProcessNode(NodeDependency(samples_input_->get_connected_node(), 0, 0));
NodeValueTable t = traverser.GenerateTable(samples_input_->get_connected_node(), 0, 0);
audio_length = t.Get(NodeParam::kNumber, "length").value<rational>();
}
return qMax(video_length, qMax(audio_length, timeline_length_));
}
const QUuid &ViewerOutput::uuid() const
{
return uuid_;
}
void ViewerOutput::UpdateTrackCache()
{
track_cache_.clear();
@@ -231,11 +214,6 @@ void ViewerOutput::Retranslate()
}
}
const QString &ViewerOutput::media_name() const
{
return media_name_;
}
void ViewerOutput::set_media_name(const QString &name)
{
media_name_ = name;
@@ -243,21 +221,6 @@ void ViewerOutput::set_media_name(const QString &name)
emit MediaNameChanged(media_name_);
}
const QVector<TrackOutput *>& ViewerOutput::Tracks() const
{
return track_cache_;
}
NodeInput *ViewerOutput::track_input(Timeline::TrackType type) const
{
return track_inputs_.at(type);
}
TrackList *ViewerOutput::track_list(Timeline::TrackType type) const
{
return track_lists_.at(type);
}
void ViewerOutput::TrackListAddedBlock(Block *block, int index)
{
Timeline::TrackType type = static_cast<TrackList*>(sender())->type();
+48 -14
View File
@@ -28,8 +28,10 @@
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "node/node.h"
#include "render/videoparams.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
#include "render/framehashcache.h"
#include "render/videoparams.h"
#include "timeline/trackreference.h"
OLIVE_NAMESPACE_ENTER
@@ -52,39 +54,66 @@ public:
virtual QList<CategoryID> Category() const override;
virtual QString Description() const override;
NodeInput* texture_input() const;
NodeInput* samples_input() const;
NodeInput* texture_input() const {
return texture_input_;
}
NodeInput* samples_input() const {
return samples_input_;
}
virtual void InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput* source) override;
virtual void InvalidateVisible(NodeInput *from, NodeInput* source) override;
const VideoParams& video_params() const;
const AudioParams& audio_params() const;
const VideoParams& video_params() const {
return video_params_;
}
const AudioParams& audio_params() const {
return audio_params_;
}
void set_video_params(const VideoParams& video);
void set_audio_params(const AudioParams& audio);
rational Length();
rational GetLength();
const QUuid& uuid() const;
const QUuid& uuid() const {
return uuid_;
}
const QVector<TrackOutput *> &Tracks() const;
const QVector<TrackOutput *> &GetTracks() const {
return track_cache_;
}
NodeInput* track_input(Timeline::TrackType type) const;
NodeInput* track_input(Timeline::TrackType type) const {
return track_inputs_.at(type);
}
TrackList* track_list(Timeline::TrackType type) const;
TrackList* track_list(Timeline::TrackType type) const {
return track_lists_.at(type);
}
virtual void Retranslate() override;
const QString& media_name() const;
const QString& media_name() const {
return media_name_;
}
void set_media_name(const QString& name);
FrameHashCache* video_frame_cache() {
return &video_frame_cache_;
}
AudioPlaybackCache* audio_playback_cache() {
return &audio_playback_cache_;
}
signals:
void TimebaseChanged(const rational&);
void VideoChangedBetween(const TimeRange& range, NodeInput* source);
void AudioChangedBetween(const TimeRange& range, NodeInput* source);
void GraphChangedFrom(NodeInput* from, NodeInput* source);
void VisibleInvalidated(NodeInput* source);
@@ -93,6 +122,7 @@ signals:
void SizeChanged(int width, int height);
void VideoParamsChanged();
void AudioParamsChanged();
void BlockAdded(Block* block, TrackReference track);
void BlockRemoved(Block* block);
@@ -125,6 +155,10 @@ private:
QString media_name_;
FrameHashCache video_frame_cache_;
AudioPlaybackCache audio_playback_cache_;
private slots:
void UpdateTrackCache();
+25 -27
View File
@@ -24,7 +24,7 @@
OLIVE_NAMESPACE_ENTER
NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRange &range)
NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRange &range) const
{
NodeValueDatabase database;
@@ -38,7 +38,17 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa
TimeRange input_time = node->InputTimeAdjustment(input, range);
NodeValueTable table = ProcessInput(input, input_time);
NodeValueTable table;
if (input->IsConnected()) {
// Value will equal something from the connected node, follow it
table = GenerateTable(input->get_connected_node(), range);
} else {
// Push onto the table the value at this time from the input
QVariant input_value = input->get_value_at_time(range.in());
table.Push(input->data_type(), input_value);
}
// Exception for Footage types where we actually retrieve some Footage data from a decoder
if (input->data_type() == NodeParam::kFootage) {
@@ -63,29 +73,32 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa
return database;
}
NodeValueTable NodeTraverser::ProcessNode(const NodeDependency& dep)
NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& range) const
{
const Node* node = dep.node();
if (node->IsTrack()) {
if (n->IsTrack()) {
// If the range is not wholly contained in this Block, we'll need to do some extra processing
return RenderBlock(static_cast<const TrackOutput*>(node), dep.range());
return GenerateBlockTable(static_cast<const TrackOutput*>(n), range);
}
// FIXME: Cache certain values here if we've already processed them before
// Generate database of input values of node
NodeValueDatabase database = GenerateDatabase(node, dep.range());
NodeValueDatabase database = GenerateDatabase(n, range);
// By this point, the node should have all the inputs it needs to render correctly
NodeValueTable table = node->Value(database);
NodeValueTable table = n->Value(database);
ProcessNodeEvent(node, dep.range(), database, table);
ProcessNodeEvent(n, range, database, table);
return table;
}
NodeValueTable NodeTraverser::RenderBlock(const TrackOutput *track, const TimeRange &range)
NodeValueTable NodeTraverser::GenerateTable(const Node *n, const rational &in, const rational &out) const
{
return GenerateTable(n, TimeRange(in, out));
}
NodeValueTable NodeTraverser::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const
{
// By default, just follow the in point
Block* active_block = track->BlockAtTime(range.in());
@@ -93,7 +106,7 @@ NodeValueTable NodeTraverser::RenderBlock(const TrackOutput *track, const TimeRa
NodeValueTable table;
if (active_block) {
table = ProcessNode(NodeDependency(active_block, range));
table = GenerateTable(active_block, range);
}
return table;
@@ -104,19 +117,4 @@ StreamPtr NodeTraverser::ResolveStreamFromInput(NodeInput *input)
return input->get_standard_value().value<StreamPtr>();
}
NodeValueTable NodeTraverser::ProcessInput(const NodeInput *input, const TimeRange& range)
{
if (input->IsConnected()) {
// Value will equal something from the connected node, follow it
return ProcessNode(NodeDependency(input->get_connected_node(), range));
} else {
// Push onto the table the value at this time from the input
QVariant input_value = input->get_value_at_time(range.in());
NodeValueTable table;
table.Push(input->data_type(), input_value);
return table;
}
}
OLIVE_NAMESPACE_EXIT
+10 -9
View File
@@ -23,7 +23,6 @@
#include "codec/decoder.h"
#include "common/cancelableobject.h"
#include "dependency.h"
#include "node/output/track/track.h"
#include "project/item/footage/stream.h"
#include "value.h"
@@ -35,21 +34,23 @@ class NodeTraverser : public CancelableObject
public:
NodeTraverser() = default;
NodeValueTable ProcessNode(const NodeDependency &dep);
NodeValueTable GenerateTable(const Node *n, const TimeRange &range) const;
NodeValueTable GenerateTable(const Node *n, const rational &in, const rational& out) const;
NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range);
NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range) const;
protected:
virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range);
virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange& range) const;
NodeValueTable ProcessInput(const NodeInput* input, const TimeRange &range);
virtual void FootageProcessingEvent(StreamPtr, const TimeRange&, NodeValueTable*) const {}
virtual void FootageProcessingEvent(StreamPtr, const TimeRange&, NodeValueTable*){}
virtual void ProcessNodeEvent(const Node*, const TimeRange&, NodeValueDatabase&, NodeValueTable&){}
virtual void ProcessNodeEvent(const Node*,
const TimeRange&,
NodeValueDatabase&,
NodeValueTable&) const {}
private:
StreamPtr ResolveStreamFromInput(NodeInput* input);
static StreamPtr ResolveStreamFromInput(NodeInput* input);
};
-5
View File
@@ -72,11 +72,6 @@ void ViewerPanelBase::DisconnectTimeBasedPanel(TimeBasedPanel *panel)
disconnect(panel, &TimeBasedPanel::ShuttleRightRequested, this, &ViewerPanelBase::ShuttleRight);
}
VideoRenderBackend *ViewerPanelBase::video_renderer() const
{
return static_cast<ViewerWidget*>(GetTimeBasedWidget())->video_renderer();
}
void ViewerPanelBase::ConnectPixelSamplerPanel(PixelSamplerPanel *psp)
{
ViewerWidget* vw = static_cast<ViewerWidget*>(GetTimeBasedWidget());
-2
View File
@@ -47,8 +47,6 @@ public:
void DisconnectTimeBasedPanel(TimeBasedPanel* panel);
VideoRenderBackend* video_renderer() const;
void ConnectPixelSamplerPanel(PixelSamplerPanel *psp);
/**
+1 -1
View File
@@ -193,7 +193,7 @@ QIcon Sequence::icon()
QString Sequence::duration()
{
rational timeline_length = viewer_output_->Length();
rational timeline_length = viewer_output_->GetLength();
int64_t timestamp = Timecode::time_to_timestamp(timeline_length, video_params().time_base());
+6
View File
@@ -21,6 +21,8 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
render/audioparams.h
render/audioparams.cpp
render/audioplaybackcache.h
render/audioplaybackcache.cpp
render/color.h
render/color.cpp
render/colormanager.h
@@ -29,10 +31,14 @@ set(OLIVE_SOURCES
render/colorprocessor.cpp
render/diskmanager.h
render/diskmanager.cpp
render/framehashcache.h
render/framehashcache.cpp
render/managedcolor.h
render/managedcolor.cpp
render/pixelformat.h
render/pixelformat.cpp
render/playbackcache.h
render/playbackcache.cpp
render/rendermodes.h
render/videoparams.h
render/videoparams.cpp
+56
View File
@@ -0,0 +1,56 @@
/***
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 "audioplaybackcache.h"
#include <QDir>
#include <QRandomGenerator>
#include "common/filefunctions.h"
OLIVE_NAMESPACE_ENTER
AudioPlaybackCache::AudioPlaybackCache()
{
// FIXME: We just use a randomly generated number, one day this should be matchable to the
// project so it can be reused.
quint32 r = QRandomGenerator::global()->generate();
filename_ = QDir(FileFunctions::GetMediaCacheLocation()).filePath(QString::number(r));
filename_.append(QStringLiteral(".pcm"));
}
void AudioPlaybackCache::SetParameters(const AudioRenderingParams &params)
{
if (params_ == params) {
return;
}
// Our current audio cache is unusable, so we truncate it automatically
InvalidateAll();
emit ParametersChanged();
}
const QString &AudioPlaybackCache::GetCacheFilename() const
{
return filename_;
}
OLIVE_NAMESPACE_EXIT
@@ -18,35 +18,41 @@
***/
#ifndef NODEDEPENDENCY_H
#define NODEDEPENDENCY_H
#include <QMetaType>
#ifndef AUDIOPLAYBACKCACHE_H
#define AUDIOPLAYBACKCACHE_H
#include "common/timerange.h"
#include "codec/samplebuffer.h"
#include "render/playbackcache.h"
OLIVE_NAMESPACE_ENTER
class Node;
class NodeDependency {
class AudioPlaybackCache : public PlaybackCache
{
Q_OBJECT
public:
NodeDependency();
NodeDependency(const Node* node, const TimeRange& range);
NodeDependency(const Node* node, const rational& in, const rational &out);
AudioPlaybackCache();
const Node* node() const;
const rational& in() const;
const rational& out() const;
const TimeRange& range() const;
const AudioRenderingParams& GetParameters() const {
return params_;
}
void SetParameters(const AudioRenderingParams& params);
void WritePCM(SampleBufferPtr samples);
const QString& GetCacheFilename() const;
signals:
void ParametersChanged();
private:
const Node* node_;
TimeRange range_;
QString filename_;
AudioRenderingParams params_;
};
OLIVE_NAMESPACE_EXIT
Q_DECLARE_METATYPE(OLIVE_NAMESPACE::NodeDependency)
#endif // NODEDEPENDENCY_H
#endif // AUDIOPLAYBACKCACHE_H
-15
View File
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(audio)
add_subdirectory(opengl)
set(OLIVE_SOURCES
@@ -27,20 +26,6 @@ set(OLIVE_SOURCES
render/backend/renderbackend.h
render/backend/renderbackend.cpp
render/backend/renderworker.h
render/backend/renderworker.cpp
render/backend/audiorenderbackend.h
render/backend/audiorenderbackend.cpp
render/backend/audiorenderworker.h
render/backend/audiorenderworker.cpp
render/backend/videorenderbackend.h
render/backend/videorenderbackend.cpp
render/backend/videorenderframecache.h
render/backend/videorenderframecache.cpp
render/backend/videorenderworker.h
render/backend/videorenderworker.cpp
render/backend/rendercache.h
render/backend/colorprocessorcache.h
-24
View File
@@ -1,24 +0,0 @@
# 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
render/backend/audio/audiobackend.h
render/backend/audio/audiobackend.cpp
render/backend/audio/audioworker.h
render/backend/audio/audioworker.cpp
PARENT_SCOPE
)
-116
View File
@@ -1,116 +0,0 @@
/***
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 "audiobackend.h"
#include "audioworker.h"
OLIVE_NAMESPACE_ENTER
AudioBackend::AudioBackend(QObject *parent) :
AudioRenderBackend(parent)
{
}
AudioBackend::~AudioBackend()
{
Close();
}
bool AudioBackend::InitInternal()
{
// Initiate one thread per CPU core
for (int i=0;i<threads().size();i++) {
// Create one processor object for each thread
AudioWorker* processor = new AudioWorker(&node_copy_map_);
processor->SetParameters(params());
processors_.append(processor);
}
return true;
}
void AudioBackend::CloseInternal()
{
}
void AudioBackend::ConnectWorkerToThis(RenderWorker *worker)
{
AudioRenderBackend::ConnectWorkerToThis(worker);
connect(worker, &RenderWorker::CompletedCache, this, &AudioBackend::ThreadCompletedCache);
}
void AudioBackend::ThreadCompletedCache(NodeDependency dep, NodeValueTable data, qint64 job_time)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (job_time == render_job_info_.value(dep.range())) {
render_job_info_.remove(dep.range());
SampleBufferPtr cached_sample_ptr = data.Get(NodeParam::kSamples).value<SampleBufferPtr>();
if (cached_sample_ptr) {
QByteArray cached_samples = cached_sample_ptr->toPackedData();
int offset = params().time_to_bytes(dep.in());
int length = params().time_to_bytes(dep.range().length());
int out_point = qMin(offset + length, params().time_to_bytes(GetSequenceLength()));
if (offset < out_point) {
if (offset + length > out_point) {
length = out_point - offset;
}
QFile f(CachePathName());
if (f.open(QFile::ReadWrite)) {
if (f.size() < out_point && !f.resize(out_point)) {
qCritical() << "Failed to resize file" << CachePathName();
}
if (!f.seek(offset)) {
qCritical() << "Failed to seek file" << CachePathName();
}
// Replace data with this data
int copy_length = qMin(length, cached_samples.size());
f.write(cached_samples.data(), copy_length);
if (copy_length < length) {
// Fill in remainder with silence
QByteArray empty_space(length - copy_length, 0);
f.write(empty_space);
}
f.close();
} else {
qWarning() << "Failed to write to cached PCM file";
}
}
}
}
CacheNext();
}
OLIVE_NAMESPACE_EXIT
-52
View File
@@ -1,52 +0,0 @@
/***
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/>.
***/
#ifndef AUDIOBACKEND_H
#define AUDIOBACKEND_H
#include <QFile>
#include "../audiorenderbackend.h"
OLIVE_NAMESPACE_ENTER
class AudioBackend : public AudioRenderBackend
{
Q_OBJECT
public:
AudioBackend(QObject* parent = nullptr);
virtual ~AudioBackend() override;
protected:
virtual bool InitInternal() override;
virtual void CloseInternal() override;
virtual void ConnectWorkerToThis(RenderWorker* worker) override;
private slots:
void ThreadCompletedCache(NodeDependency dep, NodeValueTable data, qint64 job_time);
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIOBACKEND_H
-105
View File
@@ -1,105 +0,0 @@
/***
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 "audioworker.h"
OLIVE_NAMESPACE_ENTER
AudioWorker::AudioWorker(QHash<Node *, Node *> *copy_map, QObject *parent) :
AudioRenderWorker(copy_map, parent)
{
}
NodeValue AudioWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range)
{
if (decoder->HasConformedVersion(audio_params())) {
SampleBufferPtr frame = decoder->RetrieveAudio(range.in(), range.out() - range.in(), audio_params());
if (frame) {
return NodeValue(NodeParam::kSamples, QVariant::fromValue(frame));
}
} else {
emit ConformUnavailable(decoder->stream(), CurrentPath().range(), range.out(), audio_params());
}
return NodeValue();
}
void AudioWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params)
{
// Check if node processes samples
if (!(node->GetCapabilities(input_params_in) & Node::kSampleProcessor)) {
return;
}
// Copy database so we can make some temporary modifications to it
NodeValueDatabase input_params = input_params_in;
NodeInput* sample_input = node->ProcessesSamplesFrom(input_params);
// Try to find the sample buffer in the table
QVariant samples_var = input_params[sample_input].Get(NodeParam::kSamples);
// If there isn't one, there's nothing to do
if (samples_var.isNull()) {
return;
}
SampleBufferPtr input_buffer = samples_var.value<SampleBufferPtr>();
if (!input_buffer) {
return;
}
SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(input_buffer->audio_params(), input_buffer->sample_count_per_channel());
int sample_count = input_buffer->sample_count_per_channel();
// FIXME: Hardcoded float sample format
for (int i=0;i<sample_count;i++) {
// Calculate the exact rational time at this sample
int sample_out_of_channel = i / audio_params().channel_count();
double sample_to_second = static_cast<double>(sample_out_of_channel) / static_cast<double>(audio_params().sample_rate());
rational this_sample_time = rational::fromDouble(range.in().toDouble() + sample_to_second);
// Update all non-sample and non-footage inputs
foreach (NodeParam* param, node->parameters()) {
if (param->type() == NodeParam::kInput
&& param != sample_input) {
NodeInput* input = static_cast<NodeInput*>(param);
// If the input isn't keyframing, we don't need to update it unless it's connected, in which case it may change
if (input->IsConnected() || input->is_keyframing()) {
input_params.Insert(input, ProcessInput(input, TimeRange(this_sample_time, this_sample_time)));
}
}
}
node->ProcessSamples(input_params,
audio_params(),
input_buffer,
output_buffer,
i);
}
output_params.Push(NodeParam::kSamples, QVariant::fromValue(output_buffer));
}
OLIVE_NAMESPACE_EXIT
-275
View File
@@ -1,275 +0,0 @@
/***
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)
{
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);
}
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
-105
View File
@@ -1,105 +0,0 @@
/***
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/>.
***/
#ifndef AUDIORENDERBACKEND_H
#define AUDIORENDERBACKEND_H
#include "common/timerange.h"
#include "renderbackend.h"
OLIVE_NAMESPACE_ENTER
class AudioRenderBackend : public RenderBackend
{
Q_OBJECT
public:
AudioRenderBackend(QObject* parent = nullptr);
/**
* @brief Set parameters of the Renderer
*
* The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers
* to use. The Renderer must be stopped when calling this function.
*/
void SetParameters(const AudioRenderingParams &params);
const AudioRenderingParams& params() const;
QString CachePathName() const;
signals:
void ParamsChanged();
void AudioComplete();
protected:
virtual void ConnectViewer(ViewerOutput* node) override;
virtual void DisconnectViewer(ViewerOutput* node) override;
/**
* @brief Internal function for generating the cache ID
*/
virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override;
virtual NodeInput* GetDependentInput() override;
virtual bool CanRender() override;
virtual void ConnectWorkerToThis(RenderWorker* worker) override;
virtual TimeRange PopNextFrameFromQueue() override;
virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override;
private:
struct ConformWaitInfo {
StreamPtr stream;
AudioRenderingParams params;
TimeRange affected_range;
rational stream_time;
bool operator==(const ConformWaitInfo& rhs) const;
};
void ListenForConformSignal(AudioStreamPtr s);
void StopListeningForConformSignal(AudioStream *s);
QList<ConformWaitInfo> conform_wait_info_;
AudioRenderingParams params_;
bool ic_from_conform_;
private slots:
void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params);
void ConformUpdated(OLIVE_NAMESPACE::AudioRenderingParams params);
void TruncateCache(const rational& r);
void FilterQueueCompleteSignal();
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIORENDERBACKEND_H
-173
View File
@@ -1,173 +0,0 @@
/***
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 "audiorenderworker.h"
#include <QDir>
#include <QFloat16>
#include "audio/audiomanager.h"
#include "audio/sumsamples.h"
#include "config/config.h"
#include "node/block/clip/clip.h"
OLIVE_NAMESPACE_ENTER
AudioRenderWorker::AudioRenderWorker(QHash<Node *, Node *> *copy_map, QObject *parent) :
RenderWorker(parent),
copy_map_(copy_map)
{
}
void AudioRenderWorker::SetParameters(const AudioRenderingParams &audio_params)
{
audio_params_ = audio_params;
}
bool AudioRenderWorker::InitInternal()
{
// Nothing to init yet
return true;
}
void AudioRenderWorker::CloseInternal()
{
// Nothing to init yet
}
NodeValueTable AudioRenderWorker::RenderBlock(const TrackOutput *track, const TimeRange &range)
{
QList<Block*> active_blocks = track->BlocksAtTimeRange(range);
// All these blocks will need to output to a buffer so we create one here
SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params_, audio_params_.time_to_samples(range.length()));
block_range_buffer->fill(0);
NodeValueTable merged_table;
// Loop through active blocks retrieving their audio
foreach (Block* b, active_blocks) {
TimeRange range_for_block(qMax(b->in(), range.in()),
qMin(b->out(), range.out()));
int destination_offset = audio_params_.time_to_samples(range_for_block.in() - range.in());
int max_dest_sz = audio_params_.time_to_samples(range_for_block.length());
// Destination buffer
NodeValueTable table = ProcessNode(NodeDependency(b, range_for_block));
QVariant sample_val = table.Take(NodeParam::kSamples);
SampleBufferPtr samples_from_this_block;
if (sample_val.isNull()
|| !(samples_from_this_block = sample_val.value<SampleBufferPtr>())) {
// If we retrieved no samples from this block, do nothing
continue;
}
// Stretch samples here
rational abs_speed = qAbs(b->speed());
if (abs_speed != 1) {
samples_from_this_block->speed(abs_speed.toDouble());
}
if (b->is_reversed()) {
// Reverse the audio buffer
samples_from_this_block->reverse();
}
int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count_per_channel());
// Copy samples into destination buffer
block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length);
{
// Save waveform to file
Block* src_block = static_cast<Block*>(copy_map_->key(b));
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
QDir waveform_loc = local_appdata_dir.filePath(QStringLiteral("waveform"));
waveform_loc.mkpath(".");
QString wave_fn(waveform_loc.filePath(QString::number(reinterpret_cast<quintptr>(src_block))));
QFile wave_file(wave_fn);
if (wave_file.open(QFile::ReadWrite)) {
// We use S32 as a size-compatible substitute for SampleSummer::Sum which is 4 bytes in size
AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32);
int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate());
{
// Write metadata header
SampleSummer::Info info;
info.channels = audio_params_.channel_count();
wave_file.write(reinterpret_cast<char*>(&info), sizeof(SampleSummer::Info));
}
qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in());
qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length());
qint64 end_offset = start_offset + length_offset;
if (wave_file.size() < end_offset) {
wave_file.resize(end_offset);
}
wave_file.seek(start_offset);
for (int i=0;i<samples_from_this_block->sample_count_per_channel();i+=chunk_size) {
QVector<SampleSummer::Sum> summary = SampleSummer::SumSamples(samples_from_this_block,
i,
qMin(chunk_size, samples_from_this_block->sample_count_per_channel() - i));
wave_file.write(reinterpret_cast<const char*>(summary.constData()),
summary.size() * sizeof(SampleSummer::Sum));
}
wave_file.close();
if (src_block->type() == Block::kClip) {
emit static_cast<ClipBlock*>(src_block)->PreviewUpdated();
}
}
}
NodeValueTable::Merge({merged_table, table});
}
merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer));
return merged_table;
}
void AudioRenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table)
{
if (stream->type() != Stream::kAudio) {
return;
}
NodeValue value = GetDataFromStream(stream, input_time);
table->Push(value);
}
const AudioRenderingParams &AudioRenderWorker::audio_params() const
{
return audio_params_;
}
OLIVE_NAMESPACE_EXIT
-59
View File
@@ -1,59 +0,0 @@
/***
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/>.
***/
#ifndef AUDIORENDERWORKER_H
#define AUDIORENDERWORKER_H
#include "renderworker.h"
OLIVE_NAMESPACE_ENTER
class AudioRenderWorker : public RenderWorker
{
Q_OBJECT
public:
AudioRenderWorker(QHash<Node*, Node*>* copy_map, QObject* parent = nullptr);
void SetParameters(const AudioRenderingParams& audio_params);
signals:
void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params);
protected:
virtual bool InitInternal() override;
virtual void CloseInternal() override;
virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range) override;
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override;
const AudioRenderingParams& audio_params() const;
private:
AudioRenderingParams audio_params_;
QHash<Node*, Node*>* copy_map_;
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIORENDERWORKER_H
+26 -43
View File
@@ -22,7 +22,6 @@
#include <QtConcurrent/QtConcurrent>
#include "render/backend/audio/audiobackend.h"
#include "render/backend/opengl/openglbackend.h"
#include "render/colormanager.h"
#include "render/pixelformat.h"
@@ -36,8 +35,7 @@ Exporter::Exporter(ViewerOutput *viewer_node,
QObject(parent),
viewer_node_(viewer_node),
params_(params),
video_backend_(nullptr),
audio_backend_(nullptr),
renderer_(nullptr),
export_status_(false),
export_msg_(tr("Export hasn't started yet"))
{
@@ -54,7 +52,7 @@ Exporter::Exporter(ViewerOutput *viewer_node,
if (params_.has_custom_range()) {
export_range_ = params_.custom_range();
} else {
export_range_ = TimeRange(0, viewer_node_->Length());
export_range_ = TimeRange(0, viewer_node_->GetLength());
}
if (params_.video_enabled()) {
@@ -87,18 +85,10 @@ const QString &Exporter::GetExportError() const
void Exporter::Cancel()
{
if (video_backend_) {
video_backend_->CancelQueue();
video_backend_->Close();
video_backend_->deleteLater();
video_backend_ = nullptr;
}
if (audio_backend_) {
audio_backend_->CancelQueue();
audio_backend_->Close();
audio_backend_->deleteLater();
audio_backend_ = nullptr;
if (renderer_) {
renderer_->CancelQueue();
renderer_->deleteLater();
renderer_ = nullptr;
}
SetExportMessage(tr("User cancelled export"));
@@ -111,25 +101,18 @@ void Exporter::StartExporting()
export_status_ = false;
// Create renderers
if (!video_done_) {
video_backend_ = new OpenGLBackend();
renderer_ = new OpenGLBackend();
renderer_->SetViewerNode(viewer_node_);
video_backend_->SetLimitCaching(false);
video_backend_->SetViewerNode(viewer_node_);
video_backend_->SetParameters(VideoRenderingParams(viewer_node_->video_params().width(),
viewer_node_->video_params().height(),
params_.video_params().time_base(),
params_.video_params().format(),
params_.video_params().mode()));
if (!video_done_) {
renderer_->SetPixelFormat(params_.video_params().format());
renderer_->SetMode(params_.video_params().mode());
waiting_for_frame_ = 0;
}
if (!audio_done_) {
audio_backend_ = new AudioBackend();
audio_backend_->SetViewerNode(viewer_node_);
audio_backend_->SetParameters(params_.audio_params());
renderer_->SetSampleFormat(params_.audio_params().format());
}
// Open encoder and wait for result
@@ -153,10 +136,9 @@ void Exporter::ExportSucceeded()
return;
}
if (video_backend_) {
video_backend_->Close();
video_backend_->deleteLater();
video_backend_ = nullptr;
if (renderer_) {
renderer_->deleteLater();
renderer_ = nullptr;
}
export_status_ = true;
@@ -189,10 +171,10 @@ void Exporter::EncodeFrame()
waiting_for_frame_ += params_.video_params().time_base();
// Calculate progress
emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->Length().toDouble());
emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->GetLength().toDouble());
}
if (waiting_for_frame_ >= viewer_node_->Length()) {
if (waiting_for_frame_ >= viewer_node_->GetLength()) {
video_done_ = true;
debug_timer_.stop();
@@ -261,6 +243,7 @@ void Exporter::FrameRendered(FramePtr frame)
void Exporter::AudioRendered()
{
/*
// Retrieve the audio filename
QString cache_fn = audio_backend_->CachePathName();
@@ -270,11 +253,7 @@ void Exporter::AudioRendered()
OLIVE_NS_ARG(AudioRenderingParams, audio_backend_->params()),
Q_ARG(const QString&, cache_fn),
OLIVE_NS_ARG(TimeRange, export_range_));
// We don't need the audio backend anymorea
audio_backend_->Close();
audio_backend_->deleteLater();
audio_backend_ = nullptr;
*/
}
void Exporter::AudioEncodeComplete()
@@ -286,6 +265,7 @@ void Exporter::AudioEncodeComplete()
void Exporter::EncoderOpenedSuccessfully()
{
/*
// Invalidate caches
if (!video_done_) {
// First we generate the hashes so we know exactly how many frames we need
@@ -301,6 +281,7 @@ void Exporter::EncoderOpenedSuccessfully()
audio_backend_->InvalidateCache(export_range_, nullptr);
}
*/
}
void Exporter::EncoderOpenFailed()
@@ -317,12 +298,13 @@ void Exporter::EncoderClosed()
void Exporter::VideoHashesComplete()
{
/*
// We've got our hashes, time to kick off actual rendering
disconnect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
// Determine what frames will be hashed
TimeRangeList ranges;
ranges.append(TimeRange(0, viewer_node_->Length()));
ranges.append(TimeRange(0, viewer_node_->GetLength()));
// Set video backend to render mode but NOT hash or download
video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly);
@@ -347,6 +329,7 @@ void Exporter::VideoHashesComplete()
foreach (const TimeRange& range, ranges) {
video_backend_->InvalidateCache(range, nullptr);
}
*/
}
void Exporter::DebugTimerMessage()
@@ -356,7 +339,7 @@ void Exporter::DebugTimerMessage()
void Exporter::FrameColorFinished()
{
if (!video_backend_ && !audio_backend_) {
if (!renderer_) {
return;
}
@@ -366,7 +349,7 @@ void Exporter::FrameColorFinished()
debug_timer_.stop();
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
const QMap<rational, QByteArray>& time_hash_map = viewer_node_->video_frame_cache()->time_hash_map();
QByteArray this_hash = time_hash_map.value(frame->timestamp());
+2 -4
View File
@@ -28,9 +28,8 @@
#include "codec/encoder.h"
#include "node/output/viewer/viewer.h"
#include "render/backend/audiorenderbackend.h"
#include "render/backend/exportparams.h"
#include "render/backend/videorenderbackend.h"
#include "render/backend/renderbackend.h"
#include "render/colorprocessor.h"
OLIVE_NAMESPACE_ENTER
@@ -76,8 +75,7 @@ private:
ExportParams params_;
// Renderers
VideoRenderBackend* video_backend_;
AudioRenderBackend* audio_backend_;
RenderBackend* renderer_;
// Export transform
QMatrix4x4 transform_;
-2
View File
@@ -33,7 +33,5 @@ set(OLIVE_SOURCES
render/backend/opengl/opengltexture.cpp
render/backend/opengl/opengltexturecache.h
render/backend/opengl/opengltexturecache.cpp
render/backend/opengl/openglworker.h
render/backend/opengl/openglworker.cpp
PARENT_SCOPE
)
+2 -67
View File
@@ -20,77 +20,12 @@
#include "openglbackend.h"
#include <QEventLoop>
#include <QThread>
#include "openglrenderfunctions.h"
OLIVE_NAMESPACE_ENTER
OpenGLBackend::OpenGLBackend(QObject *parent) :
VideoRenderBackend(parent),
proxy_(nullptr)
OpenGLBackend::OpenGLBackend(QObject* parent) :
RenderBackend(parent)
{
}
OpenGLBackend::~OpenGLBackend()
{
Close();
}
bool OpenGLBackend::InitInternal()
{
if (!VideoRenderBackend::InitInternal()) {
return false;
}
proxy_ = new OpenGLProxy();
proxy_->SetParameters(params());
QThread* proxy_thread = new QThread();
proxy_thread->start(QThread::IdlePriority);
proxy_->moveToThread(proxy_thread);
if (!proxy_->Init()) {
proxy_thread->quit();
proxy_thread->wait();
proxy_thread->deleteLater();
proxy_->deleteLater();
proxy_ = nullptr;
return false;
}
// Initiate one thread per CPU core
for (int i=0;i<threads().size();i++) {
// Create one processor object for each thread
OpenGLWorker* processor = new OpenGLWorker(frame_cache(), proxy_);
processor->SetParameters(params());
processors_.append(processor);
}
return true;
}
void OpenGLBackend::CloseInternal()
{
if (proxy_) {
proxy_->thread()->quit();
proxy_->thread()->wait();
proxy_->thread()->deleteLater();
proxy_->deleteLater();
proxy_ = nullptr;
}
VideoRenderBackend::CloseInternal();
}
void OpenGLBackend::ParamsChangedEvent()
{
// If we're initiated, we need to recreate the texture. Otherwise this backend isn't active so it doesn't matter.
if (IsInitiated()) {
proxy_->SetParameters(params());
}
}
OLIVE_NAMESPACE_EXIT
+5 -16
View File
@@ -21,32 +21,21 @@
#ifndef OPENGLBACKEND_H
#define OPENGLBACKEND_H
#include "../videorenderbackend.h"
#include "openglframebuffer.h"
#include "openglproxy.h"
#include "openglshader.h"
#include "opengltexture.h"
#include "openglworker.h"
#include "render/backend/renderbackend.h"
OLIVE_NAMESPACE_ENTER
class OpenGLBackend : public VideoRenderBackend
class OpenGLBackend : public RenderBackend
{
Q_OBJECT
public:
OpenGLBackend(QObject* parent = nullptr);
virtual ~OpenGLBackend() override;
protected:
virtual bool InitInternal() override;
virtual void TextureToFrame(const QVariant& texture, FramePtr frame) const override;
virtual void CloseInternal() override;
virtual QVariant FrameToTexture(FramePtr frame) const override;
virtual void ParamsChangedEvent() override;
private:
OpenGLProxy* proxy_;
virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) const override;
};
@@ -22,6 +22,7 @@
#define OPENGLCOLORPROCESSOR_H
#include "openglshader.h"
#include "render/backend/rendercache.h"
#include "render/colorprocessor.h"
OLIVE_NAMESPACE_ENTER
@@ -62,6 +63,8 @@ private slots:
};
using OpenGLColorProcessorCache = RenderCache<QString, OpenGLColorProcessorPtr>;
OLIVE_NAMESPACE_EXIT
#endif // OPENGLCOLORPROCESSOR_H
+4 -2
View File
@@ -24,7 +24,9 @@
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include "../videorenderworker.h"
#include "common/timerange.h"
#include "node/value.h"
#include "openglcolorprocessor.h"
#include "openglframebuffer.h"
#include "openglshadercache.h"
#include "opengltexturecache.h"
@@ -81,7 +83,7 @@ private:
OpenGLFramebuffer buffer_;
ColorProcessorCache color_cache_;
OpenGLColorProcessorCache color_cache_;
VideoRenderingParams video_params_;
@@ -1,84 +0,0 @@
/***
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 "openglworker.h"
#include "common/clamp.h"
#include "core.h"
#include "node/block/transition/transition.h"
#include "node/node.h"
#include "openglcolorprocessor.h"
#include "openglrenderfunctions.h"
#include "render/colormanager.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, OpenGLProxy *proxy, QObject *parent) :
VideoRenderWorker(frame_cache, parent),
proxy_(proxy)
{
}
NodeValue OpenGLWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range)
{
FramePtr frame = decoder->RetrieveVideo(range.in(),
video_params().divider(),
video_params().mode() == RenderMode::kOffline);
NodeValue value;
if (frame) {
QMetaObject::invokeMethod(proxy_,
"FrameToValue",
Qt::BlockingQueuedConnection,
OLIVE_NS_RETURN_ARG(NodeValue, value),
OLIVE_NS_ARG(FramePtr, frame),
OLIVE_NS_ARG(StreamPtr, stream));
}
return value;
}
void OpenGLWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params)
{
QMetaObject::invokeMethod(proxy_,
"RunNodeAccelerated",
Qt::BlockingQueuedConnection,
OLIVE_NS_CONST_ARG(Node*, node),
OLIVE_NS_CONST_ARG(TimeRange&, range),
OLIVE_NS_ARG(NodeValueDatabase&, input_params),
OLIVE_NS_ARG(NodeValueTable&, output_params));
}
void OpenGLWorker::TextureToBuffer(const QVariant &tex_in, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize)
{
QMetaObject::invokeMethod(proxy_,
"TextureToBuffer",
Qt::BlockingQueuedConnection,
Q_ARG(const QVariant&, tex_in),
Q_ARG(int, width),
Q_ARG(int, height),
Q_ARG(const QMatrix4x4&, matrix),
Q_ARG(void*, buffer),
Q_ARG(int, linesize));
}
OLIVE_NAMESPACE_EXIT
-56
View File
@@ -1,56 +0,0 @@
/***
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/>.
***/
#ifndef OPENGLPROCESSOR_H
#define OPENGLPROCESSOR_H
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include "../videorenderworker.h"
#include "openglframebuffer.h"
#include "openglproxy.h"
#include "openglshadercache.h"
#include "opengltexturecache.h"
OLIVE_NAMESPACE_ENTER
class OpenGLWorker : public VideoRenderWorker {
Q_OBJECT
public:
OpenGLWorker(VideoRenderFrameCache* frame_cache,
OpenGLProxy* proxy,
QObject* parent = nullptr);
protected:
virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) override;
virtual void RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable& output_params) override;
virtual void TextureToBuffer(const QVariant& texture, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize) override;
private:
OpenGLProxy* proxy_;
};
OLIVE_NAMESPACE_EXIT
#endif // OPENGLPROCESSOR_H
+342 -331
View File
@@ -23,6 +23,7 @@
#include <QDateTime>
#include <QThread>
#include "config/config.h"
#include "core.h"
#include "window/mainwindow/mainwindow.h"
@@ -30,331 +31,409 @@ OLIVE_NAMESPACE_ENTER
RenderBackend::RenderBackend(QObject *parent) :
QObject(parent),
started_(false),
viewer_node_(nullptr),
copied_viewer_node_(nullptr)
divider_(1),
render_mode_(RenderMode::kOnline),
pix_fmt_(PixelFormat::PIX_FMT_RGBA32F),
sample_fmt_(SampleFormat::SAMPLE_FMT_FLT)
{
// FIXME: Don't create in CLI mode
cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window());
}
bool RenderBackend::Init()
void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
{
if (started_) {
return true;
}
threads_.resize(QThread::idealThreadCount());
for (int i=0;i<threads_.size();i++) {
QThread* thread = new QThread(this);
threads_.replace(i, thread);
// We use low priority to keep the app responsive at all times (GUI thread should always prioritize over this one)
thread->start(QThread::IdlePriority);
}
cancel_dialog_->SetWorkerCount(threads_.size());
started_ = InitInternal();
// Connects workers and moves them to their respective threads
InitWorkers();
if (!started_) {
Close();
}
return started_;
}
void RenderBackend::Close()
{
if (!started_) {
if (viewer_node_ == viewer_node) {
return;
}
started_ = false;
CancelQueue();
SetViewerNode(nullptr);
CloseInternal();
for (int i=0;i<processors_.size();i++) {
// Invoke close and quit signals on processor and thread
QMetaObject::invokeMethod(processors_.at(i),
"Close",
Qt::QueuedConnection);
threads_.at(i)->quit();
}
for (int i=0;i<processors_.size();i++) {
threads_.at(i)->wait(); // FIXME: Maximum time in case a thread is stuck?
delete threads_.at(i);
delete processors_.at(i);
}
threads_.clear();
processors_.clear();
}
const QString &RenderBackend::GetError() const
{
return error_;
}
void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
{
if (viewer_node_) {
// Clear queue and wait for any currently running actions to complete
CancelQueue();
DisconnectViewer(viewer_node_);
// Delete all of our copied nodes
video_copy_map_.Clear();
audio_copy_map_.Clear();
copied_graph_.Clear();
copied_viewer_node_ = nullptr;
node_copy_map_.clear();
disconnect(viewer_node_,
&ViewerOutput::GraphChangedFrom,
this,
&RenderBackend::NodeGraphChanged);
disconnect(viewer_node_->audio_playback_cache(),
&AudioPlaybackCache::Invalidated,
this,
&RenderBackend::AudioCallback);
}
// Set viewer node
viewer_node_ = viewer_node;
if (viewer_node_) {
ConnectViewer(viewer_node_);
// Start copying viewer
video_copy_map_.Init(viewer_node_);
audio_copy_map_.Init(viewer_node_);
RegenerateCacheID();
video_copy_map_.Queue(viewer_node_->texture_input());
audio_copy_map_.Queue(viewer_node_->samples_input());
copied_viewer_node_ = static_cast<ViewerOutput*>(viewer_node_->copy());
copied_graph_.AddNode(copied_viewer_node_);
node_copy_map_.insert(viewer_node_, copied_viewer_node_);
video_copy_map_.ProcessQueue();
audio_copy_map_.ProcessQueue();
InvalidateCache(TimeRange(0, RATIONAL_MAX),
static_cast<NodeInput*>(viewer_node_->GetInputWithID(GetDependentInput()->id())));
connect(viewer_node_,
&ViewerOutput::GraphChangedFrom,
this,
&RenderBackend::NodeGraphChanged);
connect(viewer_node_->audio_playback_cache(),
&AudioPlaybackCache::Invalidated,
this,
&RenderBackend::AudioCallback);
}
}
bool RenderBackend::IsInitiated()
{
return started_;
}
void RenderBackend::RegenerateCacheID()
{
QCryptographicHash hash(QCryptographicHash::Sha1);
if (!viewer_node_
|| !GenerateCacheIDInternal(hash)) {
cache_id_.clear();
CacheIDChangedEvent(QString());
return;
}
hash.addData(viewer_node_->uuid().toByteArray());
QByteArray bytes = hash.result();
cache_id_ = bytes.toHex();
CacheIDChangedEvent(cache_id_);
}
bool RenderBackend::InitInternal()
{
return true;
}
void RenderBackend::CloseInternal()
{
}
bool RenderBackend::CanRender()
{
return true;
}
TimeRange RenderBackend::PopNextFrameFromQueue()
{
return cache_queue_.takeFirst();
}
rational RenderBackend::GetSequenceLength()
{
if (viewer_node_ == nullptr) {
return 0;
}
return viewer_node_->Length();
}
void RenderBackend::SetError(const QString &error)
{
error_ = error;
}
void RenderBackend::ConnectViewer(ViewerOutput *node)
{
Q_UNUSED(node)
}
void RenderBackend::DisconnectViewer(ViewerOutput *node)
{
Q_UNUSED(node)
}
void RenderBackend::CacheNext()
{
if (cache_queue_.isEmpty()) {
if (AllProcessorsAreAvailable()) {
emit QueueComplete();
}
return;
}
if (!ViewerIsConnected()
|| !CanRender()
|| !Init()) {
return;
}
while (!input_update_queued_.isEmpty()) {
if (!AllProcessorsAreAvailable()) {
// To update the inputs, we need all workers to stop
return;
}
CopyNodeInputValue(input_update_queued_.takeFirst());
}
Node* node_connected_to_viewer = GetDependentInput()->get_connected_node();
if (!node_connected_to_viewer) {
return;
}
foreach (RenderWorker* worker, processors_) {
if (cache_queue_.isEmpty()) {
break;
}
if (!WorkerIsBusy(worker)) {
TimeRange cache_frame = PopNextFrameFromQueue();
NodeDependency dep = NodeDependency(node_connected_to_viewer,
cache_frame);
// Timestamp this render job
qint64 job_time = QDateTime::currentMSecsSinceEpoch();
// Ensure the job's time is unique (since that's the whole point)
// NOTE: This value will be 0 if it doesn't exist, which will never be the result of currentMSecsSinceEpoch so we
// can safely assume 0 means it doesn't exist.
qint64 existing_job_time = render_job_info_.value(cache_frame);
if (existing_job_time == job_time) {
job_time = existing_job_time + 1;
}
render_job_info_.insert(cache_frame, job_time);
SetWorkerBusyState(worker, true);
cancel_dialog_->WorkerStarted();
WorkerAboutToStartEvent(worker);
QMetaObject::invokeMethod(worker,
"Render",
Qt::QueuedConnection,
OLIVE_NS_ARG(NodeDependency, dep),
Q_ARG(qint64, job_time));
}
}
}
ViewerOutput *RenderBackend::viewer_node() const
{
return copied_viewer_node_;
}
void RenderBackend::CancelQueue()
{
cache_queue_.clear();
// FIXME: Implement something better than this...
video_copy_map_.thread_pool()->waitForDone();
audio_copy_map_.thread_pool()->waitForDone();
}
int busy = 0;
for (int i=0;i<processor_busy_state_.size();i++) {
if (processor_busy_state_.at(i))
busy++;
QByteArray HashInternal(Node *node,
const VideoRenderingParams &params,
const rational &time)
{
QCryptographicHash hasher(QCryptographicHash::Sha1);
// Embed video parameters into this hash
hasher.addData(reinterpret_cast<const char*>(&params.effective_width()), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&params.effective_height()), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&params.format()), sizeof(PixelFormat::Format));
hasher.addData(reinterpret_cast<const char*>(&params.mode()), sizeof(RenderMode::Mode));
node->Hash(hasher, time);
return hasher.result();
}
QFuture<QByteArray> RenderBackend::Hash(const rational &time)
{
if (!viewer_node_) {
return QFuture<QByteArray>();
}
if (busy) {
qDebug() << this << "is waiting for" << busy << "busy workers";
return QtConcurrent::run(video_copy_map_.thread_pool(),
HashInternal,
viewer_node_,
video_params(),
time);
}
QFuture<FramePtr> RenderBackend::RenderFrame(const rational &time)
{
if (!viewer_node_) {
return QFuture<FramePtr>();
}
cancel_dialog_->RunIfWorkersAreBusy();
return QtConcurrent::run(video_copy_map_.thread_pool(),
this,
&RenderBackend::RenderFrameInternal,
time);
}
void RenderBackend::InvalidateCache(const TimeRange &range, NodeInput *from)
void RenderBackend::SetDivider(const int &divider)
{
// Adjust range to min/max values
rational start_range_adj = qMax(rational(0), range.in());
rational end_range_adj = qMin(GetSequenceLength(), range.out());
divider_ = divider;
}
qDebug() << "Cache invalidated between"
<< start_range_adj.toDouble()
<< "and"
<< end_range_adj.toDouble();
void RenderBackend::SetMode(const RenderMode::Mode &mode)
{
render_mode_ = mode;
}
if (from) {
// Queue value update
qDebug() << " from" << from->parentNode()->id() << "::" << from->id();
QueueValueUpdate(from);
void RenderBackend::SetPixelFormat(const PixelFormat::Format &pix_fmt)
{
pix_fmt_ = pix_fmt;
}
void RenderBackend::SetSampleFormat(const SampleFormat::Format &sample_fmt)
{
sample_fmt_ = sample_fmt;
}
void RenderBackend::NodeGraphChanged(NodeInput *from, NodeInput *source)
{
if (from == viewer_node_->texture_input()) {
video_copy_map_.Queue(source);
} else if (from == viewer_node_->samples_input()) {
audio_copy_map_.Queue(source);
}
}
void RenderBackend::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table) const
{
if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) {
ImageStreamPtr video_stream = std::static_pointer_cast<ImageStream>(stream);
rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in();
QString colorspace_match = video_stream->get_colorspace_match_string();
NodeValue value;
bool found_cache = false;
if (still_image_cache_.Has(stream.get())) {
CachedStill cs = still_image_cache_.Get(stream.get());
if (cs.colorspace == colorspace_match
&& cs.alpha_is_associated == video_stream->premultiplied_alpha()
&& cs.divider == video_params_.divider()
&& cs.time == time_match) {
value = cs.texture;
found_cache = true;
} else {
still_image_cache_.Remove(stream.get());
}
}
if (!found_cache) {
value = GetDataFromStream(stream, input_time);
still_image_cache_.Add(stream.get(), {value,
colorspace_match,
video_stream->premultiplied_alpha(),
video_params_.divider(),
time_match});
}
table->Push(value);
} else if (stream->type() != Stream::kAudio) {
NodeValue value = GetDataFromStream(stream, input_time);
table->Push(value);
}
}
NodeValueTable RenderBackend::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const
{
if (track->track_type() == Timeline::kTrackTypeAudio) {
QList<Block*> active_blocks = track->BlocksAtTimeRange(range);
// All these blocks will need to output to a buffer so we create one here
SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params_,
audio_params_.time_to_samples(range.length()));
block_range_buffer->fill(0);
NodeValueTable merged_table;
// Loop through active blocks retrieving their audio
foreach (Block* b, active_blocks) {
TimeRange range_for_block(qMax(b->in(), range.in()),
qMin(b->out(), range.out()));
int destination_offset = audio_params_.time_to_samples(range_for_block.in() - range.in());
int max_dest_sz = audio_params_.time_to_samples(range_for_block.length());
// Destination buffer
NodeValueTable table = ProcessNode(NodeDependency(b, range_for_block));
QVariant sample_val = table.Take(NodeParam::kSamples);
SampleBufferPtr samples_from_this_block;
if (sample_val.isNull()
|| !(samples_from_this_block = sample_val.value<SampleBufferPtr>())) {
// If we retrieved no samples from this block, do nothing
continue;
}
// Stretch samples here
rational abs_speed = qAbs(b->speed());
if (abs_speed != 1) {
samples_from_this_block->speed(abs_speed.toDouble());
}
if (b->is_reversed()) {
// Reverse the audio buffer
samples_from_this_block->reverse();
}
int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count_per_channel());
// Copy samples into destination buffer
block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length);
{
// Save waveform to file
Block* src_block = static_cast<Block*>(copy_map_->key(b));
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
QDir waveform_loc = local_appdata_dir.filePath(QStringLiteral("waveform"));
waveform_loc.mkpath(".");
QString wave_fn(waveform_loc.filePath(QString::number(reinterpret_cast<quintptr>(src_block))));
QFile wave_file(wave_fn);
if (wave_file.open(QFile::ReadWrite)) {
// We use S32 as a size-compatible substitute for SampleSummer::Sum which is 4 bytes in size
AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32);
int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate());
{
// Write metadata header
SampleSummer::Info info;
info.channels = audio_params_.channel_count();
wave_file.write(reinterpret_cast<char*>(&info), sizeof(SampleSummer::Info));
}
qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in());
qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length());
qint64 end_offset = start_offset + length_offset;
if (wave_file.size() < end_offset) {
wave_file.resize(end_offset);
}
wave_file.seek(start_offset);
for (int i=0;i<samples_from_this_block->sample_count_per_channel();i+=chunk_size) {
QVector<SampleSummer::Sum> summary = SampleSummer::SumSamples(samples_from_this_block,
i,
qMin(chunk_size, samples_from_this_block->sample_count_per_channel() - i));
wave_file.write(reinterpret_cast<const char*>(summary.constData()),
summary.size() * sizeof(SampleSummer::Sum));
}
wave_file.close();
if (src_block->type() == Block::kClip) {
emit static_cast<ClipBlock*>(src_block)->PreviewUpdated();
}
}
}
NodeValueTable::Merge({merged_table, table});
}
merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer));
return merged_table;
} else {
return NodeTraverser::GenerateBlockTable(track, range);
}
}
FramePtr RenderBackend::RenderFrameInternal(const rational &time) const
{
NodeValueTable table = GenerateTable(viewer_node_,
TimeRange(time, time + viewer_node_->video_params().time_base()));
QVariant texture = table.Get(NodeParam::kTexture);
FramePtr frame = Frame::Create();
frame->set_video_params(video_params());
frame->allocate();
if (texture.isNull()) {
memset(frame->data(), 0, frame->allocated_size());
} else {
TextureToFrame(texture, frame);
}
InvalidateCacheInternal(start_range_adj, end_range_adj);
return frame;
}
bool RenderBackend::ViewerIsConnected() const
VideoRenderingParams RenderBackend::video_params() const
{
return viewer_node_;
return VideoRenderingParams(viewer_node_->video_params(),
pix_fmt_,
render_mode_,
divider_);
}
const QString &RenderBackend::cache_id() const
void RenderBackend::AudioCallback()
{
return cache_id_;
qDebug() << "STUB";
/*
AudioPlaybackCache* pb_cache = viewer_node_->audio_playback_cache();
QThreadPool* thread_pool = audio_copy_map_.thread_pool();
while (!pb_cache->IsFullyValidated()
&& thread_pool->activeThreadCount() < thread_pool->maxThreadCount()) {
// FIXME: Trigger a background audio thread
TimeRange r = viewer_node_->audio_playback_cache()->GetInvalidatedRanges().first();
qDebug() << "FIXME: Start rendering audio at:" << r;
break;
}
*/
}
void RenderBackend::QueueValueUpdate(NodeInput* from)
bool RenderBackend::ConformWaitInfo::operator==(const RenderBackend::ConformWaitInfo &rhs) const
{
if (!input_update_queued_.isEmpty()) {
return rhs.stream == stream
&& rhs.stream_time == stream_time
&& rhs.affected_range == affected_range;
}
RenderBackend::CopyMap::CopyMap() :
original_viewer_(nullptr),
copied_viewer_(nullptr)
{
}
void RenderBackend::CopyMap::Init(ViewerOutput *viewer)
{
original_viewer_ = viewer;
copied_viewer_ = static_cast<ViewerOutput*>(original_viewer_->copy());
copy_map_.insert(original_viewer_, copied_viewer_);
}
void RenderBackend::CopyMap::Queue(NodeInput *input)
{
if (!queued_updates_.isEmpty()) {
// Remove any inputs that are dependents of this input since they may have been removed since
// it was queued
QList<Node*> deps = from->GetDependencies();
QList<Node*> deps = input->GetDependencies();
for (int i=0;i<input_update_queued_.size();i++) {
if (deps.contains(input_update_queued_.at(i)->parentNode())) {
for (int i=0;i<queued_updates_.size();i++) {
if (deps.contains(queued_updates_.at(i)->parentNode())) {
// We don't need to queue this value since this input supersedes it
input_update_queued_.removeAt(i);
queued_updates_.removeAt(i);
i--;
}
}
}
input_update_queued_.append(from);
queued_updates_.append(input);
}
bool RenderBackend::WorkerIsBusy(RenderWorker *worker) const
void RenderBackend::CopyMap::ProcessQueue()
{
return processor_busy_state_.at(processors_.indexOf(worker));
while (!queued_updates_.isEmpty()) {
CopyNodeInputValue(queued_updates_.takeFirst());
}
}
void RenderBackend::SetWorkerBusyState(RenderWorker *worker, bool busy)
void RenderBackend::CopyMap::Clear()
{
processor_busy_state_.replace(processors_.indexOf(worker), busy);
qDeleteAll(copy_map_);
copy_map_.clear();
original_viewer_ = nullptr;
copied_viewer_ = nullptr;
}
void RenderBackend::CopyNodeInputValue(NodeInput *input)
void RenderBackend::CopyMap::CopyNodeInputValue(NodeInput *input)
{
// Find our copy of this parameter
Node* our_copy_node = node_copy_map_.value(input->parentNode());
Node* our_copy_node = copy_map_.value(input->parentNode());
NodeInput* our_copy = our_copy_node->GetInputWithID(input->id());
// Copy the standard/keyframe values between these two inputs
@@ -372,8 +451,7 @@ void RenderBackend::CopyNodeInputValue(NodeInput *input)
QList<Node*> old_deps = our_copy->GetExclusiveDependencies();
foreach (Node* i, old_deps) {
Node* n = node_copy_map_.take(node_copy_map_.key(i));
copied_graph_.TakeNode(n);
Node* n = copy_map_.take(copy_map_.key(i));
delete n;
}
@@ -395,16 +473,15 @@ void RenderBackend::CopyNodeInputValue(NodeInput *input)
}
}
Node* RenderBackend::CopyNodeConnections(Node* src_node)
Node* RenderBackend::CopyMap::CopyNodeConnections(Node* src_node)
{
// Check if this node is already in the map
Node* dst_node = node_copy_map_.value(src_node);
Node* dst_node = copy_map_.value(src_node);
// If not, create it now
if (!dst_node) {
dst_node = src_node->copy();
copied_graph_.AddNode(dst_node);
node_copy_map_.insert(src_node, dst_node);
copy_map_.insert(src_node, dst_node);
}
// Make sure its values are copied
@@ -423,10 +500,8 @@ Node* RenderBackend::CopyNodeConnections(Node* src_node)
return dst_node;
}
void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input)
void RenderBackend::CopyMap::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input)
{
//qDebug() << "Copying input" << src_input->id() << "from" << src_input->parentNode()->id();
if (src_input->IsConnected()) {
Node* dst_node = CopyNodeConnections(src_input->get_connected_node());
@@ -437,68 +512,4 @@ void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_
}
}
bool RenderBackend::AllProcessorsAreAvailable() const
{
foreach (bool busy, processor_busy_state_) {
if (busy) {
return false;
}
}
return true;
}
const QVector<QThread *> &RenderBackend::threads()
{
return threads_;
}
void RenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range)
{
// Add the range to the list
cache_queue_.InsertTimeRange(TimeRange(start_range, end_range));
CacheNext();
}
void RenderBackend::CacheIDChangedEvent(const QString &id)
{
Q_UNUSED(id)
}
void RenderBackend::WorkerAboutToStartEvent(RenderWorker *worker)
{
Q_UNUSED(worker)
}
void RenderBackend::InitWorkers()
{
for (int i=0;i<processors_.size();i++) {
RenderWorker* processor = processors_.at(i);
QThread* thread = threads().at(i);
// Connect to it
ConnectWorkerToThis(processor);
// Connect cancel dialog to it
connect(processor, &RenderWorker::CompletedCache, cancel_dialog_, &RenderCancelDialog::WorkerDone, Qt::QueuedConnection);
// Finally, we can move it to its own thread
processor->moveToThread(thread);
// This function blocks the main thread intentionally. See the documentation for this function to see why.
processor->Init();
}
processor_busy_state_.resize(processors_.size());
processor_busy_state_.fill(false);
}
bool RenderBackend::FootageWaitInfo::operator==(const RenderBackend::FootageWaitInfo &rhs) const
{
return rhs.stream == stream
&& rhs.stream_time == stream_time
&& rhs.affected_range == affected_range;
}
OLIVE_NAMESPACE_EXIT
+81 -108
View File
@@ -21,153 +21,126 @@
#ifndef RENDERBACKEND_H
#define RENDERBACKEND_H
#include <QLinkedList>
#include <QtConcurrent/QtConcurrent>
#include "dialog/rendercancel/rendercancel.h"
#include "decodercache.h"
#include "node/graph.h"
#include "node/output/viewer/viewer.h"
#include "renderworker.h"
#include "node/traverser.h"
#include "render/backend/colorprocessorcache.h"
OLIVE_NAMESPACE_ENTER
class RenderBackend : public QObject
class RenderBackend : public QObject, public NodeTraverser
{
Q_OBJECT
public:
RenderBackend(QObject* parent = nullptr);
bool Init();
void Close();
const QString& GetError() const;
void SetViewerNode(ViewerOutput* viewer_node);
bool IsInitiated();
ViewerOutput* viewer_node() const;
void CancelQueue();
public slots:
void InvalidateCache(const TimeRange &range, NodeInput *from);
QFuture<QByteArray> Hash(const rational& time);
signals:
void QueueComplete();
QFuture<FramePtr> RenderFrame(const rational& time);
void SetDivider(const int& divider);
void SetMode(const RenderMode::Mode& mode);
void SetPixelFormat(const PixelFormat::Format& pix_fmt);
void SetSampleFormat(const SampleFormat::Format& sample_fmt);
public slots:
void NodeGraphChanged(NodeInput *from, NodeInput *source);
protected:
void RegenerateCacheID();
virtual void TextureToFrame(const QVariant& texture, FramePtr frame) const = 0;
virtual bool InitInternal();
virtual QVariant FrameToTexture(FramePtr frame) const = 0;
virtual void CloseInternal();
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) const override;
virtual bool CanRender();
virtual TimeRange PopNextFrameFromQueue();
rational GetSequenceLength();
const QVector<QThread*>& threads();
/**
* @brief Internal function for generating the cache ID
*/
virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) = 0;
virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range);
virtual void CacheIDChangedEvent(const QString& id);
virtual void WorkerAboutToStartEvent(RenderWorker* worker);
void SetError(const QString& error);
virtual void ConnectViewer(ViewerOutput* node);
virtual void DisconnectViewer(ViewerOutput* node);
/**
* @brief Function called when there are frames in the queue to cache
*
* This function is NOT thread-safe and should only be called in the main thread.
*/
void CacheNext();
void InitWorkers();
virtual NodeInput* GetDependentInput() = 0;
virtual void ConnectWorkerToThis(RenderWorker* worker) = 0;
bool ViewerIsConnected() const;
const QString& cache_id() const;
void QueueValueUpdate(NodeInput *from);
bool AllProcessorsAreAvailable() const;
bool WorkerIsBusy(RenderWorker* worker) const;
void SetWorkerBusyState(RenderWorker* worker, bool busy);
TimeRangeList cache_queue_;
QVector<RenderWorker*> processors_;
QHash<TimeRange, qint64> render_job_info_;
QHash<Node*, Node*> node_copy_map_;
NodeGraph copied_graph_;
virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const override;
private:
void CopyNodeInputValue(NodeInput* input);
Node *CopyNodeConnections(Node *src_node);
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
/**
* @brief Internal list of RenderProcessThreads
*/
QVector<QThread*> threads_;
/**
* @brief Internal variable that contains whether the Renderer has started or not
*/
bool started_;
FramePtr RenderFrameInternal(const rational& time) const;
/**
* @brief Internal reference to attached viewer node
*/
ViewerOutput* viewer_node_;
/**
* @brief Internal reference to the copied viewer node we made in the compilation process
*/
ViewerOutput* copied_viewer_node_;
/**
* @brief Error string that can be set in SetError() to handle failures
*/
QString error_;
QString cache_id_;
QList<NodeInput*> input_update_queued_;
QVector<bool> processor_busy_state_;
RenderCancelDialog* cancel_dialog_;
struct FootageWaitInfo {
VideoRenderingParams video_params() const;
class CopyMap {
public:
CopyMap();
void Init(ViewerOutput* viewer);
void Queue(NodeInput* input);
void ProcessQueue();
void Clear();
QThreadPool* thread_pool() {
return &thread_pool_;
}
private:
void CopyNodeInputValue(NodeInput* input);
Node *CopyNodeConnections(Node *src_node);
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
ViewerOutput* original_viewer_;
ViewerOutput* copied_viewer_;
QList<NodeInput*> queued_updates_;
QHash<Node*, Node*> copy_map_;
QThreadPool thread_pool_;
};
// VIDEO MEMBERS
CopyMap video_copy_map_;
int divider_;
RenderMode::Mode render_mode_;
PixelFormat::Format pix_fmt_;
ColorProcessorCache color_cache_;
struct CachedStill {
NodeValue texture;
QString colorspace;
bool alpha_is_associated;
int divider;
rational time;
};
RenderCache<Stream*, CachedStill> still_image_cache_;
// AUDIO MEMBERS
CopyMap audio_copy_map_;
SampleFormat::Format sample_fmt_;
struct ConformWaitInfo {
StreamPtr stream;
TimeRange affected_range;
rational stream_time;
bool operator==(const FootageWaitInfo& rhs) const;
bool operator==(const ConformWaitInfo& rhs) const;
};
QList<FootageWaitInfo> footage_wait_info_;
QList<ConformWaitInfo> footage_wait_info_;
private slots:
void AudioCallback();
};
-128
View File
@@ -1,128 +0,0 @@
/***
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 "renderworker.h"
#include <QThread>
#include "node/block/block.h"
OLIVE_NAMESPACE_ENTER
RenderWorker::RenderWorker(QObject *parent) :
QObject(parent),
started_(false)
{
}
bool RenderWorker::Init()
{
if (started_) {
return true;
}
if (!(started_ = InitInternal())) {
Close();
}
return started_;
}
void RenderWorker::Close()
{
CloseInternal();
decoder_cache_.Clear();
started_ = false;
}
void RenderWorker::Render(NodeDependency path, qint64 job_time)
{
path_ = path;
emit CompletedCache(path, RenderInternal(path, job_time), job_time);
}
NodeValueTable RenderWorker::RenderInternal(const NodeDependency &path, const qint64 &job_time)
{
Q_UNUSED(job_time)
return ProcessNode(path);
}
void RenderWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable& output_params)
{
Q_UNUSED(node)
Q_UNUSED(range)
Q_UNUSED(input_params)
Q_UNUSED(output_params)
}
DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream)
{
// Access a map of Node inputs and decoder instances and retrieve a frame!
DecoderPtr decoder = decoder_cache_.Get(stream.get());
if (!decoder && stream) {
// Create a new Decoder here
decoder = Decoder::CreateFromID(stream->footage()->decoder());
decoder->set_stream(stream);
if (decoder->Open()) {
decoder_cache_.Add(stream.get(), decoder);
} else {
decoder = nullptr;
qWarning() << "Failed to open decoder for" << stream->footage()->filename() << "::" << stream->index();
}
}
return decoder;
}
NodeValue RenderWorker::GetDataFromStream(StreamPtr stream, const TimeRange &input_time)
{
DecoderPtr decoder = ResolveDecoderFromInput(stream);
if (decoder) {
return FrameToValue(decoder, stream, input_time);
}
return NodeValue();
}
bool RenderWorker::IsStarted()
{
return started_;
}
void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params)
{
// Check if we have a shader for this output
RunNodeAccelerated(node, range, input_params, output_params);
}
const NodeDependency &RenderWorker::CurrentPath() const
{
return path_;
}
OLIVE_NAMESPACE_EXIT
-81
View File
@@ -1,81 +0,0 @@
/***
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/>.
***/
#ifndef RENDERWORKER_H
#define RENDERWORKER_H
#include <QObject>
#include "decodercache.h"
#include "node/node.h"
#include "node/output/track/track.h"
#include "node/traverser.h"
OLIVE_NAMESPACE_ENTER
class RenderWorker : public QObject, public NodeTraverser
{
Q_OBJECT
public:
RenderWorker(QObject* parent = nullptr);
bool Init();
bool IsStarted();
public slots:
void Close();
void Render(OLIVE_NAMESPACE::NodeDependency path, qint64 job_time);
signals:
void CompletedCache(OLIVE_NAMESPACE::NodeDependency dep, OLIVE_NAMESPACE::NodeValueTable data, qint64 job_time);
protected:
virtual bool InitInternal() = 0;
virtual void CloseInternal() = 0;
virtual NodeValueTable RenderInternal(const NodeDependency& CurrentPath, const qint64& job_time);
virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, NodeValueDatabase &input_params, NodeValueTable &output_params);
virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) = 0;
virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) override;
DecoderPtr ResolveDecoderFromInput(StreamPtr stream);
NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time);
const NodeDependency& CurrentPath() const;
private:
bool started_;
DecoderCache decoder_cache_;
NodeDependency path_;
};
OLIVE_NAMESPACE_EXIT
#endif // RENDERWORKER_H
-354
View File
@@ -1,354 +0,0 @@
/***
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 "videorenderbackend.h"
#include <OpenImageIO/imageio.h>
#include <QApplication>
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QtMath>
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "render/diskmanager.h"
#include "render/diskmanager.h"
#include "render/pixelformat.h"
#include "videorenderworker.h"
OLIVE_NAMESPACE_ENTER
VideoRenderBackend::VideoRenderBackend(QObject *parent) :
RenderBackend(parent),
operating_mode_(VideoRenderWorker::kRenderOnly),
only_signal_last_frame_requested_(true),
limit_caching_(true)
{
connect(DiskManager::instance(), &DiskManager::DeletedFrame, this, &VideoRenderBackend::FrameRemovedFromDiskCache);
}
void VideoRenderBackend::ConnectViewer(ViewerOutput *node)
{
connect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache);
connect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength);
}
void VideoRenderBackend::DisconnectViewer(ViewerOutput *node)
{
disconnect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache);
disconnect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength);
frame_cache_.Clear();
}
const VideoRenderingParams &VideoRenderBackend::params() const
{
return params_;
}
void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
{
CancelQueue();
// Set new parameters
params_ = params;
// Handle custom events from derivatives
ParamsChangedEvent();
// Set params on all processors
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetParameters(params_);
}
// Regenerate the cache ID
RegenerateCacheID();
}
void VideoRenderBackend::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
{
CancelQueue();
operating_mode_ = mode;
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetOperatingMode(operating_mode_);
}
}
void VideoRenderBackend::SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix)
{
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetFrameGenerationParams(width, height, matrix);
}
}
void VideoRenderBackend::SetOnlySignalLastFrameRequested(bool enabled)
{
only_signal_last_frame_requested_ = enabled;
}
bool VideoRenderBackend::IsRendered(const rational &time) const
{
TimeRange range(time, time);
return !TimeIsQueued(range) && !render_job_info_.contains(range);
}
void VideoRenderBackend::SetLimitCaching(bool limit)
{
limit_caching_ = limit;
}
bool VideoRenderBackend::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_.width()).toUtf8());
hash.addData(QString::number(params_.height()).toUtf8());
hash.addData(QString::number(params_.format()).toUtf8());
hash.addData(QString::number(params_.divider()).toUtf8());
return true;
}
void VideoRenderBackend::CacheIDChangedEvent(const QString &id)
{
frame_cache_.SetCacheID(id);
}
void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor)
{
VideoRenderWorker* video_processor = static_cast<VideoRenderWorker*>(processor);
video_processor->SetOperatingMode(operating_mode_);
connect(video_processor, &VideoRenderWorker::HashAlreadyBeingCached, this, &VideoRenderBackend::ThreadSkippedFrame, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::CompletedDownload, this, &VideoRenderBackend::ThreadCompletedDownload, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::HashAlreadyExists, this, &VideoRenderBackend::ThreadHashAlreadyExists, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::GeneratedFrame, this, &VideoRenderBackend::GeneratedFrame, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::GeneratedFrame, this, &VideoRenderBackend::ThreadGeneratedFrame, Qt::QueuedConnection);
}
void VideoRenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range)
{
TimeRange invalidated(start_range, end_range);
if (limit_caching_) {
invalidated_.InsertTimeRange(invalidated);
} else {
RenderBackend::InvalidateCacheInternal(start_range, end_range);
}
emit RangeInvalidated(invalidated);
}
void VideoRenderBackend::RenderFrame(const rational &time)
{
RenderBackend::InvalidateCacheInternal(time, time);
}
VideoRenderFrameCache *VideoRenderBackend::frame_cache()
{
return &frame_cache_;
}
QString VideoRenderBackend::GetCachedFrame(const rational &time)
{
UpdateLastRequestedTime(time);
if (viewer_node() == nullptr) {
// Nothing is connected - nothing to show or render
return nullptr;
}
if (cache_id().isEmpty()) {
qWarning() << "No cache ID";
return nullptr;
}
if (!params_.is_valid()) {
qWarning() << "Invalid parameters";
return nullptr;
}
// Find frame in map
QByteArray frame_hash = frame_cache_.TimeToHash(time);
if (!frame_hash.isEmpty()) {
DiskManager::instance()->Accessed(frame_hash);
return frame_cache_.CachePathName(frame_hash, params_.format());
}
return QString();
}
void VideoRenderBackend::UpdateLastRequestedTime(const rational &time)
{
last_time_requested_ = time;
}
NodeInput *VideoRenderBackend::GetDependentInput()
{
return viewer_node()->texture_input();
}
bool VideoRenderBackend::CanRender()
{
return params_.is_valid();
}
TimeRange VideoRenderBackend::PopNextFrameFromQueue()
{
rational earliest_frame = RATIONAL_MAX;
// Find earliest frame in the cache queue
foreach (const TimeRange& range, cache_queue_) {
if (range.in() < earliest_frame) {
earliest_frame = range.in();
}
}
// Snap this frame to the timebase
rational snapped_frame = Timecode::snap_time_to_timebase(earliest_frame, params_.time_base());
rational next_frame;
if (snapped_frame > earliest_frame) {
next_frame = snapped_frame;
snapped_frame -= params_.time_base();
} else {
next_frame = snapped_frame + params_.time_base();
}
TimeRange frame_range(snapped_frame, next_frame);
// Remove this particular frame from the queue
cache_queue_.RemoveTimeRange(frame_range);
// Remove this particular frame from missing frames
invalidated_.RemoveTimeRange(frame_range);
// Return the snapped frame
return TimeRange(frame_range.in(), frame_range.in());
}
void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
SetFrameHash(dep, hash, job_time);
QList<rational> hashes_with_time = frame_cache()->FramesWithHash(hash);
foreach (const rational& t, hashes_with_time) {
emit CachedTimeReady(t, job_time);
}
// Queue up a new frame for this worker
CacheNext();
}
void VideoRenderBackend::ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (SetFrameHash(dep, hash, job_time)
&& frame_cache_.HasHash(hash, params_.format())) {
emit CachedTimeReady(dep.in(), job_time);
}
// Queue up a new frame for this worker
CacheNext();
}
void VideoRenderBackend::ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (SetFrameHash(dep, hash, job_time)) {
emit CachedTimeReady(dep.in(), job_time);
}
// Queue up a new frame for this worker
CacheNext();
}
void VideoRenderBackend::ThreadGeneratedFrame()
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
CacheNext();
}
void VideoRenderBackend::TruncateFrameCacheLength(const rational &length)
{
// Remove frames after this time code if it's changed
frame_cache_.Truncate(length);
invalidated_.RemoveTimeRange(TimeRange(length, RATIONAL_MAX));
// If the playhead is past the length, update the viewer to a null texture because it won't be cached through the
// queue, but will now be a null texture
if (last_time_requested_ >= length) {
emit CachedTimeReady(last_time_requested_, QDateTime::currentMSecsSinceEpoch());
}
}
void VideoRenderBackend::FrameRemovedFromDiskCache(const QByteArray &hash)
{
QList<rational> deleted_frames = frame_cache()->TakeFramesWithHash(hash);
foreach (const rational& frame, deleted_frames) {
TimeRange invalidated(frame, frame+params_.time_base());
invalidated_.InsertTimeRange(invalidated);
emit RangeInvalidated(invalidated);
}
}
bool VideoRenderBackend::TimeIsQueued(const TimeRange &time) const
{
return cache_queue_.ContainsTimeRange(time, true, false);
}
bool VideoRenderBackend::JobIsCurrent(const NodeDependency &dep, const qint64& job_time) const
{
return (render_job_info_.value(dep.range()) == job_time && !TimeIsQueued(dep.range()));
}
bool VideoRenderBackend::SetFrameHash(const NodeDependency &dep, const QByteArray &hash, const qint64& job_time)
{
if (JobIsCurrent(dep, job_time)) {
frame_cache_.SetHash(dep.in(), hash);
render_job_info_.remove(dep.range());
return true;
}
return false;
}
OLIVE_NAMESPACE_EXIT
-150
View File
@@ -1,150 +0,0 @@
/***
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/>.
***/
#ifndef VIDEORENDERERBACKEND_H
#define VIDEORENDERERBACKEND_H
#include <QLinkedList>
#include "colorprocessorcache.h"
#include "node/output/viewer/viewer.h"
#include "renderbackend.h"
#include "render/pixelformat.h"
#include "render/rendermodes.h"
#include "videorenderframecache.h"
#include "videorenderworker.h"
OLIVE_NAMESPACE_ENTER
/**
* @brief A multithreaded OpenGL based renderer for node systems
*/
class VideoRenderBackend : public RenderBackend
{
Q_OBJECT
public:
/**
* @brief Renderer Constructor
*
* Constructing a Renderer object will not start any threads/backend on its own. Use Start() to do this and Stop()
* when the Renderer is about to be destroyed.
*/
VideoRenderBackend(QObject* parent = nullptr);
/**
* @brief Set parameters of the Renderer
*
* The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers
* to use. The Renderer must be stopped when calling this function.
*/
void SetParameters(const VideoRenderingParams &params);
void SetOperatingMode(const VideoRenderWorker::OperatingMode& mode);
void SetFrameGenerationParams(int width, int height, const QMatrix4x4& matrix);
void SetOnlySignalLastFrameRequested(bool enabled);
bool IsRendered(const rational& time) const;
void SetLimitCaching(bool limit);
QString GetCachedFrame(const rational& time);
void UpdateLastRequestedTime(const rational& time);
VideoRenderFrameCache* frame_cache();
const VideoRenderingParams& params() const;
void RenderFrame(const rational& time);
protected:
struct HashTimeMapping {
rational time;
QByteArray hash;
};
virtual void ConnectViewer(ViewerOutput* node) override;
virtual void DisconnectViewer(ViewerOutput* node) override;
virtual NodeInput* GetDependentInput() override;
virtual bool CanRender() override;
virtual TimeRange PopNextFrameFromQueue() override;
/**
* @brief Internal function for generating the cache ID
*/
virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override;
virtual void CacheIDChangedEvent(const QString& id) override;
virtual void ConnectWorkerToThis(RenderWorker* processor) override;
virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override;
virtual void ParamsChangedEvent(){}
VideoRenderWorker::OperatingMode operating_mode_;
signals:
void CachedTimeReady(const rational& time, qint64 job_time);
void RangeInvalidated(const TimeRange& range);
void GeneratedFrame(FramePtr frame);
private:
bool TimeIsQueued(const TimeRange &time) const;
bool JobIsCurrent(const NodeDependency &dep, const qint64& job_time) const;
bool SetFrameHash(const NodeDependency& dep, const QByteArray& hash, const qint64& job_time);
VideoRenderingParams params_;
VideoRenderFrameCache frame_cache_;
TimeRangeList invalidated_;
rational last_time_requested_;
bool only_signal_last_frame_requested_;
bool limit_caching_;
private slots:
void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash);
void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash);
void ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash);
void ThreadGeneratedFrame();
void TruncateFrameCacheLength(const rational& length);
void FrameRemovedFromDiskCache(const QByteArray& hash);
};
OLIVE_NAMESPACE_EXIT
#endif // VIDEORENDERERBACKEND_H
-251
View File
@@ -1,251 +0,0 @@
/***
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 "videorenderworker.h"
#include "common/define.h"
#include "common/functiontimer.h"
#include "node/block/transition/transition.h"
#include "node/node.h"
#include "project/project.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) :
RenderWorker(parent),
frame_cache_(frame_cache),
operating_mode_(kHashRenderCache)
{
}
const VideoRenderingParams &VideoRenderWorker::video_params()
{
return video_params_;
}
void VideoRenderWorker::TextureToBuffer(const QVariant &texture, void *buffer, int linesize)
{
TextureToBuffer(texture,
video_params_.effective_width(),
video_params_.effective_height(),
QMatrix4x4(),
buffer,
linesize);
}
NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, const qint64 &job_time)
{
// Get hash of node graph
// We use SHA-1 for speed (benchmarks show it's the fastest hash available to us)
QByteArray hash;
if (operating_mode_ & kHashOnly) {
QCryptographicHash hasher(QCryptographicHash::Sha1);
// Embed video parameters into this hash
int vwidth = video_params_.effective_width();
int vheight = video_params_.effective_height();
PixelFormat::Format vfmt = video_params_.format();
RenderMode::Mode vmode = video_params_.mode();
hasher.addData(reinterpret_cast<const char*>(&vwidth), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&vheight), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&vfmt), sizeof(PixelFormat::Format));
hasher.addData(reinterpret_cast<const char*>(&vmode), sizeof(RenderMode::Mode));
path.node()->Hash(hasher, path.in());
hash = hasher.result();
}
NodeValueTable value;
if (!(operating_mode_ & kRenderOnly)) {
// Emit only the hash
emit CompletedDownload(path, job_time, hash);
} else if ((operating_mode_ & kHashOnly) && frame_cache_->HasHash(hash, video_params_.format())) {
// We've already cached this hash, no need to continue
emit HashAlreadyExists(path, job_time, hash);
} else if (!(operating_mode_ & kHashOnly) || frame_cache_->TryCache(hash)) {
// This hash is available for us to cache, start traversing graph
value = ProcessNode(path);
// Find texture in hash
QVariant texture = value.Get(NodeParam::kTexture);
// If we actually have a texture, download it into the disk cache
if (!texture.isNull() || (!(operating_mode_ & kDownloadOnly))) {
Download(hash, path.in(), texture);
}
frame_cache_->RemoveHashFromCurrentlyCaching(hash);
// Signal that this job is complete
if (operating_mode_ & kDownloadOnly) {
emit CompletedDownload(path, job_time, hash);
}
} else {
// Another thread must be caching this already, nothing to be done
emit HashAlreadyBeingCached(path, job_time, hash);
}
return value;
}
void VideoRenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table)
{
if (stream->type() != Stream::kVideo && stream->type() != Stream::kImage) {
return;
}
ImageStreamPtr video_stream = std::static_pointer_cast<ImageStream>(stream);
rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in();
QString colorspace_match = video_stream->get_colorspace_match_string();
NodeValue value;
bool found_cache = false;
if (still_image_cache_.Has(stream.get())) {
CachedStill cs = still_image_cache_.Get(stream.get());
if (cs.colorspace == colorspace_match
&& cs.alpha_is_associated == video_stream->premultiplied_alpha()
&& cs.divider == video_params_.divider()
&& cs.time == time_match) {
value = cs.texture;
found_cache = true;
} else {
still_image_cache_.Remove(stream.get());
}
}
if (!found_cache) {
value = GetDataFromStream(stream, input_time);
still_image_cache_.Add(stream.get(), {value,
colorspace_match,
video_stream->premultiplied_alpha(),
video_params_.divider(),
time_match});
}
table->Push(value);
}
void VideoRenderWorker::SetParameters(const VideoRenderingParams &video_params)
{
video_params_ = video_params;
if (IsStarted()) {
ResizeDownloadBuffer();
}
ParametersChangedEvent();
}
void VideoRenderWorker::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
{
operating_mode_ = mode;
}
void VideoRenderWorker::SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix)
{
frame_gen_params_ = VideoRenderingParams(width,
height,
video_params_.time_base(),
video_params_.format(),
video_params_.mode(),
video_params_.divider());
frame_gen_mat_ = matrix;
}
bool VideoRenderWorker::InitInternal()
{
if (video_params_.is_valid()) {
ResizeDownloadBuffer();
}
return true;
}
void VideoRenderWorker::CloseInternal()
{
download_buffer_.clear();
}
void VideoRenderWorker::Download(const QByteArray& hash, const rational& time, QVariant texture)
{
if (operating_mode_ & kDownloadOnly) {
TextureToBuffer(texture, download_buffer_.data(), 0);
frame_cache_->SaveCacheFrame(hash,
download_buffer_.data(),
VideoRenderingParams(video_params_.effective_width(),
video_params_.effective_height(),
video_params_.format()));
} else {
FramePtr frame = Frame::Create();
if (frame_gen_params_.is_valid()) {
frame->set_video_params(frame_gen_params_);
} else {
frame->set_video_params(VideoRenderingParams(video_params_.effective_width(),
video_params_.effective_height(),
video_params_.format()));
}
frame->allocate();
if (texture.isNull()) {
memset(frame->data(), 0, frame->allocated_size());
} else {
TextureToBuffer(texture, frame->width(), frame->height(), frame_gen_mat_, frame->data(), frame->linesize_pixels());
}
frame->set_timestamp(time);
emit GeneratedFrame(frame);
}
}
void VideoRenderWorker::ResizeDownloadBuffer()
{
download_buffer_.resize(PixelFormat::GetBufferSize(video_params_.format(), video_params_.effective_width(), video_params_.effective_height()));
}
ColorProcessorCache *VideoRenderWorker::color_cache()
{
return &color_cache_;
}
OLIVE_NAMESPACE_EXIT
-140
View File
@@ -1,140 +0,0 @@
/***
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/>.
***/
#ifndef VIDEORENDERWORKER_H
#define VIDEORENDERWORKER_H
#include <QCryptographicHash>
#include <QMatrix4x4>
#include "colorprocessorcache.h"
#include "node/dependency.h"
#include "render/videoparams.h"
#include "renderworker.h"
#include "videorenderframecache.h"
OLIVE_NAMESPACE_ENTER
class VideoRenderWorker : public RenderWorker {
Q_OBJECT
public:
/**
* @brief VideoRenderWorker uses hashes to recognize frames that are identical to others in the render queue
*
* This mode can modify the behavior of the worker, either to disable hash verification or only generate hashes and
* not render. These are only used in the context of exporting.
*
* These are also flags that can be or'd together, mostly for the convenience of checking which functionalities are
* disabled and enabled.
*/
enum OperatingMode {
/// Generate hashes but don't render or download anything
kHashOnly = 0x1,
/// Render but don't download or hash
kRenderOnly = 0x2,
/// Enable download (NEVER USE THIS ON ITS OWN, this is only here for checking flags, download-only mode makes no sense)
kDownloadOnly = 0x4,
/// Use hash verification and render, but don't cache any frames to disk
kHashAndRenderOnly = 0x3,
/// Render and download, but don't use hash verification
kRenderAndCacheOnly = 0x6,
/// Render and use hashes to identify exact matches (default)
kHashRenderCache = 0x7
};
VideoRenderWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr);
void SetParameters(const VideoRenderingParams& video_params);
void SetOperatingMode(const OperatingMode& mode);
void SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix);
signals:
void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash);
void HashAlreadyBeingCached(NodeDependency path, qint64 job_time, QByteArray hash);
void HashAlreadyExists(NodeDependency path, qint64 job_time, QByteArray hash);
void GeneratedFrame(FramePtr frame);
void Aborted();
protected:
virtual bool InitInternal() override;
virtual void CloseInternal() override;
const VideoRenderingParams& video_params();
virtual void ParametersChangedEvent(){}
void TextureToBuffer(const QVariant& texture, void *buffer, int linesize);
virtual void TextureToBuffer(const QVariant& texture, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize) = 0;
virtual NodeValueTable RenderInternal(const NodeDependency& CurrentPath, const qint64& job_time) override;
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override;
ColorProcessorCache* color_cache();
private:
void Download(const QByteArray &hash, const rational &time, QVariant texture);
void ResizeDownloadBuffer();
VideoRenderingParams frame_gen_params_;
QMatrix4x4 frame_gen_mat_;
VideoRenderingParams video_params_;
VideoRenderFrameCache* frame_cache_;
ColorProcessorCache color_cache_;
QByteArray download_buffer_;
OperatingMode operating_mode_;
struct CachedStill {
NodeValue texture;
QString colorspace;
bool alpha_is_associated;
int divider;
rational time;
};
RenderCache<Stream*, CachedStill> still_image_cache_;
private slots:
};
OLIVE_NAMESPACE_EXIT
#endif // VIDEORENDERWORKER_H
@@ -18,7 +18,7 @@
***/
#include "videorenderframecache.h"
#include "framehashcache.h"
#include <OpenEXR/ImfFloatAttribute.h>
#include <OpenEXR/ImfInputFile.h>
@@ -33,80 +33,24 @@
OLIVE_NAMESPACE_ENTER
void VideoRenderFrameCache::Clear()
{
time_hash_map_.clear();
{
QMutexLocker locker(&currently_caching_lock_);
currently_caching_list_.clear();
}
cache_id_.clear();
}
bool VideoRenderFrameCache::HasHash(const QByteArray &hash, const PixelFormat::Format& format)
{
return QFileInfo::exists(CachePathName(hash, format)) && !IsCaching(hash);
}
bool VideoRenderFrameCache::IsCaching(const QByteArray &hash)
{
QMutexLocker locker(&currently_caching_lock_);
return currently_caching_list_.contains(hash);
}
bool VideoRenderFrameCache::TryCache(const QByteArray &hash)
{
QMutexLocker locker(&currently_caching_lock_);
if (!currently_caching_list_.contains(hash)) {
currently_caching_list_.append(hash);
return true;
}
return false;
}
void VideoRenderFrameCache::SetCacheID(const QString &id)
{
Clear();
cache_id_ = id;
}
QByteArray VideoRenderFrameCache::TimeToHash(const rational &time) const
QByteArray FrameHashCache::GetHash(const rational &time) const
{
return time_hash_map_.value(time);
}
void VideoRenderFrameCache::SetHash(const rational &time, const QByteArray &hash)
void FrameHashCache::SetHash(const rational &time, const QByteArray &hash)
{
time_hash_map_.insert(time, hash);
Validate(TimeRange(time, time + timebase_));
}
void VideoRenderFrameCache::Truncate(const rational &time)
void FrameHashCache::SetTimebase(const rational &tb)
{
QMap<rational, QByteArray>::iterator i = time_hash_map_.begin();
while (i != time_hash_map_.end()) {
if (i.key() >= time) {
i = time_hash_map_.erase(i);
} else {
i++;
}
}
timebase_ = tb;
}
void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &hash)
{
QMutexLocker locker(&currently_caching_lock_);
currently_caching_list_.removeOne(hash);
}
QList<rational> VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) const
QList<rational> FrameHashCache::GetFramesWithHash(const QByteArray &hash) const
{
QList<rational> times;
@@ -121,7 +65,7 @@ QList<rational> VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) co
return times;
}
QList<rational> VideoRenderFrameCache::TakeFramesWithHash(const QByteArray &hash)
QList<rational> FrameHashCache::TakeFramesWithHash(const QByteArray &hash)
{
QList<rational> times;
@@ -140,12 +84,12 @@ QList<rational> VideoRenderFrameCache::TakeFramesWithHash(const QByteArray &hash
return times;
}
const QMap<rational, QByteArray> &VideoRenderFrameCache::time_hash_map() const
const QMap<rational, QByteArray> &FrameHashCache::time_hash_map() const
{
return time_hash_map_;
}
QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f)
QString FrameHashCache::GetFormatExtension(const PixelFormat::Format &f)
{
if (PixelFormat::FormatIsFloat(f)) {
// EXR is only fast with float buffers so we only use it for those
@@ -160,9 +104,9 @@ QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f)
}
}
void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash,
char* data,
const VideoRenderingParams& vparam) const
void FrameHashCache::SaveCacheFrame(const QByteArray& hash,
char* data,
const VideoRenderingParams& vparam)
{
QString fn = CachePathName(hash, vparam.format());
@@ -172,7 +116,35 @@ void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash,
}
}
QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt) const
void FrameHashCache::LengthChangedEvent(const rational &old, const rational &newlen)
{
if (newlen < old) {
QMap<rational, QByteArray>::iterator i = time_hash_map_.begin();
while (i != time_hash_map_.end()) {
if (i.key() >= newlen) {
i = time_hash_map_.erase(i);
} else {
i++;
}
}
}
}
void FrameHashCache::InvalidateEvent(const TimeRange &r)
{
QMap<rational, QByteArray>::iterator i = time_hash_map_.begin();
while (i != time_hash_map_.end()) {
if (i.key() >= r.in() && i.key() < r.out()) {
i = time_hash_map_.erase(i);
} else {
i++;
}
}
}
QString FrameHashCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt)
{
QString ext = GetFormatExtension(pix_fmt);
@@ -184,7 +156,7 @@ QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const Pixel
return cache_dir.filePath(filename);
}
bool VideoRenderFrameCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam)
bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam)
{
switch (vparam.format()) {
case PixelFormat::PIX_FMT_RGB8:
@@ -24,47 +24,29 @@
#include <QMutex>
#include "common/rational.h"
#include "common/timerange.h"
#include "render/pixelformat.h"
#include "render/playbackcache.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class VideoRenderFrameCache
class FrameHashCache : public PlaybackCache
{
Q_OBJECT
public:
VideoRenderFrameCache() = default;
FrameHashCache() = default;
void Clear();
/**
* @brief Return whether a frame with this hash already exists
*/
bool HasHash(const QByteArray& hash, const PixelFormat::Format &format);
/**
* @brief Return whether a frame is currently being cached
*/
bool IsCaching(const QByteArray& hash);
/**
* @brief Check if a frame is currently being cached, and if not reserve it
*/
bool TryCache(const QByteArray& hash);
void SetCacheID(const QString& id);
QByteArray TimeToHash(const rational& time) const;
QByteArray GetHash(const rational& time) const;
void SetHash(const rational& time, const QByteArray& hash);
void Truncate(const rational& time);
void RemoveHashFromCurrentlyCaching(const QByteArray& hash);
void SetTimebase(const rational& tb);
/**
* @brief Returns a list of frames that use a particular hash
*/
QList<rational> FramesWithHash(const QByteArray& hash) const;
QList<rational> GetFramesWithHash(const QByteArray& hash) const;
/**
* @brief Same as FramesWithHash() but also removes these frames from the map
@@ -73,23 +55,26 @@ public:
const QMap<rational, QByteArray>& time_hash_map() const;
static QString GetFormatExtension(const PixelFormat::Format& f);
/**
* @brief Return the path of the cached image at this time
*/
QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt) const;
static QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt);
static bool SaveCacheFrame(const QString& filename, char *data, const VideoRenderingParams &vparam);
void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam) const;
static void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam);
static QString GetFormatExtension(const PixelFormat::Format& f);
protected:
virtual void LengthChangedEvent(const rational& old, const rational& newlen) override;
virtual void InvalidateEvent(const TimeRange& range) override;
private:
QMap<rational, QByteArray> time_hash_map_;
QMutex currently_caching_lock_;
QVector<QByteArray> currently_caching_list_;
rational timebase_;
QString cache_id_;
};
OLIVE_NAMESPACE_EXIT
+84
View File
@@ -0,0 +1,84 @@
/***
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 "playbackcache.h"
OLIVE_NAMESPACE_ENTER
PlaybackCache::PlaybackCache()
{
}
void PlaybackCache::Invalidate(const TimeRange &r)
{
invalidated_.InsertTimeRange(r);
InvalidateEvent(r);
emit Invalidated(r);
}
void PlaybackCache::SetLength(const rational &r)
{
if (length_ == r) {
// Same length - do nothing
return;
}
if (r > length_) {
// If new length is greater, simply extend the invalidated range for now
invalidated_.InsertTimeRange(TimeRange(r, length_));
} else {
// If new length is smaller, removed hashes
invalidated_.RemoveTimeRange(TimeRange(length_, r));
}
LengthChangedEvent(length_, r);
length_ = r;
}
bool PlaybackCache::IsFullyValidated() const
{
return invalidated_.isEmpty();
}
void PlaybackCache::Validate(const TimeRange &r)
{
invalidated_.RemoveTimeRange(r);
emit Validated(r);
}
void PlaybackCache::InvalidateAll()
{
Invalidate(TimeRange(0, length_));
}
void PlaybackCache::LengthChangedEvent(const rational &, const rational &)
{
}
void PlaybackCache::InvalidateEvent(const TimeRange &)
{
}
OLIVE_NAMESPACE_EXIT
@@ -18,27 +18,55 @@
***/
#ifndef AUDIOWORKER_H
#define AUDIOWORKER_H
#ifndef PLAYBACKCACHE_H
#define PLAYBACKCACHE_H
#include "../audiorenderworker.h"
#include <QObject>
#include "common/timerange.h"
OLIVE_NAMESPACE_ENTER
class AudioWorker : public AudioRenderWorker
class PlaybackCache : public QObject
{
Q_OBJECT
public:
AudioWorker(QHash<Node*, Node*>* copy_map, QObject* parent = nullptr);
PlaybackCache();
void Invalidate(const TimeRange& r);
void SetLength(const rational& r);
bool IsFullyValidated() const;
const TimeRangeList& GetInvalidatedRanges() const
{
return invalidated_;
}
signals:
void Invalidated(const TimeRange& r);
void Validated(const TimeRange& r);
void LengthChanged(const rational& r);
protected:
virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) override;
void Validate(const TimeRange& r);
virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, NodeValueDatabase& input_params, NodeValueTable& output_params) override;
void InvalidateAll();
virtual void LengthChangedEvent(const rational& old, const rational& newlen);
virtual void InvalidateEvent(const TimeRange& range);
private:
TimeRangeList invalidated_;
rational length_;
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIOWORKER_H
#endif // PLAYBACKCACHE_H
+5 -5
View File
@@ -103,7 +103,7 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
void TimeBasedWidget::UpdateMaximumScroll()
{
rational length = (viewer_node_) ? viewer_node_->Length() : rational();
rational length = (viewer_node_) ? viewer_node_->GetLength() : rational();
if (auto_max_scrollbar_) {
scrollbar_->setMaximum(qMax(0, qCeil(TimeToScene(length)) - width()));
@@ -244,7 +244,7 @@ void TimeBasedWidget::GoToPrevCut()
int64_t closest_cut = 0;
foreach (TrackOutput* track, viewer_node_->Tracks()) {
foreach (TrackOutput* track, viewer_node_->GetTracks()) {
int64_t this_track_closest_cut = 0;
foreach (Block* block, track->Blocks()) {
@@ -271,7 +271,7 @@ void TimeBasedWidget::GoToNextCut()
int64_t closest_cut = INT64_MAX;
foreach (TrackOutput* track, GetConnectedNode()->Tracks()) {
foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) {
int64_t this_track_closest_cut = Timecode::time_to_timestamp(track->track_length(), timebase());
if (this_track_closest_cut <= GetTimestamp()) {
@@ -319,7 +319,7 @@ void TimeBasedWidget::NextFrame()
void TimeBasedWidget::GoToEnd()
{
if (viewer_node_) {
SetTimeAndSignal(Timecode::time_to_timestamp(viewer_node_->Length(), timebase()));
SetTimeAndSignal(Timecode::time_to_timestamp(viewer_node_->GetLength(), timebase()));
}
}
@@ -462,7 +462,7 @@ void TimeBasedWidget::ToggleShowAll()
w = w / 10 * 9;
toggle_show_all_old_scale_ = GetScale();
SetScale(w / GetConnectedNode()->Length().toDouble());
SetScale(w / GetConnectedNode()->GetLength().toDouble());
toggle_show_all_ = true;
}
}
+7 -7
View File
@@ -425,7 +425,7 @@ void TimelineWidget::SplitAtPlayhead()
bool some_blocks_are_selected = false;
// Get all blocks at the playhead
foreach (TrackOutput* track, GetConnectedNode()->Tracks()) {
foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) {
Block* b = track->BlockContainingTime(playhead_time);
if (b && b->type() == Block::kClip) {
@@ -566,7 +566,7 @@ void TimelineWidget::IncreaseTrackHeight()
return;
}
QVector<TrackOutput*> all_tracks = GetConnectedNode()->Tracks();
QVector<TrackOutput*> all_tracks = GetConnectedNode()->GetTracks();
// Increase the height of each track by one "unit"
foreach (TrackOutput* t, all_tracks) {
@@ -580,7 +580,7 @@ void TimelineWidget::DecreaseTrackHeight()
return;
}
QVector<TrackOutput*> all_tracks = GetConnectedNode()->Tracks();
QVector<TrackOutput*> all_tracks = GetConnectedNode()->GetTracks();
// Decrease the height of each track by one "unit"
foreach (TrackOutput* t, all_tracks) {
@@ -714,7 +714,7 @@ void TimelineWidget::DeleteInToOut(bool ripple)
QUndoCommand* command = new QUndoCommand();
foreach (TrackOutput* track, GetConnectedNode()->Tracks()) {
foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) {
if (!track->IsLocked()) {
if (ripple) {
new TrackRippleRemoveAreaCommand(track,
@@ -794,7 +794,7 @@ void TimelineWidget::RippleEditTo(Timeline::MovementMode mode, bool insert_gaps)
closest_point_to_playhead = RATIONAL_MAX;
}
foreach (TrackOutput* track, GetConnectedNode()->Tracks()) {
foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) {
Block* b = track->NearestBlockBefore(playhead_time);
if (b != nullptr) {
@@ -821,7 +821,7 @@ void TimelineWidget::RippleEditTo(Timeline::MovementMode mode, bool insert_gaps)
rational out_ripple = qMax(closest_point_to_playhead, playhead_time);
rational ripple_length = out_ripple - in_ripple;
foreach (TrackOutput* track, GetConnectedNode()->Tracks()) {
foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) {
GapBlock* gap = nullptr;
if (insert_gaps) {
gap = new GapBlock();
@@ -854,7 +854,7 @@ void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational
QList<Block*> blocks_to_append_gap_to;
QList<Block*> gaps_to_extend;
foreach (TrackOutput* track, GetConnectedNode()->Tracks()) {
foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) {
if (track->IsLocked()) {
continue;
}
+1 -1
View File
@@ -118,7 +118,7 @@ void TimelineWidget::RippleTool::InitiateGhosts(TimelineViewBlockItem *clicked_i
}
// For each track that does NOT have a ghost, we need to make one for Gaps
foreach (TrackOutput* track, parent()->GetConnectedNode()->Tracks()) {
foreach (TrackOutput* track, parent()->GetConnectedNode()->GetTracks()) {
// Determine if we've already created a ghost on this track
bool ghost_on_this_track_exists = false;
+1 -1
View File
@@ -727,7 +727,7 @@ void TimelineRippleDeleteGapsAtRegionsCommand::redo_internal()
QList<Block*> blocks_around_range;
foreach (TrackOutput* track, timeline_->Tracks()) {
foreach (TrackOutput* track, timeline_->GetTracks()) {
// Get the block from every other track that is either at or just before our block's in point
Block* block_at_time = track->NearestBlockBeforeOrAt(range.in());
+18 -16
View File
@@ -31,28 +31,28 @@ OLIVE_NAMESPACE_ENTER
AudioWaveformView::AudioWaveformView(QWidget *parent) :
SeekableWidget(parent),
backend_(nullptr)
playback_(nullptr)
{
setAutoFillBackground(true);
setBackgroundRole(QPalette::Base);
}
void AudioWaveformView::SetBackend(AudioRenderBackend *backend)
void AudioWaveformView::SetViewer(AudioPlaybackCache *playback)
{
if (backend_) {
disconnect(backend_, &AudioRenderBackend::QueueComplete, this, &AudioWaveformView::ForceUpdate);
disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
if (playback_) {
disconnect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::ForceUpdate);
disconnect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::BackendParamsChanged);
SetTimebase(0);
}
backend_ = backend;
playback_ = playback;
if (backend_) {
connect(backend_, &AudioRenderBackend::QueueComplete, this, &AudioWaveformView::ForceUpdate);
connect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
if (playback_) {
connect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::ForceUpdate);
connect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::BackendParamsChanged);
SetTimebase(backend_->params().time_base());
SetTimebase(playback_->GetParameters().time_base());
}
ForceUpdate();
@@ -113,12 +113,14 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) {
const AudioRenderingParams& params = playback_->GetParameters();
if (!playback_
|| playback_->GetCacheFilename().isEmpty()
|| !params.is_valid()) {
return;
}
const AudioRenderingParams& params = backend_->params();
if (cached_size_ != size()
|| cached_scale_ != GetScale()
|| cached_scroll_ != GetScroll()) {
@@ -126,7 +128,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
cached_waveform_ = QPixmap(size());
cached_waveform_.fill(Qt::transparent);
QFile fs(backend_->CachePathName());
QFile fs(playback_->GetCacheFilename());
if (fs.open(QFile::ReadOnly)) {
@@ -207,12 +209,12 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
void AudioWaveformView::BackendParamsChanged()
{
SetTimebase(backend_->params().time_base());
SetTimebase(playback_->GetParameters().time_base());
}
void AudioWaveformView::ForceUpdate()
{
cached_size_ = QSize();
cached_size_ = QSize();
update();
}
+3 -3
View File
@@ -25,7 +25,7 @@
#include "audio/sumsamples.h"
#include "render/audioparams.h"
#include "render/backend/audiorenderbackend.h"
#include "render/audioplaybackcache.h"
#include "widget/timeruler/seekablewidget.h"
OLIVE_NAMESPACE_ENTER
@@ -38,7 +38,7 @@ public:
//void SetData(const QString& file, const AudioRenderingParams& params);
void SetBackend(AudioRenderBackend* backend);
void SetViewer(AudioPlaybackCache *playback);
static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const SampleSummer::Sum *samples, int nb_samples, int channels);
@@ -46,7 +46,7 @@ protected:
virtual void paintEvent(QPaintEvent* event) override;
private:
AudioRenderBackend* backend_;
AudioPlaybackCache *playback_;
QPixmap cached_waveform_;
QSize cached_size_;
+1 -1
View File
@@ -22,7 +22,7 @@
OLIVE_NAMESPACE_ENTER
void GizmoTraverser::FootageProcessingEvent(StreamPtr stream, const TimeRange &/*input_time*/, NodeValueTable *table)
void GizmoTraverser::FootageProcessingEvent(StreamPtr stream, const TimeRange &/*input_time*/, NodeValueTable *table) const
{
if (stream->type() == Stream::kVideo || stream->type() == Stream::kAudio) {
+1 -1
View File
@@ -31,7 +31,7 @@ public:
GizmoTraverser() = default;
protected:
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override;
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) const override;
};
+42 -58
View File
@@ -100,14 +100,14 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
SetScale(48.0);
// Start background renderers
video_renderer_ = new OpenGLBackend(this);
connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, this, &ViewerWidget::RendererCachedTime);
connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, ruler(), &TimeRuler::CacheTimeReady);
connect(video_renderer_, &VideoRenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange);
connect(video_renderer_, &VideoRenderBackend::GeneratedFrame, this, &ViewerWidget::RendererGeneratedFrame);
audio_renderer_ = new AudioBackend(this);
renderer_ = new OpenGLBackend(this);
/*
connect(renderer_, &RenderBackend::CachedTimeReady, this, &ViewerWidget::RendererCachedTime);
connect(renderer_, &RenderBackend::CachedTimeReady, ruler(), &TimeRuler::CacheTimeReady);
connect(renderer_, &RenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange);
connect(renderer_, &RenderBackend::GeneratedFrame, this, &ViewerWidget::RendererGeneratedFrame);
*/
waveform_view_->SetBackend(audio_renderer_);
connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal);
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters);
@@ -161,11 +161,10 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
connect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
connect(n, &ViewerOutput::VideoChangedBetween, this, &ViewerWidget::UpdateStack);
connect(n, &ViewerOutput::AudioChangedBetween, this, &ViewerWidget::UpdateStack);
connect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack);
SizeChangedSlot(n->video_params().width(), n->video_params().height());
LengthChangedSlot(n->Length());
LengthChangedSlot(n->GetLength());
ColorManager* using_manager;
if (override_color_manager_) {
@@ -189,6 +188,7 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
UpdateStack();
if (GetConnectedTimelinePoints()) {
waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache());
waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints());
}
}
@@ -204,8 +204,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
disconnect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
disconnect(n, &ViewerOutput::VideoChangedBetween, this, &ViewerWidget::UpdateStack);
disconnect(n, &ViewerOutput::AudioChangedBetween, this, &ViewerWidget::UpdateStack);
disconnect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack);
// Effectively disables the viewer and clears the state
SizeChangedSlot(0, 0);
@@ -215,13 +214,13 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
window->display_widget()->DisconnectColorManager();
}
waveform_view_->SetViewer(nullptr);
waveform_view_->ConnectTimelinePoints(nullptr);
}
void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n)
{
video_renderer_->SetViewerNode(n);
audio_renderer_->SetViewerNode(n);
renderer_->SetViewerNode(n);
}
void ViewerWidget::ScaleChangedEvent(const double &s)
@@ -332,16 +331,6 @@ void ViewerWidget::ForceUpdate()
UpdateTextureFromNode(GetTime());
}
VideoRenderBackend *ViewerWidget::video_renderer() const
{
return video_renderer_;
}
ColorManager *ViewerWidget::color_manager() const
{
return display_widget_->color_manager();
}
void ViewerWidget::SetGizmos(Node *node)
{
display_widget_->SetTimeTarget(GetConnectedNode());
@@ -376,14 +365,14 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
QString frame_fn = GetCachedFilenameFromTime(time);
if (frame_fn.isEmpty()) {
video_renderer_->RenderFrame(time);
// FIXME: Connect QFutureWatcher to this
renderer_->RenderFrame(time);
} else {
FramePtr f = DecodeCachedImage(frame_fn);
SetDisplayImage(f, false);
}
} else {
SetDisplayImage(nullptr, false);
video_renderer_->UpdateLastRequestedTime(time);
}
}
@@ -399,11 +388,11 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
playback_speed_ = speed;
play_in_to_out_only_ = in_to_out_only;
QString audio_fn = audio_renderer_->CachePathName();
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetCacheFilename();
if (!audio_fn.isEmpty()) {
AudioManager::instance()->SetOutputParams(audio_renderer_->params());
AudioManager::instance()->SetOutputParams(GetConnectedNode()->audio_playback_cache()->GetParameters());
AudioManager::instance()->StartOutput(audio_fn,
audio_renderer_->params().time_to_bytes(GetTime()),
GetConnectedNode()->audio_playback_cache()->GetParameters().time_to_bytes(GetTime()),
playback_speed_);
}
@@ -433,17 +422,19 @@ void ViewerWidget::PushScrubbedAudio()
{
if (!IsPlaying() && Config::Current()["AudioScrubbing"].toBool()) {
// Get audio src device from renderer
QString audio_fn = audio_renderer_->CachePathName();
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetCacheFilename();
QFile audio_src(audio_fn);
if (audio_src.open(QFile::ReadOnly)) {
const AudioRenderingParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters();
// FIXME: Hardcoded scrubbing interval (20ms)
int size_of_sample = audio_renderer_->params().time_to_bytes(rational(20, 1000));
int size_of_sample = params.time_to_bytes(rational(20, 1000));
// Push audio
audio_src.seek(audio_renderer_->params().time_to_bytes(GetTime()));
audio_src.seek(params.time_to_bytes(GetTime()));
QByteArray frame_audio = audio_src.read(size_of_sample);
AudioManager::instance()->SetOutputParams(audio_renderer_->params());
AudioManager::instance()->SetOutputParams(params);
AudioManager::instance()->PushToOutput(frame_audio);
audio_src.close();
@@ -469,11 +460,11 @@ void ViewerWidget::UpdateMinimumScale()
return;
}
if (GetConnectedNode()->Length().isNull()) {
if (GetConnectedNode()->GetLength().isNull()) {
// Avoids divide by zero
SetMinimumScale(0);
} else {
SetMinimumScale(static_cast<double>(ruler()->width()) / GetConnectedNode()->Length().toDouble());
SetMinimumScale(static_cast<double>(ruler()->width()) / GetConnectedNode()->GetLength().toDouble());
}
}
@@ -526,7 +517,10 @@ void ViewerWidget::FillPlaybackQueue()
QString ViewerWidget::GetCachedFilenameFromTime(const rational &time)
{
if (FrameExistsAtTime(time)) {
return video_renderer_->GetCachedFrame(time);
QByteArray hash = GetConnectedNode()->video_frame_cache()->GetHash(time);
return GetConnectedNode()->video_frame_cache()->CachePathName(
hash,
PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline));
} else {
return QString();
}
@@ -534,7 +528,7 @@ QString ViewerWidget::GetCachedFilenameFromTime(const rational &time)
bool ViewerWidget::FrameExistsAtTime(const rational &time)
{
return GetConnectedNode() && time < GetConnectedNode()->Length();
return GetConnectedNode() && time < GetConnectedNode()->GetLength();
}
FramePtr ViewerWidget::DecodeCachedImage(const QString &fn)
@@ -683,25 +677,13 @@ void ViewerWidget::UpdateRendererParameters()
RenderMode::Mode render_mode = RenderMode::kOffline;
VideoRenderingParams vparam(GetConnectedNode()->video_params(),
PixelFormat::instance()->GetConfiguredFormatForMode(render_mode),
render_mode,
divider_);
renderer_->SetDivider(divider_);
renderer_->SetMode(render_mode);
renderer_->SetPixelFormat(PixelFormat::instance()->GetConfiguredFormatForMode(render_mode));
if (video_renderer_->params() != vparam) {
video_renderer_->SetParameters(vparam);
video_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr);
}
display_widget_->SetVideoParams(GetConnectedNode()->video_params());
display_widget_->SetVideoParams(vparam);
AudioRenderingParams aparam(GetConnectedNode()->audio_params(),
SampleFormat::kInternalFormat);
if (audio_renderer_->params() != aparam) {
audio_renderer_->SetParameters(aparam);
audio_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr);
}
renderer_->SetSampleFormat(SampleFormat::kInternalFormat);
}
void ViewerWidget::ShowContextMenu(const QPoint &pos)
@@ -925,7 +907,7 @@ void ViewerWidget::TimebaseChangedEvent(const rational &timebase)
controls_->SetTimebase(timebase);
controls_->SetTime(ruler()->GetTime());
LengthChangedSlot(GetConnectedNode() ? GetConnectedNode()->Length() : 0);
LengthChangedSlot(GetConnectedNode() ? GetConnectedNode()->GetLength() : 0);
}
void ViewerWidget::PlaybackTimerUpdate()
@@ -947,7 +929,7 @@ void ViewerWidget::PlaybackTimerUpdate()
// Otherwise set the bounds to the range of the sequence
min_time = 0;
max_time = Timecode::time_to_timestamp(GetConnectedNode()->Length(), timebase());
max_time = Timecode::time_to_timestamp(GetConnectedNode()->GetLength(), timebase());
}
}
@@ -1039,8 +1021,10 @@ void ViewerWidget::SetZoomFromMenu(QAction *action)
void ViewerWidget::InvalidateVisible(NodeInput* source)
{
video_renderer_->InvalidateCache(TimeRange(GetTime(), GetTime()), source);
video_renderer_->RenderFrame(GetTime());
/*
renderer_->NodeGraphChanged(source);
renderer_->RenderFrame(GetTime());
*/
}
OLIVE_NAMESPACE_EXIT
+7 -6
View File
@@ -33,8 +33,6 @@
#include "node/output/viewer/viewer.h"
#include "panel/scope/scope.h"
#include "render/backend/opengl/openglbackend.h"
#include "render/backend/opengl/opengltexture.h"
#include "render/backend/audio/audiobackend.h"
#include "viewerdisplay.h"
#include "viewerplaybacktimer.h"
#include "viewerqueue.h"
@@ -87,9 +85,13 @@ public:
void ForceUpdate();
VideoRenderBackend* video_renderer() const;
RenderBackend* renderer() const {
return renderer_;
}
ColorManager* color_manager() const;
ColorManager* color_manager() const {
return display_widget_->color_manager();
}
void SetGizmos(Node* node);
@@ -153,8 +155,7 @@ protected:
virtual void resizeEvent(QResizeEvent *event) override;
OpenGLBackend* video_renderer_;
AudioBackend* audio_renderer_;
RenderBackend* renderer_;
PlaybackControls* controls_;
+1 -1
View File
@@ -113,7 +113,7 @@ void ViewerDisplayWidget::SetGizmos(Node *node)
}
}
void ViewerDisplayWidget::SetVideoParams(const VideoRenderingParams &params)
void ViewerDisplayWidget::SetVideoParams(const VideoParams &params)
{
gizmo_params_ = params;
+2 -2
View File
@@ -72,7 +72,7 @@ public:
void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin);
void SetGizmos(Node* node);
void SetVideoParams(const VideoRenderingParams& params);
void SetVideoParams(const VideoParams &params);
void SetTime(const rational& time);
FramePtr last_loaded_buffer() const;
@@ -171,7 +171,7 @@ private:
Node* gizmos_;
NodeValueDatabase gizmo_db_;
rational gizmo_drag_time_;
VideoRenderingParams gizmo_params_;
VideoParams gizmo_params_;
bool gizmo_click_;
rational time_;
-2
View File
@@ -493,8 +493,6 @@ TimelinePanel* MainWindow::AppendTimelinePanel()
connect(panel, &TimelinePanel::SelectionChanged, node_panel_, &NodePanel::SelectBlocks);
connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp);
connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp);
connect(sequence_viewer_panel_->video_renderer(), &VideoRenderBackend::CachedTimeReady, panel->ruler(), &TimeRuler::CacheTimeReady);
connect(sequence_viewer_panel_->video_renderer(), &VideoRenderBackend::RangeInvalidated, panel->ruler(), &TimeRuler::CacheInvalidatedRange);
sequence_viewer_panel_->ConnectTimeBasedPanel(panel);