OpenShot Library | libopenshot 0.5.0
Loading...
Searching...
No Matches
VideoCacheThread.cpp
Go to the documentation of this file.
1
8
9// Copyright (c) 2008-2025 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "VideoCacheThread.h"
14#include "CacheBase.h"
15#include "Exceptions.h"
16#include "Frame.h"
17#include "Settings.h"
18#include "Timeline.h"
19#include <thread>
20#include <chrono>
21#include <algorithm>
22
23namespace openshot
24{
25 // Constructor
27 : Thread("video-cache")
28 , speed(0)
29 , last_speed(1)
30 , last_dir(1) // assume forward (+1) on first launch
31 , userSeeked(false)
37 , reader(nullptr)
40 {
41 }
42
43 // Destructor
47
48 // Is cache ready for playback (pre-roll)
50 {
51 if (!reader) {
52 return false;
53 }
54
55 if (min_frames_ahead < 0) {
56 return true;
57 }
58
60 }
61
62 void VideoCacheThread::setSpeed(int new_speed)
63 {
64 // Only update last_speed and last_dir when new_speed != 0
65 if (new_speed != 0) {
66 last_speed = new_speed;
67 last_dir = (new_speed > 0 ? 1 : -1);
68 }
69 speed = new_speed;
70 }
71
72 // Get the size in bytes of a frame (rough estimate)
73 int64_t VideoCacheThread::getBytes(int width,
74 int height,
75 int sample_rate,
76 int channels,
77 float fps)
78 {
79 // RGBA video frame
80 int64_t bytes = static_cast<int64_t>(width) * height * sizeof(char) * 4;
81 // Approximate audio: (sample_rate * channels)/fps samples per frame
82 bytes += ((sample_rate * channels) / fps) * sizeof(float);
83 return bytes;
84 }
85
88 {
89 // JUCE’s startThread() returns void, so we launch it and then check if
90 // the thread actually started:
91 startThread(Priority::high);
92 return isThreadRunning();
93 }
94
96 bool VideoCacheThread::StopThread(int timeoutMs)
97 {
98 stopThread(timeoutMs);
99 return !isThreadRunning();
100 }
101
102 void VideoCacheThread::Seek(int64_t new_position, bool start_preroll)
103 {
104 if (start_preroll) {
105 userSeeked = true;
106
107 CacheBase* cache = reader ? reader->GetCache() : nullptr;
108
109 if (cache && !cache->Contains(new_position))
110 {
111 // If user initiated seek, and current frame not found (
112 Timeline* timeline = static_cast<Timeline*>(reader);
113 timeline->ClearAllCache();
115 }
116 else if (cache)
117 {
118 cached_frame_count = cache->Count();
119 }
120 }
121 requested_display_frame = new_position;
122 }
123
124 void VideoCacheThread::Seek(int64_t new_position)
125 {
126 Seek(new_position, false);
127 }
128
130 {
131 // If speed ≠ 0, use its sign; if speed==0, keep last_dir
132 return (speed != 0 ? (speed > 0 ? 1 : -1) : last_dir);
133 }
134
135 void VideoCacheThread::handleUserSeek(int64_t playhead, int dir)
136 {
137 // Place last_cached_index just “behind” playhead in the given dir
138 last_cached_index = playhead - dir;
139 }
140
142 bool paused,
143 CacheBase* cache)
144 {
145 if (paused && !cache->Contains(playhead)) {
146 // If paused and playhead not in cache, clear everything
147 Timeline* timeline = static_cast<Timeline*>(reader);
148 timeline->ClearAllCache();
150 return true;
151 }
152 return false;
153 }
154
156 int dir,
157 int64_t ahead_count,
158 int64_t timeline_end,
159 int64_t& window_begin,
160 int64_t& window_end) const
161 {
162 if (dir > 0) {
163 // Forward window: [playhead ... playhead + ahead_count]
164 window_begin = playhead;
165 window_end = playhead + ahead_count;
166 }
167 else {
168 // Backward window: [playhead - ahead_count ... playhead]
169 window_begin = playhead - ahead_count;
170 window_end = playhead;
171 }
172 // Clamp to [1 ... timeline_end]
173 window_begin = std::max<int64_t>(window_begin, 1);
174 window_end = std::min<int64_t>(window_end, timeline_end);
175 }
176
178 int64_t window_begin,
179 int64_t window_end,
180 int dir,
182 {
183 bool window_full = true;
184 int64_t next_frame = last_cached_index + dir;
185
186 // Advance from last_cached_index toward window boundary
187 while ((dir > 0 && next_frame <= window_end) ||
188 (dir < 0 && next_frame >= window_begin))
189 {
190 if (threadShouldExit()) {
191 break;
192 }
193 // If a Seek was requested mid-caching, bail out immediately
194 if (userSeeked) {
195 break;
196 }
197
198 if (!cache->Contains(next_frame)) {
199 // Frame missing, fetch and add
200 try {
201 auto framePtr = reader->GetFrame(next_frame);
202 cache->Add(framePtr);
203 cached_frame_count = cache->Count();
204 }
205 catch (const OutOfBoundsFrame&) {
206 break;
207 }
208 window_full = false;
209 }
210 else {
211 cache->Touch(next_frame);
212 }
213
214 last_cached_index = next_frame;
215 next_frame += dir;
216 }
217
218 return window_full;
219 }
220
222 {
223 using micro_sec = std::chrono::microseconds;
224 using double_micro_sec = std::chrono::duration<double, micro_sec::period>;
225
226 while (!threadShouldExit()) {
227 Settings* settings = Settings::Instance();
228 CacheBase* cache = reader ? reader->GetCache() : nullptr;
229
230 // If caching disabled or no reader, mark cache as ready and sleep briefly
231 if (!settings->ENABLE_PLAYBACK_CACHING || !cache) {
232 cached_frame_count = (cache ? cache->Count() : 0);
233 min_frames_ahead = -1;
234 std::this_thread::sleep_for(double_micro_sec(50000));
235 continue;
236 }
237
238 // init local vars
240
241 Timeline* timeline = static_cast<Timeline*>(reader);
242 int64_t timeline_end = timeline->GetMaxFrame();
243 int64_t playhead = requested_display_frame;
244 bool paused = (speed == 0);
245
246 cached_frame_count = cache->Count();
247
248 // Compute effective direction (±1)
249 int dir = computeDirection();
250 if (speed != 0) {
251 last_dir = dir;
252 }
253
254 // Compute bytes_per_frame, max_bytes, and capacity once
255 int64_t bytes_per_frame = getBytes(
256 (timeline->preview_width ? timeline->preview_width : reader->info.width),
257 (timeline->preview_height ? timeline->preview_height : reader->info.height),
258 reader->info.sample_rate,
259 reader->info.channels,
260 reader->info.fps.ToFloat()
261 );
262 int64_t max_bytes = cache->GetMaxBytes();
263 int64_t capacity = 0;
264 if (max_bytes > 0 && bytes_per_frame > 0) {
265 capacity = max_bytes / bytes_per_frame;
266 if (capacity > settings->VIDEO_CACHE_MAX_FRAMES) {
267 capacity = settings->VIDEO_CACHE_MAX_FRAMES;
268 }
269 }
270
271 // Handle a user-initiated seek
272 if (userSeeked) {
273 handleUserSeek(playhead, dir);
274 userSeeked = false;
275 }
276 else if (!paused && capacity >= 1) {
277 // In playback mode, check if last_cached_index drifted outside the new window
278 int64_t base_ahead = static_cast<int64_t>(capacity * settings->VIDEO_CACHE_PERCENT_AHEAD);
279
280 int64_t window_begin, window_end;
282 playhead,
283 dir,
284 base_ahead,
285 timeline_end,
286 window_begin,
287 window_end
288 );
289
290 bool outside_window =
291 (dir > 0 && last_cached_index > window_end) ||
292 (dir < 0 && last_cached_index < window_begin);
293 if (outside_window) {
294 handleUserSeek(playhead, dir);
295 }
296 }
297
298 // If capacity is insufficient, sleep and retry
299 if (capacity < 1) {
300 std::this_thread::sleep_for(double_micro_sec(50000));
301 continue;
302 }
303 int64_t ahead_count = static_cast<int64_t>(capacity *
304 settings->VIDEO_CACHE_PERCENT_AHEAD);
305 int64_t window_size = ahead_count + 1;
306 if (window_size < 1) {
307 window_size = 1;
308 }
309 int64_t ready_target = window_size - 1;
310 if (ready_target < 0) {
311 ready_target = 0;
312 }
313 int64_t configured_min = settings->VIDEO_CACHE_MIN_PREROLL_FRAMES;
314 min_frames_ahead = std::min<int64_t>(configured_min, ready_target);
315
316 // If paused and playhead is no longer in cache, clear everything
317 bool did_clear = clearCacheIfPaused(playhead, paused, cache);
318 if (did_clear) {
319 handleUserSeek(playhead, dir);
320 }
321
322 // Compute the current caching window
323 int64_t window_begin, window_end;
324 computeWindowBounds(playhead,
325 dir,
326 ahead_count,
327 timeline_end,
328 window_begin,
329 window_end);
330
331 // Attempt to fill any missing frames in that window
332 bool window_full = prefetchWindow(cache, window_begin, window_end, dir, reader);
333
334 // If paused and window was already full, keep playhead fresh
335 if (paused && window_full) {
336 cache->Touch(playhead);
337 }
338
339 // Sleep a short fraction of a frame interval
340 int64_t sleep_us = static_cast<int64_t>(
341 1000000.0 / reader->info.fps.ToFloat() / 4.0
342 );
343 std::this_thread::sleep_for(double_micro_sec(sleep_us));
344 }
345 }
346
347} // namespace openshot
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.
All cache managers in libopenshot are based on this CacheBase class.
Definition CacheBase.h:35
virtual bool Contains(int64_t frame_number)=0
Check if frame is already contained in cache.
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.
Definition CacheBase.h:101
Exception for frames that are out of bounds.
Definition Exceptions.h:301
This abstract class is the base class, used by all readers in libopenshot.
Definition ReaderBase.h:76
This class is contains settings used by libopenshot (and can be safely toggled at any point).
Definition Settings.h:26
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method).
Definition Settings.cpp:23
int VIDEO_CACHE_MIN_PREROLL_FRAMES
Minimum number of frames to cache before playback begins.
Definition Settings.h:89
int VIDEO_CACHE_MAX_FRAMES
Max number of frames (when paused) to cache for playback.
Definition Settings.h:95
float VIDEO_CACHE_PERCENT_AHEAD
Percentage of cache in front of the playhead (0.0 to 1.0).
Definition Settings.h:86
bool ENABLE_PLAYBACK_CACHING
Enable/Disable the cache thread to pre-fetch and cache video frames before we need them.
Definition Settings.h:98
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.
Definition Timeline.h:154
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition Timeline.cpp:474
void ClearAllCache(bool deep=false)
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.
bool force_directional_cache
(Reserved for future use).
bool prefetchWindow(CacheBase *cache, int64_t window_begin, int64_t window_end, int dir, ReaderBase *reader)
Prefetch all missing frames in [window_begin ... window_end] or [window_end ... window_begin].
int64_t requested_display_frame
Frame index the user requested.
int64_t timeline_max_frame
Highest valid frame index in the timeline.
bool clearCacheIfPaused(int64_t playhead, bool paused, CacheBase *cache)
When paused and playhead is outside current cache, clear all frames.
int64_t last_cached_index
Index of the most recently cached frame.
int64_t cached_frame_count
Estimated count of frames currently stored in cache.
void run() override
Thread entry point: loops until threadShouldExit() is true.
int last_speed
Last non-zero speed (for tracking).
int64_t current_display_frame
Currently displayed frame (unused here, reserved).
VideoCacheThread()
Constructor: initializes member variables and assumes forward direction on first launch.
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).
void Seek(int64_t new_position)
Seek to a specific frame (no preroll).
bool userSeeked
True if Seek(..., true) was called (forces a cache reset).
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.
int64_t min_frames_ahead
Minimum number of frames considered “ready” (pre-roll).
int last_dir
Last direction sign (+1 forward, –1 backward).
bool StartThread()
Start the cache thread at high priority. Returns true if it’s actually running.
int speed
Current playback speed (0=paused, >0 forward, <0 backward).
This namespace is the default namespace for all code in the openshot library.
Definition Compressor.h:29