From a6ca7d9a2f3eec3562e6f738607ecce4b8e5054d Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 6 Jun 2025 15:25:46 -0500 Subject: [PATCH] Adding back Setting::VIDEO_CACHE_MAX_FRAMES, to limit the video cache thread to a hard #. Also, minor refactor to reduce duplication of capacity. --- src/Qt/VideoCacheThread.cpp | 107 +++++++++++++++++++----------------- src/Qt/VideoCacheThread.h | 7 ++- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/Qt/VideoCacheThread.cpp b/src/Qt/VideoCacheThread.cpp index 4697ffe8..9c0aa9aa 100644 --- a/src/Qt/VideoCacheThread.cpp +++ b/src/Qt/VideoCacheThread.cpp @@ -34,7 +34,6 @@ namespace openshot , current_display_frame(1) , cached_frame_count(0) , min_frames_ahead(4) - , max_frames_ahead(8) , timeline_max_frame(0) , reader(nullptr) , force_directional_cache(false) @@ -89,6 +88,22 @@ namespace openshot return bytes; } + /// Start the cache thread at high priority, and return true if it’s actually running. + bool VideoCacheThread::StartThread() + { + // JUCE’s startThread() returns void, so we launch it and then check if + // the thread actually started: + startThread(Priority::high); + return isThreadRunning(); + } + + /// Stop the cache thread, waiting up to timeoutMs ms. Returns true if it actually stopped. + bool VideoCacheThread::StopThread(int timeoutMs) + { + stopThread(timeoutMs); + return !isThreadRunning(); + } + void VideoCacheThread::Seek(int64_t new_position, bool start_preroll) { if (start_preroll) { @@ -203,11 +218,14 @@ namespace openshot CacheBase* cache = reader ? reader->GetCache() : nullptr; // If caching disabled or no reader, sleep briefly - if (!settings->ENABLE_PLAYBACK_CACHING || !cache) { + if (!settings->ENABLE_PLAYBACK_CACHING || !cache || !is_playing) { std::this_thread::sleep_for(double_micro_sec(50000)); continue; } + // init local vars + min_frames_ahead = settings->VIDEO_CACHE_MIN_PREROLL_FRAMES; + Timeline* timeline = static_cast(reader); int64_t timeline_end = timeline->GetMaxFrame(); int64_t playhead = requested_display_frame; @@ -219,45 +237,7 @@ namespace openshot last_dir = dir; } - // If a seek was requested, reset last_cached_index - if (userSeeked) { - handleUserSeek(playhead, dir); - userSeeked = false; - } - else if (!paused) { - // Check if last_cached_index drifted outside the new window; if so, reset it - int64_t bytes_per_frame = getBytes( - (timeline->preview_width ? timeline->preview_width : reader->info.width), - (timeline->preview_height ? timeline->preview_height : reader->info.height), - reader->info.sample_rate, - reader->info.channels, - reader->info.fps.ToFloat() - ); - int64_t max_bytes = cache->GetMaxBytes(); - if (max_bytes > 0 && bytes_per_frame > 0) { - int64_t capacity = max_bytes / bytes_per_frame; - if (capacity >= 1) { - int64_t ahead_count = static_cast(capacity * - settings->VIDEO_CACHE_PERCENT_AHEAD); - int64_t window_begin, window_end; - computeWindowBounds(playhead, - dir, - ahead_count, - timeline_end, - window_begin, - window_end); - - bool outside_window = - (dir > 0 && last_cached_index > window_end) || - (dir < 0 && last_cached_index < window_begin); - if (outside_window) { - handleUserSeek(playhead, dir); - } - } - } - } - - // Recompute capacity & ahead_count now that we’ve possibly updated last_cached_index + // Compute bytes_per_frame, max_bytes, and capacity once int64_t bytes_per_frame = getBytes( (timeline->preview_width ? timeline->preview_width : reader->info.width), (timeline->preview_height ? timeline->preview_height : reader->info.height), @@ -266,11 +246,42 @@ namespace openshot reader->info.fps.ToFloat() ); int64_t max_bytes = cache->GetMaxBytes(); - if (max_bytes <= 0 || bytes_per_frame <= 0) { - std::this_thread::sleep_for(double_micro_sec(50000)); - continue; + int64_t capacity = 0; + if (max_bytes > 0 && bytes_per_frame > 0) { + capacity = max_bytes / bytes_per_frame; + if (capacity > settings->VIDEO_CACHE_MAX_FRAMES) { + capacity = settings->VIDEO_CACHE_MAX_FRAMES; + } } - int64_t capacity = max_bytes / bytes_per_frame; + + // Handle a user-initiated seek + if (userSeeked) { + handleUserSeek(playhead, dir); + userSeeked = false; + } + else if (!paused && capacity >= 1) { + // In playback mode, check if last_cached_index drifted outside the new window + int64_t base_ahead = static_cast(capacity * settings->VIDEO_CACHE_PERCENT_AHEAD); + + int64_t window_begin, window_end; + computeWindowBounds( + playhead, + dir, + base_ahead, + timeline_end, + window_begin, + window_end + ); + + bool outside_window = + (dir > 0 && last_cached_index > window_end) || + (dir < 0 && last_cached_index < window_begin); + if (outside_window) { + handleUserSeek(playhead, dir); + } + } + + // If capacity is insufficient, sleep and retry if (capacity < 1) { std::this_thread::sleep_for(double_micro_sec(50000)); continue; @@ -294,11 +305,7 @@ namespace openshot window_end); // Attempt to fill any missing frames in that window - bool window_full = prefetchWindow(cache, - window_begin, - window_end, - dir, - reader); + bool window_full = prefetchWindow(cache, window_begin, window_end, dir, reader); // If paused and window was already full, keep playhead fresh if (paused && window_full) { diff --git a/src/Qt/VideoCacheThread.h b/src/Qt/VideoCacheThread.h index fc2c944f..bfc0c490 100644 --- a/src/Qt/VideoCacheThread.h +++ b/src/Qt/VideoCacheThread.h @@ -68,6 +68,12 @@ namespace openshot */ void Seek(int64_t new_position, bool start_preroll); + /// Start the cache thread at high priority. Returns true if it’s actually running. + bool StartThread(); + + /// Stop the cache thread (wait up to timeoutMs ms). Returns true if it stopped. + bool StopThread(int timeoutMs = 0); + /** * @brief Attach a ReaderBase (e.g. Timeline, FFmpegReader) and begin caching. * @param new_reader @@ -165,7 +171,6 @@ namespace openshot int64_t cached_frame_count; ///< Count of frames currently added to cache. int64_t min_frames_ahead; ///< Minimum number of frames considered “ready” (pre-roll). - int64_t max_frames_ahead; ///< Maximum frames to attempt to cache (mem capped). int64_t timeline_max_frame; ///< Highest valid frame index in the timeline. ReaderBase* reader; ///< The source reader (e.g., Timeline, FFmpegReader).