cache: heavily reworked exporting for new system
Also cleans up cache in general and moves it to the Task infrastructure.
This commit is contained in:
Vendored
+14
-1
@@ -21,6 +21,7 @@
|
||||
#include "cache.h"
|
||||
|
||||
#include <QLinkedList>
|
||||
#include <QMatrix4x4>
|
||||
|
||||
#include "project/item/sequence/sequence.h"
|
||||
|
||||
@@ -48,9 +49,21 @@ bool CacheTask::Run()
|
||||
}
|
||||
}
|
||||
|
||||
Render(range_to_cache, 2);
|
||||
Render(range_to_cache, RenderMode::kOffline, QMatrix4x4(), false, divider_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QFuture<void> CacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
|
||||
{
|
||||
return QtConcurrent::run(FrameHashCache::SaveCacheFrame, hash, frame);
|
||||
}
|
||||
|
||||
void CacheTask::FrameDownloaded(const QByteArray &hash, const QLinkedList<rational> ×)
|
||||
{
|
||||
foreach (const rational& t, times) {
|
||||
viewer()->video_frame_cache()->SetHash(t, hash);
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
Vendored
+5
@@ -36,6 +36,11 @@ public:
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
protected:
|
||||
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) override;
|
||||
|
||||
virtual void FrameDownloaded(const QByteArray& hash, const QLinkedList<rational>& times) override;
|
||||
|
||||
private:
|
||||
bool in_out_only_;
|
||||
|
||||
|
||||
+118
-1
@@ -20,6 +20,9 @@
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "render/colormanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ExportTask::ExportTask(ViewerOutput* viewer_node,
|
||||
@@ -29,11 +32,125 @@ ExportTask::ExportTask(ViewerOutput* viewer_node,
|
||||
color_manager_(color_manager),
|
||||
params_(params)
|
||||
{
|
||||
SetTitle(tr("Exporting \"%1\"").arg(viewer_node->media_name()));
|
||||
}
|
||||
|
||||
bool ExportTask::Run()
|
||||
{
|
||||
return true;
|
||||
TimeRange range;
|
||||
|
||||
encoder_ = Encoder::CreateFromID(params_.encoder(), params_);
|
||||
if (!encoder_) {
|
||||
SetError(tr("Failed to create encoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!encoder_->Open()) {
|
||||
SetError(tr("Failed to open file"));
|
||||
encoder_->deleteLater();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params_.has_custom_range()) {
|
||||
// Render custom range only
|
||||
range = params_.custom_range();
|
||||
} else {
|
||||
// Render entire sequence
|
||||
range = TimeRange(0, viewer()->GetLength());
|
||||
}
|
||||
|
||||
frame_time_ = Timecode::time_to_timestamp(range.in(), viewer()->video_params().time_base());
|
||||
|
||||
QMatrix4x4 mat;
|
||||
|
||||
if (params_.video_enabled()) {
|
||||
|
||||
// If a transformation matrix is applied to this video, create it here
|
||||
if (params_.video_scaling_method() != ExportParams::kStretch) {
|
||||
mat = ExportParams::GenerateMatrix(params_.video_scaling_method(),
|
||||
viewer()->video_params().width(),
|
||||
viewer()->video_params().height(),
|
||||
params_.video_params().width(),
|
||||
params_.video_params().height());
|
||||
}
|
||||
|
||||
// Create color processor
|
||||
color_processor_ = ColorProcessor::Create(color_manager_,
|
||||
color_manager_->GetReferenceColorSpace(),
|
||||
params_.color_transform());
|
||||
}
|
||||
|
||||
TimeRangeList ranges;
|
||||
ranges.InsertTimeRange(range);
|
||||
Render(ranges, RenderMode::kOnline, mat, true, 1);
|
||||
|
||||
bool success = true;
|
||||
|
||||
foreach (QFuture<bool> f, write_frame_futures_) {
|
||||
f.waitForFinished();
|
||||
|
||||
if (!f.result()) {
|
||||
SetError(tr("Failed to write AVFrame"));
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
encoder_->Close();
|
||||
|
||||
encoder_->deleteLater();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void FrameColorConvert(ColorProcessorPtr processor, FramePtr frame)
|
||||
{
|
||||
qDebug() << "Converting" << frame->timestamp() << "from" << frame->format();
|
||||
|
||||
// OCIO conversion requires a frame in 32F format
|
||||
if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) {
|
||||
frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F);
|
||||
}
|
||||
|
||||
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
|
||||
ColorManager::DisassociateAlpha(frame);
|
||||
|
||||
// Convert color space
|
||||
processor->ConvertFrame(frame);
|
||||
|
||||
// Re-associate alpha
|
||||
ColorManager::ReassociateAlpha(frame);
|
||||
}
|
||||
|
||||
QFuture<void> ExportTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
|
||||
{
|
||||
rendered_frame_.insert(hash, frame);
|
||||
|
||||
return QtConcurrent::run(FrameColorConvert, color_processor_, frame);
|
||||
}
|
||||
|
||||
void ExportTask::FrameDownloaded(const QByteArray &hash, const QLinkedList<rational> ×)
|
||||
{
|
||||
FramePtr f = rendered_frame_.value(hash);
|
||||
|
||||
foreach (const rational& t, times) {
|
||||
time_map_.insert(t, f);
|
||||
}
|
||||
|
||||
forever {
|
||||
rational real_time = Timecode::timestamp_to_time(frame_time_,
|
||||
viewer()->video_params().time_base());
|
||||
|
||||
if (!time_map_.contains(real_time)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Unfortunately this can't be done in another thread since the frames need to be sent
|
||||
// one after the other chronologically.
|
||||
encoder_->WriteFrame(time_map_.value(real_time), real_time);
|
||||
|
||||
frame_time_++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -38,11 +38,28 @@ public:
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
protected:
|
||||
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) override;
|
||||
|
||||
virtual void FrameDownloaded(const QByteArray& hash, const QLinkedList<rational>& times) override;
|
||||
|
||||
private:
|
||||
QHash<QByteArray, FramePtr> rendered_frame_;
|
||||
|
||||
QHash<rational, FramePtr> time_map_;
|
||||
|
||||
QList< QFuture<bool> > write_frame_futures_;
|
||||
|
||||
ColorManager* color_manager_;
|
||||
|
||||
ExportParams params_;
|
||||
|
||||
Encoder* encoder_;
|
||||
|
||||
ColorProcessorPtr color_processor_;
|
||||
|
||||
int64_t frame_time_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+43
-45
@@ -36,6 +36,11 @@ struct TimeHashFuturePair {
|
||||
QFuture<QByteArray> hash_future;
|
||||
};
|
||||
|
||||
struct HashTimePair {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
};
|
||||
|
||||
struct HashFrameFuturePair {
|
||||
QByteArray hash;
|
||||
QFuture<FramePtr> frame_future;
|
||||
@@ -46,24 +51,23 @@ struct HashDownloadFuturePair {
|
||||
QFuture<void> download_future;
|
||||
};
|
||||
|
||||
struct HashTimePair {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
};
|
||||
|
||||
void RenderTask::Render(TimeRangeList range_to_cache, int divider)
|
||||
void RenderTask::Render(TimeRangeList range_to_cache,
|
||||
RenderMode::Mode mode,
|
||||
const QMatrix4x4& mat,
|
||||
bool audio_enabled,
|
||||
int divider)
|
||||
{
|
||||
OpenGLBackend backend;
|
||||
|
||||
RenderMode::Mode mode = RenderMode::kOffline;
|
||||
PixelFormat::Format format = PixelFormat::instance()->GetConfiguredFormatForMode(mode);
|
||||
|
||||
backend.SetAudioEnabled(false);
|
||||
backend.SetAudioMode(audio_enabled ? RenderBackend::kAudioRender : RenderBackend::kAudioDisabled);
|
||||
backend.SetViewerNode(viewer_);
|
||||
backend.SetPixelFormat(format);
|
||||
backend.SetMode(mode);
|
||||
backend.SetDivider(divider);
|
||||
backend.SetSampleFormat(SampleFormat::kInternalFormat);
|
||||
backend.SetVideoDownloadMatrix(mat);
|
||||
|
||||
// Get hashes for each frame
|
||||
QLinkedList<TimeHashFuturePair> hash_list;
|
||||
@@ -126,58 +130,52 @@ void RenderTask::Render(TimeRangeList range_to_cache, int divider)
|
||||
}
|
||||
}
|
||||
|
||||
// Start downloading frames that have finished
|
||||
OIIO::TypeDesc output_desc = PixelFormat::GetOIIOTypeDesc(format);
|
||||
OIIO::ImageSpec output_spec(viewer_->video_params().width() / divider,
|
||||
viewer_->video_params().height() / divider,
|
||||
OIIO::ImageSpec output_spec(viewer()->video_params().width() / divider,
|
||||
viewer()->video_params().height() / divider,
|
||||
PixelFormat::ChannelCount(format),
|
||||
output_desc);
|
||||
|
||||
// Start downloading frames that have finished
|
||||
{
|
||||
int counter = 0;
|
||||
int nb_frames = render_lookup_table.size();
|
||||
int counter = 0;
|
||||
int nb_frames = render_lookup_table.size();
|
||||
|
||||
QLinkedList<HashDownloadFuturePair> download_futures;
|
||||
QLinkedList<HashDownloadFuturePair> download_futures;
|
||||
|
||||
// Iterators
|
||||
QLinkedList<HashFrameFuturePair>::iterator i;
|
||||
QLinkedList<HashDownloadFuturePair>::iterator j;
|
||||
// Iterators
|
||||
QLinkedList<HashFrameFuturePair>::iterator i;
|
||||
QLinkedList<HashDownloadFuturePair>::iterator j;
|
||||
|
||||
while (!render_lookup_table.isEmpty() || !download_futures.isEmpty()) {
|
||||
i = render_lookup_table.begin();
|
||||
while (!render_lookup_table.isEmpty() || !download_futures.isEmpty()) {
|
||||
i = render_lookup_table.begin();
|
||||
|
||||
while (i != render_lookup_table.end()) {
|
||||
if (i->frame_future.isFinished()) {
|
||||
FramePtr f = i->frame_future.result();
|
||||
while (i != render_lookup_table.end()) {
|
||||
if (i->frame_future.isFinished()) {
|
||||
FramePtr f = i->frame_future.result();
|
||||
|
||||
// Start multithreaded download here
|
||||
download_futures.append({i->hash,
|
||||
QtConcurrent::run(FrameHashCache::SaveCacheFrame, i->hash, f)});
|
||||
// Start multithreaded download here
|
||||
download_futures.append({i->hash, DownloadFrame(f, i->hash)});
|
||||
|
||||
i = render_lookup_table.erase(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
i = render_lookup_table.erase(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
j = download_futures.begin();
|
||||
j = download_futures.begin();
|
||||
|
||||
while (j != download_futures.end()) {
|
||||
if (j->download_future.isFinished()) {
|
||||
// Place it in the cache
|
||||
const QLinkedList<rational>& times_with_hash = times_to_render.value(j->hash);
|
||||
foreach (const rational& t, times_with_hash) {
|
||||
viewer_->video_frame_cache()->SetHash(t, j->hash);
|
||||
}
|
||||
while (j != download_futures.end()) {
|
||||
if (j->download_future.isFinished()) {
|
||||
// Place it in the cache
|
||||
FrameDownloaded(j->hash, times_to_render.value(j->hash));
|
||||
|
||||
// Signal process
|
||||
counter++;
|
||||
emit ProgressChanged(static_cast<double>(counter) / static_cast<double>(nb_frames));
|
||||
// Signal process
|
||||
counter++;
|
||||
emit ProgressChanged(static_cast<double>(counter) / static_cast<double>(nb_frames));
|
||||
|
||||
j = download_futures.erase(j);
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
j = download_futures.erase(j);
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#ifndef RENDERTASK_H
|
||||
#define RENDERTASK_H
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "task/task.h"
|
||||
|
||||
@@ -32,13 +34,17 @@ public:
|
||||
RenderTask(ViewerOutput* viewer);
|
||||
|
||||
protected:
|
||||
void Render(TimeRangeList range_to_cache, int divider = 1);
|
||||
void Render(TimeRangeList range_to_cache, RenderMode::Mode mode, const QMatrix4x4 &mat, bool audio_enabled, int divider);
|
||||
|
||||
ViewerOutput* viewer() const
|
||||
{
|
||||
return viewer_;
|
||||
}
|
||||
|
||||
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) = 0;
|
||||
|
||||
virtual void FrameDownloaded(const QByteArray& hash, const QLinkedList<rational>& times) = 0;
|
||||
|
||||
private:
|
||||
ViewerOutput* viewer_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user