OpenShot Library | libopenshot  0.4.0
VideoCacheThread.cpp
Go to the documentation of this file.
1 
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 
23 namespace 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  , is_playing(false)
32  , userSeeked(false)
33  , requested_display_frame(1)
34  , current_display_frame(1)
35  , cached_frame_count(0)
36  , min_frames_ahead(4)
37  , max_frames_ahead(8)
38  , timeline_max_frame(0)
39  , reader(nullptr)
40  , force_directional_cache(false)
41  , last_cached_index(0)
42  {
43  }
44 
45  // Destructor
47  {
48  }
49 
50  // Play the video
52  {
53  is_playing = true;
54  }
55 
56  // Stop the video
58  {
59  is_playing = false;
60  }
61 
62  // Is cache ready for playback (pre-roll)
64  {
66  }
67 
68  void VideoCacheThread::setSpeed(int new_speed)
69  {
70  // Only update last_speed and last_dir when new_speed != 0
71  if (new_speed != 0) {
72  last_speed = new_speed;
73  last_dir = (new_speed > 0 ? 1 : -1);
74  }
75  speed = new_speed;
76  }
77 
78  // Get the size in bytes of a frame (rough estimate)
79  int64_t VideoCacheThread::getBytes(int width,
80  int height,
81  int sample_rate,
82  int channels,
83  float fps)
84  {
85  // RGBA video frame
86  int64_t bytes = static_cast<int64_t>(width) * height * sizeof(char) * 4;
87  // Approximate audio: (sample_rate * channels)/fps samples per frame
88  bytes += ((sample_rate * channels) / fps) * sizeof(float);
89  return bytes;
90  }
91 
92  void VideoCacheThread::Seek(int64_t new_position, bool start_preroll)
93  {
94  if (start_preroll) {
95  userSeeked = true;
96  }
97  requested_display_frame = new_position;
98  }
99 
100  void VideoCacheThread::Seek(int64_t new_position)
101  {
102  Seek(new_position, false);
103  }
104 
106  {
107  // If speed ≠ 0, use its sign; if speed==0, keep last_dir
108  return (speed != 0 ? (speed > 0 ? 1 : -1) : last_dir);
109  }
110 
111  void VideoCacheThread::handleUserSeek(int64_t playhead, int dir)
112  {
113  // Place last_cached_index just “behind” playhead in the given dir
114  last_cached_index = playhead - dir;
115  }
116 
118  bool paused,
119  CacheBase* cache)
120  {
121  if (paused && !cache->Contains(playhead)) {
122  // If paused and playhead not in cache, clear everything
123  Timeline* timeline = static_cast<Timeline*>(reader);
124  timeline->ClearAllCache();
125  return true;
126  }
127  return false;
128  }
129 
131  int dir,
132  int64_t ahead_count,
133  int64_t timeline_end,
134  int64_t& window_begin,
135  int64_t& window_end) const
136  {
137  if (dir > 0) {
138  // Forward window: [playhead ... playhead + ahead_count]
139  window_begin = playhead;
140  window_end = playhead + ahead_count;
141  }
142  else {
143  // Backward window: [playhead - ahead_count ... playhead]
144  window_begin = playhead - ahead_count;
145  window_end = playhead;
146  }
147  // Clamp to [1 ... timeline_end]
148  window_begin = std::max<int64_t>(window_begin, 1);
149  window_end = std::min<int64_t>(window_end, timeline_end);
150  }
151 
153  int64_t window_begin,
154  int64_t window_end,
155  int dir,
156  ReaderBase* reader)
157  {
158  bool window_full = true;
159  int64_t next_frame = last_cached_index + dir;
160 
161  // Advance from last_cached_index toward window boundary
162  while ((dir > 0 && next_frame <= window_end) ||
163  (dir < 0 && next_frame >= window_begin))
164  {
165  if (threadShouldExit()) {
166  break;
167  }
168  // If a Seek was requested mid-caching, bail out immediately
169  if (userSeeked) {
170  break;
171  }
172 
173  if (!cache->Contains(next_frame)) {
174  // Frame missing, fetch and add
175  try {
176  auto framePtr = reader->GetFrame(next_frame);
177  cache->Add(framePtr);
179  }
180  catch (const OutOfBoundsFrame&) {
181  break;
182  }
183  window_full = false;
184  }
185  else {
186  cache->Touch(next_frame);
187  }
188 
189  last_cached_index = next_frame;
190  next_frame += dir;
191  }
192 
193  return window_full;
194  }
195 
197  {
198  using micro_sec = std::chrono::microseconds;
199  using double_micro_sec = std::chrono::duration<double, micro_sec::period>;
200 
201  while (!threadShouldExit()) {
202  Settings* settings = Settings::Instance();
203  CacheBase* cache = reader ? reader->GetCache() : nullptr;
204 
205  // If caching disabled or no reader, sleep briefly
206  if (!settings->ENABLE_PLAYBACK_CACHING || !cache) {
207  std::this_thread::sleep_for(double_micro_sec(50000));
208  continue;
209  }
210 
211  Timeline* timeline = static_cast<Timeline*>(reader);
212  int64_t timeline_end = timeline->GetMaxFrame();
213  int64_t playhead = requested_display_frame;
214  bool paused = (speed == 0);
215 
216  // Compute effective direction (±1)
217  int dir = computeDirection();
218  if (speed != 0) {
219  last_dir = dir;
220  }
221 
222  // If a seek was requested, reset last_cached_index
223  if (userSeeked) {
224  handleUserSeek(playhead, dir);
225  userSeeked = false;
226  }
227  else if (!paused) {
228  // Check if last_cached_index drifted outside the new window; if so, reset it
229  int64_t bytes_per_frame = getBytes(
230  (timeline->preview_width ? timeline->preview_width : reader->info.width),
231  (timeline->preview_height ? timeline->preview_height : reader->info.height),
235  );
236  int64_t max_bytes = cache->GetMaxBytes();
237  if (max_bytes > 0 && bytes_per_frame > 0) {
238  int64_t capacity = max_bytes / bytes_per_frame;
239  if (capacity >= 1) {
240  int64_t ahead_count = static_cast<int64_t>(capacity *
241  settings->VIDEO_CACHE_PERCENT_AHEAD);
242  int64_t window_begin, window_end;
243  computeWindowBounds(playhead,
244  dir,
245  ahead_count,
246  timeline_end,
247  window_begin,
248  window_end);
249 
250  bool outside_window =
251  (dir > 0 && last_cached_index > window_end) ||
252  (dir < 0 && last_cached_index < window_begin);
253  if (outside_window) {
254  handleUserSeek(playhead, dir);
255  }
256  }
257  }
258  }
259 
260  // Recompute capacity & ahead_count now that we’ve possibly updated last_cached_index
261  int64_t bytes_per_frame = getBytes(
262  (timeline->preview_width ? timeline->preview_width : reader->info.width),
263  (timeline->preview_height ? timeline->preview_height : reader->info.height),
267  );
268  int64_t max_bytes = cache->GetMaxBytes();
269  if (max_bytes <= 0 || bytes_per_frame <= 0) {
270  std::this_thread::sleep_for(double_micro_sec(50000));
271  continue;
272  }
273  int64_t capacity = max_bytes / bytes_per_frame;
274  if (capacity < 1) {
275  std::this_thread::sleep_for(double_micro_sec(50000));
276  continue;
277  }
278  int64_t ahead_count = static_cast<int64_t>(capacity *
279  settings->VIDEO_CACHE_PERCENT_AHEAD);
280 
281  // If paused and playhead is no longer in cache, clear everything
282  bool did_clear = clearCacheIfPaused(playhead, paused, cache);
283  if (did_clear) {
284  handleUserSeek(playhead, dir);
285  }
286 
287  // Compute the current caching window
288  int64_t window_begin, window_end;
289  computeWindowBounds(playhead,
290  dir,
291  ahead_count,
292  timeline_end,
293  window_begin,
294  window_end);
295 
296  // Attempt to fill any missing frames in that window
297  bool window_full = prefetchWindow(cache,
298  window_begin,
299  window_end,
300  dir,
301  reader);
302 
303  // If paused and window was already full, keep playhead fresh
304  if (paused && window_full) {
305  cache->Touch(playhead);
306  }
307 
308  // Sleep a short fraction of a frame interval
309  int64_t sleep_us = static_cast<int64_t>(
310  1000000.0 / reader->info.fps.ToFloat() / 4.0
311  );
312  std::this_thread::sleep_for(double_micro_sec(sleep_us));
313  }
314  }
315 
316 } // namespace openshot
Settings.h
Header file for global Settings class.
openshot::ReaderInfo::sample_rate
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60
openshot::VideoCacheThread::VideoCacheThread
VideoCacheThread()
Constructor: initializes member variables and assumes forward direction on first launch.
Definition: VideoCacheThread.cpp:26
openshot::Fraction::ToFloat
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:35
openshot::Settings::VIDEO_CACHE_PERCENT_AHEAD
float VIDEO_CACHE_PERCENT_AHEAD
Percentage of cache in front of the playhead (0.0 to 1.0)
Definition: Settings.h:86
openshot::TimelineBase::preview_width
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:44
openshot::ReaderBase::GetFrame
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
openshot::VideoCacheThread::prefetchWindow
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].
Definition: VideoCacheThread.cpp:152
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::TimelineBase::preview_height
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:45
openshot::CacheBase::Add
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
openshot::VideoCacheThread::min_frames_ahead
int64_t min_frames_ahead
Minimum number of frames considered “ready” (pre-roll).
Definition: VideoCacheThread.h:167
openshot::VideoCacheThread::computeDirection
int computeDirection() const
Definition: VideoCacheThread.cpp:105
openshot::VideoCacheThread::reader
ReaderBase * reader
The source reader (e.g., Timeline, FFmpegReader).
Definition: VideoCacheThread.h:171
openshot::ReaderBase::info
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:88
openshot::Settings
This class is contains settings used by libopenshot (and can be safely toggled at any point)
Definition: Settings.h:26
Timeline.h
Header file for Timeline class.
openshot::VideoCacheThread::handleUserSeek
void handleUserSeek(int64_t playhead, int dir)
If userSeeked is true, reset last_cached_index just behind the playhead.
Definition: VideoCacheThread.cpp:111
openshot::Timeline::ClearAllCache
void ClearAllCache(bool deep=false)
Definition: Timeline.cpp:1715
openshot::Settings::ENABLE_PLAYBACK_CACHING
bool ENABLE_PLAYBACK_CACHING
Enable/Disable the cache thread to pre-fetch and cache video frames before we need them.
Definition: Settings.h:98
openshot::ReaderInfo::width
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
openshot::CacheBase
All cache managers in libopenshot are based on this CacheBase class.
Definition: CacheBase.h:34
openshot::VideoCacheThread::Play
void Play()
Set is_playing = true, so run() will begin caching/playback.
Definition: VideoCacheThread.cpp:51
CacheBase.h
Header file for CacheBase class.
openshot::OutOfBoundsFrame
Exception for frames that are out of bounds.
Definition: Exceptions.h:300
openshot::VideoCacheThread::~VideoCacheThread
~VideoCacheThread() override
Definition: VideoCacheThread.cpp:46
openshot::ReaderInfo::height
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
openshot::VideoCacheThread::last_speed
int last_speed
Last non-zero speed (for tracking).
Definition: VideoCacheThread.h:157
openshot::Timeline
This class represents a timeline.
Definition: Timeline.h:148
openshot::VideoCacheThread::setSpeed
void setSpeed(int new_speed)
Set playback speed/direction. Positive = forward, negative = rewind, zero = pause.
Definition: VideoCacheThread.cpp:68
openshot::VideoCacheThread::userSeeked
bool userSeeked
True if Seek(..., true) was called (forces a cache reset).
Definition: VideoCacheThread.h:161
openshot::VideoCacheThread::speed
int speed
Current playback speed (0=paused, >0 forward, <0 backward).
Definition: VideoCacheThread.h:156
openshot::Settings::Instance
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
Definition: Settings.cpp:23
openshot::VideoCacheThread::Stop
void Stop()
Set is_playing = false, effectively pausing playback (caching still runs).
Definition: VideoCacheThread.cpp:57
openshot::CacheBase::Touch
virtual void Touch(int64_t frame_number)=0
Move frame to front of queue (so it lasts longer)
Frame.h
Header file for Frame class.
openshot::VideoCacheThread::run
void run() override
Thread entry point: loops until threadShouldExit() is true.
Definition: VideoCacheThread.cpp:196
openshot::VideoCacheThread::last_cached_index
int64_t last_cached_index
Index of the most recently cached frame.
Definition: VideoCacheThread.h:174
VideoCacheThread.h
Header file for VideoCacheThread class.
openshot::VideoCacheThread::getBytes
int64_t getBytes(int width, int height, int sample_rate, int channels, float fps)
Estimate memory usage for a single frame (video + audio).
Definition: VideoCacheThread.cpp:79
openshot::VideoCacheThread::clearCacheIfPaused
bool clearCacheIfPaused(int64_t playhead, bool paused, CacheBase *cache)
When paused and playhead is outside current cache, clear all frames.
Definition: VideoCacheThread.cpp:117
openshot::VideoCacheThread::last_dir
int last_dir
Last direction sign (+1 forward, –1 backward).
Definition: VideoCacheThread.h:158
openshot::VideoCacheThread::cached_frame_count
int64_t cached_frame_count
Count of frames currently added to cache.
Definition: VideoCacheThread.h:165
openshot::CacheBase::GetMaxBytes
int64_t GetMaxBytes()
Gets the maximum bytes value.
Definition: CacheBase.h:101
openshot::ReaderInfo::fps
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
openshot::CacheBase::Contains
virtual bool Contains(int64_t frame_number)=0
Check if frame is already contained in cache.
openshot::ReaderBase
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:75
openshot::Timeline::GetMaxFrame
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition: Timeline.cpp:469
openshot::VideoCacheThread::computeWindowBounds
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.
Definition: VideoCacheThread.cpp:130
openshot::VideoCacheThread::is_playing
bool is_playing
True if playback is “running” (affects thread loop, not caching).
Definition: VideoCacheThread.h:160
openshot::VideoCacheThread::Seek
void Seek(int64_t new_position)
Seek to a specific frame (no preroll).
Definition: VideoCacheThread.cpp:100
openshot::VideoCacheThread::requested_display_frame
int64_t requested_display_frame
Frame index the user requested.
Definition: VideoCacheThread.h:163
openshot::ReaderInfo::channels
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::VideoCacheThread::isReady
bool isReady()
Definition: VideoCacheThread.cpp:63
openshot::ReaderBase::GetCache
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
Exceptions.h
Header file for all Exception classes.