viewer: rewrote playback code for stability

The viewer playback used to rely primarily on the vsync, which was
extremely smooth, but had issues when there was only audio or the viewer
was hidden. This was bodged by attaching to the audio in these scenarios,
but still not perfect.

Now we run a timer in tandem with vsync for the best of both worlds.
Playback is no longer wholly reliant on vsync, yet the viewer can remain
as smooth as possible using vsync.
This commit is contained in:
itsmattkc
2020-06-06 00:20:06 +10:00
parent bad039e7a9
commit 550d30ed8f
2 changed files with 121 additions and 59 deletions
+114 -55
View File
@@ -57,7 +57,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
divider_(Config::Current()["DefaultViewerDivider"].toInt()),
override_color_manager_(nullptr),
time_changed_from_timer_(false),
playback_is_audio_only_(false),
prequeuing_(false),
busy_(false),
our_cache_background_task_(nullptr)
@@ -128,6 +127,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
// Ensures renderer is updated if the global pixel format is changed
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters);
connect(&playback_backup_timer_, &QTimer::timeout, this, &ViewerWidget::PlaybackTimerUpdate);
SetAutoMaxScrollBar(true);
}
@@ -152,9 +153,11 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
if (GetConnectedNode() && last_time_ != i) {
rational time_set = Timecode::timestamp_to_time(i, timebase());
UpdateTextureFromNode(time_set);
if (!IsPlaying()) {
UpdateTextureFromNode(time_set);
PushScrubbedAudio();
PushScrubbedAudio();
}
display_widget_->SetTime(time_set);
}
@@ -226,6 +229,12 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
{
Pause();
if (cache_background_task_ == our_cache_background_task_) {
StopAllBackgroundCacheTasks(true);
cache_background_task_ = nullptr;
}
cache_wait_timer_.stop();
SetTimebase(rational());
disconnect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
@@ -249,12 +258,6 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
waveform_view_->SetViewer(nullptr);
waveform_view_->ConnectTimelinePoints(nullptr);
if (cache_background_task_ == our_cache_background_task_) {
StopAllBackgroundCacheTasks(true);
cache_background_task_ = nullptr;
}
cache_wait_timer_.stop();
}
void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n)
@@ -438,12 +441,6 @@ void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational
void ViewerWidget::UpdateTextureFromNode(const rational& time)
{
if (!FrameExistsAtTime(time)) {
// There is definitely no frame here, we can immediately flip to showing nothing
SetDisplayImage(nullptr, false);
return;
}
// Check playback queue for a frame
if (IsPlaying()) {
@@ -460,8 +457,7 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
} else {
// Skip this frame
playback_queue_.pop_front();
RequestNextFrameForQueue();
PopOldestFrameFromPlaybackQueue();
}
}
@@ -470,6 +466,12 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
}
if (!FrameExistsAtTime(time)) {
// There is definitely no frame here, we can immediately flip to showing nothing
SetDisplayImage(nullptr, false);
return;
}
// Frame was not in queue, will require rendering or decoding from cache
RenderTicketWatcher* watcher = new RenderTicketWatcher();
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame);
@@ -486,6 +488,15 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
return;
}
// If the playhead is beyond the end, restart at 0
if (!in_to_out_only && GetTime() >= GetConnectedNode()->GetLength()) {
if (speed > 0) {
SetTimeAndSignal(0);
} else {
SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedNode()->GetLength(), timebase()));
}
}
playback_speed_ = speed;
play_in_to_out_only_ = in_to_out_only;
@@ -493,13 +504,17 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
controls_->ShowPauseButton();
playback_is_audio_only_ = (stack_->currentWidget() != sizer_ || !isVisible());
// Attempt to fill playback queue
if (stack_->currentWidget() == sizer_) {
prequeue_length_ = DeterminePlaybackQueueSize();
if (playback_is_audio_only_) {
connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate);
FinishPlayPreprocess();
} else {
FillPlaybackQueue();
if (prequeue_length_ > 0) {
prequeuing_ = true;
for (int i=0; i<prequeue_length_; i++) {
RequestNextFrameForQueue();
}
}
}
if (!busy_) {
@@ -509,6 +524,10 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
StopAllBackgroundCacheTasks(false);
cache_wait_timer_.stop();
if (!prequeuing_) {
FinishPlayPreprocess();
}
}
void ViewerWidget::PauseInternal()
@@ -518,17 +537,14 @@ void ViewerWidget::PauseInternal()
playback_speed_ = 0;
controls_->ShowPlayButton();
if (playback_is_audio_only_) {
disconnect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate);
} else {
disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
}
disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::ForceUpdate);
foreach (ViewerWindow* window, windows_) {
window->Pause();
}
playback_queue_.clear();
playback_backup_timer_.stop();
}
prequeuing_ = false;
@@ -589,15 +605,6 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp
sender->SetColorTransform(transform);
}
void ViewerWidget::FillPlaybackQueue()
{
prequeuing_ = true;
for (int i=0; i<kMaxPreQueueSize; i++) {
RequestNextFrameForQueue();
}
}
QString ViewerWidget::GetCachedFilenameFromTime(const rational &time)
{
if (FrameExistsAtTime(time)) {
@@ -615,7 +622,7 @@ QString ViewerWidget::GetCachedFilenameFromTime(const rational &time)
bool ViewerWidget::FrameExistsAtTime(const rational &time)
{
return GetConnectedNode() && time >= 0 && time < GetConnectedNode()->GetLength();
return GetConnectedNode() && time >= 0 && time < GetConnectedNode()->video_frame_cache()->GetLength();// GetConnectedNode()->GetLength();
}
void ViewerWidget::SetDisplayImage(FramePtr frame, bool main_only)
@@ -636,10 +643,6 @@ void ViewerWidget::RequestNextFrameForQueue()
rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_,
timebase());
if (!FrameExistsAtTime(next_time)) {
return;
}
playback_queue_next_frame_ += playback_speed_;
RenderTicketWatcher* watcher = new RenderTicketWatcher();
@@ -670,12 +673,13 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queu
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeVideo, TimeRange(t, t));
QtConcurrent::run(DecodeCachedImage, ticket, cache_fn, t);
return ticket;
}
}
void ViewerWidget::FinishPlayPreprocess()
{
int64_t start_time = ruler()->GetTime();
int64_t playback_start_time = ruler()->GetTime();
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetCacheFilename();
if (!audio_fn.isEmpty()) {
@@ -685,15 +689,18 @@ void ViewerWidget::FinishPlayPreprocess()
playback_speed_);
}
playback_timer_.Start(start_time, playback_speed_, timebase_dbl());
playback_timer_.Start(playback_start_time, playback_speed_, timebase_dbl());
foreach (ViewerWindow* window, windows_) {
window->Play(start_time, playback_speed_, timebase());
window->Play(playback_start_time, playback_speed_, timebase());
}
if (!playback_is_audio_only_) {
connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
}
connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::ForceUpdate);
playback_backup_timer_.setInterval(qFloor(timebase_dbl()));
playback_backup_timer_.start();
PlaybackTimerUpdate();
}
VideoRenderingParams ViewerWidget::GenerateVideoParams() const
@@ -710,6 +717,31 @@ AudioRenderingParams ViewerWidget::GenerateAudioParams() const
SampleFormat::kInternalFormat);
}
int ViewerWidget::DeterminePlaybackQueueSize()
{
int64_t end_ts;
if (playback_speed_ > 0) {
end_ts = Timecode::time_to_timestamp(GetConnectedNode()->video_frame_cache()->GetLength(),
timebase());
} else {
end_ts = 0;
}
int remaining_frames = (end_ts - GetTimestamp()) * playback_speed_;
return qMin(kMaxPreQueueSize, remaining_frames);
}
void ViewerWidget::PopOldestFrameFromPlaybackQueue()
{
playback_queue_.pop_front();
if (int(playback_queue_.size()) < DeterminePlaybackQueueSize()) {
RequestNextFrameForQueue();
}
}
void ViewerWidget::UpdateStack()
{
rational new_tb;
@@ -836,7 +868,7 @@ void ViewerWidget::RendererGeneratedFrameForQueue()
window->queue()->AppendTimewise({frame->timestamp(), frame}, playback_speed_);
}
if (prequeuing_ && playback_queue_.size() == kMaxPreQueueSize) {
if (prequeuing_ && int(playback_queue_.size()) == prequeue_length_) {
prequeuing_ = false;
FinishPlayPreprocess();
}
@@ -846,6 +878,7 @@ void ViewerWidget::RendererGeneratedFrameForQueue()
watcher->deleteLater();
}
//#define PRINT_INVALID_RANGES
void ViewerWidget::StartBackgroundCaching()
{
if (busy_) {
@@ -853,8 +886,25 @@ void ViewerWidget::StartBackgroundCaching()
busy_ = false;
}
if ((GetConnectedNode()->video_frame_cache()->HasInvalidatedRanges()
|| GetConnectedNode()->audio_playback_cache()->HasInvalidatedRanges())
#ifdef PRINT_INVALID_RANGES
if (GetConnectedNode()->video_frame_cache()->HasInvalidatedRanges()) {
qDebug() << "Video invalid:";
foreach (const TimeRange& r, GetConnectedNode()->video_frame_cache()->GetInvalidatedRanges()) {
qDebug() << " " << r;
}
}
if (GetConnectedNode()->audio_playback_cache()->HasInvalidatedRanges()) {
qDebug() << "Audio invalid:";
foreach (const TimeRange& r, GetConnectedNode()->audio_playback_cache()->GetInvalidatedRanges()) {
qDebug() << " " << r;
}
}
#endif
if (GetConnectedNode()
&& (GetConnectedNode()->video_frame_cache()->HasInvalidatedRanges()
|| GetConnectedNode()->audio_playback_cache()->HasInvalidatedRanges())
&& Config::Current()["AutoCache"].toBool()) {
if (cache_background_task_ || busy_viewers_) {
@@ -1020,11 +1070,14 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos)
void ViewerWidget::Play(bool in_to_out_only)
{
if (in_to_out_only
&& GetConnectedTimelinePoints()
if (in_to_out_only) {
if (GetConnectedTimelinePoints()
&& GetConnectedTimelinePoints()->workarea()->enabled()) {
// Jump to in point
SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedTimelinePoints()->workarea()->in(), timebase()));
// Jump to in point
SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedTimelinePoints()->workarea()->in(), timebase()));
} else {
in_to_out_only = false;
}
}
PlayInternal(1, in_to_out_only);
@@ -1168,6 +1221,12 @@ void ViewerWidget::PlaybackTimerUpdate()
time_changed_from_timer_ = false;
}
if (!isVisible()) {
while (!playback_queue_.empty() && playback_queue_.front().timestamp != GetTime()) {
PopOldestFrameFromPlaybackQueue();
}
}
}
void ViewerWidget::SizeChangedSlot(int width, int height)
+7 -4
View File
@@ -183,8 +183,6 @@ private:
void SetColorTransform(const ColorTransform& transform, ViewerDisplayWidget* sender);
void FillPlaybackQueue();
QString GetCachedFilenameFromTime(const rational& time);
bool FrameExistsAtTime(const rational& time);
@@ -202,6 +200,10 @@ private:
VideoRenderingParams GenerateVideoParams() const;
AudioRenderingParams GenerateAudioParams() const;
int DeterminePlaybackQueueSize();
void PopOldestFrameFromPlaybackQueue();
QStackedWidget* stack_;
ViewerSizer* sizer_;
@@ -222,8 +224,6 @@ private:
bool play_in_to_out_only_;
bool playback_is_audio_only_;
AudioWaveformView* waveform_view_;
QList<ViewerWindow*> windows_;
@@ -233,6 +233,7 @@ private:
ViewerDisplayWidget* context_menu_widget_;
ViewerPlaybackTimer playback_timer_;
QTimer playback_backup_timer_;
ViewerQueue playback_queue_;
int64_t playback_queue_next_frame_;
@@ -251,6 +252,8 @@ private:
rational last_length_;
int prequeue_length_;
static CacheTask* cache_background_task_;
static int busy_viewers_;