diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index 9f9e4c84e..73fd1cade 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -111,4 +111,18 @@ void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type) vw->ForceUpdate(); } +void ViewerPanelBase::showEvent(QShowEvent *e) +{ + static_cast(GetTimeBasedWidget())->Pause(); + + TimeBasedPanel::showEvent(e); +} + +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..d620a94f1 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -62,6 +62,10 @@ public slots: protected: void CreateScopePanel(ScopePanel::Type type); + virtual void showEvent(QShowEvent* e) override; + + virtual void closeEvent(QCloseEvent* e) override; + }; OLIVE_NAMESPACE_EXIT 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 34fd47a6f..4b9461cde 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,27 +339,29 @@ 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) { { - QMutexLocker locker(&playback_frame_queue_lock_); - while (!playback_frame_queue_.isEmpty()) { - PlaybackFrame pf = playback_frame_queue_.takeFirst(); + 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 - main_gl_widget()->SetImageFromLoadBuffer(pf.frame.get()); + SetDisplayImage(pf.frame, true); QtConcurrent::run(this, &ViewerWidget::FillPlaybackQueue); return; + } else { + playback_queue_.removeFirst(); } } } @@ -365,10 +372,10 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time) if (!frame_fn.isEmpty()) { FramePtr f = DecodeCachedImage(frame_fn); - main_gl_widget()->SetImageFromLoadBuffer(f.get()); + SetDisplayImage(f, false); } } else { - main_gl_widget()->SetImageFromLoadBuffer(nullptr); + SetDisplayImage(nullptr, false); video_renderer_->UpdateLastRequestedTime(time); } } @@ -393,9 +400,12 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) playback_speed_); } - start_msec_ = QDateTime::currentMSecsSinceEpoch(); - start_timestamp_ = ruler()->GetTime(); - playback_frame_queue_next_frame_ = start_timestamp_; + playback_queue_next_frame_ = ruler()->GetTime(); + playback_timer_.Start(playback_queue_next_frame_, playback_speed_, timebase_dbl()); + + foreach (ViewerWindow* window, windows_) { + window->Play(playback_queue_next_frame_, playback_speed_, timebase()); + } controls_->ShowPauseButton(); @@ -404,10 +414,10 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) if (playback_is_audio_only_) { connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate); } else { - FillPlaybackQueue(); - - connect(main_gl_widget(), &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); + connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate); } + + FillPlaybackQueue(); } void ViewerWidget::PushScrubbedAudio() @@ -436,7 +446,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); } @@ -465,12 +475,16 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp void ViewerWidget::FillPlaybackQueue() { - playback_frame_queue_lock_.lock(); - while (playback_frame_queue_.size() < 8) { + 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_frame_queue_next_frame_, + rational rtime = Timecode::timestamp_to_time(playback_queue_next_frame_, timebase()); QString frame_fn = GetCachedFilenameFromTime(rtime); @@ -479,15 +493,25 @@ void ViewerWidget::FillPlaybackQueue() frame = DecodeCachedImage(frame_fn); } - playback_frame_queue_.append({rtime, frame}); + ViewerPlaybackFrame f = {rtime, frame}; + + playback_queue_.append(f); + + foreach (ViewerWindow* window, windows_) { + window->queue()->append(f); + } if (!playback_speed_) { break; } - playback_frame_queue_next_frame_ += playback_speed_; + playback_queue_next_frame_ += playback_speed_; } - playback_frame_queue_lock_.unlock(); + + foreach (ViewerWindow* window, windows_) { + window->queue()->lock()->unlock(); + } + playback_queue_.lock()->unlock(); } QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) @@ -543,6 +567,19 @@ FramePtr ViewerWidget::DecodeCachedImage(const QString &fn) 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() @@ -616,10 +653,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) @@ -629,9 +663,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() @@ -652,7 +684,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); @@ -809,12 +841,18 @@ 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); } - playback_frame_queue_lock_.lock(); - playback_frame_queue_.clear(); - playback_frame_queue_lock_.unlock(); + foreach (ViewerWindow* window, windows_) { + window->Pause(); + } + + { + playback_queue_.lock()->lock(); + playback_queue_.clear(); + playback_queue_.lock()->unlock(); + } } } @@ -859,13 +897,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); } } @@ -881,11 +921,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 26abc3985..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); @@ -180,13 +183,12 @@ private: FramePtr DecodeCachedImage(const QString& fn); + void SetDisplayImage(FramePtr frame, bool main_only); + QStackedWidget* stack_; ViewerSizer* sizer_; - qint64 start_msec_; - int64_t start_timestamp_; - QAtomicInt playback_speed_; qint64 frame_cache_job_time_; @@ -209,18 +211,14 @@ private: QList windows_; - QList gl_widgets_; + ViewerDisplayWidget* display_widget_; ViewerDisplayWidget* context_menu_widget_; - struct PlaybackFrame { - rational timestamp; - FramePtr frame; - }; + ViewerPlaybackTimer playback_timer_; - QMutex playback_frame_queue_lock_; - QLinkedList playback_frame_queue_; - int64_t playback_frame_queue_next_frame_; + 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 68584b836..6fcc8e0e2 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -44,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) { } @@ -68,18 +68,18 @@ void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool 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); } @@ -88,14 +88,6 @@ void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer) } update(); - - emit LoadedBuffer(in_buffer); -} - -void ViewerDisplayWidget::ConnectSibling(ViewerDisplayWidget *sibling) -{ - connect(this, &ViewerDisplayWidget::LoadedBuffer, sibling, &ViewerDisplayWidget::SetImageFromLoadBuffer, Qt::QueuedConnection); - sibling->SetImageFromLoadBuffer(&load_buffer_); } const ViewerSafeMarginInfo &ViewerDisplayWidget::GetSafeMargin() const @@ -135,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_ @@ -161,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); } @@ -223,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 307c350a6..0f0d2b81c 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -68,8 +68,6 @@ public: const QMatrix4x4& GetMatrix(); - void ConnectSibling(ViewerDisplayWidget* sibling); - const ViewerSafeMarginInfo& GetSafeMargin() const; void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin); @@ -77,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 @@ -100,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: /** @@ -113,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 @@ -169,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_; @@ -192,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_; };