From 9e8a384f149cefdc01c4efcf5b1436847ff96e59 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 3 Mar 2020 01:47:04 +1100 Subject: [PATCH] encoder/exportdialog: add compression options for H264 exporting --- app/codec/encoder.cpp | 53 +++++++ app/codec/encoder.h | 20 +++ app/codec/ffmpeg/ffmpegencoder.cpp | 22 +++ app/codec/ffmpeg/ffmpegencoder.h | 1 + app/dialog/export/CMakeLists.txt | 2 + app/dialog/export/codec/CMakeLists.txt | 26 ++++ app/dialog/export/codec/codecsection.cpp | 6 + app/dialog/export/codec/codecsection.h | 17 +++ app/dialog/export/codec/h264section.cpp | 186 +++++++++++++++++++++++ app/dialog/export/codec/h264section.h | 89 +++++++++++ app/dialog/export/codec/imagesection.cpp | 23 +++ app/dialog/export/codec/imagesection.h | 20 +++ app/dialog/export/export.cpp | 12 +- app/dialog/export/exportvideotab.cpp | 42 +++-- app/dialog/export/exportvideotab.h | 16 +- 15 files changed, 513 insertions(+), 22 deletions(-) create mode 100644 app/dialog/export/codec/CMakeLists.txt create mode 100644 app/dialog/export/codec/codecsection.cpp create mode 100644 app/dialog/export/codec/codecsection.h create mode 100644 app/dialog/export/codec/h264section.cpp create mode 100644 app/dialog/export/codec/h264section.h create mode 100644 app/dialog/export/codec/imagesection.cpp create mode 100644 app/dialog/export/codec/imagesection.h diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index b0d4ec685..122b97609 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -15,6 +15,9 @@ const EncodingParams &Encoder::params() const EncodingParams::EncodingParams() : video_enabled_(false), + video_bit_rate_(0), + video_max_bit_rate_(0), + video_buffer_size_(0), audio_enabled_(false) { } @@ -38,6 +41,26 @@ void EncodingParams::EnableAudio(const AudioRenderingParams &audio_params, const audio_codec_ = acodec; } +void EncodingParams::SetVideoOption(const QString &key, const QString &value) +{ + video_opts_.insert(key, value); +} + +void EncodingParams::SetVideoBitRate(const int64_t &rate) +{ + video_bit_rate_ = rate; +} + +void EncodingParams::SetVideoMaxBitRate(const int64_t &rate) +{ + video_max_bit_rate_ = rate; +} + +void EncodingParams::SetVideoBufferSize(const int64_t &sz) +{ + video_buffer_size_ = sz; +} + const QString &EncodingParams::filename() const { return filename_; @@ -58,6 +81,26 @@ const VideoRenderingParams &EncodingParams::video_params() const return video_params_; } +const QHash &EncodingParams::video_opts() const +{ + return video_opts_; +} + +const int64_t &EncodingParams::video_bit_rate() const +{ + return video_bit_rate_; +} + +const int64_t &EncodingParams::video_max_bit_rate() const +{ + return video_max_bit_rate_; +} + +const int64_t &EncodingParams::video_buffer_size() const +{ + return video_buffer_size_; +} + bool EncodingParams::audio_enabled() const { return audio_enabled_; @@ -73,6 +116,16 @@ const AudioRenderingParams &EncodingParams::audio_params() const return audio_params_; } +const rational &EncodingParams::GetExportLength() const +{ + return export_length_; +} + +void EncodingParams::SetExportLength(const rational &export_length) +{ + export_length_ = export_length; +} + Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params) { Q_UNUSED(id) diff --git a/app/codec/encoder.h b/app/codec/encoder.h index d06983a07..e6a8fb76e 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -17,29 +17,49 @@ public: EncodingParams(); void SetFilename(const QString& filename); + void EnableVideo(const VideoRenderingParams& video_params, const QString& vcodec); void EnableAudio(const AudioRenderingParams& audio_params, const QString& acodec); + void SetVideoOption(const QString& key, const QString& value); + void SetVideoBitRate(const int64_t& rate); + void SetVideoMaxBitRate(const int64_t& rate); + void SetVideoBufferSize(const int64_t& sz); + const QString& filename() const; bool video_enabled() const; const QString& video_codec() const; const VideoRenderingParams& video_params() const; + const QHash& video_opts() const; + const int64_t& video_bit_rate() const; + const int64_t& video_max_bit_rate() const; + const int64_t& video_buffer_size() const; bool audio_enabled() const; const QString& audio_codec() const; const AudioRenderingParams& audio_params() const; + const rational& GetExportLength() const; + void SetExportLength(const rational& GetExportLength); + private: QString filename_; bool video_enabled_; QString video_codec_; VideoRenderingParams video_params_; + QHash video_opts_; + int64_t video_bit_rate_; + int64_t video_max_bit_rate_; + int64_t video_buffer_size_; bool audio_enabled_; QString audio_codec_; AudioRenderingParams audio_params_; + + rational export_length_; + }; class Encoder : public QObject diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index ac3556f03..e92189472 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -337,6 +337,28 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV // FIXME: Make this customizable again codec_ctx->pix_fmt = encoder->pix_fmts[0]; + + // Set custom options + { + QHash::const_iterator i; + + for (i=params().video_opts().begin();i!=params().video_opts().end();i++) { + av_opt_set(video_codec_ctx_->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN); + } + + if (params().video_bit_rate() > 0) { + video_codec_ctx_->bit_rate = params().video_bit_rate(); + } + + if (params().video_max_bit_rate() > 0) { + video_codec_ctx_->rc_max_rate = params().video_max_bit_rate(); + } + + if (params().video_buffer_size() > 0) { + video_codec_ctx_->rc_buffer_size = params().video_buffer_size(); + } + } + } else { codec_ctx->sample_rate = params().audio_params().sample_rate(); codec_ctx->channel_layout = params().audio_params().channel_layout(); diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index 1f26643c1..8da572954 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -5,6 +5,7 @@ extern "C" { #include #include #include +#include } #include "codec/encoder.h" diff --git a/app/dialog/export/CMakeLists.txt b/app/dialog/export/CMakeLists.txt index 2981b494b..4a592ff21 100644 --- a/app/dialog/export/CMakeLists.txt +++ b/app/dialog/export/CMakeLists.txt @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(codec) + set(OLIVE_SOURCES ${OLIVE_SOURCES} dialog/export/export.h diff --git a/app/dialog/export/codec/CMakeLists.txt b/app/dialog/export/codec/CMakeLists.txt new file mode 100644 index 000000000..cb8d22213 --- /dev/null +++ b/app/dialog/export/codec/CMakeLists.txt @@ -0,0 +1,26 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/export/codec/codecsection.h + dialog/export/codec/codecsection.cpp + dialog/export/codec/h264section.h + dialog/export/codec/h264section.cpp + dialog/export/codec/imagesection.h + dialog/export/codec/imagesection.cpp + PARENT_SCOPE +) diff --git a/app/dialog/export/codec/codecsection.cpp b/app/dialog/export/codec/codecsection.cpp new file mode 100644 index 000000000..ab1cd80e7 --- /dev/null +++ b/app/dialog/export/codec/codecsection.cpp @@ -0,0 +1,6 @@ +#include "codecsection.h" + +CodecSection::CodecSection(QWidget *parent) : + QWidget(parent) +{ +} diff --git a/app/dialog/export/codec/codecsection.h b/app/dialog/export/codec/codecsection.h new file mode 100644 index 000000000..147f5870d --- /dev/null +++ b/app/dialog/export/codec/codecsection.h @@ -0,0 +1,17 @@ +#ifndef CODECSECTION_H +#define CODECSECTION_H + +#include + +#include "codec/encoder.h" + +class CodecSection : public QWidget +{ +public: + CodecSection(QWidget* parent = nullptr); + + virtual void AddOpts(EncodingParams* params){} + +}; + +#endif // CODECSECTION_H diff --git a/app/dialog/export/codec/h264section.cpp b/app/dialog/export/codec/h264section.cpp new file mode 100644 index 000000000..cfbd49f2f --- /dev/null +++ b/app/dialog/export/codec/h264section.cpp @@ -0,0 +1,186 @@ +#include "h264section.h" + +#include +#include +#include +#include + +#include "widget/slider/integerslider.h" + +H264Section::H264Section(QWidget *parent) : + CodecSection(parent) +{ + QGridLayout* layout = new QGridLayout(this); + layout->setMargin(0); + + int row = 0; + + layout->addWidget(new QLabel(tr("Compression Method:")), row, 0); + + QComboBox* compression_box = new QComboBox(); + + // These items must correspond to the CompressionMethod enum + compression_box->addItem(tr("Constant Rate Factor")); + compression_box->addItem(tr("Target Bit Rate")); + compression_box->addItem(tr("Target File Size")); + + layout->addWidget(compression_box, row, 1); + + row++; + + compression_method_stack_ = new QStackedWidget(); + layout->addWidget(compression_method_stack_, row, 0, 1, 2); + + crf_section_ = new H264CRFSection(); + compression_method_stack_->addWidget(crf_section_); + + bitrate_section_ = new H264BitRateSection(); + compression_method_stack_->addWidget(bitrate_section_); + + filesize_section_ = new H264FileSizeSection(); + compression_method_stack_->addWidget(filesize_section_); + + connect(compression_box, + static_cast(&QComboBox::currentIndexChanged), + compression_method_stack_, + &QStackedWidget::setCurrentIndex); +} + +void H264Section::AddOpts(EncodingParams *params) +{ + // FIXME: Implement two-pass + + CompressionMethod method = static_cast(compression_method_stack_->currentIndex()); + + if (method == kConstantRateFactor) { + + // Simply set CRF value + params->SetVideoOption(QStringLiteral("crf"), QString::number(crf_section_->GetValue())); + + } else { + + int64_t target_rate, max_rate; + + if (method == kTargetBitRate) { + // Use user-supplied values for the bit rate + target_rate = bitrate_section_->GetTargetBitRate(); + max_rate = bitrate_section_->GetMaximumBitRate(); + } else { + // Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second) + target_rate = qRound64(static_cast(filesize_section_->GetFileSize()) / params->GetExportLength().toDouble()); + max_rate = target_rate; + } + + // Disable CRF encoding + params->SetVideoOption(QStringLiteral("crf"), QStringLiteral("-1")); + + params->SetVideoBitRate(target_rate); + params->SetVideoMaxBitRate(max_rate); + params->SetVideoBufferSize(2000000); + + } +} + +H264CRFSection::H264CRFSection(QWidget *parent) : + QWidget(parent) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + + crf_slider_ = new QSlider(Qt::Horizontal); + crf_slider_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + crf_slider_->setMinimum(kMinimumCRF); + crf_slider_->setMaximum(kMaximumCRF); + crf_slider_->setValue(kDefaultCRF); + layout->addWidget(crf_slider_); + + IntegerSlider* crf_input = new IntegerSlider(); + crf_input->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + crf_input->SetMinimum(kMinimumCRF); + crf_input->SetMaximum(kMaximumCRF); + crf_input->SetValue(kDefaultCRF); + layout->addWidget(crf_input); + + connect(crf_slider_, &QSlider::valueChanged, crf_input, &IntegerSlider::SetValue); + connect(crf_input, &IntegerSlider::ValueChanged, crf_slider_, &QSlider::setValue); +} + +int H264CRFSection::GetValue() const +{ + return crf_slider_->value(); +} + +H264BitRateSection::H264BitRateSection(QWidget *parent) : + QWidget(parent) +{ + QGridLayout* layout = new QGridLayout(this); + layout->setMargin(0); + + int row = 0; + + layout->addWidget(new QLabel(tr("Target Bit Rate (Mbps):")), row, 0); + + target_rate_ = new FloatSlider(); + target_rate_->SetMinimum(0); + layout->addWidget(target_rate_, row, 1); + + row++; + + layout->addWidget(new QLabel(tr("Maximum Bit Rate (Mbps):")), row, 0); + + max_rate_ = new FloatSlider(); + max_rate_->SetMinimum(0); + layout->addWidget(max_rate_, row, 1); + + row++; + + layout->addWidget(new QLabel(tr("Two-Pass")), row, 0); + + QCheckBox* two_pass_box = new QCheckBox(); + layout->addWidget(two_pass_box, row, 1); + + // Bit rate defaults + target_rate_->SetValue(16.0); + max_rate_->SetValue(32.0); +} + +int64_t H264BitRateSection::GetTargetBitRate() const +{ + return qRound64(target_rate_->GetValue() * 1000000.0); +} + +int64_t H264BitRateSection::GetMaximumBitRate() const +{ + return qRound64(max_rate_->GetValue() * 1000000.0); +} + +H264FileSizeSection::H264FileSizeSection(QWidget *parent) : + QWidget(parent) +{ + QGridLayout* layout = new QGridLayout(this); + layout->setMargin(0); + + int row = 0; + + layout->addWidget(new QLabel(tr("Target File Size (MB):")), row, 0); + + file_size_ = new FloatSlider(); + file_size_->SetMinimum(0); + layout->addWidget(file_size_, row, 1); + + row++; + + layout->addWidget(new QLabel(tr("Two-Pass")), row, 0); + + QCheckBox* two_pass_box = new QCheckBox(); + layout->addWidget(two_pass_box, row, 1); + + // File size defaults + file_size_->SetValue(700.0); +} + +int64_t H264FileSizeSection::GetFileSize() const +{ + // Convert megabytes to BITS + return qRound64(file_size_->GetValue() * 1024.0 * 1024.0 * 8.0); +} diff --git a/app/dialog/export/codec/h264section.h b/app/dialog/export/codec/h264section.h new file mode 100644 index 000000000..22260ed1a --- /dev/null +++ b/app/dialog/export/codec/h264section.h @@ -0,0 +1,89 @@ +#ifndef H264SECTION_H +#define H264SECTION_H + +#include +#include + +#include "codecsection.h" +#include "widget/slider/floatslider.h" + +class H264CRFSection : public QWidget +{ +public: + H264CRFSection(QWidget* parent = nullptr); + + int GetValue() const; + +private: + static const int kMinimumCRF = 0; + + static const int kDefaultCRF = 23; + + static const int kMaximumCRF = 51; + + QSlider* crf_slider_; + +}; + +class H264BitRateSection : public QWidget +{ +public: + H264BitRateSection(QWidget* parent = nullptr); + + /** + * @brief Get user-selected target bit rate (returns in BITS) + */ + int64_t GetTargetBitRate() const; + + /** + * @brief Get user-selected maximum bit rate (returns in BITS) + */ + int64_t GetMaximumBitRate() const; + +private: + FloatSlider* target_rate_; + + FloatSlider* max_rate_; + +}; + +class H264FileSizeSection : public QWidget +{ +public: + H264FileSizeSection(QWidget* parent = nullptr); + + /** + * @brief Returns file size in BITS + */ + int64_t GetFileSize() const; + +private: + FloatSlider* file_size_; + +}; + +class H264Section : public CodecSection +{ +public: + enum CompressionMethod { + kConstantRateFactor, + kTargetBitRate, + kTargetFileSize + }; + + H264Section(QWidget* parent = nullptr); + + virtual void AddOpts(EncodingParams* params) override; + +private: + QStackedWidget* compression_method_stack_; + + H264CRFSection* crf_section_; + + H264BitRateSection* bitrate_section_; + + H264FileSizeSection* filesize_section_; + +}; + +#endif // H264SECTION_H diff --git a/app/dialog/export/codec/imagesection.cpp b/app/dialog/export/codec/imagesection.cpp new file mode 100644 index 000000000..4bc10c3b0 --- /dev/null +++ b/app/dialog/export/codec/imagesection.cpp @@ -0,0 +1,23 @@ +#include "imagesection.h" + +#include +#include + +ImageSection::ImageSection(QWidget* parent) : + CodecSection(parent) +{ + QGridLayout* layout = new QGridLayout(this); + layout->setMargin(0); + + int row = 0; + + layout->addWidget(new QLabel(tr("Image Sequence:")), row, 0); + + image_sequence_checkbox_ = new QCheckBox(); + layout->addWidget(new QCheckBox(), row, 1); +} + +QCheckBox *ImageSection::image_sequence_checkbox() const +{ + return image_sequence_checkbox_; +} diff --git a/app/dialog/export/codec/imagesection.h b/app/dialog/export/codec/imagesection.h new file mode 100644 index 000000000..deb75c407 --- /dev/null +++ b/app/dialog/export/codec/imagesection.h @@ -0,0 +1,20 @@ +#ifndef IMAGESECTION_H +#define IMAGESECTION_H + +#include + +#include "codecsection.h" + +class ImageSection : public CodecSection +{ +public: + ImageSection(QWidget* parent = nullptr); + + QCheckBox* image_sequence_checkbox() const; + +private: + QCheckBox* image_sequence_checkbox_; + +}; + +#endif // IMAGESECTION_H diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 1c8f62a8a..2edfe6f18 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -226,11 +226,14 @@ void ExportDialog::accept() // Set up encoder EncodingParams encoding_params; encoding_params.SetFilename(filename_edit_->text()); // FIXME: Validate extension + encoding_params.SetExportLength(viewer_node_->Length()); if (video_enabled_->isChecked()) { const ExportCodec& video_codec = codecs_.at(video_tab_->codec_combobox()->currentData().toInt()); encoding_params.EnableVideo(video_render_params, video_codec.id()); + + video_tab_->GetCodecSection()->AddOpts(&encoding_params); } if (audio_enabled_->isChecked()) { @@ -329,9 +332,14 @@ void ExportDialog::ResolutionChanged() void ExportDialog::VideoCodecChanged() { - const ExportCodec& codec = codecs_.at(video_tab_->codec_combobox()->currentData().toInt()); + int codec_index = video_tab_->codec_combobox()->currentData().toInt(); + const ExportCodec& codec = codecs_.at(codec_index); - video_tab_->show_image_sequence_section(codec.flags() & ExportCodec::kStillImage); + if (codec_index == kCodecH264) { + video_tab_->SetCodecSection(video_tab_->h264_section()); + } else if (codec.flags() & ExportCodec::kStillImage) { + video_tab_->SetCodecSection(video_tab_->image_section()); + } } void ExportDialog::SetUpFormats() diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 330a5ea35..78b5421d0 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -48,17 +48,6 @@ QComboBox *ExportVideoTab::scaling_method_combobox() const return scaling_method_combobox_; } -QCheckBox *ExportVideoTab::image_sequence_checkbox() const -{ - return image_sequence_checkbox_; -} - -void ExportVideoTab::show_image_sequence_section(bool visible) -{ - image_sequence_checkbox_->setVisible(visible); - image_sequence_label_->setVisible(visible); -} - const rational &ExportVideoTab::frame_rate() const { return frame_rates_.at(frame_rate_combobox_->currentIndex()); @@ -84,6 +73,26 @@ QString ExportVideoTab::CurrentOCIOLook() return looks_combobox_->currentData().toString(); } +CodecSection *ExportVideoTab::GetCodecSection() const +{ + return static_cast(codec_stack_->currentWidget()); +} + +void ExportVideoTab::SetCodecSection(CodecSection *section) +{ + codec_stack_->setCurrentWidget(section); +} + +ImageSection *ExportVideoTab::image_section() const +{ + return image_section_; +} + +H264Section *ExportVideoTab::h264_section() const +{ + return h264_section_; +} + QWidget* ExportVideoTab::SetupResolutionSection() { int row = 0; @@ -203,11 +212,14 @@ QWidget *ExportVideoTab::SetupCodecSection() row++; - image_sequence_label_ = new QLabel(tr("Image Sequence:")); - codec_layout->addWidget(image_sequence_label_, row, 0); + codec_stack_ = new QStackedWidget(); + codec_layout->addWidget(codec_stack_, row, 0, 1, 2); - image_sequence_checkbox_ = new QCheckBox(); - codec_layout->addWidget(image_sequence_checkbox_, row, 1); + image_section_ = new ImageSection(); + codec_stack_->addWidget(image_section_); + + h264_section_ = new H264Section(); + codec_stack_->addWidget(h264_section_); return codec_group; } diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index 8cffe5d60..96334d7df 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -6,6 +6,8 @@ #include #include "common/rational.h" +#include "dialog/export/codec/h264section.h" +#include "dialog/export/codec/imagesection.h" #include "render/colormanager.h" #include "widget/slider/integerslider.h" @@ -27,9 +29,6 @@ public: IntegerSlider* height_slider() const; QCheckBox* maintain_aspect_checkbox() const; QComboBox* scaling_method_combobox() const; - QCheckBox* image_sequence_checkbox() const; - - void show_image_sequence_section(bool visible); const rational& frame_rate() const; void set_frame_rate(const rational& frame_rate); @@ -38,6 +37,11 @@ public: QString CurrentOCIOView(); QString CurrentOCIOLook(); + CodecSection* GetCodecSection() const; + void SetCodecSection(CodecSection* section); + ImageSection* image_section() const; + H264Section* h264_section() const; + signals: void DisplayChanged(const QString& display); void ViewChanged(const QString& view); @@ -52,8 +56,10 @@ private: QComboBox* frame_rate_combobox_; QCheckBox* maintain_aspect_checkbox_; QComboBox* scaling_method_combobox_; - QCheckBox* image_sequence_checkbox_; - QLabel* image_sequence_label_; + + QStackedWidget* codec_stack_; + ImageSection* image_section_; + H264Section* h264_section_; IntegerSlider* width_slider_; IntegerSlider* height_slider_;