Loading...
Searching...
No Matches
VideoCacheThread.cpp
Go to the documentation of this file.
// Copyright (c) 2008-2025 OpenShot Studios, LLC
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "VideoCacheThread.h"
#include "CacheBase.h"
#include "Exceptions.h"
#include "Frame.h"
#include "Settings.h"
#include "Timeline.h"
#include <thread>
#include <chrono>
#include <algorithm>
namespace openshot
{
// Constructor
VideoCacheThread::VideoCacheThread()
: Thread("video-cache")
, speed(0)
, last_speed(1)
, last_dir(1) // assume forward (+1) on first launch
, userSeeked(false)
, requested_display_frame(1)
, current_display_frame(1)
, cached_frame_count(0)
, min_frames_ahead(4)
, timeline_max_frame(0)
, reader(nullptr)
, force_directional_cache(false)
, last_cached_index(0)
{
}
// Destructor
VideoCacheThread::~VideoCacheThread()
{
}
// Is cache ready for playback (pre-roll)
bool VideoCacheThread::isReady()
{
if (!reader) {
return false;
}
if (min_frames_ahead < 0) {
return true;
}
return (cached_frame_count > min_frames_ahead);
}
void VideoCacheThread::setSpeed(int new_speed)
{
// Only update last_speed and last_dir when new_speed != 0
if (new_speed != 0) {
last_speed = new_speed;
last_dir = (new_speed > 0 ? 1 : -1);
}
speed = new_speed;
}
// Get the size in bytes of a frame (rough estimate)
int64_t VideoCacheThread::getBytes(int width,
int height,
int sample_rate,
int channels,
float fps)
{
// RGBA video frame
int64_t bytes = static_cast<int64_t>(width) * height * sizeof(char) * 4;
// Approximate audio: (sample_rate * channels)/fps samples per frame
bytes += ((sample_rate * channels) / fps) * sizeof(float);
return bytes;
}
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();
}
bool VideoCacheThread::StopThread(int timeoutMs)
{
stopThread(timeoutMs);
return !isThreadRunning();
}
void VideoCacheThread::Seek(int64_t new_position, bool start_preroll)
{
if (start_preroll) {
userSeeked = true;
CacheBase* cache = reader ? reader->GetCache() : nullptr;
if (cache && !cache->Contains(new_position))
{
// If user initiated seek, and current frame not found (
Timeline* timeline = static_cast<Timeline*>(reader);
timeline->ClearAllCache();
cached_frame_count = 0;
}
else if (cache)
{
cached_frame_count = cache->Count();
}
}
requested_display_frame = new_position;
}
void VideoCacheThread::Seek(int64_t new_position)
{
Seek(new_position, false);
}
int VideoCacheThread::computeDirection() const
{
// If speed ≠ 0, use its sign; if speed==0, keep last_dir
return (speed != 0 ? (speed > 0 ? 1 : -1) : last_dir);
}
void VideoCacheThread::handleUserSeek(int64_t playhead, int dir)
{
// Place last_cached_index just “behind” playhead in the given dir
last_cached_index = playhead - dir;
}
bool VideoCacheThread::clearCacheIfPaused(int64_t playhead,
bool paused,
CacheBase* cache)
{
if (paused && !cache->Contains(playhead)) {
// If paused and playhead not in cache, clear everything
Timeline* timeline = static_cast<Timeline*>(reader);
timeline->ClearAllCache();
cached_frame_count = 0;
return true;
}
return false;
}
void VideoCacheThread::computeWindowBounds(int64_t playhead,
int dir,
int64_t ahead_count,
int64_t timeline_end,
int64_t& window_begin,
int64_t& window_end) const
{
if (dir > 0) {
// Forward window: [playhead ... playhead + ahead_count]
window_begin = playhead;
window_end = playhead + ahead_count;
}
else {
// Backward window: [playhead - ahead_count ... playhead]
window_begin = playhead - ahead_count;
window_end = playhead;
}
// Clamp to [1 ... timeline_end]
window_begin = std::max<int64_t>(window_begin, 1);
window_end = std::min<int64_t>(window_end, timeline_end);
}
bool VideoCacheThread::prefetchWindow(CacheBase* cache,
int64_t window_begin,
int64_t window_end,
int dir,
ReaderBase* reader)
{
bool window_full = true;
int64_t next_frame = last_cached_index + dir;
// Advance from last_cached_index toward window boundary
while ((dir > 0 && next_frame <= window_end) ||
(dir < 0 && next_frame >= window_begin))
{
if (threadShouldExit()) {
break;
}
// If a Seek was requested mid-caching, bail out immediately
if (userSeeked) {
break;
}
if (!cache->Contains(next_frame)) {
// Frame missing, fetch and add
try {
auto framePtr = reader->GetFrame(next_frame);
cache->Add(framePtr);
cached_frame_count = cache->Count();
}
catch (const OutOfBoundsFrame&) {
break;
}
window_full = false;
}
else {
cache->Touch(next_frame);
}
last_cached_index = next_frame;
next_frame += dir;
}
return window_full;
}
void VideoCacheThread::run()
{
using micro_sec = std::chrono::microseconds;
using double_micro_sec = std::chrono::duration<double, micro_sec::period>;
while (!threadShouldExit()) {
Settings* settings = Settings::Instance();
CacheBase* cache = reader ? reader->GetCache() : nullptr;
// If caching disabled or no reader, mark cache as ready and sleep briefly
if (!settings->ENABLE_PLAYBACK_CACHING || !cache) {
cached_frame_count = (cache ? cache->Count() : 0);
min_frames_ahead = -1;
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<Timeline*>(reader);
int64_t timeline_end = timeline->GetMaxFrame();
int64_t playhead = requested_display_frame;
bool paused = (speed == 0);
cached_frame_count = cache->Count();
// Compute effective direction (±1)
int dir = computeDirection();
if (speed != 0) {
last_dir = dir;
}
// 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),
reader->info.sample_rate,
reader->info.channels,
reader->info.fps.ToFloat()
);
int64_t max_bytes = cache->GetMaxBytes();
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;
}
}
// 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<int64_t>(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;
}
int64_t ahead_count = static_cast<int64_t>(capacity *
settings->VIDEO_CACHE_PERCENT_AHEAD);
int64_t window_size = ahead_count + 1;
if (window_size < 1) {
window_size = 1;
}
int64_t ready_target = window_size - 1;
if (ready_target < 0) {
ready_target = 0;
}
int64_t configured_min = settings->VIDEO_CACHE_MIN_PREROLL_FRAMES;
min_frames_ahead = std::min<int64_t>(configured_min, ready_target);
// If paused and playhead is no longer in cache, clear everything
bool did_clear = clearCacheIfPaused(playhead, paused, cache);
if (did_clear) {
handleUserSeek(playhead, dir);
}
// Compute the current caching window
int64_t window_begin, window_end;
computeWindowBounds(playhead,
dir,
ahead_count,
timeline_end,
window_begin,
window_end);
// Attempt to fill any missing frames in that window
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) {
cache->Touch(playhead);
}
// Sleep a short fraction of a frame interval
int64_t sleep_us = static_cast<int64_t>(
1000000.0 / reader->info.fps.ToFloat() / 4.0
);
std::this_thread::sleep_for(double_micro_sec(sleep_us));
}
}
} // namespace openshot
CacheBase.h
Header file for CacheBase class.
Exceptions.h
Header file for all Exception classes.
Frame.h
Header file for Frame class.
Settings.h
Header file for global Settings class.
Timeline.h
Header file for Timeline class.
VideoCacheThread.h
Header file for VideoCacheThread class.
openshot::CacheBase
All cache managers in libopenshot are based on this CacheBase class.
Definition CacheBase.h:35
openshot::CacheBase::Contains
virtual bool Contains(int64_t frame_number)=0
Check if frame is already contained in cache.
openshot::CacheBase::Count
virtual int64_t Count()=0
Count the frames in the queue.
openshot::CacheBase::Add
virtual void Add(std::shared_ptr< openshot::Frame > frame)=0
Add a Frame to the cache.
openshot::CacheBase::Touch
virtual void Touch(int64_t frame_number)=0
Move frame to front of queue (so it lasts longer).
openshot::CacheBase::GetMaxBytes
int64_t GetMaxBytes()
Gets the maximum bytes value.
Definition CacheBase.h:101
openshot::OutOfBoundsFrame
Exception for frames that are out of bounds.
Definition Exceptions.h:301
openshot::ReaderBase
This abstract class is the base class, used by all readers in libopenshot.
Definition ReaderBase.h:76
openshot::Settings
This class is contains settings used by libopenshot (and can be safely toggled at any point).
Definition Settings.h:26
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::Settings::VIDEO_CACHE_MIN_PREROLL_FRAMES
int VIDEO_CACHE_MIN_PREROLL_FRAMES
Minimum number of frames to cache before playback begins.
Definition Settings.h:89
openshot::Settings::VIDEO_CACHE_MAX_FRAMES
int VIDEO_CACHE_MAX_FRAMES
Max number of frames (when paused) to cache for playback.
Definition Settings.h:95
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::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::Thread
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::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::Timeline
This class represents a timeline.
Definition Timeline.h:154
openshot::Timeline::GetMaxFrame
int64_t GetMaxFrame()
Look up the end frame number of the latest element on the timeline.
Definition Timeline.cpp:474
openshot::Timeline::ClearAllCache
void ClearAllCache(bool deep=false)
Definition Timeline.cpp:1746
openshot::VideoCacheThread::StopThread
bool StopThread(int timeoutMs=0)
Stop the cache thread (wait up to timeoutMs ms). Returns true if it stopped.
Definition VideoCacheThread.cpp:96
openshot::VideoCacheThread::setSpeed
void setSpeed(int new_speed)
Set playback speed/direction. Positive = forward, negative = rewind, zero = pause.
Definition VideoCacheThread.cpp:62
openshot::VideoCacheThread::force_directional_cache
bool force_directional_cache
(Reserved for future use).
Definition VideoCacheThread.h:175
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:177
openshot::VideoCacheThread::~VideoCacheThread
~VideoCacheThread() override
Definition VideoCacheThread.cpp:44
openshot::VideoCacheThread::requested_display_frame
int64_t requested_display_frame
Frame index the user requested.
Definition VideoCacheThread.h:167
openshot::VideoCacheThread::timeline_max_frame
int64_t timeline_max_frame
Highest valid frame index in the timeline.
Definition VideoCacheThread.h:172
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:141
openshot::VideoCacheThread::last_cached_index
int64_t last_cached_index
Index of the most recently cached frame.
Definition VideoCacheThread.h:177
openshot::VideoCacheThread::cached_frame_count
int64_t cached_frame_count
Estimated count of frames currently stored in cache.
Definition VideoCacheThread.h:169
openshot::VideoCacheThread::run
void run() override
Thread entry point: loops until threadShouldExit() is true.
Definition VideoCacheThread.cpp:221
openshot::VideoCacheThread::last_speed
int last_speed
Last non-zero speed (for tracking).
Definition VideoCacheThread.h:163
openshot::VideoCacheThread::isReady
bool isReady()
Definition VideoCacheThread.cpp:49
openshot::VideoCacheThread::current_display_frame
int64_t current_display_frame
Currently displayed frame (unused here, reserved).
Definition VideoCacheThread.h:168
openshot::VideoCacheThread::VideoCacheThread
VideoCacheThread()
Constructor: initializes member variables and assumes forward direction on first launch.
Definition VideoCacheThread.cpp:26
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:135
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:73
openshot::VideoCacheThread::reader
ReaderBase * reader
The source reader (e.g., Timeline, FFmpegReader).
Definition VideoCacheThread.h:174
openshot::VideoCacheThread::Seek
void Seek(int64_t new_position)
Seek to a specific frame (no preroll).
Definition VideoCacheThread.cpp:124
openshot::VideoCacheThread::userSeeked
bool userSeeked
True if Seek(..., true) was called (forces a cache reset).
Definition VideoCacheThread.h:165
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:155
openshot::VideoCacheThread::min_frames_ahead
int64_t min_frames_ahead
Minimum number of frames considered “ready” (pre-roll).
Definition VideoCacheThread.h:171
openshot::VideoCacheThread::last_dir
int last_dir
Last direction sign (+1 forward, –1 backward).
Definition VideoCacheThread.h:164
openshot::VideoCacheThread::StartThread
bool StartThread()
Start the cache thread at high priority. Returns true if it’s actually running.
Definition VideoCacheThread.cpp:87
openshot::VideoCacheThread::computeDirection
int computeDirection() const
Definition VideoCacheThread.cpp:129
openshot::VideoCacheThread::speed
int speed
Current playback speed (0=paused, >0 forward, <0 backward).
Definition VideoCacheThread.h:162
openshot
This namespace is the default namespace for all code in the openshot library.
Definition Compressor.h:29