cache: streamlined audio into cache task
Since we always cache audio, it makes sense to lump it into the same auto-cache task we use for everything else.
This commit is contained in:
@@ -83,7 +83,7 @@ void Config::SetDefaults()
|
||||
config_map_["DropWithoutSequenceBehavior"] = TimelineWidget::kDWSAsk;
|
||||
config_map_["Loop"] = false;
|
||||
|
||||
config_map_["AutoCache"] = true;//false;//true;
|
||||
config_map_["AutoCache"] = true;
|
||||
config_map_["AutoCacheInterval"] = 1000;
|
||||
|
||||
config_map_["NodeCatColor0"] = QVariant::fromValue(Color(0.75f, 0.75f, 0.75f));
|
||||
|
||||
@@ -49,7 +49,10 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewer_node_) {
|
||||
ViewerOutput* old_viewer = viewer_node_;
|
||||
viewer_node_ = viewer_node;
|
||||
|
||||
if (old_viewer) {
|
||||
// Delete all of our copied nodes
|
||||
pool_.clear();
|
||||
pool_.waitForDone();
|
||||
@@ -61,19 +64,19 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
render_queue_.clear();
|
||||
|
||||
// Delete all the nodes
|
||||
foreach (Node* c, copy_map_) {
|
||||
c->DisconnectAll();
|
||||
}
|
||||
qDeleteAll(copy_map_);
|
||||
copy_map_.clear();
|
||||
copied_viewer_node_ = nullptr;
|
||||
|
||||
disconnect(viewer_node_,
|
||||
disconnect(old_viewer,
|
||||
&ViewerOutput::GraphChangedFrom,
|
||||
this,
|
||||
&RenderBackend::NodeGraphChanged);
|
||||
}
|
||||
|
||||
// Set viewer node
|
||||
viewer_node_ = viewer_node;
|
||||
|
||||
if (viewer_node_) {
|
||||
// Copy graph
|
||||
copied_viewer_node_ = static_cast<ViewerOutput*>(viewer_node_->copy());
|
||||
@@ -107,7 +110,7 @@ void RenderBackend::ClearVideoQueue()
|
||||
|
||||
QFuture<QList<QByteArray> > RenderBackend::Hash(const QList<rational> ×)
|
||||
{
|
||||
return QtConcurrent::run([this](const QList<rational> ×){
|
||||
return QtConcurrent::run(&pool_, [this](const QList<rational> ×){
|
||||
QList<QByteArray> hashes;
|
||||
|
||||
foreach (const rational& t, times) {
|
||||
@@ -155,6 +158,8 @@ RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r)
|
||||
|
||||
render_queue_.append(ticket);
|
||||
|
||||
RunNextJob();
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
@@ -294,14 +299,16 @@ void RenderBackend::RunNextJob()
|
||||
|
||||
switch (ticket->GetType()) {
|
||||
case RenderTicket::kTypeVideo:
|
||||
QtConcurrent::run(worker,
|
||||
QtConcurrent::run(&pool_,
|
||||
worker,
|
||||
&RenderWorker::RenderFrame,
|
||||
ticket,
|
||||
copied_viewer_node_,
|
||||
ticket->GetTime().in());
|
||||
break;
|
||||
case RenderTicket::kTypeAudio:
|
||||
QtConcurrent::run(worker,
|
||||
QtConcurrent::run(&pool_,
|
||||
worker,
|
||||
&RenderWorker::RenderAudio,
|
||||
ticket,
|
||||
copied_viewer_node_,
|
||||
@@ -319,9 +326,15 @@ void RenderBackend::RunNextJob()
|
||||
|
||||
void RenderBackend::ProcessUpdateQueue()
|
||||
{
|
||||
/*
|
||||
while (!graph_update_queue_.isEmpty()) {
|
||||
CopyNodeInputValue(graph_update_queue_.takeFirst());
|
||||
}
|
||||
*/
|
||||
|
||||
// FIXME: SLOW DEBUGGING CODE
|
||||
CopyNodeInputValue(viewer_node_->texture_input());
|
||||
CopyNodeInputValue(viewer_node_->samples_input());
|
||||
}
|
||||
|
||||
void RenderBackend::WorkerFinished()
|
||||
@@ -336,7 +349,11 @@ void RenderBackend::WorkerFinished()
|
||||
}
|
||||
}
|
||||
|
||||
RunNextJob();
|
||||
if (viewer_node_) {
|
||||
RunNextJob();
|
||||
} else {
|
||||
qDebug() << "Ignored job finish because no viewer";
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::CopyNodeInputValue(NodeInput *input)
|
||||
|
||||
@@ -57,12 +57,10 @@ public:
|
||||
*/
|
||||
RenderTicketPtr RenderFrame(const rational& time);
|
||||
|
||||
QFuture<FramePtr> RenderFrames(const QList<rational>& frames);
|
||||
|
||||
/**
|
||||
* @brief Asynchronously generate a chunk of audio
|
||||
*/
|
||||
QFuture<SampleBufferPtr> RenderAudio(const TimeRange& r);
|
||||
RenderTicketPtr RenderAudio(const TimeRange& r);
|
||||
|
||||
void SetVideoParams(const VideoRenderingParams& params);
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, con
|
||||
|
||||
ticket->Finish(QVariant::fromValue(frame));
|
||||
|
||||
FinishedJob();
|
||||
emit FinishedJob();
|
||||
}
|
||||
|
||||
void RenderWorker::RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, const TimeRange &range)
|
||||
@@ -71,7 +71,7 @@ void RenderWorker::RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, con
|
||||
|
||||
ticket->Finish(samples);
|
||||
|
||||
FinishedJob();
|
||||
emit FinishedJob();
|
||||
}
|
||||
|
||||
NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const TimeRange &range)
|
||||
|
||||
Vendored
+10
-3
@@ -37,18 +37,20 @@ CacheTask::CacheTask(ViewerOutput* viewer, const VideoRenderingParams& vparams,
|
||||
bool CacheTask::Run()
|
||||
{
|
||||
// Get list of invalidated ranges
|
||||
TimeRangeList range_to_cache = viewer()->video_frame_cache()->GetInvalidatedRanges();
|
||||
TimeRangeList video_range = viewer()->video_frame_cache()->GetInvalidatedRanges();
|
||||
TimeRangeList audio_range = viewer()->audio_playback_cache()->GetInvalidatedRanges();
|
||||
|
||||
// If we're caching only in-out, limit the range to that
|
||||
if (in_out_only_) {
|
||||
Sequence* s = static_cast<Sequence*>(viewer()->parent());
|
||||
|
||||
if (s->workarea()->enabled()) {
|
||||
range_to_cache = range_to_cache.Intersects(s->workarea()->range());
|
||||
video_range = video_range.Intersects(s->workarea()->range());
|
||||
audio_range = audio_range.Intersects(s->workarea()->range());
|
||||
}
|
||||
}
|
||||
|
||||
Render(range_to_cache, QMatrix4x4(), false, true);
|
||||
Render(video_range, audio_range, QMatrix4x4(), true);
|
||||
|
||||
download_threads_.waitForDone();
|
||||
|
||||
@@ -67,4 +69,9 @@ void CacheTask::FrameDownloaded(const QByteArray &hash, const QLinkedList<ration
|
||||
}
|
||||
}
|
||||
|
||||
void CacheTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples)
|
||||
{
|
||||
viewer()->audio_playback_cache()->WritePCM(range, samples);
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
Vendored
+2
@@ -44,6 +44,8 @@ protected:
|
||||
|
||||
virtual void FrameDownloaded(const QByteArray& hash, const QLinkedList<rational>& times) override;
|
||||
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) override;
|
||||
|
||||
private:
|
||||
bool in_out_only_;
|
||||
|
||||
|
||||
@@ -85,7 +85,17 @@ bool ExportTask::Run()
|
||||
}
|
||||
|
||||
// Start render process
|
||||
Render({range}, mat, params_.audio_enabled(), false);
|
||||
TimeRangeList video_range, audio_range;
|
||||
|
||||
if (params_.video_enabled()) {
|
||||
video_range.append(range);
|
||||
}
|
||||
|
||||
if (params_.audio_enabled()) {
|
||||
audio_range.append(range);
|
||||
}
|
||||
|
||||
Render(video_range, audio_range, mat, false);
|
||||
|
||||
bool success = true;
|
||||
|
||||
@@ -167,8 +177,6 @@ void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples
|
||||
adjusted_range -= params_.custom_range().in();
|
||||
}
|
||||
|
||||
qDebug() << "Downloaded audio" << adjusted_range;
|
||||
|
||||
audio_data_.WritePCM(adjusted_range, samples);
|
||||
}
|
||||
|
||||
|
||||
+75
-77
@@ -57,81 +57,25 @@ struct HashDownloadFuturePair {
|
||||
QFuture<void> download_future;
|
||||
};
|
||||
|
||||
void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
void RenderTask::Render(const TimeRangeList& video_range,
|
||||
const TimeRangeList &audio_range,
|
||||
const QMatrix4x4& mat,
|
||||
bool audio_enabled,
|
||||
bool use_disk_cache)
|
||||
{
|
||||
OpenGLBackend backend;
|
||||
|
||||
backend.moveToThread(qApp->thread());
|
||||
|
||||
// FIXME: This makes a full copy of the node graph every time it starts, there must be a better
|
||||
// way.
|
||||
backend.SetViewerNode(viewer_);
|
||||
backend.SetVideoParams(video_params_);
|
||||
backend.SetAudioParams(audio_params_);
|
||||
backend.SetVideoDownloadMatrix(mat);
|
||||
|
||||
// Get hashes for each frame and group likes together
|
||||
QMap< QByteArray, QLinkedList<rational> > times_to_render;
|
||||
|
||||
{
|
||||
QList<rational> times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(range_to_cache);
|
||||
|
||||
QFuture<QList<QByteArray> > hash_future = backend.Hash(times);
|
||||
QList<QByteArray> hashes = hash_future.result();
|
||||
|
||||
// Determine any duplicates
|
||||
int index = 0;
|
||||
foreach (const QByteArray& hash, hashes) {
|
||||
const rational& time = times.at(index);
|
||||
|
||||
if (use_disk_cache
|
||||
&& QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(hash, video_params_.format()))) {
|
||||
// Already exists, no need to render it again
|
||||
FrameDownloaded(hash, {time});
|
||||
} else {
|
||||
times_to_render[hash].append(time);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Render all frames necessary
|
||||
QLinkedList<HashFrameFuturePair> render_lookup_table;
|
||||
{
|
||||
QLinkedList<HashTimePair> sorted_times;
|
||||
QLinkedList<HashTimePair>::iterator sorted_iterator;
|
||||
|
||||
// Rendering is more efficient if we cache in order, so here we sort
|
||||
QMap< QByteArray, QLinkedList<rational> >::const_iterator i;
|
||||
for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
|
||||
const QByteArray& hash = i.key();
|
||||
const rational& time = i.value().first();
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
|
||||
if (sorted_iterator->time > time) {
|
||||
sorted_times.insert(sorted_iterator, {time, hash});
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
sorted_times.append({time, hash});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const HashTimePair& p, sorted_times) {
|
||||
render_lookup_table.append({p.hash, backend.RenderFrame(p.time)});
|
||||
}
|
||||
}
|
||||
|
||||
QLinkedList<RangeSampleFuturePair> audio_lookup_table;
|
||||
if (audio_enabled) {
|
||||
foreach (const TimeRange& r, range_to_cache) {
|
||||
if (!audio_range.isEmpty()) {
|
||||
foreach (const TimeRange& r, audio_range) {
|
||||
QList<TimeRange> ranges = RenderBackend::SplitRangeIntoChunks(r);
|
||||
|
||||
foreach (const TimeRange& split, ranges) {
|
||||
@@ -140,6 +84,66 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
}
|
||||
}
|
||||
|
||||
// Get hashes for each frame and group likes together
|
||||
QMap< QByteArray, QLinkedList<rational> > times_to_render;
|
||||
QLinkedList<HashFrameFuturePair> render_lookup_table;
|
||||
if (!video_range.isEmpty()) {
|
||||
|
||||
{
|
||||
QList<rational> times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(video_range);
|
||||
|
||||
QFuture<QList<QByteArray> > hash_future = backend.Hash(times);
|
||||
QList<QByteArray> hashes = hash_future.result();
|
||||
|
||||
// Determine any duplicates
|
||||
int index = 0;
|
||||
foreach (const QByteArray& hash, hashes) {
|
||||
const rational& time = times.at(index);
|
||||
|
||||
if (use_disk_cache
|
||||
&& QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(hash, video_params_.format()))) {
|
||||
// Already exists, no need to render it again
|
||||
FrameDownloaded(hash, {time});
|
||||
} else {
|
||||
times_to_render[hash].append(time);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Render all frames necessary
|
||||
{
|
||||
QLinkedList<HashTimePair> sorted_times;
|
||||
QLinkedList<HashTimePair>::iterator sorted_iterator;
|
||||
|
||||
// Rendering is more efficient if we cache in order, so here we sort
|
||||
QMap< QByteArray, QLinkedList<rational> >::const_iterator i;
|
||||
for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
|
||||
const QByteArray& hash = i.key();
|
||||
const rational& time = i.value().first();
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
|
||||
if (sorted_iterator->time > time) {
|
||||
sorted_times.insert(sorted_iterator, {time, hash});
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
sorted_times.append({time, hash});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const HashTimePair& p, sorted_times) {
|
||||
render_lookup_table.append({p.hash, backend.RenderFrame(p.time)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start downloading frames that have finished
|
||||
|
||||
int counter = 0;
|
||||
@@ -159,7 +163,7 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
|
||||
i = render_lookup_table.begin();
|
||||
|
||||
while (i != render_lookup_table.end()) {
|
||||
while (!IsCancelled() && i != render_lookup_table.end()) {
|
||||
if (i->frame_future->IsFinished()) {
|
||||
FramePtr f = i->frame_future->Get().value<FramePtr>();
|
||||
|
||||
@@ -174,7 +178,7 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
|
||||
j = download_futures.begin();
|
||||
|
||||
while (j != download_futures.end()) {
|
||||
while (!IsCancelled() && j != download_futures.end()) {
|
||||
if (j->download_future.isFinished()) {
|
||||
// Place it in the cache
|
||||
FrameDownloaded(j->hash, times_to_render.value(j->hash));
|
||||
@@ -189,26 +193,20 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
}
|
||||
}
|
||||
|
||||
if (audio_enabled) {
|
||||
k = audio_lookup_table.begin();
|
||||
k = audio_lookup_table.begin();
|
||||
|
||||
while (k != audio_lookup_table.end()) {
|
||||
if (k->sample_future->IsFinished()) {
|
||||
AudioDownloaded(k->range, k->sample_future->Get().value<SampleBufferPtr>());
|
||||
while (!IsCancelled() && k != audio_lookup_table.end()) {
|
||||
if (k->sample_future->IsFinished()) {
|
||||
AudioDownloaded(k->range, k->sample_future->Get().value<SampleBufferPtr>());
|
||||
|
||||
k = audio_lookup_table.erase(k);
|
||||
} else {
|
||||
k++;
|
||||
}
|
||||
k = audio_lookup_table.erase(k);
|
||||
} else {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTask::AudioDownloaded(const TimeRange &, SampleBufferPtr)
|
||||
{
|
||||
}
|
||||
|
||||
void RenderTask::SetAnchorPoint(const rational &r)
|
||||
{
|
||||
anchor_point_ = r;
|
||||
|
||||
@@ -34,13 +34,16 @@ public:
|
||||
RenderTask(ViewerOutput* viewer, const VideoRenderingParams &vparams, const AudioRenderingParams &aparams);
|
||||
|
||||
protected:
|
||||
void Render(const TimeRangeList &range_to_cache, const QMatrix4x4 &mat, bool audio_enabled, bool use_disk_cache);
|
||||
void Render(const TimeRangeList &video_range,
|
||||
const TimeRangeList &audio_range,
|
||||
const QMatrix4x4 &mat,
|
||||
bool use_disk_cache);
|
||||
|
||||
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) = 0;
|
||||
|
||||
virtual void FrameDownloaded(const QByteArray& hash, const QLinkedList<rational>& times) = 0;
|
||||
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples);
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) = 0;
|
||||
|
||||
ViewerOutput* viewer() const
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user