diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 7e35ed5be..155539d57 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -32,7 +32,7 @@ #include #ifndef NO_OCIO #include -namespace OCIO = OCIO_NAMESPACE; +namespace OCIO = OCIO_NAMESPACE::v1; #endif #include "project/footage.h" diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h index 3d502854d..804b8b051 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -35,7 +35,7 @@ #include #ifndef NO_OCIO #include -namespace OCIO = OCIO_NAMESPACE; +namespace OCIO = OCIO_NAMESPACE::v1; #endif #include "timeline/sequence.h" diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp index 34dd51488..2d6bd9b5f 100644 --- a/project/sourcescommon.cpp +++ b/project/sourcescommon.cpp @@ -270,6 +270,9 @@ void SourcesCommon::dropEvent(QWidget* parent, const QModelIndexList& items) { const QMimeData* mimeData = event->mimeData(); MediaPtr m = project_parent->item_to_media_ptr(drop_item); + if (m == nullptr) { + return; + } if (mimeData->hasUrls()) { // drag files in from outside QList urls = mimeData->urls(); diff --git a/rendering/cacher.cpp b/rendering/cacher.cpp index 740571810..5e78c5c40 100644 --- a/rendering/cacher.cpp +++ b/rendering/cacher.cpp @@ -116,7 +116,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; @@ -461,6 +461,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? @@ -495,6 +501,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_)); @@ -572,39 +581,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; @@ -630,7 +655,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 @@ -640,16 +665,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()); } } @@ -660,7 +695,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; @@ -692,7 +727,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 da05b6194..31d78f954 100644 --- a/rendering/exportthread.cpp +++ b/rendering/exportthread.cpp @@ -390,7 +390,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/renderfunctions.h b/rendering/renderfunctions.h index 6d81b5795..86752e7ad 100644 --- a/rendering/renderfunctions.h +++ b/rendering/renderfunctions.h @@ -26,7 +26,7 @@ #include #ifndef NO_OCIO #include -namespace OCIO = OCIO_NAMESPACE; +namespace OCIO = OCIO_NAMESPACE::v1; #endif #include "timeline/sequence.h" diff --git a/rendering/renderthread.cpp b/rendering/renderthread.cpp index 1130f1f83..076cf635e 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -29,7 +29,7 @@ #ifndef NO_OCIO #include -namespace OCIO = OCIO_NAMESPACE; +namespace OCIO = OCIO_NAMESPACE::v1; #endif #include "timeline/sequence.h" @@ -220,7 +220,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.get(); params.pipeline = pipeline_program.get(); params.backend_buffer1 = &back_buffer_1; @@ -315,6 +315,7 @@ void RenderThread::paint() { void RenderThread::start_render(QOpenGLContext *share, Sequence* s, + int playback_speed, const QString& save, GLvoid* pixels, int pixel_linesize, @@ -323,6 +324,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 e959c98d4..184657f92 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -48,6 +48,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, @@ -104,6 +105,7 @@ private: Sequence* seq; + int playback_speed_; int divider; int tex_width; int tex_height; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index d4351e644..5d8617abd 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); } } @@ -240,7 +240,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 @@ -767,7 +767,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()); } } }