27 : Thread(
"video-cache")
32 , preroll_on_next_fill(false)
33 , clear_cache_on_next_fill(false)
35 , requested_display_frame(1)
36 , current_display_frame(1)
37 , cached_frame_count(0)
39 , timeline_max_frame(0)
41 , force_directional_cache(false)
42 , last_cached_index(0)
43 , seen_timeline_cache_epoch(0)
44 , timeline_cache_epoch_initialized(false)
66 if (!settings->ENABLE_PLAYBACK_CACHING || !timeline || !
reader->
GetCache()
67 || settings->VIDEO_CACHE_MIN_PREROLL_FRAMES < 0)
77 && !timeline->CacheRefreshPending();
86 const bool playable = cache->
Contains(playhead)
108 while (contiguous_ahead < required && cache->Contains(playhead + (
contiguous_ahead + 1) * dir))
116 if (new_speed !=
speed.load()) {
121 if (new_speed != 0) {
123 last_dir.store(new_speed > 0 ? 1 : -1);
127 speed.store(new_speed);
143 int64_t bytes =
static_cast<int64_t
>(width) * height *
sizeof(
char) * 4;
145 if (fps > 0) bytes += (
static_cast<double>(sample_rate) * channels / fps) *
sizeof(float);
151 return std::chrono::duration_cast<std::chrono::microseconds>(
152 std::chrono::steady_clock::now().time_since_epoch()).count();
171 if (isThreadRunning())
172 return !threadShouldExit();
173 startThread(Priority::high);
174 return isThreadRunning();
182 signalThreadShouldExit();
192 return waitForThreadToExit(timeoutMs);
198 const bool restart = isThreadRunning();
201 waitForThreadToExit(-1);
216 startThread(Priority::high);
227 bool should_mark_seek =
false;
228 bool should_preroll =
false;
230 bool entering_scrub =
false;
231 bool leaving_scrub =
false;
232 bool cache_contains =
false;
233 bool should_clear_cache =
false;
235 const bool same_frame_refresh = (new_position == current_requested);
237 cache_contains = cache->
Contains(clamped_new_position);
241 if (same_frame_refresh) {
242 const bool is_paused = (
speed.load() == 0);
245 if (was_scrubbing && cache && cache_contains) {
247 should_mark_seek =
false;
248 should_preroll =
false;
249 should_clear_cache =
false;
250 new_cached_count = cache->
Count();
254 timeline->RequestClearAllCache();
256 new_cached_count = 0;
257 should_mark_seek =
true;
258 should_preroll =
true;
259 should_clear_cache =
false;
263 should_mark_seek =
false;
264 should_preroll =
false;
265 should_clear_cache =
false;
266 if (cache && cache_contains) {
267 cache->
Remove(clamped_new_position);
270 new_cached_count = cache->
Count();
274 if (cache && !cache_contains) {
275 should_mark_seek =
true;
277 new_cached_count = 0;
278 should_preroll =
true;
279 should_clear_cache =
true;
284 should_mark_seek =
false;
285 should_preroll =
false;
286 should_clear_cache =
false;
287 new_cached_count = cache->
Count();
290 should_mark_seek =
true;
293 leaving_scrub =
true;
297 const bool is_paused = (
speed.load() == 0);
298 if (is_paused && same_frame_refresh) {
300 should_mark_seek =
false;
301 should_preroll =
false;
302 should_clear_cache =
false;
303 if (cache && cache_contains) {
304 cache->
Remove(clamped_new_position);
307 new_cached_count = cache->
Count();
309 leaving_scrub =
true;
311 else if (is_paused) {
312 if (cache && !cache_contains) {
313 should_mark_seek =
true;
314 new_cached_count = 0;
315 should_clear_cache =
true;
319 should_mark_seek =
false;
320 new_cached_count = cache->
Count();
322 should_mark_seek =
true;
324 entering_scrub =
true;
327 should_mark_seek =
false;
328 should_preroll =
false;
329 should_clear_cache =
false;
331 new_cached_count = cache->
Count();
333 leaving_scrub =
true;
340 if (should_mark_seek || should_preroll || should_clear_cache) {
349 if (entering_scrub) {
363 if (!same_frame_refresh &&
speed.load() != 0 && cache_contains
365 && timeline && !timeline->CacheRefreshPending()
379 if (new_position <= 0) {
388 new_cached_count = cache->Count();
399 const int current_speed =
speed.load();
400 if (current_speed != 0) {
401 return (current_speed > 0 ? 1 : -1);
414 int64_t timeline_end,
415 int64_t preroll_frames)
417 int64_t preroll_start = playhead;
418 if (preroll_frames > 0) {
420 preroll_start = std::max<int64_t>(1, playhead - preroll_frames);
423 preroll_start = std::min<int64_t>(timeline_end, playhead + preroll_frames);
436 if (min_frames < 0) {
439 if (max_frames > 0 && min_frames > max_frames) {
440 min_frames = max_frames;
452 const int64_t timeline_max = timeline->GetMaxFrame();
453 if (timeline_max > 0) {
454 timeline_end = timeline_max;
462 if (timeline_end < 1) {
465 return std::clamp<int64_t>(frame, 1, timeline_end);
473 int64_t cache_playhead = playhead;
477 if (paused && !cache->
Contains(cache_playhead)) {
480 timeline->ClearAllCache();
491 int64_t timeline_end,
492 int64_t& window_begin,
493 int64_t& window_end)
const
497 window_begin = playhead;
498 window_end = playhead + ahead_count;
502 window_begin = playhead - ahead_count;
503 window_end = playhead;
506 window_begin = std::max<int64_t>(window_begin, 1);
507 window_end = std::min<int64_t>(window_end, timeline_end);
511 int64_t window_begin,
515 int64_t max_frames_to_fetch,
518 if (generation == UINT64_MAX)
521 const uint64_t epoch = timeline ? timeline->
CacheEpoch() : 0;
522 bool window_full =
true;
524 int64_t fetched_this_pass = 0;
527 while ((dir > 0 && next_frame <= window_end) ||
528 (dir < 0 && next_frame >= window_begin))
530 if (threadShouldExit()) {
535 || (timeline && timeline->CacheEpoch() != epoch)) {
539 if ((timeline && timeline->CacheRefreshPending()) || !cache->
Contains(next_frame)) {
544 const int64_t decode_us =
monotonicUs() - decode_started;
550 || (timeline && timeline->CacheEpoch() != epoch)) {
558 cache->
Add(framePtr);
568 cache->
Touch(next_frame);
574 || (timeline && timeline->CacheEpoch() != epoch))
583 settings->VIDEO_CACHE_MIN_PREROLL_FRAMES, settings->VIDEO_CACHE_MAX_PREROLL_FRAMES));
596 if (max_frames_to_fetch > 0 && fetched_this_pass >= max_frames_to_fetch) {
606 while (!threadShouldExit()) {
613 int64_t raw_playhead;
615 bool paused, did_user_seek, use_preroll, should_clear;
621 paused =
speed.load() == 0;
651 const int64_t estimated_frame_bytes =
getBytes(
655 int64_t bytes_per_frame;
662 const int64_t capacity = (max_bytes > 0 && bytes_per_frame > 0)
665 const int64_t ahead_count = std::max<int64_t>(0, std::min<int64_t>(capacity - 1,
static_cast<int64_t
>(capacity * settings->
VIDEO_CACHE_PERCENT_AHEAD)));
666 int64_t window_begin, window_end;
672 const uint64_t epoch = timeline->
CacheEpoch();
687 const int64_t available = dir > 0 ? timeline_end - playhead : playhead - 1;
696 const bool missing_current = !cache->
Contains(playhead);
698 const int64_t missing = missing_current ? playhead
707 if (use_preroll && paused)
711 }
else if (!paused && capacity >= 1) {
728 bool window_full =
prefetchWindow(cache, window_begin, window_end, dir,
729 reader, paused ? -1 : 8, generation);
732 if (paused && window_full)
733 cache->
Touch(playhead);
735 wait(fps > 0 ? std::max(1,
static_cast<int>(1000.0 / fps / 4.0)) : 10);
Header file for CacheBase class.
Header file for all Exception classes.
Header file for Frame class.
Header file for global Settings class.
Header file for Timeline class.
Header file for VideoCacheThread class.
void Observe(int64_t duration_us)
int64_t Target(double fps, int speed, int64_t capacity, int64_t conservative, int64_t maximum)
bool Ready(int64_t now, int64_t contiguous_ahead, bool current_available, int64_t required, int64_t minimum_step=1)
All cache managers in libopenshot are based on this CacheBase class.
virtual bool Contains(int64_t frame_number)=0
Check if frame is already contained in cache.
virtual void Remove(int64_t frame_number)=0
Remove a specific frame.
virtual int64_t Count()=0
Count the frames in the queue.
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
virtual void Touch(int64_t frame_number)=0
Move frame to front of queue (so it lasts longer)
int64_t GetMaxBytes()
Gets the maximum bytes value.
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Exception for frames that are out of bounds.
This abstract class is the base class, used by all readers in libopenshot.
openshot::ReaderInfo info
Information about the current media file.
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
This class is contains settings used by libopenshot (and can be safely toggled at any point)
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
int VIDEO_CACHE_MIN_PREROLL_FRAMES
Minimum number of frames to cache before playback begins.
int VIDEO_CACHE_MAX_FRAMES
Max number of frames (when paused) to cache for playback.
float VIDEO_CACHE_PERCENT_AHEAD
Percentage of cache in front of the playhead (0.0 to 1.0)
bool ENABLE_PLAYBACK_CACHING
Enable/Disable the cache thread to pre-fetch and cache video frames before we need them.
int VIDEO_CACHE_MAX_PREROLL_FRAMES
Max number of frames (ahead of playhead) to cache during playback.
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
This class represents a timeline.
uint64_t CacheEpoch() const
Return the current cache invalidation epoch.
std::atomic< bool > diagnostics_enabled
bool StopThread(int timeoutMs=0)
Stop the cache thread (wait up to timeoutMs ms). Returns true if it stopped.
void setSpeed(int new_speed)
Set playback speed/direction. Positive = forward, negative = rewind, zero = pause.
std::atomic< int64_t > acknowledgement_us
void handleUserSeekWithPreroll(int64_t playhead, int dir, int64_t timeline_end, int64_t preroll_frames)
Reset last_cached_index to start caching with a directional preroll offset.
~VideoCacheThread() override
int64_t resolveTimelineEnd() const
Resolve timeline end frame from reader/timeline metadata.
int64_t readiness_bytes_per_frame
int64_t clampToTimelineRange(int64_t frame, int64_t timeline_end) const
Clamp frame index to [1, timeline_end] when timeline_end is valid.
void Stop()
Stop method is unimplemented.
std::atomic< bool > preroll_on_next_fill
True if next cache rebuild should include preroll offset.
bool clearCacheIfPaused(int64_t playhead, bool paused, CacheBase *cache)
When paused and playhead is outside current cache, clear all frames.
std::atomic< int64_t > last_cached_index
Index of the most recently cached frame.
std::atomic< int > speed
Current playback speed (0=paused, >0 forward, <0 backward).
void updateReadiness(CacheBase *cache, int64_t playhead, int dir)
Worker-only cache probing; never runs in the audio callback.
void run() override
Thread entry point: loops until threadShouldExit() is true.
int64_t readiness_minimum_step
int64_t computePrerollFrames(const Settings *settings) const
Compute preroll frame count from settings.
std::atomic< uint64_t > processed_generation
Last request whose scheduling baseline was consumed.
bool isReady()
Shared startup gate; bounded fallback resumes established video decoding.
std::mutex seek_state_mutex
Protects coherent seek state updates/consumption.
uint64_t readiness_generation
Request covered by the readiness snapshot.
bool prefetchWindow(CacheBase *cache, int64_t window_begin, int64_t window_end, int dir, ReaderBase *reader, int64_t max_frames_to_fetch=-1, uint64_t generation=UINT64_MAX)
Prefetch all missing frames in [window_begin ... window_end] or [window_end ... window_begin].
std::atomic< bool > cache_audio_only
VideoCacheThread()
Constructor: initializes member variables and assumes forward direction on first launch.
uint64_t seen_timeline_cache_epoch
Last observed Timeline cache invalidation epoch.
std::atomic< int64_t > requested_display_frame
Frame index the user requested.
void Reader(ReaderBase *new_reader)
Attach a ReaderBase (e.g. Timeline, FFmpegReader) and begin caching.
void handleUserSeek(int64_t playhead, int dir)
If userSeeked is true, reset last_cached_index just behind the playhead.
int64_t getBytes(int width, int height, int sample_rate, int channels, float fps)
Estimate memory usage for a single frame (video + audio).
ReaderBase * reader
The source reader (e.g., Timeline, FFmpegReader).
int64_t readiness_timeline_end
std::atomic< int64_t > first_current_frame_us
AdaptivePreroll preroll_policy
void Seek(int64_t new_position)
Backward-compatible alias for playback position updates (no seek side effects).
std::atomic< int64_t > request_time_us
std::atomic< int64_t > cached_frame_count
Estimated count of frames currently stored in cache.
std::atomic< int > last_dir
Last direction sign (+1 forward, –1 backward).
int64_t readiness_physical_capacity
std::atomic< int64_t > min_frames_ahead
Minimum number of frames considered “ready” (pre-roll).
std::mutex lifecycle_mutex
Start/stop/reader drain; never acquired by worker.
std::atomic< bool > userSeeked
True if Seek(..., true) was called (forces a cache reset).
std::atomic< bool > audio_cache_miss
int64_t readiness_capacity
std::atomic< uint64_t > request_generation
Invalidates obsolete fills, including seeks retaining cache.
void computeWindowBounds(int64_t playhead, int dir, int64_t ahead_count, int64_t timeline_end, int64_t &window_begin, int64_t &window_end) const
Compute the “window” of frames to cache around playhead.
std::atomic< int64_t > obsolete_completion_us
void acknowledgeRequest(int64_t started_us)
static int64_t monotonicUs()
bool StartThread()
Start the cache thread at high priority. Returns true if it’s actually running.
bool timeline_cache_epoch_initialized
True once an initial epoch snapshot has been taken.
std::atomic< bool > scrub_active
True while user is dragging/scrubbing the playhead.
int computeDirection() const
int64_t observed_frame_bytes
void NotifyPlaybackPosition(int64_t new_position)
Update playback position without triggering seek behavior or cache invalidation.
std::atomic< int > last_speed
Last non-zero speed (for tracking).
std::atomic< bool > clear_cache_on_next_fill
True if next cache loop should clear existing cache ranges.
int64_t readiness_playhead
This namespace is the default namespace for all code in the openshot library.
int width
The width of the video (in pixesl)
int channels
The number of audio channels used in the audio stream.
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
int height
The height of the video (in pixels)
int64_t video_length
The number of frames in the video stream.
int sample_rate
The number of audio samples per second (44100 is a common sample rate)