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:
+1
-38
@@ -25,8 +25,7 @@
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
Encoder::Encoder(const EncodingParams ¶ms) :
|
||||
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
@@ -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
@@ -35,10 +35,141 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) :
|
||||
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_) {
|
||||
|
||||
@@ -38,13 +38,14 @@ class FFmpegEncoder : public Encoder
|
||||
public:
|
||||
FFmpegEncoder(const EncodingParams ¶ms);
|
||||
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
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_;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user