diff --git a/src/Qt/VideoCacheThread.cpp b/src/Qt/VideoCacheThread.cpp index e58c52cc..b36f0ad2 100644 --- a/src/Qt/VideoCacheThread.cpp +++ b/src/Qt/VideoCacheThread.cpp @@ -16,6 +16,8 @@ #include "Exceptions.h" #include "Frame.h" #include "OpenMPUtilities.h" +#include "Settings.h" +#include "Timeline.h" #include #include // for std::this_thread::sleep_for @@ -27,7 +29,7 @@ namespace openshot VideoCacheThread::VideoCacheThread() : Thread("video-cache"), speed(0), last_speed(1), is_playing(false), reader(NULL), current_display_frame(1), cached_frame_count(0), - min_frames_ahead(4), max_frames_ahead(8) + min_frames_ahead(4), max_frames_ahead(8), should_pause_cache(false) { } @@ -45,8 +47,33 @@ namespace openshot // Seek the reader to a particular frame number and optionally start the pre-roll void VideoCacheThread::Seek(int64_t new_position, bool start_preroll) { + // Determine previous frame number (depending on speed) + int64_t previous_frame = new_position; + if (last_speed < 0) { + // backwards + previous_frame++; + } else if (last_speed > 0) { + // forward + previous_frame--; + } + if (previous_frame <= 0) { + // min frame is 1 + previous_frame = 1; + } + + // Clear cache if previous frame outside the cached range, which means we are + // requesting a non-contiguous frame compared to our current cache range + if (!reader->GetCache()->Contains(previous_frame)) { + Timeline *t = (Timeline *) reader; + t->ClearAllCache(); + } + + // Reset pre-roll when requested frame is not currently cached if (start_preroll && reader && reader->GetCache() && !reader->GetCache()->Contains(new_position)) { cached_frame_count = 0; + if (speed == 0) { + should_pause_cache = false; + } } Seek(new_position); } @@ -71,22 +98,38 @@ namespace openshot // Start the thread void VideoCacheThread::run() { + // Get settings + Settings *s = Settings::Instance(); + // Types for storing time durations in whole and fractional microseconds using micro_sec = std::chrono::microseconds; using double_micro_sec = std::chrono::duration; - bool should_pause_cache = false; while (!threadShouldExit() && is_playing) { + // init local vars + min_frames_ahead = s->VIDEO_CACHE_MIN_PREROLL_FRAMES; + max_frames_ahead = s->VIDEO_CACHE_MAX_PREROLL_FRAMES; + // Calculate on-screen time for a single frame const auto frame_duration = double_micro_sec(1000000.0 / reader->info.fps.ToDouble()); int current_speed = speed; + + // Check for empty cache (and re-trigger preroll) + // This can happen when the user manually empties the timeline cache + if (reader->GetCache()->Count() == 0) { + should_pause_cache = false; + cached_frame_count = 0; + } - // Calculate increment (based on speed) + // Calculate increment (based on current_speed) // Support caching in both directions - int16_t increment = speed; - if (current_speed == 0 && should_pause_cache) { + int16_t increment = current_speed; + + if (current_speed == 0 && should_pause_cache || !s->ENABLE_PLAYBACK_CACHING) { // Sleep during pause (after caching additional frames when paused) - std::this_thread::sleep_for(frame_duration / 4); + // OR sleep when playback caching is disabled + current_display_frame = requested_display_frame; + std::this_thread::sleep_for(frame_duration / 2); continue; } else if (current_speed == 0) { @@ -106,14 +149,14 @@ namespace openshot if (reader->GetCache() && reader->GetCache()->GetMaxBytes() > 0) { // When paused, use 1/2 the cache size (so our cache will be 50% before the play-head, and 50% after it) max_frames_ahead = (reader->GetCache()->GetMaxBytes() / bytes_per_frame) / 2; - if (max_frames_ahead > 300) { + if (max_frames_ahead > s->VIDEO_CACHE_MAX_FRAMES) { // Ignore values that are too large, and default to a safer value - max_frames_ahead = 300; + max_frames_ahead = s->VIDEO_CACHE_MAX_FRAMES; } } // Overwrite the increment to our cache position - // to fully cache frames while paused (support forward and rewind) + // to fully cache frames while paused (support forward and rewind caching) if (last_speed > 0) { increment = 1; } else { @@ -121,8 +164,7 @@ namespace openshot } } else { - // Default max frames ahead (normal playback) - max_frames_ahead = 8; + // normal playback should_pause_cache = false; } @@ -134,19 +176,23 @@ namespace openshot int64_t ending_frame = starting_frame + max_frames_ahead; // Adjust ending frame for cache loop - if (speed < 0) { + if (last_speed < 0) { // Reverse loop (if we are going backwards) ending_frame = starting_frame - max_frames_ahead; } - if (ending_frame < 0) { + if (starting_frame < 1) { // Don't allow negative frame number caching - ending_frame = 0; + starting_frame = 1; + } + if (ending_frame < 1) { + // Don't allow negative frame number caching + ending_frame = 1; } // Loop through range of frames (and cache them) int64_t uncached_frame_count = 0; int64_t already_cached_frame_count = 0; - for (int64_t cache_frame = starting_frame; cache_frame != ending_frame; cache_frame += increment) { + for (int64_t cache_frame = starting_frame; cache_frame != (ending_frame + increment); cache_frame += increment) { cached_frame_count++; if (reader && reader->GetCache() && !reader->GetCache()->Contains(cache_frame)) { try @@ -165,8 +211,10 @@ namespace openshot if (requested_display_frame != current_display_frame) { // cache will restart at a new position if (speed >= 0 && (requested_display_frame < starting_frame || requested_display_frame > ending_frame)) { + should_pause_cache = false; break; } else if (speed < 0 && (requested_display_frame > starting_frame || requested_display_frame < ending_frame)) { + should_pause_cache = false; break; } } @@ -174,10 +222,14 @@ namespace openshot if (current_speed != speed) { break; } + // Check if playback has stopped + if (!is_playing) { + break; + } } // Update cache counts - if (cached_frame_count > max_frames_ahead && uncached_frame_count > (min_frames_ahead / 4)) { + if (current_speed == 1 && cached_frame_count > max_frames_ahead && uncached_frame_count > min_frames_ahead) { // start cached count again (we have too many uncached frames) cached_frame_count = 0; } @@ -189,7 +241,7 @@ namespace openshot } // Sleep for a fraction of frame duration - std::this_thread::sleep_for(frame_duration / 4); + std::this_thread::sleep_for(frame_duration / 2); } return; diff --git a/src/Qt/VideoCacheThread.h b/src/Qt/VideoCacheThread.h index 4160b899..d74628f3 100644 --- a/src/Qt/VideoCacheThread.h +++ b/src/Qt/VideoCacheThread.h @@ -39,7 +39,7 @@ namespace openshot ReaderBase *reader; int64_t min_frames_ahead; int64_t max_frames_ahead; - + bool should_pause_cache; /// Constructor VideoCacheThread(); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index f436175a..14c133cb 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -778,8 +778,8 @@ void Timeline::Close() // Mark timeline as closed is_open = false; - // Clear all cache - ClearAllCache(); + // Clear all cache (deep clear, including nested Readers) + ClearAllCache(true); } // Open the reader (and start consuming resources) @@ -818,7 +818,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) } else { - // Get a list of clips that intersect with the requested section of timeline + // Get a list of clips that intersect with the requested section of timeline // This also opens the readers for intersecting clips, and marks non-intersecting clips as 'needs closing' std::vector nearby_clips; nearby_clips = find_intersecting_clips(requested_frame, 1, true); @@ -1525,7 +1525,7 @@ void Timeline::apply_json_to_timeline(Json::Value change) { } // Clear all caches -void Timeline::ClearAllCache() { +void Timeline::ClearAllCache(bool deep) { // Clear primary cache if (final_cache) { @@ -1538,8 +1538,8 @@ void Timeline::ClearAllCache() { // Clear cache on clip clip->Reader()->GetCache()->Clear(); - // Clear nested Reader (if any) - if (clip->Reader()->Name() == "FrameMapper") { + // Clear nested Reader (if deep clear requested) + if (deep && clip->Reader()->Name() == "FrameMapper") { FrameMapper* nested_reader = (FrameMapper*) clip->Reader(); if (nested_reader->Reader() && nested_reader->Reader()->GetCache()) nested_reader->Reader()->GetCache()->Clear(); diff --git a/src/Timeline.h b/src/Timeline.h index 21e74951..e1f26998 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -258,8 +258,9 @@ namespace openshot { /// @brief Automatically map all clips to the timeline's framerate and samplerate void AutoMapClips(bool auto_map) { auto_map_clips = auto_map; }; - /// Clear all cache for this timeline instance, and all clips, mappers, and readers under it - void ClearAllCache(); + /// Clear all cache for this timeline instance, including all clips' cache + /// @param deep If True, clear all FrameMappers and nested Readers (QtImageReader, FFmpegReader, etc...) + void ClearAllCache(bool deep=false); /// Return a list of clips on the timeline std::list Clips() override { return clips; };