diff --git a/rendering/cacher.cpp b/rendering/cacher.cpp index 35fd02f72..d7082b52d 100644 --- a/rendering/cacher.cpp +++ b/rendering/cacher.cpp @@ -117,7 +117,7 @@ void Cacher::CacheAudioWorker() { long target_frame = audio_target_frame; bool temp_reverse = (playback_speed_ < 0); - bool reverse_audio = (clip->reversed() != temp_reverse); + bool reverse_audio = IsReversed(); long frame_skip = 0; double last_fr = clip->sequence->frame_rate; @@ -462,6 +462,12 @@ void Cacher::CacheAudioWorker() { QMetaObject::invokeMethod(panel_sequence_viewer, "play_wake", Qt::QueuedConnection); } +bool Cacher::IsReversed() +{ + // Here, the Clip reverse and reversed playback speed cancel each other out to produce normal playback + return (clip->reversed() != playback_speed_ < 0); +} + void Cacher::CacheVideoWorker() { // is this media a still image? @@ -496,6 +502,9 @@ void Cacher::CacheVideoWorker() { // main thread waits until cacher starts fully, wake it up here WakeMainThread(); + // determine if this media is reversed, which will affect how the queue is constructed + bool reversed = IsReversed(); + // get the timestamp we want in terms of the media's timebase int64_t target_pts = seconds_to_timestamp(clip, playhead_to_clip_seconds(clip, playhead_)); @@ -573,39 +582,55 @@ void Cacher::CacheVideoWorker() { // for FRAME_QUEUE_TYPE_FRAMES, this is used to store the maximum number of frames that can be added int64_t minimum_ts; - if (olive::CurrentConfig.previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { - // get the maximum number of previous frames that can be in the queue - minimum_ts = qCeil(olive::CurrentConfig.previous_queue_size); - } else { - // get the minimum frame timestamp that can be added to the queue - minimum_ts = target_pts - seconds_to_timestamp(clip, olive::CurrentConfig.previous_queue_size); - } - // check if we can add more frames to this queue or not // for FRAME_QUEUE_TYPE_SECONDS, this is used to store the maximum timestamp // for FRAME_QUEUE_TYPE_FRAMES, this is used to store the maximum number of frames that can be added int64_t maximum_ts; - bool start_loop = true; + // Get queue configuration + int previous_queue_type, upcoming_queue_type; + double previous_queue_size, upcoming_queue_size; - if (olive::CurrentConfig.upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { - maximum_ts = qCeil(olive::CurrentConfig.upcoming_queue_size); + // For reversed playback, we flip the queue stats as "upcoming" frames are going to be played before the "previous" + // frames now + if (reversed) { + previous_queue_type = olive::CurrentConfig.upcoming_queue_type; + previous_queue_size = olive::CurrentConfig.upcoming_queue_size; + upcoming_queue_type = olive::CurrentConfig.previous_queue_type; + upcoming_queue_size = olive::CurrentConfig.previous_queue_size; + } else { + previous_queue_type = olive::CurrentConfig.previous_queue_type; + previous_queue_size = olive::CurrentConfig.previous_queue_size; + upcoming_queue_type = olive::CurrentConfig.upcoming_queue_type; + upcoming_queue_size = olive::CurrentConfig.upcoming_queue_size; + } - // if we already have the maximum number of upcoming frames, don't bother running the below loop at all - if (frames_greater_than_target >= maximum_ts) { - start_loop = false; - } + // Determine "previous" queue statistics + if (previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + // get the maximum number of previous frames that can be in the queue + minimum_ts = qCeil(previous_queue_size); + } else { + // get the minimum frame timestamp that can be added to the queue + minimum_ts = qRound(target_pts - second_pts * previous_queue_size); + } + + // Determine "upcoming" queue statistics + if (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + maximum_ts = qCeil(upcoming_queue_size); } else { // get the maximum frame timestamp that can be added to the queue - maximum_ts = target_pts + seconds_to_timestamp(clip, olive::CurrentConfig.upcoming_queue_size); - - // if the latest frame is already past the maximum queue seconds - if (latest_pts > maximum_ts) { - start_loop = false; - } + maximum_ts = qRound(target_pts + second_pts * upcoming_queue_size); } + // if we already have the maximum number of upcoming frames, don't bother running the retrieving any frames at all + bool start_loop = true; + if ((upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES && frames_greater_than_target >= maximum_ts) + || (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS && latest_pts > maximum_ts)) { + start_loop = false; + } + + if (start_loop) { interrupt_ = false; @@ -631,7 +656,7 @@ void Cacher::CacheVideoWorker() { } else if (decoded_frame->pts != AV_NOPTS_VALUE) { // check if this frame exceeds the minimum timestamp - if (olive::CurrentConfig.previous_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS + if (previous_queue_type == olive::FRAME_QUEUE_TYPE_SECONDS && decoded_frame->pts < minimum_ts) { // if so, we don't need it @@ -641,16 +666,26 @@ void Cacher::CacheVideoWorker() { if (retrieved_frame == nullptr) { if (decoded_frame->pts == target_pts) { - SetRetrievedFrame(decoded_frame); - } else if (seeked_to_zero) { - // If this flag is set, it means this was somehow the earliest frame we could get for this timestamp + // We retrieved the exact frame we're looking for + SetRetrievedFrame(decoded_frame); - seeked_to_zero = false; + + } else if (decoded_frame->pts > target_pts) { + + if (queue_.size() > 0) { + + SetRetrievedFrame(queue_.last()); + + } else if (seeked_to_zero) { + + // If this flag is set but we still got a frame after the target timestamp, it means this was somehow + // the earliest frame we could get + SetRetrievedFrame(decoded_frame); + seeked_to_zero = false; + + } - } else if (decoded_frame->pts > target_pts - && queue_.size() > 0) { - SetRetrievedFrame(queue_.last()); } } @@ -661,7 +696,7 @@ void Cacher::CacheVideoWorker() { // check the amount of previous frames in the queue by using the current queue size for if we need to // remove any old entries (assumes the queue is chronological) - if (olive::CurrentConfig.previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + if (previous_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { int previous_frame_count = 0; @@ -693,7 +728,7 @@ void Cacher::CacheVideoWorker() { } // check if the queue is full according to olive::CurrentConfig - if (olive::CurrentConfig.upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { + if (upcoming_queue_type == olive::FRAME_QUEUE_TYPE_FRAMES) { // if this frame is later than the target, it's an "upcoming" frame if (decoded_frame->pts > target_pts) { diff --git a/rendering/cacher.h b/rendering/cacher.h index 9692f1192..57fca0e75 100644 --- a/rendering/cacher.h +++ b/rendering/cacher.h @@ -570,6 +570,11 @@ private: * audio to the audio buffer which will later be sent to the audio output device. */ void CacheAudioWorker(); + + /** + * @brief Internal function using the Cacher's known information to determine whether this media is playing in reverse + */ + bool IsReversed(); }; #endif // CACHER_H diff --git a/rendering/exportthread.cpp b/rendering/exportthread.cpp index 06cc9970c..3c50776ed 100644 --- a/rendering/exportthread.cpp +++ b/rendering/exportthread.cpp @@ -391,7 +391,7 @@ void ExportThread::run() { if (params.video_enabled) { do { // TODO optimize by rendering the next frame while encoding the last - renderer->start_render(nullptr, olive::ActiveSequence.get(), nullptr, video_frame->data[0], video_frame->linesize[0]/4); + renderer->start_render(nullptr, olive::ActiveSequence.get(), 1, nullptr, video_frame->data[0], video_frame->linesize[0]/4); waitCond.wait(&mutex); if (!continueEncode) break; } while (renderer->did_texture_fail()); diff --git a/rendering/renderthread.cpp b/rendering/renderthread.cpp index cbb16b5fa..6d92ea422 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -148,7 +148,7 @@ void RenderThread::paint() { params.video = true; params.texture_failed = false; params.wait_for_mutexes = true; - params.playback_speed = 1; + params.playback_speed = playback_speed_; params.blend_mode_program = blend_mode_program; params.premultiply_program = premultiply_program; params.backend_buffer1 = back_buffer_1.buffer(); @@ -230,6 +230,7 @@ void RenderThread::paint() { void RenderThread::start_render(QOpenGLContext *share, Sequence* s, + int playback_speed, const QString& save, GLvoid* pixels, int pixel_linesize, @@ -238,6 +239,8 @@ void RenderThread::start_render(QOpenGLContext *share, seq = s; + playback_speed_ = playback_speed; + // stall any dependent actions texture_failed = true; diff --git a/rendering/renderthread.h b/rendering/renderthread.h index 4a3a95144..1f89d3043 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -53,6 +53,7 @@ public: void paint(); void start_render(QOpenGLContext* share, Sequence *s, + int playback_speed, const QString &save = nullptr, GLvoid *pixels = nullptr, int pixel_linesize = 0, @@ -102,6 +103,7 @@ private: QOpenGLShaderProgram* ocio_shader; Sequence* seq; + int playback_speed_; int divider; int tex_width; int tex_height; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 581c6a11c..523b56c24 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -169,7 +169,7 @@ void ViewerWidget::save_frame() { fn += selected_ext; } - renderer->start_render(context(), viewer->seq.get(), fn); + renderer->start_render(context(), viewer->seq.get(), 1, fn); } } @@ -229,7 +229,7 @@ void ViewerWidget::frame_update() { update(); } else { doneCurrent(); - renderer->start_render(context(), viewer->seq.get()); + renderer->start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); } // render the audio @@ -613,7 +613,7 @@ void ViewerWidget::paintGL() { if (renderer->did_texture_fail() && !viewer->playing) { doneCurrent(); - renderer->start_render(context(), viewer->seq.get()); + renderer->start_render(context(), viewer->seq.get(), viewer->get_playback_speed()); } } }