diff --git a/.travis/install.sh b/.travis/install.sh index fa29d2ef8..8d57de697 100644 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -6,7 +6,13 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - sudo apt-get -y -o Dpkg::Options::="--force-overwrite" install qt511base qt511multimedia qt511svg qt511tools libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev cmake libopencolorio-dev libopenimageio-dev libgl1-mesa-dev + sudo apt-get -y -o Dpkg::Options::="--force-overwrite" install qt511base qt511multimedia qt511svg qt511tools libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev libopencolorio-dev libopenimageio-dev libgl1-mesa-dev source /opt/qt*/bin/qt*-env.sh + # Acquire latest cmake (apt somehow gets the wrong version?) + wget -c https://github.com/Kitware/CMake/releases/download/v3.17.2/cmake-3.17.2-Linux-x86_64.sh -O cmake.sh + chmod +x cmake.sh + ./cmake.sh --skip-license --prefix=cmake --exclude-dir + export PATH=$PWD/cmake/bin:$PATH + fi diff --git a/CMakeLists.txt b/CMakeLists.txt index ccb6e9478..ee3cacf7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -cmake_minimum_required(VERSION 3.9 FATAL_ERROR) +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) project(olive-editor VERSION 0.2.0 LANGUAGES CXX) @@ -41,7 +41,7 @@ find_package(OpenGL REQUIRED) find_package(OpenColorIO REQUIRED) -find_package(OpenImageIO REQUIRED) +find_package(OpenImageIO 1.6 REQUIRED) find_package(OpenEXR REQUIRED) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index abea45088..5b2c45a90 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -835,7 +835,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderi p.channel_layout(), FFmpegCommon::GetFFmpegSampleFormat(p.format()), p.sample_rate(), - static_cast(index_instance.stream()->codecpar->channel_layout), + channel_layout, static_cast(index_instance.stream()->codecpar->format), index_instance.stream()->codecpar->sample_rate, 0, diff --git a/app/dialog/export/codec/h264section.cpp b/app/dialog/export/codec/h264section.cpp index 42833f665..e0e50fb55 100644 --- a/app/dialog/export/codec/h264section.cpp +++ b/app/dialog/export/codec/h264section.cpp @@ -121,6 +121,7 @@ H264CRFSection::H264CRFSection(QWidget *parent) : crf_input->SetMinimum(kMinimumCRF); crf_input->SetMaximum(kMaximumCRF); crf_input->SetValue(kDefaultCRF); + crf_input->SetDefaultValue(kDefaultCRF); layout->addWidget(crf_input); connect(crf_slider_, &QSlider::valueChanged, crf_input, &IntegerSlider::SetValue); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 23480684b..3071d92c5 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -204,7 +204,9 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : // Set defaults format_combobox_->setCurrentIndex(kFormatMPEG4); video_tab_->width_slider()->SetValue(viewer_node_->video_params().width()); + video_tab_->width_slider()->SetDefaultValue(viewer_node_->video_params().width()); video_tab_->height_slider()->SetValue(viewer_node_->video_params().height()); + video_tab_->height_slider()->SetDefaultValue(viewer_node_->video_params().height()); video_tab_->set_frame_rate(viewer_node_->video_params().time_base().flipped()); audio_tab_->set_sample_rate(viewer_node_->audio_params().sample_rate()); audio_tab_->set_channel_layout(viewer_node_->audio_params().channel_layout()); diff --git a/app/dialog/export/exportadvancedvideodialog.cpp b/app/dialog/export/exportadvancedvideodialog.cpp index bbaba2ee6..1b11d85b9 100644 --- a/app/dialog/export/exportadvancedvideodialog.cpp +++ b/app/dialog/export/exportadvancedvideodialog.cpp @@ -19,6 +19,7 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(QWidget *parent) : thread_slider_ = new IntegerSlider(); thread_slider_->SetMinimum(0); + thread_slider_->SetDefaultValue(0); layout->addWidget(thread_slider_, row, 1); row++; diff --git a/app/dialog/speedduration/speedduration.cpp b/app/dialog/speedduration/speedduration.cpp index 4c6fc4ac9..84e1b1875 100644 --- a/app/dialog/speedduration/speedduration.cpp +++ b/app/dialog/speedduration/speedduration.cpp @@ -93,6 +93,7 @@ SpeedDurationDialog::SpeedDurationDialog(const rational& timebase, const QListSetMinimum(0); speed_slider_->SetDisplayType(FloatSlider::kPercentage); + speed_slider_->SetDefaultValue(1); speed_layout->addWidget(speed_slider_, row, 1); if (same_speed) { @@ -113,6 +114,9 @@ SpeedDurationDialog::SpeedDurationDialog(const rational& timebase, const QListSetMinimum(1); speed_layout->addWidget(duration_slider_, row, 1); + // Calculate duration that would occur if the speed was 100% + duration_slider_->SetDefaultValue(GetUnadjustedLengthTimestamp(clips_.first())); + if (same_duration) { duration_slider_->SetValue(Timecode::time_to_timestamp(clips_.first()->length(), timebase_)); } else { diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index c9ef24520..156c69818 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -27,7 +27,7 @@ PanNode::PanNode() samples_input_ = new NodeInput("samples_in", NodeParam::kSamples); AddInput(samples_input_); - panning_input_ = new NodeInput("panning_in", NodeParam::kFloat); + panning_input_ = new NodeInput("panning_in", NodeParam::kFloat, 0.0); panning_input_->set_property("min", -1.0); panning_input_->set_property("max", 1.0); panning_input_->set_property("view", "percent"); diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 9d815b19a..41b7dbcda 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -27,7 +27,7 @@ VolumeNode::VolumeNode() samples_input_ = new NodeInput("samples_in", NodeParam::kSamples); AddInput(samples_input_); - volume_input_ = new NodeInput("volume_in", NodeParam::kFloat, 1); + volume_input_ = new NodeInput("volume_in", NodeParam::kFloat, 1.0); volume_input_->set_property("min", 0.0); volume_input_->set_property("view", "db"); AddInput(volume_input_); diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index bafa5d530..ee9ae0a31 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -30,10 +30,10 @@ OLIVE_NAMESPACE_ENTER MatrixGenerator::MatrixGenerator() { - position_input_ = new NodeInput("pos_in", NodeParam::kVec2); + position_input_ = new NodeInput("pos_in", NodeParam::kVec2, QVector2D()); AddInput(position_input_); - rotation_input_ = new NodeInput("rot_in", NodeParam::kFloat); + rotation_input_ = new NodeInput("rot_in", NodeParam::kFloat, 0.0f); AddInput(rotation_input_); scale_input_ = new NodeInput("scale_in", NodeParam::kVec2, QVector2D(1.0f, 1.0f)); @@ -48,7 +48,7 @@ MatrixGenerator::MatrixGenerator() connect(uniform_scale_input_, &NodeInput::ValueChanged, this, &MatrixGenerator::UniformScaleChanged); AddInput(uniform_scale_input_); - anchor_input_ = new NodeInput("anchor_in", NodeParam::kVec2); + anchor_input_ = new NodeInput("anchor_in", NodeParam::kVec2, QVector2D()); AddInput(anchor_input_); } diff --git a/app/node/input.cpp b/app/node/input.cpp index 17c41c02b..db1bbb5c9 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -386,9 +386,22 @@ void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusi QVariant NodeInput::GetDefaultValue() const { + if (default_value_.isEmpty()) { + return QVariant(); + } + return combine_track_values_into_normal_value(default_value_); } +QVariant NodeInput::GetDefaultValueForTrack(int track) const +{ + if (default_value_.isEmpty()) { + return QVariant(); + } + + return default_value_.at(track); +} + QList NodeInput::GetDependencies(bool traverse, bool exclusive_only) const { QList list; diff --git a/app/node/input.h b/app/node/input.h index a8b776261..c9e1b1d8d 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -279,6 +279,8 @@ public: QVariant GetDefaultValue() const; + QVariant GetDefaultValueForTrack(int track) const; + QList GetDependencies(bool traverse = true, bool exclusive_only = false) const; QList GetExclusiveDependencies() const; diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index 9f2f4ca3e..149bcb08a 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -35,12 +35,12 @@ MathNode::MathNode() method_in_->set_is_keyframable(false); AddInput(method_in_); - param_a_in_ = new NodeInput(QStringLiteral("param_a_in"), NodeParam::kFloat); + param_a_in_ = new NodeInput(QStringLiteral("param_a_in"), NodeParam::kFloat, 0.0); param_a_in_->set_property(QStringLiteral("decimalplaces"), 8); param_a_in_->set_property(QStringLiteral("autotrim"), true); AddInput(param_a_in_); - param_b_in_ = new NodeInput(QStringLiteral("param_b_in"), NodeParam::kFloat); + param_b_in_ = new NodeInput(QStringLiteral("param_b_in"), NodeParam::kFloat, 0.0); param_b_in_->set_property(QStringLiteral("decimalplaces"), 8); param_b_in_->set_property(QStringLiteral("autotrim"), true); AddInput(param_b_in_); diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp index 7ab8ddb3a..9352fded8 100644 --- a/app/node/math/trigonometry/trigonometry.cpp +++ b/app/node/math/trigonometry/trigonometry.cpp @@ -29,7 +29,7 @@ TrigonometryNode::TrigonometryNode() method_in_->set_is_keyframable(false); AddInput(method_in_); - x_in_ = new NodeInput(QStringLiteral("x_in"), NodeParam::kFloat); + x_in_ = new NodeInput(QStringLiteral("x_in"), NodeParam::kFloat, 0.0); AddInput(x_in_); } diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index 9f9e4c84e..fd1a066c5 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -111,4 +111,11 @@ void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type) vw->ForceUpdate(); } +void ViewerPanelBase::closeEvent(QCloseEvent *e) +{ + static_cast(GetTimeBasedWidget())->Pause(); + + TimeBasedPanel::closeEvent(e); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index ed4c5651a..99340a983 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -62,6 +62,8 @@ public slots: protected: void CreateScopePanel(ScopePanel::Type type); + virtual void closeEvent(QCloseEvent* e) override; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 3f9933218..ab1f20b49 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -66,6 +66,7 @@ const QList &NodeParamViewWidgetBridge::widgets() const void NodeParamViewWidgetBridge::CreateWidgets() { + if (input_->IsArray()) { NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(static_cast(input_)); @@ -91,6 +92,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeParam::kInt: { IntegerSlider* slider = new IntegerSlider(); + slider->SetDefaultValue(input_->GetDefaultValue()); widgets_.append(slider); connect(slider, &IntegerSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; @@ -363,6 +365,7 @@ void NodeParamViewWidgetBridge::CreateSliders(int count) { for (int i=0;iSetDefaultValue(input_->GetDefaultValueForTrack(i)); widgets_.append(fs); connect(fs, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); } diff --git a/app/widget/slider/sliderbase.cpp b/app/widget/slider/sliderbase.cpp index 4e41119bf..5de18603d 100644 --- a/app/widget/slider/sliderbase.cpp +++ b/app/widget/slider/sliderbase.cpp @@ -45,12 +45,13 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) : editor_ = new FocusableLineEdit(this); addWidget(editor_); - connect(label_, SIGNAL(drag_start()), this, SLOT(LabelPressed())); - connect(label_, SIGNAL(dragged(int)), this, SLOT(LabelDragged(int))); - connect(label_, SIGNAL(drag_stop()), this, SLOT(LabelClicked())); - connect(label_, SIGNAL(focused()), this, SLOT(LabelClicked())); - connect(editor_, SIGNAL(Confirmed()), this, SLOT(LineEditConfirmed())); - connect(editor_, SIGNAL(Cancelled()), this, SLOT(LineEditCancelled())); + connect(label_, &SliderLabel::drag_start, this, &SliderBase::LabelPressed); + connect(label_, &SliderLabel::dragged, this, &SliderBase::LabelDragged); + connect(label_, &SliderLabel::drag_stop, this, &SliderBase::LabelClicked); + connect(label_, &SliderLabel::focused, this, &SliderBase::LabelClicked); + connect(label_, &SliderLabel::RequestReset, this, &SliderBase::ResetValue); + connect(editor_, &FocusableLineEdit::Confirmed, this, &SliderBase::LineEditConfirmed); + connect(editor_, &FocusableLineEdit::Cancelled, this, &SliderBase::LineEditCancelled); // Set valid cursor based on mode switch (mode_) { @@ -137,6 +138,11 @@ void SliderBase::SetValue(const QVariant &v) UpdateLabel(value_); } +void SliderBase::SetDefaultValue(const QVariant &v) +{ + default_value_ = v; +} + void SliderBase::SetMinimumInternal(const QVariant &v) { min_value_ = v; @@ -241,7 +247,6 @@ void SliderBase::LabelClicked() emit ValueChanged(value_); } else { // This was a simple click - // Load label's text into editor editor_->setText(ValueToString(value_)); @@ -327,4 +332,12 @@ void SliderBase::LineEditCancelled() label_->blockSignals(false); } +void SliderBase::ResetValue() +{ + if (!default_value_.isNull()) { + SetValue(default_value_); + emit ValueChanged(value_); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/slider/sliderbase.h b/app/widget/slider/sliderbase.h index 2a4b8899f..316e82f37 100644 --- a/app/widget/slider/sliderbase.h +++ b/app/widget/slider/sliderbase.h @@ -45,6 +45,8 @@ public: void SetAlignment(Qt::Alignment alignment); + void SetDefaultValue(const QVariant& v); + bool IsTristate() const; void SetTristate(); @@ -89,6 +91,7 @@ private: FocusableLineEdit* editor_; QVariant value_; + QVariant default_value_; bool has_min_; QVariant min_value_; @@ -120,6 +123,8 @@ private slots: void LineEditConfirmed(); void LineEditCancelled(); + + void ResetValue(); }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/slider/sliderlabel.cpp b/app/widget/slider/sliderlabel.cpp index aee4a8409..79f077757 100644 --- a/app/widget/slider/sliderlabel.cpp +++ b/app/widget/slider/sliderlabel.cpp @@ -31,7 +31,8 @@ OLIVE_NAMESPACE_ENTER SliderLabel::SliderLabel(QWidget *parent) : - QLabel(parent) + QLabel(parent), + dragging_(false) { QPalette p = palette(); @@ -53,51 +54,70 @@ SliderLabel::SliderLabel(QWidget *parent) : setFocusPolicy(Qt::TabFocus); } -void SliderLabel::mousePressEvent(QMouseEvent *) +void SliderLabel::mousePressEvent(QMouseEvent *e) { - emit drag_start(); + if (e->modifiers() & Qt::AltModifier) { + + emit RequestReset(); + + } else { #if defined(Q_OS_MAC) - CGAssociateMouseAndMouseCursorPosition(false); - CGDisplayHideCursor(kCGDirectMainDisplay); - CGGetLastMouseDelta(nullptr, nullptr); + CGAssociateMouseAndMouseCursorPosition(false); + CGDisplayHideCursor(kCGDirectMainDisplay); + CGGetLastMouseDelta(nullptr, nullptr); #else - drag_start_ = QCursor::pos(); + drag_start_ = QCursor::pos(); - static_cast(QApplication::instance())->setOverrideCursor(Qt::BlankCursor); + static_cast(QApplication::instance())->setOverrideCursor(Qt::BlankCursor); #endif + + emit drag_start(); + + dragging_ = true; + + } } void SliderLabel::mouseMoveEvent(QMouseEvent *) { - int32_t x_mvmt, y_mvmt; + if (dragging_) { - // Keep cursor in the same position + int32_t x_mvmt, y_mvmt; + + // Keep cursor in the same position #if defined(Q_OS_MAC) - CGGetLastMouseDelta(&x_mvmt, &y_mvmt); + CGGetLastMouseDelta(&x_mvmt, &y_mvmt); #else - QPoint current_pos = QCursor::pos(); + QPoint current_pos = QCursor::pos(); - x_mvmt = current_pos.x() - drag_start_.x(); - y_mvmt = drag_start_.y() - current_pos.y(); + x_mvmt = current_pos.x() - drag_start_.x(); + y_mvmt = drag_start_.y() - current_pos.y(); - QCursor::setPos(drag_start_); + QCursor::setPos(drag_start_); #endif - emit dragged(x_mvmt + y_mvmt); + emit dragged(x_mvmt + y_mvmt); + + } } void SliderLabel::mouseReleaseEvent(QMouseEvent *) { + if (dragging_) { + #if defined(Q_OS_MAC) - CGAssociateMouseAndMouseCursorPosition(true); - CGDisplayShowCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(true); + CGDisplayShowCursor(kCGDirectMainDisplay); #else - static_cast(QApplication::instance())->restoreOverrideCursor(); + static_cast(QApplication::instance())->restoreOverrideCursor(); #endif - // Emit a clicked signal - emit drag_stop(); + emit drag_stop(); + + dragging_ = false; + + } } void SliderLabel::focusInEvent(QFocusEvent *event) diff --git a/app/widget/slider/sliderlabel.h b/app/widget/slider/sliderlabel.h index c83e86bfc..641caeac4 100644 --- a/app/widget/slider/sliderlabel.h +++ b/app/widget/slider/sliderlabel.h @@ -51,10 +51,12 @@ signals: void focused(); + void RequestReset(); + private: QPoint drag_start_; - bool cancel_mm_event_; + bool dragging_; }; diff --git a/app/widget/viewer/CMakeLists.txt b/app/widget/viewer/CMakeLists.txt index 70df051ae..f745ca0d6 100644 --- a/app/widget/viewer/CMakeLists.txt +++ b/app/widget/viewer/CMakeLists.txt @@ -26,6 +26,9 @@ set(OLIVE_SOURCES widget/viewer/viewer.cpp widget/viewer/viewerdisplay.h widget/viewer/viewerdisplay.cpp + widget/viewer/viewerplaybacktimer.h + widget/viewer/viewerplaybacktimer.cpp + widget/viewer/viewerqueue.h widget/viewer/viewersafemargininfo.h widget/viewer/viewersizer.h widget/viewer/viewersizer.cpp diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 3ee2569b0..b11acc76b 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -35,7 +35,7 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) : audio_node_ = new AudioInput(); viewer_node_ = new ViewerOutput(); - connect(main_gl_widget(), &ViewerDisplayWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag); + connect(display_widget(), &ViewerDisplayWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag); controls_->SetAudioVideoDragButtonsVisible(true); connect(controls_, &PlaybackControls::VideoPressed, this, &FootageViewerWidget::StartVideoDrag); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 5d8cf5e62..26e8f188c 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -63,15 +63,13 @@ ViewerWidget::ViewerWidget(QWidget *parent) : sizer_ = new ViewerSizer(); stack_->addWidget(sizer_); - ViewerDisplayWidget* main_widget = new ViewerDisplayWidget(); - connect(main_widget, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); - connect(main_widget, &ViewerDisplayWidget::CursorColor, this, &ViewerWidget::CursorColor); - connect(main_widget, &ViewerDisplayWidget::LoadedBuffer, this, &ViewerWidget::LoadedBuffer); - connect(main_widget, &ViewerDisplayWidget::ColorProcessorChanged, this, &ViewerWidget::ColorProcessorChanged); - connect(main_widget, &ViewerDisplayWidget::ColorManagerChanged, this, &ViewerWidget::ColorManagerChanged); - connect(sizer_, &ViewerSizer::RequestMatrix, main_widget, &ViewerDisplayWidget::SetMatrix); - sizer_->SetWidget(main_widget); - gl_widgets_.append(main_widget); + display_widget_ = new ViewerDisplayWidget(); + connect(display_widget_, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); + connect(display_widget_, &ViewerDisplayWidget::CursorColor, this, &ViewerWidget::CursorColor); + connect(display_widget_, &ViewerDisplayWidget::ColorProcessorChanged, this, &ViewerWidget::ColorProcessorChanged); + connect(display_widget_, &ViewerDisplayWidget::ColorManagerChanged, this, &ViewerWidget::ColorManagerChanged); + connect(sizer_, &ViewerSizer::RequestMatrix, display_widget_, &ViewerDisplayWidget::SetMatrix); + sizer_->SetWidget(display_widget_); // Create waveform view when audio is connected and video isn't waveform_view_ = new AudioWaveformView(); @@ -117,6 +115,15 @@ ViewerWidget::ViewerWidget(QWidget *parent) : SetAutoMaxScrollBar(true); } +ViewerWidget::~ViewerWidget() +{ + QList windows = windows_; + + foreach (ViewerWindow* window, windows) { + delete window; + } +} + void ViewerWidget::TimeChangedEvent(const int64_t &i) { if (!time_changed_from_timer_) { @@ -133,7 +140,7 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i) PushScrubbedAudio(); - main_gl_widget()->SetTime(time_set); + display_widget_->SetTime(time_set); } last_time_ = i; @@ -170,8 +177,9 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) using_manager = nullptr; } - foreach (ViewerDisplayWidget* glw, gl_widgets_) { - glw->ConnectColorManager(using_manager); + display_widget_->ConnectColorManager(using_manager); + foreach (ViewerWindow* window, windows_) { + window->display_widget()->ConnectColorManager(using_manager); } divider_ = CalculateDivider(); @@ -202,8 +210,9 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) // Effectively disables the viewer and clears the state SizeChangedSlot(0, 0); - foreach (ViewerDisplayWidget* glw, gl_widgets_) { - glw->DisconnectColorManager(); + display_widget_->DisconnectColorManager(); + foreach (ViewerWindow* window, windows_) { + window->display_widget()->DisconnectColorManager(); } waveform_view_->ConnectTimelinePoints(nullptr); @@ -236,14 +245,9 @@ void ViewerWidget::resizeEvent(QResizeEvent *event) UpdateMinimumScale(); } -const QList &ViewerWidget::gl_widgets() const +ViewerDisplayWidget *ViewerWidget::display_widget() const { - return gl_widgets_; -} - -ViewerDisplayWidget *ViewerWidget::main_gl_widget() const -{ - return gl_widgets_.first(); + return display_widget_; } void ViewerWidget::TogglePlayPause() @@ -282,8 +286,9 @@ void ViewerWidget::SetOverrideSize(int width, int height) void ViewerWidget::SetMatrix(const QMatrix4x4 &mat) { - foreach (ViewerDisplayWidget* glw, gl_widgets_) { - glw->SetMatrix(mat); + display_widget_->SetMatrix(mat); + foreach (ViewerWindow* vw, windows_) { + vw->display_widget()->SetMatrix(mat); } } @@ -306,19 +311,19 @@ void ViewerWidget::SetFullScreen(QScreen *screen) ViewerWindow* vw = new ViewerWindow(this); - vw->showFullScreen(); vw->setGeometry(screen->geometry()); - vw->gl_widget()->ConnectColorManager(main_gl_widget()->color_manager()); - main_gl_widget()->ConnectSibling(vw->gl_widget()); + vw->showFullScreen(); + vw->display_widget()->ConnectColorManager(color_manager()); connect(vw, &ViewerWindow::destroyed, this, &ViewerWidget::WindowAboutToClose); - connect(vw->gl_widget(), &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); + connect(vw->display_widget(), &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); if (GetConnectedNode()) { vw->SetResolution(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height()); } + vw->display_widget()->SetImage(display_widget_->last_loaded_buffer()); + windows_.append(vw); - gl_widgets_.append(vw->gl_widget()); } void ViewerWidget::ForceUpdate() @@ -334,26 +339,45 @@ VideoRenderBackend *ViewerWidget::video_renderer() const ColorManager *ViewerWidget::color_manager() const { - return main_gl_widget()->color_manager(); + return display_widget_->color_manager(); } void ViewerWidget::SetGizmos(Node *node) { - main_gl_widget()->SetTimeTarget(GetConnectedNode()); - main_gl_widget()->SetGizmos(node); + display_widget_->SetTimeTarget(GetConnectedNode()); + display_widget_->SetGizmos(node); } void ViewerWidget::UpdateTextureFromNode(const rational& time) { - if (!GetConnectedNode() || time >= GetConnectedNode()->Length()) { - main_gl_widget()->SetImage(QString()); - video_renderer_->UpdateLastRequestedTime(time); - } else { - QString frame_fn = video_renderer_->GetCachedFrame(time); + { + QMutexLocker locker(playback_queue_.lock()); + + while (!playback_queue_.isEmpty()) { + const ViewerPlaybackFrame& pf = playback_queue_.first(); + + if (pf.timestamp == time) { + // Frame was in queue, no need to decode anything + SetDisplayImage(pf.frame, true); + QtConcurrent::run(this, &ViewerWidget::FillPlaybackQueue); + return; + } else { + playback_queue_.removeFirst(); + } + } + } + + // Frame was not in queue, will require decoding + if (FrameExistsAtTime(time)) { + QString frame_fn = GetCachedFilenameFromTime(time); if (!frame_fn.isEmpty()) { - main_gl_widget()->SetImage(frame_fn); + FramePtr f = DecodeCachedImage(frame_fn); + SetDisplayImage(f, false); } + } else { + SetDisplayImage(nullptr, false); + video_renderer_->UpdateLastRequestedTime(time); } } @@ -372,11 +396,21 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) QString audio_fn = audio_renderer_->CachePathName(); if (!audio_fn.isEmpty()) { AudioManager::instance()->SetOutputParams(audio_renderer_->params()); - AudioManager::instance()->StartOutput(audio_fn, audio_renderer_->params().time_to_bytes(GetTime()), playback_speed_); + AudioManager::instance()->StartOutput(audio_fn, + audio_renderer_->params().time_to_bytes(GetTime()), + playback_speed_); } - start_msec_ = QDateTime::currentMSecsSinceEpoch(); - start_timestamp_ = ruler()->GetTime(); + int64_t start_time = ruler()->GetTime(); + + playback_queue_next_frame_ = start_time; + FillPlaybackQueue(); + + playback_timer_.Start(start_time, playback_speed_, timebase_dbl()); + + foreach (ViewerWindow* window, windows_) { + window->Play(start_time, playback_speed_, timebase()); + } controls_->ShowPauseButton(); @@ -385,7 +419,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) if (playback_is_audio_only_) { connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate); } else { - connect(main_gl_widget(), &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); } } @@ -415,7 +449,7 @@ int ViewerWidget::CalculateDivider() { if (GetConnectedNode() && Config::Current()["AutoSelectDivider"].toBool()) { int long_side_of_video = qMax(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height()); - int long_side_of_widget = qMax(main_gl_widget()->width(), main_gl_widget()->height()); + int long_side_of_widget = qMax(display_widget_->width(), display_widget_->height()); return qMax(1, long_side_of_video / long_side_of_widget); } @@ -442,6 +476,113 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp sender->SetColorTransform(transform); } +void ViewerWidget::FillPlaybackQueue() +{ + playback_queue_.lock()->lock(); + foreach (ViewerWindow* window, windows_) { + window->queue()->lock()->lock(); + } + + while (playback_queue_.size() < 8) { + // Load frame from cache + FramePtr frame = nullptr; + + rational rtime = Timecode::timestamp_to_time(playback_queue_next_frame_, + timebase()); + + QString frame_fn = GetCachedFilenameFromTime(rtime); + + if (!frame_fn.isEmpty()) { + frame = DecodeCachedImage(frame_fn); + } + + ViewerPlaybackFrame f = {rtime, frame}; + + playback_queue_.append(f); + + foreach (ViewerWindow* window, windows_) { + window->queue()->append(f); + } + + if (!playback_speed_) { + break; + } + + playback_queue_next_frame_ += playback_speed_; + } + + foreach (ViewerWindow* window, windows_) { + window->queue()->lock()->unlock(); + } + playback_queue_.lock()->unlock(); +} + +QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) +{ + if (FrameExistsAtTime(time)) { + return video_renderer_->GetCachedFrame(time); + } else { + return QString(); + } +} + +bool ViewerWidget::FrameExistsAtTime(const rational &time) +{ + return GetConnectedNode() && time < GetConnectedNode()->Length(); +} + +FramePtr ViewerWidget::DecodeCachedImage(const QString &fn) +{ + FramePtr frame = nullptr; + + if (!fn.isEmpty() && QFileInfo::exists(fn)) { + auto input = OIIO::ImageInput::open(fn.toStdString()); + + if (input) { + + PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format, + input->spec().nchannels == kRGBAChannels); + + frame = Frame::Create(); + + frame->set_video_params(VideoRenderingParams(input->spec().width, + input->spec().height, + image_format)); + + frame->allocate(); + + input->read_image(input->spec().format, + frame->data(), + OIIO::AutoStride, + frame->linesize_bytes()); + + input->close(); + +#if OIIO_VERSION < 10903 + OIIO::ImageInput::destroy(input); +#endif + + } else { + qWarning() << "OIIO Error:" << OIIO::geterror().c_str(); + } + } + + return frame; +} + +void ViewerWidget::SetDisplayImage(FramePtr frame, bool main_only) +{ + display_widget_->SetImage(frame); + + if (!main_only) { + foreach (ViewerWindow* vw, windows_) { + vw->display_widget()->SetImage(frame); + } + } + + emit LoadedBuffer(frame.get()); +} + void ViewerWidget::UpdateStack() { if (GetConnectedNode() @@ -515,10 +656,7 @@ void ViewerWidget::ContextMenuSetCustomSafeMargins() void ViewerWidget::WindowAboutToClose() { - ViewerWindow* vw = static_cast(sender()); - - windows_.removeAll(vw); - gl_widgets_.removeAll(vw->gl_widget()); + windows_.removeOne(static_cast(sender())); } void ViewerWidget::ContextMenuScopeTriggered(QAction *action) @@ -528,9 +666,7 @@ void ViewerWidget::ContextMenuScopeTriggered(QAction *action) void ViewerWidget::RendererGeneratedFrame(FramePtr f) { - foreach (ViewerDisplayWidget* glw, gl_widgets_) { - glw->SetImageFromLoadBuffer(f.get()); - } + SetDisplayImage(f, false); } void ViewerWidget::UpdateRendererParameters() @@ -551,7 +687,7 @@ void ViewerWidget::UpdateRendererParameters() video_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr); } - main_gl_widget()->SetVideoParams(vparam); + display_widget_->SetVideoParams(vparam); AudioRenderingParams aparam(GetConnectedNode()->audio_params(), SampleFormat::kInternalFormat); @@ -708,7 +844,17 @@ void ViewerWidget::Pause() if (playback_is_audio_only_) { disconnect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate); } else { - disconnect(main_gl_widget(), &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + } + + foreach (ViewerWindow* window, windows_) { + window->Pause(); + } + + { + playback_queue_.lock()->lock(); + playback_queue_.clear(); + playback_queue_.lock()->unlock(); } } } @@ -754,13 +900,15 @@ void ViewerWidget::ShuttleRight() void ViewerWidget::SetColorTransform(const ColorTransform &transform) { - SetColorTransform(transform, main_gl_widget()); + SetColorTransform(transform, display_widget_); } void ViewerWidget::SetSignalCursorColorEnabled(bool e) { - foreach (ViewerDisplayWidget* glw, gl_widgets_) { - glw->SetSignalCursorColorEnabled(e); + display_widget_->SetSignalCursorColorEnabled(e); + + foreach (ViewerWindow* vw, windows_) { + vw->display_widget()->SetSignalCursorColorEnabled(e); } } @@ -776,11 +924,7 @@ void ViewerWidget::TimebaseChangedEvent(const rational &timebase) void ViewerWidget::PlaybackTimerUpdate() { - int64_t real_time = QDateTime::currentMSecsSinceEpoch() - start_msec_; - - int64_t frames_since_start = qRound(static_cast(real_time) / (timebase_dbl() * 1000)); - - int64_t current_time = start_timestamp_ + frames_since_start * playback_speed_; + int64_t current_time = playback_timer_.GetTimestampNow(); int64_t min_time, max_time; diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 164f77122..4fcd386bf 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -36,6 +36,8 @@ #include "render/backend/opengl/opengltexture.h" #include "render/backend/audio/audiobackend.h" #include "viewerdisplay.h" +#include "viewerplaybacktimer.h" +#include "viewerqueue.h" #include "viewersizer.h" #include "viewerwindow.h" #include "widget/playbackcontrols/playbackcontrols.h" @@ -52,6 +54,8 @@ class ViewerWidget : public TimeBasedWidget public: ViewerWidget(QWidget* parent = nullptr); + virtual ~ViewerWidget() override; + void SetPlaybackControlsEnabled(bool enabled); void SetTimeRulerEnabled(bool enabled); @@ -116,7 +120,7 @@ signals: void CursorColor(const Color& reference, const Color& display); /** - * @brief Wrapper for ViewerGLWidget::LoadedBuffer() + * @brief Signal emitted when a new frame is loaded */ void LoadedBuffer(Frame* load_buffer); @@ -154,8 +158,7 @@ protected: PlaybackControls* controls_; - const QList &gl_widgets() const; - ViewerDisplayWidget* main_gl_widget() const; + ViewerDisplayWidget* display_widget() const; private: void UpdateTimeInternal(int64_t i); @@ -172,14 +175,21 @@ private: void SetColorTransform(const ColorTransform& transform, ViewerDisplayWidget* sender); + void FillPlaybackQueue(); + + QString GetCachedFilenameFromTime(const rational& time); + + bool FrameExistsAtTime(const rational& time); + + FramePtr DecodeCachedImage(const QString& fn); + + void SetDisplayImage(FramePtr frame, bool main_only); + QStackedWidget* stack_; ViewerSizer* sizer_; - qint64 start_msec_; - int64_t start_timestamp_; - - int playback_speed_; + QAtomicInt playback_speed_; qint64 frame_cache_job_time_; @@ -201,10 +211,15 @@ private: QList windows_; - QList gl_widgets_; + ViewerDisplayWidget* display_widget_; ViewerDisplayWidget* context_menu_widget_; + ViewerPlaybackTimer playback_timer_; + + ViewerQueue playback_queue_; + int64_t playback_queue_next_frame_; + private slots: void PlaybackTimerUpdate(); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 3d86eb5d6..6fcc8e0e2 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -30,6 +30,7 @@ #include #include "common/define.h" +#include "common/functiontimer.h" #include "gizmotraverser.h" #include "render/backend/opengl/openglrenderfunctions.h" #include "render/backend/opengl/openglshader.h" @@ -43,10 +44,10 @@ bool ViewerDisplayWidget::nouveau_check_done_ = false; ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : ManagedDisplayWidget(parent), - has_image_(false), signal_cursor_color_(false), gizmos_(nullptr), - gizmo_click_(false) + gizmo_click_(false), + last_loaded_buffer_(nullptr) { } @@ -61,81 +62,24 @@ void ViewerDisplayWidget::SetMatrix(const QMatrix4x4 &mat) update(); } -void ViewerDisplayWidget::SetImage(const QString &fn) -{ - has_image_ = false; - - if (!fn.isEmpty() && QFileInfo::exists(fn)) { - auto input = OIIO::ImageInput::open(fn.toStdString()); - - if (input) { - - PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format, - input->spec().nchannels == kRGBAChannels); - - // Ensure the following texture operations are done in our context (in case we're in a separate window for instance) - makeCurrent(); - - if (!texture_.IsCreated() - || texture_.width() != input->spec().width - || texture_.height() != input->spec().height - || texture_.format() != image_format) { - load_buffer_.destroy(); - texture_.Destroy(); - - load_buffer_.set_video_params(VideoRenderingParams(input->spec().width, input->spec().height, image_format)); - load_buffer_.allocate(); - - texture_.Create(context(), VideoRenderingParams(input->spec().width, input->spec().height, image_format)); - } - - input->read_image(input->spec().format, load_buffer_.data(), OIIO::AutoStride, load_buffer_.linesize_bytes()); - input->close(); - - texture_.Upload(&load_buffer_); - - doneCurrent(); - - emit LoadedBuffer(&load_buffer_); - - has_image_ = true; - -#if OIIO_VERSION < 10903 - OIIO::ImageInput::destroy(input); -#endif - - } else { - qWarning() << "OIIO Error:" << OIIO::geterror().c_str(); - } - } - - update(); - - if (has_image_) { - emit LoadedBuffer(&load_buffer_); - } else { - emit LoadedBuffer(nullptr); - } -} - void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool e) { signal_cursor_color_ = e; setMouseTracking(e); } -void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer) +void ViewerDisplayWidget::SetImage(FramePtr in_buffer) { - has_image_ = in_buffer; + last_loaded_buffer_ = in_buffer; - if (has_image_) { + if (last_loaded_buffer_) { makeCurrent(); if (!texture_.IsCreated() || texture_.width() != in_buffer->width() || texture_.height() != in_buffer->height() || texture_.format() != in_buffer->format()) { - texture_.Create(context(), in_buffer->video_params(), in_buffer->data(), load_buffer_.linesize_pixels()); + texture_.Create(context(), in_buffer->video_params(), in_buffer->data(), in_buffer->linesize_pixels()); } else { texture_.Upload(in_buffer); } @@ -146,12 +90,6 @@ void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer) update(); } -void ViewerDisplayWidget::ConnectSibling(ViewerDisplayWidget *sibling) -{ - connect(this, &ViewerDisplayWidget::LoadedBuffer, sibling, &ViewerDisplayWidget::SetImageFromLoadBuffer, Qt::QueuedConnection); - sibling->SetImageFromLoadBuffer(&load_buffer_); -} - const ViewerSafeMarginInfo &ViewerDisplayWidget::GetSafeMargin() const { return safe_margin_; @@ -189,6 +127,11 @@ void ViewerDisplayWidget::SetTime(const rational &time) } } +FramePtr ViewerDisplayWidget::last_loaded_buffer() const +{ + return last_loaded_buffer_; +} + void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) { if (gizmos_ @@ -215,17 +158,17 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) if (signal_cursor_color_) { Color reference, display; - if (has_image_) { + if (last_loaded_buffer_) { QVector3D pixel_pos(static_cast(event->x()) / static_cast(width()) * 2.0f - 1.0f, static_cast(event->y()) / static_cast(height()) * 2.0f - 1.0f, 0); pixel_pos = pixel_pos * matrix_.inverted(); - int frame_x = qRound((pixel_pos.x() + 1.0f) * 0.5f * load_buffer_.width()); - int frame_y = qRound((pixel_pos.y() + 1.0f) * 0.5f * load_buffer_.height()); + int frame_x = qRound((pixel_pos.x() + 1.0f) * 0.5f * last_loaded_buffer_->width()); + int frame_y = qRound((pixel_pos.y() + 1.0f) * 0.5f * last_loaded_buffer_->height()); - reference = load_buffer_.get_pixel(frame_x, frame_y); + reference = last_loaded_buffer_->get_pixel(frame_x, frame_y); display = color_service()->ConvertColor(reference); } @@ -277,7 +220,7 @@ void ViewerDisplayWidget::paintGL() f->glClear(GL_COLOR_BUFFER_BIT); // We only draw if we have a pipeline - if (has_image_ && color_service()) { + if (last_loaded_buffer_ && color_service()) { // Bind retrieved texture f->glBindTexture(GL_TEXTURE_2D, texture_.texture()); diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 996758718..0f0d2b81c 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -66,15 +66,8 @@ public: virtual ~ViewerDisplayWidget() override; - /** - * @brief Set an image to load and display on screen - */ - void SetImage(const QString& fn); - const QMatrix4x4& GetMatrix(); - void ConnectSibling(ViewerDisplayWidget* sibling); - const ViewerSafeMarginInfo& GetSafeMargin() const; void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin); @@ -82,6 +75,8 @@ public: void SetVideoParams(const VideoRenderingParams& params); void SetTime(const rational& time); + FramePtr last_loaded_buffer() const; + public slots: /** * @brief Set the transformation matrix to draw with @@ -105,7 +100,7 @@ public slots: * If there are multiple ViewerGLWidgets showing the same thing, this is faster than decoding the image from file * each time. */ - void SetImageFromLoadBuffer(Frame* in_buffer); + void SetImage(FramePtr in_buffer); signals: /** @@ -118,15 +113,6 @@ signals: */ void CursorColor(const Color& reference, const Color& display); - /** - * @brief Signal emitted when a buffer is loaded from file into memory - * - * This buffer will be the direct output of the renderer in reference space in CPU memory. - * - * Connect this to the SetImageFromLoadBuffer() slot of another ViewerGLWidget to show the same thing - */ - void LoadedBuffer(Frame* load_buffer); - protected: /** * @brief Override the mouse press event for the DragStarted() signal and gizmos @@ -174,17 +160,10 @@ private: */ QMatrix4x4 matrix_; - /** - * @brief Buffer to load images into RAM before sending them to the display - */ - Frame load_buffer_; - #ifdef Q_OS_LINUX static bool nouveau_check_done_; #endif - bool has_image_; - bool signal_cursor_color_; ViewerSafeMarginInfo safe_margin_; @@ -197,6 +176,8 @@ private: rational time_; + FramePtr last_loaded_buffer_; + private slots: /** * @brief Slot to connect just before the OpenGL context is destroyed to clean up resources diff --git a/app/widget/viewer/viewerplaybacktimer.cpp b/app/widget/viewer/viewerplaybacktimer.cpp new file mode 100644 index 000000000..e07e925c9 --- /dev/null +++ b/app/widget/viewer/viewerplaybacktimer.cpp @@ -0,0 +1,44 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "viewerplaybacktimer.h" + +#include + +OLIVE_NAMESPACE_ENTER + +void ViewerPlaybackTimer::Start(const int64_t &start_timestamp, const int &playback_speed, const double &timebase) +{ + start_msec_ = QDateTime::currentMSecsSinceEpoch(); + start_timestamp_ = start_timestamp; + playback_speed_ = playback_speed; + timebase_ = timebase; +} + +int64_t ViewerPlaybackTimer::GetTimestampNow() const +{ + int64_t real_time = QDateTime::currentMSecsSinceEpoch() - start_msec_; + + int64_t frames_since_start = qRound(static_cast(real_time) / (timebase_ * 1000)); + + return start_timestamp_ + frames_since_start * playback_speed_; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewerplaybacktimer.h b/app/widget/viewer/viewerplaybacktimer.h new file mode 100644 index 000000000..a4669a571 --- /dev/null +++ b/app/widget/viewer/viewerplaybacktimer.h @@ -0,0 +1,48 @@ +/*** + + 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 . + +***/ + +#ifndef VIEWERPLAYBACKTIMER_H +#define VIEWERPLAYBACKTIMER_H + +#include + +#include "common/define.h" + +OLIVE_NAMESPACE_ENTER + +class ViewerPlaybackTimer { +public: + void Start(const int64_t& start_timestamp, const int& playback_speed, const double& timebase); + + int64_t GetTimestampNow() const; + +private: + qint64 start_msec_; + int64_t start_timestamp_; + + int playback_speed_; + + double timebase_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // VIEWERPLAYBACKTIMER_H diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h new file mode 100644 index 000000000..a08816051 --- /dev/null +++ b/app/widget/viewer/viewerqueue.h @@ -0,0 +1,50 @@ +/*** + + 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 . + +***/ + +#ifndef VIEWERQUEUE_H +#define VIEWERQUEUE_H + +#include + +#include "codec/frame.h" + +OLIVE_NAMESPACE_ENTER + +struct ViewerPlaybackFrame { + rational timestamp; + FramePtr frame; +}; + +class ViewerQueue : public QLinkedList { +public: + ViewerQueue() = default; + + QMutex* lock() { + return &queue_lock_; + } + +private: + QMutex queue_lock_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // VIEWERQUEUE_H diff --git a/app/widget/viewer/viewerwindow.cpp b/app/widget/viewer/viewerwindow.cpp index 3f92f997d..04ae55afb 100644 --- a/app/widget/viewer/viewerwindow.cpp +++ b/app/widget/viewer/viewerwindow.cpp @@ -23,6 +23,8 @@ #include #include +#include "common/timecodefunctions.h" + OLIVE_NAMESPACE_ENTER ViewerWindow::ViewerWindow(QWidget *parent) : @@ -32,13 +34,13 @@ ViewerWindow::ViewerWindow(QWidget *parent) : layout->setMargin(0); layout->setSpacing(0); - gl_widget_ = new ViewerDisplayWidget(); - layout->addWidget(gl_widget_); + display_widget_ = new ViewerDisplayWidget(); + layout->addWidget(display_widget_); } -ViewerDisplayWidget *ViewerWindow::gl_widget() const +ViewerDisplayWidget *ViewerWindow::display_widget() const { - return gl_widget_; + return display_widget_; } void ViewerWindow::SetResolution(int width, int height) @@ -57,7 +59,26 @@ void ViewerWindow::SetResolution(int width, int height) mat.scale(1.0f, window_ar / image_ar, 1.0f); } - gl_widget_->SetMatrix(mat); + display_widget_->SetMatrix(mat); +} + +void ViewerWindow::Play(const int64_t& start_timestamp, const int& playback_speed, const rational &timebase) +{ + timer_.Start(start_timestamp, playback_speed, timebase.toDouble()); + + playback_timebase_ = timebase; + + connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWindow::UpdateFromQueue); + + display_widget_->update(); +} + +void ViewerWindow::Pause() +{ + disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWindow::UpdateFromQueue); + + QMutexLocker locker(queue_.lock()); + queue_.clear(); } void ViewerWindow::keyPressEvent(QKeyEvent *e) @@ -76,4 +97,24 @@ void ViewerWindow::closeEvent(QCloseEvent *e) deleteLater(); } +void ViewerWindow::UpdateFromQueue() +{ + int64_t t = timer_.GetTimestampNow(); + + rational time = Timecode::timestamp_to_time(t, playback_timebase_); + + QMutexLocker locker(queue_.lock()); + while (!queue_.isEmpty()) { + const ViewerPlaybackFrame& pf = queue_.first(); + + if (pf.timestamp == time) { + // Frame was in queue, no need to decode anything + display_widget_->SetImage(pf.frame); + return; + } else { + queue_.removeFirst(); + } + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewerwindow.h b/app/widget/viewer/viewerwindow.h index 8baa30c9c..2f7742c9f 100644 --- a/app/widget/viewer/viewerwindow.h +++ b/app/widget/viewer/viewerwindow.h @@ -24,6 +24,8 @@ #include #include "viewerdisplay.h" +#include "viewerplaybacktimer.h" +#include "viewerqueue.h" OLIVE_NAMESPACE_ENTER @@ -32,20 +34,37 @@ class ViewerWindow : public QWidget public: ViewerWindow(QWidget* parent = nullptr); - ViewerDisplayWidget* gl_widget() const; + ViewerDisplayWidget* display_widget() const; /** * @brief Used to adjust resulting picture to be the right aspect ratio */ void SetResolution(int width, int height); + ViewerQueue* queue() { + return &queue_; + } + + void Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase); + + void Pause(); + protected: virtual void keyPressEvent(QKeyEvent* e) override; virtual void closeEvent(QCloseEvent* e) override; +private slots: + void UpdateFromQueue(); + private: - ViewerDisplayWidget* gl_widget_; + ViewerDisplayWidget* display_widget_; + + ViewerQueue queue_; + + ViewerPlaybackTimer timer_; + + rational playback_timebase_; }; diff --git a/appveyor.yml b/appveyor.yml index ed053f1e1..436930972 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,9 +14,9 @@ install: - cd %APPVEYOR_BUILD_FOLDER% build_script: - cmd: .appveyor\build.bat -artifacts: -- path: Olive*.zip - name: Olive Portable -- path: Olive*.exe - name: Olive Installer +#artifacts: +#- path: Olive*.zip +# name: Olive Portable +#- path: Olive*.exe +# name: Olive Installer cache: c:\tools\vcpkg\installed\