OpenShot Library | libopenshot  0.3.2
VideoCacheThread.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2019 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "VideoCacheThread.h"
14 
15 #include "CacheBase.h"
16 #include "Exceptions.h"
17 #include "Frame.h"
18 #include "OpenMPUtilities.h"
19 #include "Settings.h"
20 #include "Timeline.h"
21 
22 #include <algorithm>
23 #include <thread> // for std::this_thread::sleep_for
24 #include <chrono> // for std::chrono::microseconds
25 
26 namespace openshot
27 {
28  // Constructor
30  : Thread("video-cache"), speed(0), last_speed(1), is_playing(false),
31  reader(NULL), current_display_frame(1), cached_frame_count(0),
32  min_frames_ahead(4), max_frames_ahead(8), should_pause_cache(false),
33  timeline_max_frame(0), should_break(false)
34  {
35  }
36 
37  // Destructor
39  {
40  }
41 
42  // Seek the reader to a particular frame number
43  void VideoCacheThread::Seek(int64_t new_position)
44  {
45  requested_display_frame = new_position;
46  }
47 
48  // Seek the reader to a particular frame number and optionally start the pre-roll
49  void VideoCacheThread::Seek(int64_t new_position, bool start_preroll)
50  {
51  // Get timeline instance
52  Timeline *t = (Timeline *) reader;
53 
54  // Calculate last frame # on timeline (to prevent caching past this point)
56 
57  // Determine previous frame number (depending on last non-zero/non-paused speed)
58  int64_t previous_frame = new_position;
59  if (last_speed < 0) {
60  // backwards
61  previous_frame++;
62  } else if (last_speed > 0) {
63  // forward
64  previous_frame--;
65  }
66  if (previous_frame <= 0) {
67  // min frame is 1
68  previous_frame = 1;
69  }
70 
71  // Clear cache if previous frame outside the cached range, which means we are
72  // requesting a non-contigous frame compared to our current cache range
73  if (new_position >= 1 && new_position <= timeline_max_frame && !reader->GetCache()->Contains(previous_frame)) {
74  // Clear cache
75  t->ClearAllCache();
76 
77  // Break out of any existing cache loop
78  should_break = true;
79 
80  // Force cache direction back to forward
81  last_speed = 1;
82  }
83 
84  // Reset pre-roll when requested frame is not currently cached
85  if (start_preroll && reader && reader->GetCache() && !reader->GetCache()->Contains(new_position)) {
86  // Break out of any existing cache loop
87  should_break = true;
88 
89  // Reset stats and allow cache to rebuild (if paused)
91  if (speed == 0) {
92  should_pause_cache = false;
93  }
94  }
95 
96  // Actually update seek position
97  Seek(new_position);
98  }
99 
100  // Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
101  void VideoCacheThread::setSpeed(int new_speed) {
102  if (new_speed != 0) {
103  // Track last non-zero speed
104  last_speed = new_speed;
105  }
106  speed = new_speed;
107  }
108 
109  // Get the size in bytes of a frame (rough estimate)
110  int64_t VideoCacheThread::getBytes(int width, int height, int sample_rate, int channels, float fps)
111  {
112  int64_t total_bytes = 0;
113  total_bytes += static_cast<int64_t>(width * height * sizeof(char) * 4);
114 
115  // approximate audio size (sample rate / 24 fps)
116  total_bytes += ((sample_rate * channels) / fps) * sizeof(float);
117 
118  // return size of this frame
119  return total_bytes;
120  }
121 
122  // Play the video
124  // Start playing
125  is_playing = true;
126  }
127 
128  // Stop the audio
130  // Stop playing
131  is_playing = false;
132  }
133 
134  // Is cache ready for playback (pre-roll)
137  }
138 
139  // Start the thread
141  {
142  // Types for storing time durations in whole and fractional microseconds
143  using micro_sec = std::chrono::microseconds;
144  using double_micro_sec = std::chrono::duration<double, micro_sec::period>;
145 
146  while (!threadShouldExit() && is_playing) {
147  // Get settings
149 
150  // init local vars
153 
154  // Calculate on-screen time for a single frame
155  const auto frame_duration = double_micro_sec(1000000.0 / reader->info.fps.ToDouble());
156  int current_speed = speed;
157 
158  // Increment and direction for cache loop
159  int64_t increment = 1;
160 
161  // Check for empty cache (and re-trigger preroll)
162  // This can happen when the user manually empties the timeline cache
163  if (reader->GetCache()->Count() == 0) {
164  should_pause_cache = false;
165  cached_frame_count = 0;
166  }
167 
168  // Update current display frame
170 
171  if (current_speed == 0 && should_pause_cache || !s->ENABLE_PLAYBACK_CACHING) {
172  // Sleep during pause (after caching additional frames when paused)
173  // OR sleep when playback caching is disabled
174  std::this_thread::sleep_for(frame_duration / 2);
175  continue;
176 
177  } else if (current_speed == 0) {
178  // Allow 'max frames' to increase when pause is detected (based on cache)
179  // To allow the cache to fill-up only on the initial pause.
180  should_pause_cache = true;
181 
182  // Calculate bytes per frame
183  int64_t bytes_per_frame = getBytes(reader->info.width, reader->info.height,
185  reader->info.fps.ToFloat());
186  Timeline *t = (Timeline *) reader;
188  // If we have a different timeline preview size, use that instead (the preview
189  // window can be smaller, can thus reduce the bytes per frame)
190  bytes_per_frame = getBytes(t->preview_width, t->preview_height,
192  reader->info.fps.ToFloat());
193  }
194 
195  // Calculate # of frames on Timeline cache (when paused)
196  if (reader->GetCache() && reader->GetCache()->GetMaxBytes() > 0) {
197  // When paused, limit the cached frames to the following % of total cache size.
198  // This allows for us to leave some cache behind the plahead, and some in front of the playhead.
199  max_frames_ahead = (reader->GetCache()->GetMaxBytes() / bytes_per_frame) * s->VIDEO_CACHE_PERCENT_AHEAD;
201  // Ignore values that are too large, and default to a safer value
203  }
204  }
205 
206  // Overwrite the increment to our cache position
207  // to fully cache frames while paused (support forward and rewind caching)
208  // Use `last_speed` which is the last non-zero/non-paused speed
209  if (last_speed < 0) {
210  increment = -1;
211  }
212 
213  } else {
214  // normal playback
215  should_pause_cache = false;
216  }
217 
218  // Always cache frames from the current display position to our maximum (based on the cache size).
219  // Frames which are already cached are basically free. Only uncached frames have a big CPU cost.
220  // By always looping through the expected frame range, we can fill-in missing frames caused by a
221  // fragmented cache object (i.e. the user clicking all over the timeline). The -1 is to always
222  // cache 1 frame previous to our current frame (to avoid our Seek method from clearing the cache).
223  int64_t starting_frame = std::min(current_display_frame, timeline_max_frame) - 1;
224  int64_t ending_frame = std::min(starting_frame + max_frames_ahead, timeline_max_frame);
225 
226  // Adjust ending frame for cache loop
227  if (increment < 0) {
228  // Reverse loop (if we are going backwards)
229  ending_frame = starting_frame - max_frames_ahead;
230  }
231  if (starting_frame < 1) {
232  // Don't allow negative frame number caching
233  starting_frame = 1;
234  }
235  if (ending_frame < 1) {
236  // Don't allow negative frame number caching
237  ending_frame = 1;
238  }
239 
240  // Reset cache break-loop flag
241  should_break = false;
242 
243  // Loop through range of frames (and cache them)
244  for (int64_t cache_frame = starting_frame; cache_frame != (ending_frame + increment); cache_frame += increment) {
246  if (reader && reader->GetCache() && !reader->GetCache()->Contains(cache_frame)) {
247  try
248  {
249  // This frame is not already cached... so request it again (to force the creation & caching)
250  // This will also re-order the missing frame to the front of the cache
251  last_cached_frame = reader->GetFrame(cache_frame);
252  }
253  catch (const OutOfBoundsFrame & e) { }
254  }
255 
256  // Check if thread has stopped OR should_break is triggered
258  should_break = false;
259  break;
260  }
261 
262  }
263 
264  // Sleep for a fraction of frame duration
265  std::this_thread::sleep_for(frame_duration / 2);
266  }
267 
268  return;
269  }
270 }
Header file for CacheBase class.
Header file for all Exception classes.
if(!codec) codec
Header file for Frame class.
Header file for OpenMPUtilities (set some common macros)
Header file for global Settings class.
Header file for Timeline class.
Source file for VideoCacheThread class.
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.
int64_t GetMaxBytes()
Gets the maximum bytes value.
Definition: CacheBase.h:97
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:35
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:40
Exception for frames that are out of bounds.
Definition: Exceptions.h:301
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:88
virtual openshot::CacheBase * GetCache()=0
Get the cache object used by this reader (note: not all readers use cache)
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
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 VIDEO_CACHE_MAX_PREROLL_FRAMES
Max number of frames (ahead of playhead) to cache during playback.
Definition: Settings.h:92
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:44
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:43
This class represents a timeline.
Definition: Timeline.h:150
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition: Timeline.cpp:469
void ClearAllCache(bool deep=false)
Definition: Timeline.cpp:1659
void setSpeed(int new_speed)
Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster,...
void Play()
Play the video.
void Stop()
Stop the audio playback.
bool isReady()
Is cache ready for video/audio playback.
int64_t getBytes(int width, int height, int sample_rate, int channels, float fps)
Get the size in bytes of a frame (rough estimate)
void Seek(int64_t new_position)
Seek the reader to a particular frame number.
void run()
Start the thread.
std::shared_ptr< Frame > last_cached_frame
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:29
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60