From 8ac1589b2d47784b1320b1682246ae6c3641e640 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 4 Oct 2022 18:35:16 -0500 Subject: [PATCH] Optimizing caching thread to exclude frames where no clips exist, and simplify some of the caching loop logic --- src/Qt/VideoCacheThread.cpp | 95 +++++++++++++++++++------------------ src/Qt/VideoCacheThread.h | 4 +- src/Settings.cpp | 4 +- src/Settings.h | 4 +- 4 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/Qt/VideoCacheThread.cpp b/src/Qt/VideoCacheThread.cpp index 8873b95d..febebc5c 100644 --- a/src/Qt/VideoCacheThread.cpp +++ b/src/Qt/VideoCacheThread.cpp @@ -29,7 +29,8 @@ 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), should_pause_cache(false) + min_frames_ahead(4), max_frames_ahead(8), should_pause_cache(false), + timeline_max_frame(0), should_break(false) { } @@ -47,7 +48,13 @@ 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) + // Get timeline instance + Timeline *t = (Timeline *) reader; + + // Calculate last frame # on timeline (to prevent caching past this point) + timeline_max_frame = t->GetMaxFrame(); + + // Determine previous frame number (depending on last non-zero/non-paused speed) int64_t previous_frame = new_position; if (last_speed < 0) { // backwards @@ -63,21 +70,42 @@ namespace openshot // Clear cache if previous frame outside the cached range, which means we are // requesting a non-contigous frame compared to our current cache range - if (!reader->GetCache()->Contains(previous_frame)) { - Timeline *t = (Timeline *) reader; + if (new_position >= 1 && new_position <= timeline_max_frame && !reader->GetCache()->Contains(previous_frame)) { + // Clear cache t->ClearAllCache(); + + // Break out of any existing cache loop + should_break = true; + + // Force cache direction back to forward + last_speed = 1; } // Reset pre-roll when requested frame is not currently cached if (start_preroll && reader && reader->GetCache() && !reader->GetCache()->Contains(new_position)) { + // Break out of any existing cache loop + should_break = true; + + // Reset stats and allow cache to rebuild (if paused) cached_frame_count = 0; if (speed == 0) { should_pause_cache = false; } } + + // Actually update seek position Seek(new_position); } + // Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...) + void VideoCacheThread::setSpeed(int new_speed) { + if (new_speed != 0) { + // Track last non-zero speed + last_speed = new_speed; + } + speed = new_speed; + } + // Get the size in bytes of a frame (rough estimate) int64_t VideoCacheThread::getBytes(int width, int height, int sample_rate, int channels, float fps) { @@ -127,6 +155,9 @@ namespace openshot const auto frame_duration = double_micro_sec(1000000.0 / reader->info.fps.ToDouble()); int current_speed = speed; + // Increment and direction for cache loop + int64_t increment = 1; + // Check for empty cache (and re-trigger preroll) // This can happen when the user manually empties the timeline cache if (reader->GetCache()->Count() == 0) { @@ -134,14 +165,12 @@ namespace openshot cached_frame_count = 0; } - // Calculate increment (based on current_speed) - // Support caching in both directions - int16_t increment = current_speed; + // Update current display frame + current_display_frame = requested_display_frame; if (current_speed == 0 && should_pause_cache || !s->ENABLE_PLAYBACK_CACHING) { // Sleep during pause (after caching additional frames when paused) // OR sleep when playback caching is disabled - current_display_frame = requested_display_frame; std::this_thread::sleep_for(frame_duration / 2); continue; @@ -176,9 +205,8 @@ namespace openshot // Overwrite the increment to our cache position // to fully cache frames while paused (support forward and rewind caching) - if (last_speed > 0) { - increment = 1; - } else { + // Use `last_speed` which is the last non-zero/non-paused speed + if (last_speed < 0) { increment = -1; } @@ -191,11 +219,11 @@ namespace openshot // Frames which are already cached are basically free. Only uncached frames have a big CPU cost. // By always looping through the expected frame range, we can fill-in missing frames caused by a // fragmented cache object (i.e. the user clicking all over the timeline). - int64_t starting_frame = current_display_frame; - int64_t ending_frame = starting_frame + max_frames_ahead; + int64_t starting_frame = std::min(current_display_frame, timeline_max_frame); + int64_t ending_frame = std::min(starting_frame + max_frames_ahead, timeline_max_frame); // Adjust ending frame for cache loop - if (last_speed < 0) { + if (increment < 0) { // Reverse loop (if we are going backwards) ending_frame = starting_frame - max_frames_ahead; } @@ -208,9 +236,10 @@ namespace openshot ending_frame = 1; } + // Reset cache break-loop flag + should_break = false; + // 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 + increment); cache_frame += increment) { cached_frame_count++; if (reader && reader->GetCache() && !reader->GetCache()->Contains(cache_frame)) { @@ -219,44 +248,16 @@ namespace openshot // This frame is not already cached... so request it again (to force the creation & caching) // This will also re-order the missing frame to the front of the cache last_cached_frame = reader->GetFrame(cache_frame); - uncached_frame_count++; } catch (const OutOfBoundsFrame & e) { } - } else if (reader && reader->GetCache() && reader->GetCache()->Contains(cache_frame)) { - already_cached_frame_count++; } - // Check if the user has seeked outside the cache range - 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; - } - } - // Check if playback speed changed (if so, break out of cache loop) - if (current_speed != speed) { + // Check if thread has stopped OR should_break is triggered + if (!is_playing || should_break) { + should_break = false; break; } - // Check if thread has stopped - if (!is_playing) { - break; - } - } - // Update cache counts - 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; - } - - // Update current display frame & last non-paused speed - current_display_frame = requested_display_frame; - if (current_speed != 0) { - last_speed = current_speed; } // Sleep for a fraction of frame duration diff --git a/src/Qt/VideoCacheThread.h b/src/Qt/VideoCacheThread.h index 6321b932..564c8e26 100644 --- a/src/Qt/VideoCacheThread.h +++ b/src/Qt/VideoCacheThread.h @@ -39,7 +39,9 @@ namespace openshot ReaderBase *reader; int64_t min_frames_ahead; int64_t max_frames_ahead; + int64_t timeline_max_frame; bool should_pause_cache; + bool should_break; /// Constructor VideoCacheThread(); @@ -62,7 +64,7 @@ namespace openshot void Seek(int64_t new_position, bool start_preroll); /// Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...) - void setSpeed(int new_speed) { speed = new_speed; } + void setSpeed(int new_speed); /// Stop the audio playback void Stop(); diff --git a/src/Settings.cpp b/src/Settings.cpp index c9a726d6..1c14a867 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -34,8 +34,8 @@ Settings *Settings::Instance() m_pInstance->HW_DE_DEVICE_SET = 0; m_pInstance->HW_EN_DEVICE_SET = 0; m_pInstance->VIDEO_CACHE_PERCENT_AHEAD = 0.75; - m_pInstance->VIDEO_CACHE_MIN_PREROLL_FRAMES = 2; - m_pInstance->VIDEO_CACHE_MAX_PREROLL_FRAMES = 8; + m_pInstance->VIDEO_CACHE_MIN_PREROLL_FRAMES = 30; + m_pInstance->VIDEO_CACHE_MAX_PREROLL_FRAMES = 30; m_pInstance->VIDEO_CACHE_MAX_FRAMES = 30 * 20; m_pInstance->ENABLE_PLAYBACK_CACHING = true; m_pInstance->PLAYBACK_AUDIO_DEVICE_NAME = ""; diff --git a/src/Settings.h b/src/Settings.h index b9a12903..0bd386cb 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -86,10 +86,10 @@ namespace openshot { float VIDEO_CACHE_PERCENT_AHEAD = 0.75; /// Minimum number of frames to cache before playback begins - int VIDEO_CACHE_MIN_PREROLL_FRAMES = 2; + int VIDEO_CACHE_MIN_PREROLL_FRAMES = 30; /// Max number of frames (ahead of playhead) to cache during playback - int VIDEO_CACHE_MAX_PREROLL_FRAMES = 8; + int VIDEO_CACHE_MAX_PREROLL_FRAMES = 30; /// Max number of frames (when paused) to cache for playback int VIDEO_CACHE_MAX_FRAMES = 30 * 20;