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:
itsmattkc
2020-05-19 23:35:17 +10:00
parent cd847a89ce
commit 87e7564222
18 changed files with 414 additions and 263 deletions
+1 -38
View File
@@ -25,8 +25,7 @@
OLIVE_NAMESPACE_ENTER
Encoder::Encoder(const EncodingParams &params) :
params_(params),
open_(false)
params_(params)
{
}
@@ -166,40 +165,4 @@ Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params)
return new FFmpegEncoder(params);
}
bool Encoder::IsOpen() const
{
return open_;
}
void Encoder::Open()
{
if (!open_) {
open_ = OpenInternal();
}
if (open_) {
emit OpenSucceeded();
} else {
emit OpenFailed();
}
}
void Encoder::WriteFrame(FramePtr frame, rational time)
{
if (open_) {
WriteInternal(frame, time);
}
}
void Encoder::Close()
{
if (open_) {
CloseInternal();
open_ = false;
}
emit Closed();
}
OLIVE_NAMESPACE_EXIT
+5 -19
View File
@@ -104,31 +104,17 @@ public:
const EncodingParams& params() const;
public slots:
void Open();
void WriteFrame(OLIVE_NAMESPACE::FramePtr frame, OLIVE_NAMESPACE::rational time);
virtual void WriteAudio(OLIVE_NAMESPACE::AudioRenderingParams pcm_info, const QString& pcm_filename, OLIVE_NAMESPACE::TimeRange range) = 0;
void Close();
virtual bool Open() = 0;
signals:
void OpenSucceeded();
void OpenFailed();
virtual bool WriteFrame(OLIVE_NAMESPACE::FramePtr frame, OLIVE_NAMESPACE::rational time) = 0;
virtual void WriteAudio(OLIVE_NAMESPACE::AudioRenderingParams pcm_info,
const QString& pcm_filename, OLIVE_NAMESPACE::TimeRange range) = 0;
void Closed();
void AudioComplete();
protected:
virtual bool OpenInternal() = 0;
virtual void WriteInternal(FramePtr frame, rational time) = 0;
virtual void CloseInternal() = 0;
bool IsOpen() const;
virtual void Close() = 0;
private:
EncodingParams params_;
bool open_;
};
OLIVE_NAMESPACE_EXIT
+136 -126
View File
@@ -35,10 +35,141 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams &params) :
video_scale_ctx_(nullptr),
audio_stream_(nullptr),
audio_codec_ctx_(nullptr),
audio_resample_ctx_(nullptr)
audio_resample_ctx_(nullptr),
open_(false)
{
}
bool FFmpegEncoder::Open()
{
if (open_) {
return true;
}
int error_code;
// Convert QString to C string that FFmpeg expects
QByteArray filename_bytes = params().filename().toUtf8();
const char* filename_c_str = filename_bytes.constData();
// Create output format context
error_code = avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, filename_c_str);
// Check error code
if (error_code < 0) {
FFmpegError("Failed to allocate output context", error_code);
return false;
}
// Initialize a video stream if it's enabled
if (params().video_enabled()) {
if (!InitializeStream(AVMEDIA_TYPE_VIDEO, &video_stream_, &video_codec_ctx_, params().video_codec())) {
return false;
}
// This is the format we will expect frames received in Write() to be in
PixelFormat::Format native_pixel_fmt = params().video_params().format();
// This is the format we will need to convert the frame to for swscale to understand it
video_conversion_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(native_pixel_fmt);
// This is the equivalent pixel format above as an AVPixelFormat that swscale can understand
AVPixelFormat src_pix_fmt = FFmpegCommon::GetFFmpegPixelFormat(video_conversion_fmt_);
// This is the pixel format the encoder wants to encode to
AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt;
// Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it
// before encoding. Even if we don't, this may be useful for converting between linesizes, etc.
video_scale_ctx_ = sws_getContext(params().video_params().width(),
params().video_params().height(),
src_pix_fmt,
params().video_params().width(),
params().video_params().height(),
encoder_pix_fmt,
0,
nullptr,
nullptr,
nullptr);
}
// Initialize an audio stream if it's enabled
if (params().audio_enabled()
&& !InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) {
return false;
}
av_dump_format(fmt_ctx_, 0, filename_c_str, 1);
// Open output file for writing
error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE);
if (error_code < 0) {
FFmpegError("Failed to open IO context", error_code);
return false;
}
// Write header
error_code = avformat_write_header(fmt_ctx_, nullptr);
if (error_code < 0) {
FFmpegError("Failed to write format header", error_code);
return false;
}
open_ = true;
return true;
}
bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time)
{
bool success = false;
AVFrame* encoded_frame = av_frame_alloc();
int error_code;
const char* input_data;
int input_linesize;
// Frame must be video
encoded_frame->width = frame->width();
encoded_frame->height = frame->height();
encoded_frame->format = video_codec_ctx_->pix_fmt;
error_code = av_frame_get_buffer(encoded_frame, 0);
if (error_code < 0) {
FFmpegError("Failed to create AVFrame buffer", error_code);
goto fail;
}
// We may need to convert this frame to a frame that swscale will understand
if (frame->format() != video_conversion_fmt_) {
frame = PixelFormat::ConvertPixelFormat(frame, video_conversion_fmt_);
}
// Use swscale context to convert formats/linesizes
input_data = frame->const_data();
input_linesize = frame->linesize_bytes();
error_code = sws_scale(video_scale_ctx_,
reinterpret_cast<const uint8_t**>(&input_data),
&input_linesize,
0,
frame->height(),
encoded_frame->data,
encoded_frame->linesize);
if (error_code < 0) {
FFmpegError("Failed to scale frame", error_code);
goto fail;
}
encoded_frame->pts = qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base));
success = WriteAVFrame(encoded_frame, video_codec_ctx_, video_stream_);
fail:
av_frame_free(&encoded_frame);
return success;
}
void FFmpegEncoder::WriteAudio(AudioRenderingParams pcm_info, const QString &pcm_filename, TimeRange range)
{
QFile pcm(pcm_filename);
@@ -150,140 +281,19 @@ void FFmpegEncoder::WriteAudio(AudioRenderingParams pcm_info, const QString &pcm
pcm.close();
}
emit AudioComplete();
}
bool FFmpegEncoder::OpenInternal()
void FFmpegEncoder::Close()
{
int error_code;
// Convert QString to C string that FFmpeg expects
QByteArray filename_bytes = params().filename().toUtf8();
const char* filename_c_str = filename_bytes.constData();
// Create output format context
error_code = avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, filename_c_str);
// Check error code
if (error_code < 0) {
FFmpegError("Failed to allocate output context", error_code);
return false;
}
// Initialize a video stream if it's enabled
if (params().video_enabled()) {
if (!InitializeStream(AVMEDIA_TYPE_VIDEO, &video_stream_, &video_codec_ctx_, params().video_codec())) {
return false;
}
// This is the format we will expect frames received in Write() to be in
PixelFormat::Format native_pixel_fmt = params().video_params().format();
// This is the format we will need to convert the frame to for swscale to understand it
video_conversion_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(native_pixel_fmt);
// This is the equivalent pixel format above as an AVPixelFormat that swscale can understand
AVPixelFormat src_pix_fmt = FFmpegCommon::GetFFmpegPixelFormat(video_conversion_fmt_);
// This is the pixel format the encoder wants to encode to
AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt;
// Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it
// before encoding. Even if we don't, this may be useful for converting between linesizes, etc.
video_scale_ctx_ = sws_getContext(params().video_params().width(),
params().video_params().height(),
src_pix_fmt,
params().video_params().width(),
params().video_params().height(),
encoder_pix_fmt,
0,
nullptr,
nullptr,
nullptr);
}
// Initialize an audio stream if it's enabled
if (params().audio_enabled()
&& !InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) {
return false;
}
av_dump_format(fmt_ctx_, 0, filename_c_str, 1);
// Open output file for writing
error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE);
if (error_code < 0) {
FFmpegError("Failed to open IO context", error_code);
return false;
}
// Write header
error_code = avformat_write_header(fmt_ctx_, nullptr);
if (error_code < 0) {
FFmpegError("Failed to write format header", error_code);
return false;
}
return true;
}
void FFmpegEncoder::WriteInternal(FramePtr frame, rational time)
{
AVFrame* encoded_frame = av_frame_alloc();
int error_code;
const char* input_data;
int input_linesize;
// Frame must be video
encoded_frame->width = frame->width();
encoded_frame->height = frame->height();
encoded_frame->format = video_codec_ctx_->pix_fmt;
error_code = av_frame_get_buffer(encoded_frame, 0);
if (error_code < 0) {
FFmpegError("Failed to create AVFrame buffer", error_code);
goto fail;
}
// We may need to convert this frame to a frame that swscale will understand
if (frame->format() != video_conversion_fmt_) {
frame = PixelFormat::ConvertPixelFormat(frame, video_conversion_fmt_);
}
// Use swscale context to convert formats/linesizes
input_data = frame->const_data();
input_linesize = frame->linesize_bytes();
error_code = sws_scale(video_scale_ctx_,
reinterpret_cast<const uint8_t**>(&input_data),
&input_linesize,
0,
frame->height(),
encoded_frame->data,
encoded_frame->linesize);
if (error_code < 0) {
FFmpegError("Failed to scale frame", error_code);
goto fail;
}
encoded_frame->pts = qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base));
WriteAVFrame(encoded_frame, video_codec_ctx_, video_stream_);
fail:
av_frame_free(&encoded_frame);
}
void FFmpegEncoder::CloseInternal()
{
if (IsOpen()) {
if (open_) {
// Flush encoders
FlushEncoders();
// We've written a header, so we'll write a trailer
av_write_trailer(fmt_ctx_);
avio_closep(&fmt_ctx_->pb);
open_ = false;
}
if (video_scale_ctx_) {
+9 -6
View File
@@ -38,13 +38,14 @@ class FFmpegEncoder : public Encoder
public:
FFmpegEncoder(const EncodingParams &params);
public slots:
virtual void WriteAudio(OLIVE_NAMESPACE::AudioRenderingParams pcm_info, const QString& pcm_filename, OLIVE_NAMESPACE::TimeRange range) override;
virtual bool Open() override;
protected:
virtual bool OpenInternal() override;
virtual void WriteInternal(FramePtr frame, rational time) override;
virtual void CloseInternal() override;
virtual bool WriteFrame(OLIVE_NAMESPACE::FramePtr frame, OLIVE_NAMESPACE::rational time) override;
virtual void WriteAudio(OLIVE_NAMESPACE::AudioRenderingParams pcm_info,
const QString& pcm_filename, OLIVE_NAMESPACE::TimeRange range) override;
virtual void Close() override;
private:
/**
@@ -86,6 +87,8 @@ private:
AVCodecContext* audio_codec_ctx_;
SwrContext* audio_resample_ctx_;
bool open_;
};
OLIVE_NAMESPACE_EXIT
+36 -9
View File
@@ -67,7 +67,10 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
file_browse_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
file_browse_btn->setIcon(icon::Folder);
file_browse_btn->setToolTip(tr("Browse for exported file filename"));
connect(file_browse_btn, SIGNAL(clicked(bool)), this, SLOT(BrowseFilename()));
connect(file_browse_btn,
&QPushButton::clicked,
this,
&ExportDialog::BrowseFilename);
preferences_layout->addWidget(file_browse_btn, row, 3);
row++;
@@ -102,7 +105,10 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
preferences_layout->addWidget(new QLabel(tr("Format:")), row, 0);
format_combobox_ = new QComboBox();
connect(format_combobox_, SIGNAL(currentIndexChanged(int)), this, SLOT(FormatChanged(int)));
connect(format_combobox_,
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&ExportDialog::FormatChanged);
preferences_layout->addWidget(format_combobox_, row, 1, 1, 3);
row++;
@@ -141,7 +147,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
buttons_->setCenterButtons(true);
buttons_->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
buttons_->addButton(QDialogButtonBox::Cancel);
connect(buttons_, &QDialogButtonBox::accepted, this, &ExportDialog::accept);
connect(buttons_, &QDialogButtonBox::accepted, this, &ExportDialog::StartExport);
connect(buttons_, &QDialogButtonBox::rejected, this, &ExportDialog::reject);
preferences_layout->addWidget(buttons_, row, 0, 1, 4);
@@ -178,11 +184,31 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
video_aspect_ratio_ = static_cast<double>(viewer_node_->video_params().width()) / static_cast<double>(viewer_node_->video_params().height());
connect(video_tab_->width_slider(), SIGNAL(ValueChanged(int64_t)), this, SLOT(ResolutionChanged()));
connect(video_tab_->height_slider(), SIGNAL(ValueChanged(int64_t)), this, SLOT(ResolutionChanged()));
connect(video_tab_->scaling_method_combobox(), SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateViewerDimensions()));
connect(video_tab_->maintain_aspect_checkbox(), SIGNAL(toggled(bool)), this, SLOT(ResolutionChanged()));
connect(video_tab_->codec_combobox(), SIGNAL(currentIndexChanged(int)), this, SLOT(VideoCodecChanged()));
connect(video_tab_->width_slider(),
&IntegerSlider::ValueChanged,
this,
&ExportDialog::ResolutionChanged);
connect(video_tab_->height_slider(),
&IntegerSlider::ValueChanged,
this,
&ExportDialog::ResolutionChanged);
connect(video_tab_->scaling_method_combobox(),
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&ExportDialog::UpdateViewerDimensions);
connect(video_tab_->maintain_aspect_checkbox(),
&QCheckBox::toggled,
this,
&ExportDialog::ResolutionChanged);
connect(video_tab_->codec_combobox(),
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&ExportDialog::VideoCodecChanged);
connect(video_tab_,
&ExportVideoTab::ColorSpaceChanged,
preview_viewer_,
@@ -194,7 +220,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace());
}
void ExportDialog::accept()
void ExportDialog::StartExport()
{
if (!video_enabled_->isChecked() && !audio_enabled_->isChecked()) {
QMessageBox b(this);
@@ -278,6 +304,7 @@ void ExportDialog::accept()
ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams());
TaskDialog* td = new TaskDialog(task, tr("Export"), this);
connect(td, &TaskDialog::TaskSucceeded, this, &QDialog::accept);
td->open();
}
+2 -3
View File
@@ -42,9 +42,6 @@ class ExportDialog : public QDialog
public:
ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr);
public slots:
virtual void accept() override;
protected:
virtual void closeEvent(QCloseEvent *e) override;
@@ -123,6 +120,8 @@ private slots:
void UpdateViewerDimensions();
void StartExport();
};
OLIVE_NAMESPACE_EXIT
+2 -2
View File
@@ -474,8 +474,8 @@ void OpenGLProxy::TextureToBuffer(const QVariant& tex_in,
0,
frame->width(),
frame->height(),
OpenGLRenderFunctions::GetPixelFormat(texture->texture()->format()),
OpenGLRenderFunctions::GetPixelType(texture->texture()->format()),
OpenGLRenderFunctions::GetPixelFormat(frame->format()),
OpenGLRenderFunctions::GetPixelType(frame->format()),
frame->data());
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+6 -5
View File
@@ -34,7 +34,7 @@ OLIVE_NAMESPACE_ENTER
RenderBackend::RenderBackend(QObject *parent) :
QObject(parent),
viewer_node_(nullptr),
audio_enabled_(true),
audio_mode_(kAudioPreview),
divider_(1),
render_mode_(RenderMode::kOnline),
pix_fmt_(PixelFormat::PIX_FMT_RGBA32F),
@@ -91,7 +91,7 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
this,
&RenderBackend::NodeGraphChanged);
if (audio_enabled_) {
if (audio_mode_ == kAudioPreview) {
// Listen for audio invalidation signals
connect(viewer_node_->audio_playback_cache(),
&AudioPlaybackCache::Invalidated,
@@ -161,9 +161,9 @@ void RenderBackend::SetVideoDownloadMatrix(const QMatrix4x4 &mat)
video_download_matrix_ = mat;
}
void RenderBackend::SetAudioEnabled(bool e)
void RenderBackend::SetAudioMode(AudioMode e)
{
audio_enabled_ = e;
audio_mode_ = e;
}
void RenderBackend::WorkerStartedRenderingAudio(const TimeRange &r)
@@ -391,7 +391,8 @@ void RenderBackend::AudioInvalidated(const TimeRange& r)
watcher->setFuture(QtConcurrent::run(&audio_pool_.threads,
GetInstanceFromPool(audio_pool_),
&RenderWorker::RenderAudio,
this_range));
this_range,
audio_mode_ == kAudioPreview));
}
}
+8 -2
View File
@@ -64,7 +64,13 @@ public:
void SetVideoDownloadMatrix(const QMatrix4x4& mat);
void SetAudioEnabled(bool e);
enum AudioMode {
kAudioDisabled,
kAudioPreview,
kAudioRender
};
void SetAudioMode(AudioMode e);
void WorkerStartedRenderingAudio(const TimeRange& r);
@@ -107,7 +113,7 @@ private:
QMutex queued_audio_lock_;
TimeRangeList queued_audio_;
bool audio_enabled_;
AudioMode audio_mode_;
// VIDEO MEMBERS
int divider_;
+2 -2
View File
@@ -99,7 +99,7 @@ FramePtr RenderWorker::RenderFrame(const rational &time, bool block_for_update)
return frame;
}
SampleBufferPtr RenderWorker::RenderAudio(const TimeRange &range)
SampleBufferPtr RenderWorker::RenderAudio(const TimeRange &range, bool block_for_update)
{
if (!viewer_) {
return nullptr;
@@ -109,7 +109,7 @@ SampleBufferPtr RenderWorker::RenderAudio(const TimeRange &range)
parent_->WorkerStartedRenderingAudio(range);
UpdateData(true);
UpdateData(block_for_update);
audio_render_time_ = range;
+1 -1
View File
@@ -100,7 +100,7 @@ public:
*/
FramePtr RenderFrame(const rational& time, bool block_for_update);
SampleBufferPtr RenderAudio(const TimeRange& range);
SampleBufferPtr RenderAudio(const TimeRange& range, bool block_for_update);
protected:
virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const = 0;
+14 -1
View File
@@ -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> &times)
{
foreach (const rational& t, times) {
viewer()->video_frame_cache()->SetHash(t, hash);
}
}
OLIVE_NAMESPACE_EXIT
+5
View File
@@ -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
View File
@@ -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> &times)
{
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
+17
View File
@@ -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
+17 -19
View File
@@ -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,14 +130,13 @@ 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();
@@ -151,8 +154,7 @@ void RenderTask::Render(TimeRangeList range_to_cache, int divider)
FramePtr f = i->frame_future.result();
// Start multithreaded download here
download_futures.append({i->hash,
QtConcurrent::run(FrameHashCache::SaveCacheFrame, i->hash, f)});
download_futures.append({i->hash, DownloadFrame(f, i->hash)});
i = render_lookup_table.erase(i);
} else {
@@ -165,10 +167,7 @@ void RenderTask::Render(TimeRangeList range_to_cache, int divider)
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);
}
FrameDownloaded(j->hash, times_to_render.value(j->hash));
// Signal process
counter++;
@@ -181,6 +180,5 @@ void RenderTask::Render(TimeRangeList range_to_cache, int divider)
}
}
}
}
OLIVE_NAMESPACE_EXIT
+7 -1
View File
@@ -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_;
+2 -2
View File
@@ -979,7 +979,7 @@ void ViewerWidget::PlaybackTimerUpdate()
tripped_time = max_time;
}
if (Config::Current()["Loop"].toBool()) {
if (Config::Current()[QStringLiteral("Loop")].toBool()) {
// If we're looping, jump to the other side of the workarea and continue
int64_t opposing_time = (tripped_time == min_time) ? max_time : min_time;
@@ -1044,7 +1044,7 @@ void ViewerWidget::SetZoomFromMenu(QAction *action)
void ViewerWidget::ViewerInvalidatedRange(const TimeRange &range)
{
if (GetTime() >= range.in() && GetTime() < range.out()) {
if (GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) {
ForceUpdate();
}
}