diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index df4ca479e..956c4a624 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -194,6 +194,7 @@ AudioManager::AudioManager() : RefreshDevices(); connect(&output_manager_, &AudioOutputManager::SentSamples, this, &AudioManager::SentSamples); + connect(&output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified); output_manager_.SetEnableSendingSamples(true); } diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 8a806652e..73e990114 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -102,6 +102,8 @@ signals: void SentSamples(QVector averages); + void OutputNotified(); + private: AudioManager(); diff --git a/app/audio/outputmanager.cpp b/app/audio/outputmanager.cpp index de27d92ac..8d601f07c 100644 --- a/app/audio/outputmanager.cpp +++ b/app/audio/outputmanager.cpp @@ -54,7 +54,7 @@ void AudioOutputManager::Push(const QByteArray& samples) ResetToPushMode(); // Start pushing samples to the output - OutputNotified(); + PushMoreSamples(); } void AudioOutputManager::ResetToPushMode() @@ -92,7 +92,7 @@ void AudioOutputManager::PullFromDevice(QIODevice *device, int playback_speed) output_->start(&device_proxy_); } -void AudioOutputManager::OutputNotified() +void AudioOutputManager::PushMoreSamples() { // Check if we're currently in push mode and if we have samples to push if (!push_device_ || pushed_samples_.isEmpty()) { @@ -139,6 +139,7 @@ void AudioOutputManager::SetOutputDevice(QAudioDeviceInfo info, QAudioFormat for output_ = std::unique_ptr(new QAudioOutput(info, format, this)); output_->setNotifyInterval(1); push_device_ = output_->start(); + connect(output_.get(), &QAudioOutput::notify, this, &AudioOutputManager::PushMoreSamples); connect(output_.get(), &QAudioOutput::notify, this, &AudioOutputManager::OutputNotified); } diff --git a/app/audio/outputmanager.h b/app/audio/outputmanager.h index 4b00208de..06024f4d1 100644 --- a/app/audio/outputmanager.h +++ b/app/audio/outputmanager.h @@ -69,6 +69,8 @@ signals: */ void SentSamples(QVector averages); + void OutputNotified(); + private: void ProcessAverages(const char* data, int length); @@ -83,7 +85,7 @@ private: AudioOutputDeviceProxy device_proxy_; private slots: - void OutputNotified(); + void PushMoreSamples(); }; #endif // AUDIOHYBRIDDEVICE_H diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index 68bb5194f..bb8f9a42f 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -46,6 +46,8 @@ set(OLIVE_SOURCES node/output.cpp node/param.h node/param.cpp + node/traverser.h + node/traverser.cpp node/value.h node/value.cpp PARENT_SCOPE diff --git a/app/node/input/media/audio/audio.cpp b/app/node/input/media/audio/audio.cpp index ccfb52f56..6f7992de9 100644 --- a/app/node/input/media/audio/audio.cpp +++ b/app/node/input/media/audio/audio.cpp @@ -28,8 +28,3 @@ QString AudioInput::Description() const { return tr("Import an audio footage stream."); } - -NodeValueTable AudioInput::Value(const NodeValueDatabase &value) const -{ - return value[footage_input_]; -} diff --git a/app/node/input/media/audio/audio.h b/app/node/input/media/audio/audio.h index 641b4f8de..86aa8d330 100644 --- a/app/node/input/media/audio/audio.h +++ b/app/node/input/media/audio/audio.h @@ -15,9 +15,6 @@ public: virtual QString Category() const override; virtual QString Description() const override; -protected: - virtual NodeValueTable Value(const NodeValueDatabase& value) const override; - private: }; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index b86623419..f1327dcdd 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -20,6 +20,8 @@ #include "viewer.h" +#include "node/traverser.h" + ViewerOutput::ViewerOutput() { texture_input_ = new NodeInput("tex_in", NodeInput::kTexture); @@ -28,9 +30,6 @@ ViewerOutput::ViewerOutput() samples_input_ = new NodeInput("samples_in", NodeInput::kSamples); AddInput(samples_input_); - length_input_ = new NodeInput("length_in", NodeInput::kRational); - AddInput(length_input_); - // Create TrackList instances track_inputs_.resize(Timeline::kTrackTypeCount); track_lists_.resize(Timeline::kTrackTypeCount); @@ -91,11 +90,6 @@ NodeInput *ViewerOutput::samples_input() const return samples_input_; } -NodeInput *ViewerOutput::length_input() const -{ - return length_input_; -} - void ViewerOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) { Node::InvalidateCache(start_range, end_range, from); @@ -104,8 +98,6 @@ void ViewerOutput::InvalidateCache(const rational &start_range, const rational & emit VideoChangedBetween(TimeRange(start_range, end_range)); } else if (from == samples_input()) { emit AudioChangedBetween(TimeRange(start_range, end_range)); - } else if (from == length_input()) { - emit LengthChanged(Length()); } SendInvalidateCache(start_range, end_range); @@ -146,18 +138,23 @@ void ViewerOutput::set_audio_params(const AudioParams &audio) rational ViewerOutput::Length() { - if (!length_input_->IsConnected()) { - return timeline_length_; + NodeTraverser traverser; + + rational video_length; + + if (texture_input_->IsConnected()) { + NodeValueTable t = traverser.ProcessNode(NodeDependency(texture_input_->get_connected_node(), 0, 0)); + video_length = t.Get(NodeParam::kNumber, "length").value(); } - Node* connected_node = length_input_->get_connected_node(); + rational audio_length; - if (connected_node) { - // This is kind of messy? - return connected_node->Value(NodeValueDatabase()).Get(NodeParam::kNumber, "length").value(); + if (samples_input_->IsConnected()) { + NodeValueTable t = traverser.ProcessNode(NodeDependency(samples_input_->get_connected_node(), 0, 0)); + audio_length = t.Get(NodeParam::kNumber, "length").value(); } - return 0; + return qMax(video_length, qMax(audio_length, timeline_length_)); } const QUuid &ViewerOutput::uuid() const diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index abdcd3f41..2d86dfc0e 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -52,7 +52,6 @@ public: NodeInput* texture_input() const; NodeInput* samples_input() const; - NodeInput* length_input() const; virtual void InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from = nullptr) override; virtual void InvalidateVisible(NodeInput *from) override; @@ -117,8 +116,6 @@ private: NodeInput* samples_input_; - NodeInput* length_input_; - VideoParams video_params_; AudioParams audio_params_; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp new file mode 100644 index 000000000..fb3f28239 --- /dev/null +++ b/app/node/traverser.cpp @@ -0,0 +1,76 @@ +#include "traverser.h" + +#include "node.h" + +NodeTraverser::NodeTraverser() +{ + +} + +NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRange &range) +{ + NodeValueDatabase database; + + // We need to insert tables into the database for each input + foreach (NodeParam* param, node->parameters()) { + if (IsCancelled()) { + return NodeValueDatabase(); + } + + if (param->type() == NodeParam::kInput) { + NodeInput* input = static_cast(param); + TimeRange input_time = node->InputTimeAdjustment(input, range); + + NodeValueTable table = ProcessInput(input, input_time); + + InputProcessingEvent(input, input_time, &table); + + database.Insert(input, table); + } + } + + return database; +} + +NodeValueTable NodeTraverser::ProcessNode(const NodeDependency& dep) +{ + const Node* node = dep.node(); + + if (node->IsTrack()) { + // If the range is not wholly contained in this Block, we'll need to do some extra processing + return RenderBlock(static_cast(node), dep.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()); + + // By this point, the node should have all the inputs it needs to render correctly + NodeValueTable table = node->Value(database); + + ProcessNodeEvent(node, dep.range(), database, &table); + + return table; +} + +NodeValueTable NodeTraverser::RenderBlock(const TrackOutput *track, const TimeRange &range) +{ + // By default, don't bother traversing blocks + return NodeValueTable(); +} + +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; + } +} diff --git a/app/node/traverser.h b/app/node/traverser.h new file mode 100644 index 000000000..8166a7987 --- /dev/null +++ b/app/node/traverser.h @@ -0,0 +1,31 @@ +#ifndef NODETRAVERSER_H +#define NODETRAVERSER_H + +#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" + +class NodeTraverser : public CancelableObject +{ +public: + NodeTraverser(); + + NodeValueTable ProcessNode(const NodeDependency &dep); + +protected: + NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); + + virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range); + + NodeValueTable ProcessInput(const NodeInput* input, const TimeRange &range); + + virtual void InputProcessingEvent(NodeInput*, const TimeRange&, NodeValueTable*){} + + virtual void ProcessNodeEvent(const Node*, const TimeRange&, const NodeValueDatabase&, NodeValueTable*){} + +}; + +#endif // NODETRAVERSER_H diff --git a/app/node/value.cpp b/app/node/value.cpp index 2f5fc11a5..0ef541256 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -58,13 +58,9 @@ NodeValueTable::NodeValueTable() QVariant NodeValueTable::Get(const NodeParam::DataType &type, const QString &tag) const { - int value_index = GetInternal(type, tag); + NodeValue v = GetWithMeta(type, tag); - if (value_index >= 0) { - return values_.at(value_index).data(); - } - - return QVariant(); + return v.data(); } NodeValue NodeValueTable::GetWithMeta(const NodeParam::DataType &type, const QString &tag) const diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 63aa3d308..07512a4ca 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -87,95 +87,41 @@ bool RenderWorker::IsStarted() return started_; } -NodeValueTable RenderWorker::ProcessNode(const NodeDependency& dep) -{ - const Node* node = dep.node(); - - if (node->IsTrack()) { - // If the range is not wholly contained in this Block, we'll need to do some extra processing - return RenderBlock(static_cast(node), dep.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()); - - // By this point, the node should have all the inputs it needs to render correctly - NodeValueTable table = node->Value(database); - - // Check if we have a shader for this output - RunNodeAccelerated(node, dep.range(), database, &table); - - return table; -} - -NodeValueTable RenderWorker::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; - } -} - void RenderWorker::ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time) { emit FootageUnavailable(stream, state, path_.range(), stream_time); } +void RenderWorker::InputProcessingEvent(NodeInput* input, const TimeRange& input_time, NodeValueTable *table) +{ + // Exception for Footage types where we actually retrieve some Footage data from a decoder + if (input->data_type() == NodeParam::kFootage) { + StreamPtr stream = ResolveStreamFromInput(input); + + if (stream) { + DecoderPtr decoder = ResolveDecoderFromInput(stream); + + if (decoder) { + + Decoder::RetrieveState state = decoder->GetRetrieveState(input_time.out()); + + if (state == Decoder::kReady) { + FrameToValue(decoder, stream, input_time, table); + } else { + ReportUnavailableFootage(stream, state, input_time.out()); + } + } + } + } +} + +void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, const 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_; } - - -#include "common/functiontimer.h" -NodeValueDatabase RenderWorker::GenerateDatabase(const Node* node, const TimeRange &range) -{ - NodeValueDatabase database; - - // We need to insert tables into the database for each input - foreach (NodeParam* param, node->parameters()) { - if (IsCancelled()) { - return NodeValueDatabase(); - } - - if (param->type() == NodeParam::kInput) { - NodeInput* input = static_cast(param); - TimeRange input_time = node->InputTimeAdjustment(input, range); - - NodeValueTable table = ProcessInput(input, input_time); - - // Exception for Footage types where we actually retrieve some Footage data from a decoder - if (input->data_type() == NodeParam::kFootage) { - StreamPtr stream = ResolveStreamFromInput(input); - - if (stream) { - DecoderPtr decoder = ResolveDecoderFromInput(stream); - - if (decoder) { - - Decoder::RetrieveState state = decoder->GetRetrieveState(input_time.out()); - - if (state == Decoder::kReady) { - FrameToValue(decoder, stream, input_time, &table); - } else { - ReportUnavailableFootage(stream, state, input_time.out()); - } - } - } - } - - database.Insert(input, table); - } - } - - return database; -} diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index 7965e297e..885d3c60f 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -3,13 +3,13 @@ #include -#include "common/cancelableobject.h" #include "common/constructors.h" -#include "node/output/track/track.h" -#include "node/node.h" #include "decodercache.h" +#include "node/node.h" +#include "node/output/track/track.h" +#include "node/traverser.h" -class RenderWorker : public QObject, public CancelableObject +class RenderWorker : public QObject, public NodeTraverser { Q_OBJECT public: @@ -38,24 +38,21 @@ protected: virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, const NodeValueDatabase &input_params, NodeValueTable* output_params); - StreamPtr ResolveStreamFromInput(NodeInput* input); - DecoderPtr ResolveDecoderFromInput(StreamPtr stream); - virtual void FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table) = 0; - NodeValueTable ProcessNode(const NodeDependency &dep); - - virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range) = 0; - - NodeValueTable ProcessInput(const NodeInput* input, const TimeRange &range); - virtual void ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time); + virtual void InputProcessingEvent(NodeInput *input, const TimeRange &input_time, NodeValueTable* table) override; + + virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params) override; + + StreamPtr ResolveStreamFromInput(NodeInput* input); + + DecoderPtr ResolveDecoderFromInput(StreamPtr stream); + const NodeDependency& CurrentPath() const; private: - NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); - bool started_; DecoderCache* decoder_cache_; diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 9b0c18db4..8e3f7ec8d 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -13,9 +13,13 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) : audio_node_ = new AudioInput(); viewer_node_ = new ViewerOutput(); + waveform_view_ = new QWidget(); + waveform_view_->setAutoFillBackground(true); + waveform_view_->setStyleSheet("background: black;"); + stack()->addWidget(waveform_view_); + NodeParam::ConnectEdge(video_node_->output(), viewer_node_->texture_input()); NodeParam::ConnectEdge(audio_node_->output(), viewer_node_->samples_input()); - NodeParam::ConnectEdge(video_node_->output(), viewer_node_->length_input()); connect(gl_widget_, &ViewerGLWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag); } @@ -55,6 +59,10 @@ void FootageViewerWidget::SetFootage(Footage *footage) if (video_stream) { video_node_->SetFootage(video_stream); viewer_node_->set_video_params(VideoParams(video_stream->width(), video_stream->height(), video_stream->frame_rate().flipped())); + + stack()->setCurrentWidget(gl_widget_); + } else { + stack()->setCurrentWidget(waveform_view_); } if (audio_stream) { @@ -79,7 +87,6 @@ void FootageViewerWidget::StartFootageDrag() return; } - qDebug() << "Drag start!"; QDrag* drag = new QDrag(this); QMimeData* mimedata = new QMimeData(); diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h index dc1634291..055906bd5 100644 --- a/app/widget/viewer/footageviewer.h +++ b/app/widget/viewer/footageviewer.h @@ -27,6 +27,8 @@ private: ViewerOutput* viewer_node_; + QWidget* waveform_view_; + private slots: void StartFootageDrag(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index f14080eed..6f42faaae 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -47,9 +47,13 @@ ViewerWidget::ViewerWidget(QWidget *parent) : QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); + // Set up stacked widget to allow switching away from the viewer widget + stack_ = new QStackedWidget(); + layout->addWidget(stack_); + // Create main OpenGL-based view sizer_ = new ViewerSizer(); - layout->addWidget(sizer_); + stack_->addWidget(sizer_); gl_widget_ = new ViewerGLWidget(); connect(gl_widget_, &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); @@ -111,7 +115,13 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i) void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) { - SetTimebase(n->video_params().time_base()); + if (!n->video_params().time_base().isNull()) { + SetTimebase(n->video_params().time_base()); + } else if (n->audio_params().sample_rate() > 0) { + SetTimebase(rational(1, n->audio_params().sample_rate())); + } else { + SetTimebase(rational()); + } connect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase); connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); @@ -139,7 +149,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) { Pause(); - SetTimebase(0); + SetTimebase(rational()); disconnect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase); disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); @@ -171,6 +181,11 @@ void ViewerWidget::resizeEvent(QResizeEvent *event) } } +QStackedWidget *ViewerWidget::stack() const +{ + return stack_; +} + void ViewerWidget::TogglePlayPause() { if (IsPlaying()) { @@ -251,7 +266,11 @@ void ViewerWidget::PlayInternal(int speed) controls_->ShowPauseButton(); - connect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + if (stack_->currentWidget() == gl_widget_) { + connect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + } else { + connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate); + } } void ViewerWidget::PushScrubbedAudio() @@ -387,7 +406,11 @@ void ViewerWidget::Pause() playback_speed_ = 0; controls_->ShowPlayButton(); - disconnect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + if (stack_->currentWidget() == gl_widget_) { + disconnect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + } else { + disconnect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate); + } } } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index a84aebc59..a5e38febe 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -115,6 +115,8 @@ protected: virtual void resizeEvent(QResizeEvent *event) override; + QStackedWidget* stack() const; + OpenGLBackend* video_renderer_; AudioBackend* audio_renderer_; @@ -131,6 +133,8 @@ private: int CalculateDivider(); + QStackedWidget* stack_; + ViewerSizer* sizer_; PlaybackControls* controls_;