cache: mild auto-cache overhaul

Shifted from CacheTask to functionality built into RenderBackend. It was
a lot easier to control behavior this way without having to juggle a ton
of threads and race conditions.

Could likely be multithreaded further.
This commit is contained in:
itsmattkc
2020-08-05 02:19:04 +10:00
parent 07a3216a1f
commit c8def73b95
14 changed files with 692 additions and 284 deletions
+361 -41
View File
@@ -31,13 +31,27 @@
OLIVE_NAMESPACE_ENTER
QVector<RenderBackend*> RenderBackend::instances_;
QMutex RenderBackend::instance_lock_;
RenderBackend* RenderBackend::active_instance_ = nullptr;
QThreadPool RenderBackend::thread_pool_;
RenderBackend::RenderBackend(QObject *parent) :
QObject(parent),
viewer_node_(nullptr),
update_with_graph_(false),
autocache_enabled_(false),
autocache_paused_(false),
preview_job_time_(0),
render_mode_(RenderMode::kOnline)
render_mode_(RenderMode::kOnline),
autocache_has_changed_(false),
use_custom_autocache_range_(false)
{
instance_lock_.lock();
instances_.append(this);
instance_lock_.unlock();
// Set default autocache range
SetAutoCachePlayhead(rational());
}
RenderBackend::~RenderBackend()
@@ -53,22 +67,21 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
ViewerOutput* old_viewer = viewer_node_;
if (!viewer_node) {
// If setting to null, set it here before we wait for jobs to finish
viewer_node_ = viewer_node;
// If setting to null, set it here before we wait for jobs to finish to prevent WorkerFinished()
// from calling RunNextJob() again and preventing us from finishing
viewer_node_ = nullptr;
}
if (old_viewer) {
// Delete all of our copied nodes
pool_.clear();
pool_.waitForDone();
// Cancel any remaining tickets
ClearQueue();
// Cancel all tickets
foreach (RenderTicketPtr t, render_queue_) {
t->Cancel();
// Wait for any currently running jobs to finish
foreach (RenderTicketPtr ticket, running_tickets_) {
ticket->WaitForFinished();
}
render_queue_.clear();
// Delete all the nodes
// Delete all of our copied nodes
foreach (Node* c, copy_map_) {
c->deleteLater();
}
@@ -76,18 +89,27 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
copied_viewer_node_ = nullptr;
graph_update_queue_.clear();
// Disconnect signal (will be a no-op if the signal was never connected)
disconnect(old_viewer,
&ViewerOutput::GraphChangedFrom,
this,
&RenderBackend::NodeGraphChanged);
disconnect(old_viewer->video_frame_cache(),
&PlaybackCache::Invalidated,
this,
&RenderBackend::AutoCacheVideoInvalidated);
disconnect(old_viewer->audio_playback_cache(),
&PlaybackCache::Invalidated,
this,
&RenderBackend::AutoCacheAudioInvalidated);
}
if (viewer_node) {
// If setting to non-null, set it now
viewer_node_ = viewer_node;
}
if (viewer_node_) {
// Copy graph
copied_viewer_node_ = static_cast<ViewerOutput*>(viewer_node_->copy());
copy_map_.insert(viewer_node_, copied_viewer_node_);
@@ -96,65 +118,92 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
NodeGraphChanged(viewer_node_->samples_input());
ProcessUpdateQueue();
if (update_with_graph_) {
if (autocache_enabled_) {
connect(viewer_node_,
&ViewerOutput::GraphChangedFrom,
this,
&RenderBackend::NodeGraphChanged);
connect(viewer_node_->video_frame_cache(),
&PlaybackCache::Invalidated,
this,
&RenderBackend::AutoCacheVideoInvalidated);
connect(viewer_node_->audio_playback_cache(),
&PlaybackCache::Invalidated,
this,
&RenderBackend::AutoCacheAudioInvalidated);
}
}
}
void RenderBackend::ClearVideoQueue()
void RenderBackend::AutoCacheRange(const TimeRange &range)
{
foreach (RenderTicketPtr t, render_queue_) {
t->Cancel();
}
render_queue_.clear();
Q_ASSERT(autocache_enabled_);
autocache_has_changed_ = true;
use_custom_autocache_range_ = true;
custom_autocache_range_ = range;
AutoCacheRequeueFrames();
}
RenderTicketPtr RenderBackend::Hash(const QVector<rational> &times)
RenderTicketPtr RenderBackend::Hash(const QVector<rational> &times, bool prioritize)
{
if (!viewer_node_) {
return nullptr;
}
Q_ASSERT(viewer_node_);
SetActiveInstance();
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeHash,
QVariant::fromValue(times));
render_queue_.push_back(ticket);
if (prioritize) {
render_queue_.push_front(ticket);
} else {
render_queue_.push_back(ticket);
}
QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection);
return ticket;
}
RenderTicketPtr RenderBackend::RenderFrame(const rational &time)
RenderTicketPtr RenderBackend::RenderFrame(const rational &time, bool prioritize, const QByteArray& hash)
{
if (!viewer_node_) {
return nullptr;
}
Q_ASSERT(viewer_node_);
SetActiveInstance();
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeVideo,
QVariant::fromValue(time));
render_queue_.push_back(ticket);
ticket->setProperty("hash", hash);
if (prioritize) {
render_queue_.push_front(ticket);
} else {
render_queue_.push_back(ticket);
}
QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection);
return ticket;
}
RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r)
RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r, bool prioritize)
{
if (!viewer_node_) {
return nullptr;
}
Q_ASSERT(viewer_node_);
SetActiveInstance();
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeAudio,
QVariant::fromValue(r));
render_queue_.push_back(ticket);
if (prioritize) {
render_queue_.push_front(ticket);
} else {
render_queue_.push_back(ticket);
}
QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection);
@@ -194,6 +243,27 @@ std::list<TimeRange> RenderBackend::SplitRangeIntoChunks(const TimeRange &r)
return split_ranges;
}
void RenderBackend::ClearVideoQueue()
{
ClearQueueOfType(RenderTicket::kTypeVideo);
autocache_has_changed_ = true;
use_custom_autocache_range_ = false;
}
void RenderBackend::ClearAudioQueue()
{
ClearQueueOfType(RenderTicket::kTypeAudio);
}
void RenderBackend::ClearQueue()
{
foreach (RenderTicketPtr t, render_queue_) {
t->Cancel();
}
render_queue_.clear();
}
void RenderBackend::NodeGraphChanged(NodeInput *source)
{
// We need to determine:
@@ -259,6 +329,14 @@ void RenderBackend::RunNextJob()
{
// If queue is empty, nothing to be done
if (render_queue_.empty()) {
// If we're the active instance, unset it
instance_lock_.lock();
if (active_instance_ == this) {
active_instance_ = nullptr;
}
instance_lock_.unlock();
return;
}
@@ -270,7 +348,7 @@ void RenderBackend::RunNextJob()
}
// If we have a value update queued, check if all workers are available and proceed from there
if (update_with_graph_ && !graph_update_queue_.isEmpty()) {
if (autocache_enabled_ && !graph_update_queue_.isEmpty()) {
bool all_workers_available = true;
foreach (const WorkerData& data, workers_) {
@@ -291,7 +369,7 @@ void RenderBackend::RunNextJob()
// If we have no workers allocated, allocate them now
if (workers_.isEmpty()) {
// Allocate workers here
workers_.resize(pool_.maxThreadCount());
workers_.resize(thread_pool_.maxThreadCount());
for (int i=0;i<workers_.size();i++) {
RenderWorker* worker = CreateNewWorker();
@@ -323,9 +401,14 @@ void RenderBackend::RunNextJob()
RenderTicketPtr ticket = render_queue_.front();
render_queue_.pop_front();
running_tickets_.push_back(ticket);
RenderTicketWatcher* watcher = new RenderTicketWatcher();
connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::TicketFinished);
watcher->SetTicket(ticket);
switch (ticket->GetType()) {
case RenderTicket::kTypeHash:
QtConcurrent::run(&pool_,
QtConcurrent::run(&thread_pool_,
worker,
&RenderWorker::Hash,
ticket,
@@ -333,15 +416,24 @@ void RenderBackend::RunNextJob()
ticket->GetTime().value<QVector<rational> >());
break;
case RenderTicket::kTypeVideo:
QtConcurrent::run(&pool_,
{
rational frame = ticket->GetTime().value<rational>();
QtConcurrent::run(&thread_pool_,
worker,
&RenderWorker::RenderFrame,
ticket,
copied_viewer_node_,
ticket->GetTime().value<rational>());
frame);
QByteArray frame_hash = ticket->property("hash").toByteArray();
if (!frame_hash.isEmpty()) {
currently_caching_hashes_.append(frame_hash);
}
break;
}
case RenderTicket::kTypeAudio:
QtConcurrent::run(&pool_,
QtConcurrent::run(&thread_pool_,
worker,
&RenderWorker::RenderAudio,
ticket,
@@ -358,6 +450,162 @@ void RenderBackend::RunNextJob()
}
}
void RenderBackend::TicketFinished()
{
RenderTicketPtr ticket = static_cast<RenderTicketWatcher*>(sender())->GetTicket();
delete sender();
running_tickets_.remove(ticket);
}
void RenderBackend::AutoCacheVideoInvalidated(const TimeRange &range)
{
ClearVideoQueue();
// Hash these frames since that should be relatively quick.
RenderTicketWatcher* watcher = new RenderTicketWatcher();
QVector<rational> frames = viewer_node_->video_frame_cache()->GetFrameListFromTimeRange({range});
autocache_hash_tasks_.insert(watcher, {frames, QDateTime::currentMSecsSinceEpoch()});
connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheHashesGenerated);
watcher->SetTicket(Hash(frames));
}
void RenderBackend::AutoCacheAudioInvalidated(const TimeRange &range)
{
// Start a task to re-render the audio at this range
RenderTicketWatcher* watcher = new RenderTicketWatcher();
autocache_audio_tasks_.insert(watcher, {range, QDateTime::currentMSecsSinceEpoch()});
connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheAudioRendered);
watcher->SetTicket(RenderAudio(range, true));
}
void RenderBackend::SetHashes(FrameHashCache* cache, const QVector<rational>& times, const QVector<QByteArray>& hashes, qint64 job_time)
{
std::vector<QByteArray> existing_hashes;
for (int i=0; i<times.size(); i++) {
// See if hash already exists in disk cache
const QByteArray& hash = hashes.at(i);
const rational& time = times.at(i);
// Check memory list since disk checking is slow
bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), hash) != existing_hashes.end());
if (!hash_exists) {
hash_exists = QFileInfo::exists(cache->CachePathName(hash));
if (hash_exists) {
existing_hashes.push_back(hash);
}
}
cache->SetHash(time, hash, job_time, hash_exists);
}
}
void RenderBackend::AutoCacheHashesGenerated()
{
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
if (autocache_hash_tasks_.contains(watcher)) {
if (!watcher->WasCancelled()) {
const HashJobInfo& info = autocache_hash_tasks_.value(watcher);
QFutureWatcher<void>* hw = new QFutureWatcher<void>();
connect(hw, &QFutureWatcher<void>::finished, this, &RenderBackend::AutoCacheHashesProcessed);
autocache_hash_process_tasks_.append(hw);
hw->setFuture(QtConcurrent::run(this,
&RenderBackend::SetHashes,
viewer_node_->video_frame_cache(),
info.times,
watcher->Get().value<QVector<QByteArray> >(),
info.job_time));
}
autocache_hash_tasks_.remove(watcher);
}
delete watcher;
}
void RenderBackend::AutoCacheHashesProcessed()
{
QFutureWatcher<void>* watcher = static_cast<QFutureWatcher<void>*>(sender());
if (autocache_hash_process_tasks_.contains(watcher)) {
autocache_hash_process_tasks_.removeOne(watcher);
AutoCacheRequeueFrames();
}
delete watcher;
}
void RenderBackend::AutoCacheAudioRendered()
{
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
if (autocache_audio_tasks_.contains(watcher)) {
if (!watcher->WasCancelled()) {
const AudioJobInfo& job_info = autocache_audio_tasks_.value(watcher);
viewer_node_->audio_playback_cache()->WritePCM(job_info.range,
watcher->Get().value<SampleBufferPtr>(),
job_info.job_time);
}
autocache_audio_tasks_.remove(watcher);
}
delete watcher;
}
void RenderBackend::AutoCacheVideoRendered()
{
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
if (autocache_video_tasks_.contains(watcher)) {
if (!watcher->WasCancelled()) {
const VideoJobInfo& info = autocache_video_tasks_.value(watcher);
// Download frame in another thread
QFutureWatcher<bool>* w = new QFutureWatcher<bool>();
autocache_video_download_tasks_.insert(w, info);
connect(w, &QFutureWatcher<bool>::finished, this, &RenderBackend::AutoCacheVideoDownloaded);
w->setFuture(QtConcurrent::run(FrameHashCache::SaveCacheFrame,
info.hash,
watcher->Get().value<FramePtr>()));
}
autocache_video_tasks_.remove(watcher);
}
delete watcher;
}
void RenderBackend::AutoCacheVideoDownloaded()
{
QFutureWatcher<bool>* watcher = static_cast<QFutureWatcher<bool>*>(sender());
if (autocache_video_download_tasks_.contains(watcher)) {
if (!watcher->isCanceled()) {
if (watcher->result()) {
const VideoJobInfo& info = autocache_video_download_tasks_.value(watcher);
currently_caching_hashes_.removeOne(info.hash);
viewer_node_->video_frame_cache()->ValidateFramesWithHash(info.hash);
} else {
qCritical() << "Failed to download video frame";
}
}
autocache_video_download_tasks_.remove(watcher);
}
delete watcher;
}
//#define PRINT_UPDATE_QUEUE_INFO
void RenderBackend::ProcessUpdateQueue()
{
@@ -482,4 +730,76 @@ void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_
}
}
void RenderBackend::ClearQueueOfType(RenderTicket::Type type)
{
std::list<RenderTicketPtr>::iterator i = render_queue_.begin();
while (i != render_queue_.end()) {
if ((*i)->GetType() == type) {
(*i)->Cancel();
i = render_queue_.erase(i);
} else {
i++;
}
}
}
void RenderBackend::SetActiveInstance()
{
QMutexLocker locker(&instance_lock_);
if (active_instance_ != this) {
// Signal active instance to stop
QMetaObject::invokeMethod(active_instance_, "ClearVideoQueue", Qt::QueuedConnection);
active_instance_ = this;
}
}
void RenderBackend::AutoCacheRequeueFrames()
{
if (viewer_node_
&& viewer_node_->video_frame_cache()->HasInvalidatedRanges()
&& autocache_hash_tasks_.isEmpty()
&& autocache_hash_process_tasks_.isEmpty()
&& autocache_has_changed_
&& (!autocache_paused_ || use_custom_autocache_range_)) {
TimeRange using_range;
if (use_custom_autocache_range_) {
using_range = custom_autocache_range_;
use_custom_autocache_range_ = false;
} else {
using_range = autocache_range_;
}
QVector<rational> invalidated_ranges = viewer_node_->video_frame_cache()->GetInvalidatedFrames(using_range);
ClearVideoQueue();
// QMaps are automatically sorted by time which is always best for rendering
QList<QByteArray> queued_hashes;
foreach (const rational& t, invalidated_ranges) {
const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t);
if (t >= using_range.in()
&& t < using_range.out()
&& !queued_hashes.contains(hash)
&& !currently_caching_hashes_.contains(hash)) {
// Don't render any hash more than once
queued_hashes.append(hash);
RenderTicketWatcher* watcher = new RenderTicketWatcher();
connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheVideoRendered);
autocache_video_tasks_.insert(watcher, {hash, QDateTime::currentMSecsSinceEpoch()});
watcher->SetTicket(RenderFrame(t, false, hash));
}
}
autocache_has_changed_ = false;
}
}
OLIVE_NAMESPACE_EXIT
+112 -10
View File
@@ -23,12 +23,14 @@
#include <QtConcurrent/QtConcurrent>
#include "config/config.h"
#include "dialog/rendercancel/rendercancel.h"
#include "decodercache.h"
#include "node/graph.h"
#include "node/output/viewer/viewer.h"
#include "render/backend/colorprocessorcache.h"
#include "renderticket.h"
#include "renderticketwatcher.h"
#include "renderworker.h"
OLIVE_NAMESPACE_ENTER
@@ -50,9 +52,42 @@ public:
void SetViewerNode(ViewerOutput* viewer_node);
void SetUpdateWithGraph(bool e)
void SetAutoCacheEnabled(bool e)
{
update_with_graph_ = e;
autocache_enabled_ = e;
}
bool IsAutoCachePaused() const
{
return autocache_paused_;
}
void SetAutoCachePaused(bool paused)
{
autocache_paused_ = paused;
if (autocache_paused_) {
// Pause the autocache
ClearVideoQueue();
} else {
// Unpause the cache
AutoCacheRequeueFrames();
}
}
void AutoCacheRange(const TimeRange& range);
void AutoCacheRequeueFrames();
void SetAutoCachePlayhead(const rational& playhead)
{
autocache_range_ = TimeRange(playhead - Config::Current()["DiskCacheBehind"].value<rational>(),
playhead + Config::Current()["DiskCacheAhead"].value<rational>());
autocache_has_changed_ = true;
use_custom_autocache_range_ = false;
AutoCacheRequeueFrames();
}
void SetRenderMode(RenderMode::Mode e)
@@ -65,24 +100,22 @@ public:
preview_job_time_ = job_time;
}
void ClearVideoQueue();
void ProcessUpdateQueue();
/**
* @brief Asynchronously generate a hash at a given time
*/
RenderTicketPtr Hash(const QVector<rational> &times);
RenderTicketPtr Hash(const QVector<rational> &times, bool prioritize = false);
/**
* @brief Asynchronously generate a frame at a given time
*/
RenderTicketPtr RenderFrame(const rational& time);
RenderTicketPtr RenderFrame(const rational& time, bool prioritize = false, const QByteArray& hash = QByteArray());
/**
* @brief Asynchronously generate a chunk of audio
*/
RenderTicketPtr RenderAudio(const TimeRange& r);
RenderTicketPtr RenderAudio(const TimeRange& r, bool prioritize = false);
const VideoParams& GetVideoParams() const
{
@@ -105,6 +138,14 @@ public:
public slots:
void NodeGraphChanged(NodeInput *source);
void ClearVideoQueue();
void ClearAudioQueue();
void ClearQueue();
signals:
protected:
virtual RenderWorker* CreateNewWorker() = 0;
@@ -113,6 +154,10 @@ private:
Node *CopyNodeConnections(Node *src_node);
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
void ClearQueueOfType(RenderTicket::Type type);
void SetHashes(FrameHashCache* cache, const QVector<rational>& times, const QVector<QByteArray>& hashes, qint64 job_time);
ViewerOutput* viewer_node_;
// VIDEO MEMBERS
@@ -126,10 +171,10 @@ private:
QHash<Node*, Node*> copy_map_;
ViewerOutput* copied_viewer_node_;
QThreadPool pool_;
std::list<RenderTicketPtr> render_queue_;
std::list<RenderTicketPtr> running_tickets_;
struct WorkerData {
RenderWorker* worker;
bool busy;
@@ -137,17 +182,74 @@ private:
QVector<WorkerData> workers_;
bool update_with_graph_;
bool autocache_enabled_;
bool autocache_paused_;
qint64 preview_job_time_;
RenderMode::Mode render_mode_;
TimeRange autocache_range_;
bool autocache_has_changed_;
bool use_custom_autocache_range_;
TimeRange custom_autocache_range_;
static QVector<RenderBackend*> instances_;
static QMutex instance_lock_;
static RenderBackend* active_instance_;
static QThreadPool thread_pool_;
void SetActiveInstance();
struct HashJobInfo {
QVector<rational> times;
qint64 job_time;
};
struct AudioJobInfo {
TimeRange range;
qint64 job_time;
};
struct VideoJobInfo {
QByteArray hash;
qint64 job_time;
};
QMap<RenderTicketWatcher*, HashJobInfo> autocache_hash_tasks_;
QList<QFutureWatcher<void>*> autocache_hash_process_tasks_;
QMap<RenderTicketWatcher*, AudioJobInfo> autocache_audio_tasks_;
QMap<RenderTicketWatcher*, VideoJobInfo> autocache_video_tasks_;
QMap<QFutureWatcher<bool>*, VideoJobInfo> autocache_video_download_tasks_;
QVector<QByteArray> currently_caching_hashes_;
private slots:
void WorkerFinished();
void RunNextJob();
void TicketFinished();
void AutoCacheVideoInvalidated(const OLIVE_NAMESPACE::TimeRange &range);
void AutoCacheAudioInvalidated(const OLIVE_NAMESPACE::TimeRange &range);
void AutoCacheHashesGenerated();
void AutoCacheHashesProcessed();
void AutoCacheAudioRendered();
void AutoCacheVideoRendered();
void AutoCacheVideoDownloaded();
};
OLIVE_NAMESPACE_EXIT
+5
View File
@@ -31,6 +31,11 @@ class RenderTicketWatcher : public QObject
public:
RenderTicketWatcher(QObject* parent = nullptr);
RenderTicketPtr GetTicket() const
{
return ticket_;
}
void SetTicket(RenderTicketPtr ticket);
bool WasCancelled();
+52 -9
View File
@@ -41,7 +41,7 @@ QByteArray FrameHashCache::GetHash(const rational &time)
return time_hash_map_.value(time);
}
void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const qint64& job_time)
void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const qint64& job_time, bool frame_exists)
{
QMutexLocker locker(lock());
@@ -63,13 +63,17 @@ void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const
time_hash_map_.insert(time, hash);
TimeRange validated_range(time, time + timebase_);
NoLockValidate(validated_range);
TimeRange validated_range;
if (frame_exists) {
validated_range = TimeRange(time, time + timebase_);
NoLockValidate(validated_range);
}
locker.unlock();
emit Validated(validated_range);
if (frame_exists) {
emit Validated(validated_range);
}
}
void FrameHashCache::SetTimebase(const rational &tb)
@@ -79,6 +83,33 @@ void FrameHashCache::SetTimebase(const rational &tb)
timebase_ = tb;
}
void FrameHashCache::ValidateFramesWithHash(const QByteArray &hash)
{
QMutexLocker locker(lock());
QMap<rational, QByteArray>::const_iterator iterator;
const TimeRangeList& invalidated_ranges = NoLockGetInvalidatedRanges();
TimeRangeList ranges_validated;
for (iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) {
if (iterator.value() == hash) {
TimeRange frame_range(iterator.key(), iterator.key() + timebase_);
if (invalidated_ranges.ContainsTimeRange(frame_range)) {
NoLockValidate(frame_range);
ranges_validated.InsertTimeRange(frame_range);
}
}
}
locker.unlock();
foreach (const TimeRange& range, ranges_validated) {
emit Validated(range);
}
}
QList<rational> FrameHashCache::GetFramesWithHash(const QByteArray &hash)
{
QMutexLocker locker(lock());
@@ -175,10 +206,17 @@ QVector<rational> FrameHashCache::GetInvalidatedFrames()
{
QMutexLocker locker(lock());
return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges());
return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges(), timebase_);
}
void FrameHashCache::SaveCacheFrame(const QByteArray& hash,
QVector<rational> FrameHashCache::GetInvalidatedFrames(const TimeRange &intersecting)
{
QMutexLocker locker(lock());
return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges().Intersects(intersecting), timebase_);
}
bool FrameHashCache::SaveCacheFrame(const QByteArray& hash,
char* data,
const VideoParams& vparam,
int linesize_bytes)
@@ -188,15 +226,20 @@ void FrameHashCache::SaveCacheFrame(const QByteArray& hash,
if (SaveCacheFrame(fn, data, vparam, linesize_bytes)) {
// Register frame with the disk manager
DiskManager::instance()->CreatedFile(fn, hash);
return true;
} else {
return false;
}
}
void FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame)
bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame)
{
if (frame) {
SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes());
return SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes());
} else {
qWarning() << "Attempted to save a NULL frame to the cache. This may or may not be desirable.";
return false;
}
}
+6 -3
View File
@@ -44,6 +44,8 @@ public:
void SetTimebase(const rational& tb);
void ValidateFramesWithHash(const QByteArray& hash);
/**
* @brief Returns a list of frames that use a particular hash
*/
@@ -62,8 +64,8 @@ public:
static QString CachePathName(const QByteArray &hash);
static bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes);
static void SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes);
static void SaveCacheFrame(const QByteArray& hash, FramePtr frame);
static bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes);
static bool SaveCacheFrame(const QByteArray& hash, FramePtr frame);
static FramePtr LoadCacheFrame(const QByteArray& hash);
static FramePtr LoadCacheFrame(const QString& fn);
@@ -72,9 +74,10 @@ public:
static QVector<rational> GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase);
QVector<rational> GetFrameListFromTimeRange(const TimeRangeList &range);
QVector<rational> GetInvalidatedFrames();
QVector<rational> GetInvalidatedFrames(const TimeRange& intersecting);
public slots:
void SetHash(const OLIVE_NAMESPACE::rational& time, const QByteArray& hash, const qint64 &job_time);
void SetHash(const OLIVE_NAMESPACE::rational& time, const QByteArray& hash, const qint64 &job_time, bool frame_exists);
protected:
virtual void LengthChangedEvent(const rational& old, const rational& newlen) override;