From 5428151e203a8c521aa115a26622e6ccf303334e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=AE=E7=94=9F=E8=8B=A5=E6=A2=A6?= <1070753498@qq.com> Date: Fri, 1 Mar 2024 18:42:21 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84transcode-4=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 +- examples/player/mainwindow.cpp | 43 +-- examples/transcoder/audioencoderwidget.cc | 130 +++++++++- examples/transcoder/audioencoderwidget.hpp | 8 +- examples/transcoder/mainwindow.cc | 37 ++- examples/transcoder/sourcewidget.cc | 6 + examples/transcoder/sourcewidget.hpp | 2 + examples/transcoder/stautuswidget.hpp | 2 +- examples/transcoder/videoencoderwidget.cc | 157 +++++------ examples/transcoder/videoencoderwidget.hpp | 22 +- ffmpeg/CMakeLists.txt | 2 + ffmpeg/avcontextinfo.cpp | 15 ++ ffmpeg/avcontextinfo.h | 8 +- ffmpeg/averror.cpp | 26 +- ffmpeg/averror.h | 4 +- ffmpeg/averrormanager.cc | 9 +- ffmpeg/averrormanager.hpp | 6 +- ffmpeg/codeccontext.cpp | 287 ++++++++++++++++----- ffmpeg/codeccontext.h | 35 +-- ffmpeg/encodecontext.cc | 3 + ffmpeg/encodecontext.hpp | 67 +++++ ffmpeg/event/errorevent.hpp | 23 +- ffmpeg/event/event.hpp | 9 +- ffmpeg/event/trackevent.hpp | 5 +- ffmpeg/ffmpeg.pro | 2 + ffmpeg/ffmpegutils.cc | 53 +++- ffmpeg/ffmpegutils.hpp | 29 ++- ffmpeg/filter/filtergraph.cc | 2 + ffmpeg/frame.cc | 1 + ffmpeg/mediainfo.cc | 19 +- ffmpeg/player.cpp | 2 +- ffmpeg/transcoder.cc | 225 +++++----------- ffmpeg/transcoder.hpp | 32 +-- ffmpeg/transcodercontext.cc | 2 +- ffmpeg/transcodercontext.hpp | 2 +- 35 files changed, 784 insertions(+), 509 deletions(-) create mode 100644 ffmpeg/encodecontext.cc create mode 100644 ffmpeg/encodecontext.hpp diff --git a/README.md b/README.md index 080f2ed..64b309f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ zscale=p=709; 0,,en,,0000,0000,0000,,Peek-a-boo! ``` -你必须使用 ``ass_process_chunk`` 并设置 pts 和持续时间, 如在 libavfilter/vf_subtitles.c 中一样。 +你必须使用 ``ass_process_chunk`` 并设置 pts 和持续时间, 和在[vf_subtitles.c](https://github.com/FFmpeg/FFmpeg/blob/master/libavfilter/vf_subtitles.c#L490) 中一样。 #### ASS 标准格式应为(ffmpeg-n4.4.3) @@ -60,19 +60,7 @@ subtitles=filename='%1':original_size=%2x%3 如何设置编码参数以获得更小的文件和更好的视频质量? -1. 设置非常高的比特率; -2. 设置编码器 ``global_quality`` 无效。代码如下: - - ```C++ - d_ptr->codecCtx->flags |= AV_CODEC_FLAG_QSCALE; - d_ptr->codecCtx->global_quality = FF_QP2LAMBDA * quailty; - ``` - -3. 设置 ``crf`` 无效。代码如下: - - ```C++ - av_opt_set_int(d_ptr->codecCtx, "crf", crf, AV_OPT_SEARCH_CHILDREN); - ``` +1. 参考[HandBrake encavcodec](https://github.com/HandBrake/HandBrake/blob/master/libhb/encavcodec.c#L359) ### 如何从AVAudioFifo获取的帧中计算pts? @@ -89,7 +77,7 @@ transcodeCtx->audioPts += frame->nb_samples; ## QT-BUG -#### 动态切换Video Render,从opengl切换到widget,还是有GPU 0-3D占用,而且使用量是opengl的2倍!!!QT-BUG? +### 动态切换Video Render,从opengl切换到widget,还是有GPU 0-3D占用,而且使用量是opengl的2倍!!!QT-BUG? ### QOpenGLWidget内存泄漏,移动放大和缩小窗口,代码如下 diff --git a/examples/player/mainwindow.cpp b/examples/player/mainwindow.cpp index 9008483..e312dff 100644 --- a/examples/player/mainwindow.cpp +++ b/examples/player/mainwindow.cpp @@ -65,7 +65,8 @@ class MainWindow::MainWindowPrivate videoTracksGroup->setExclusive(true); subTracksGroup = new QActionGroup(q_ptr); subTracksGroup->setExclusive(true); - mediaInfoAction = new QAction(QObject::tr("Media Info"), q_ptr); + mediaInfoAction = new QAction(QCoreApplication::translate("MainWindowPrivate", "Media Info"), + q_ptr); playListMenu = new QMenu(q_ptr); @@ -106,11 +107,17 @@ class MainWindow::MainWindowPrivate if (!subTracksMenuPtr.isNull()) { delete subTracksMenuPtr.data(); } - audioTracksMenuPtr = new QMenu(QObject::tr("Select audio track"), q_ptr); - videoTracksMenuPtr = new QMenu(QObject::tr("Select video track"), q_ptr); - subTracksMenuPtr = new QMenu(QObject::tr("Select subtitle track"), q_ptr); - menu->addMenu(audioTracksMenuPtr.data()); + audioTracksMenuPtr = new QMenu(QCoreApplication::translate("MainWindowPrivate", + "Select audio track"), + q_ptr); + videoTracksMenuPtr = new QMenu(QCoreApplication::translate("MainWindowPrivate", + "Select video track"), + q_ptr); + subTracksMenuPtr = new QMenu(QCoreApplication::translate("MainWindowPrivate", + "Select subtitle track"), + q_ptr); menu->addMenu(videoTracksMenuPtr.data()); + menu->addMenu(audioTracksMenuPtr.data()); menu->addMenu(subTracksMenuPtr.data()); menu->removeAction(mediaInfoAction); @@ -412,7 +419,7 @@ void MainWindow::onProcessEvents() auto *speedEvent = dynamic_cast(eventPtr.data()); d_ptr->controlWidget->setCacheSpeed(speedEvent->speed()); } break; - case Ffmpeg::PropertyChangeEvent::MediaTrack: { + case Ffmpeg::PropertyChangeEvent::EventType::MediaTrack: { d_ptr->resetTrackMenu(); auto *tracksEvent = dynamic_cast(eventPtr.data()); @@ -444,7 +451,7 @@ void MainWindow::onProcessEvents() } } } break; - case Ffmpeg::PropertyChangeEvent::SeekChanged: { + case Ffmpeg::PropertyChangeEvent::EventType::SeekChanged: { auto *seekEvent = dynamic_cast(eventPtr.data()); int value = seekEvent->position() * 100 / d_ptr->playerPtr->duration(); auto text = tr("Seeked To %1 (key frame) / %2 (%3%)") @@ -455,8 +462,8 @@ void MainWindow::onProcessEvents() QString::number(value)); d_ptr->setTitleWidgetText(text); } break; - case Ffmpeg::PropertyChangeEvent::Error: { - auto *errorEvent = dynamic_cast(eventPtr.data()); + case Ffmpeg::PropertyChangeEvent::EventType::AVError: { + auto *errorEvent = dynamic_cast(eventPtr.data()); const auto text = tr("Error[%1]:%2.") .arg(QString::number(errorEvent->error().errorCode()), errorEvent->error().errorString()); @@ -641,19 +648,19 @@ void MainWindow::initMenu() renderMenu(); connect(d_ptr->audioTracksGroup, &QActionGroup::triggered, this, [this](QAction *action) { - d_ptr->playerPtr->addEvent( - Ffmpeg::EventPtr(new Ffmpeg::SelectedMediaTrackEvent(action->property("index").toInt(), - Ffmpeg::Event::AudioTarck))); + d_ptr->playerPtr->addEvent(Ffmpeg::EventPtr( + new Ffmpeg::SelectedMediaTrackEvent(action->property("index").toInt(), + Ffmpeg::Event::EventType::AudioTarck))); }); connect(d_ptr->videoTracksGroup, &QActionGroup::triggered, this, [this](QAction *action) { - d_ptr->playerPtr->addEvent( - Ffmpeg::EventPtr(new Ffmpeg::SelectedMediaTrackEvent(action->property("index").toInt(), - Ffmpeg::Event::VideoTrack))); + d_ptr->playerPtr->addEvent(Ffmpeg::EventPtr( + new Ffmpeg::SelectedMediaTrackEvent(action->property("index").toInt(), + Ffmpeg::Event::EventType::VideoTrack))); }); connect(d_ptr->subTracksGroup, &QActionGroup::triggered, this, [this](QAction *action) { - d_ptr->playerPtr->addEvent( - Ffmpeg::EventPtr(new Ffmpeg::SelectedMediaTrackEvent(action->property("index").toInt(), - Ffmpeg::Event::SubtitleTrack))); + d_ptr->playerPtr->addEvent(Ffmpeg::EventPtr( + new Ffmpeg::SelectedMediaTrackEvent(action->property("index").toInt(), + Ffmpeg::Event::EventType::SubtitleTrack))); }); connect(d_ptr->mediaInfoAction, &QAction::triggered, this, &MainWindow::onShowMediaInfo); diff --git a/examples/transcoder/audioencoderwidget.cc b/examples/transcoder/audioencoderwidget.cc index 1ca1d2f..31993e5 100644 --- a/examples/transcoder/audioencoderwidget.cc +++ b/examples/transcoder/audioencoderwidget.cc @@ -1,5 +1,6 @@ #include "audioencoderwidget.hpp" +#include #include #include @@ -10,22 +11,71 @@ class AudioEncoderWidget::AudioEncoderWidgetPrivate explicit AudioEncoderWidgetPrivate(AudioEncoderWidget *q) : q_ptr(q) { + const auto *comboBoxStyleSheet = "QComboBox {combobox-popup:0;}"; + audioEncoderCbx = new QComboBox(q_ptr); audioEncoderCbx->setView(new QListView(audioEncoderCbx)); audioEncoderCbx->setMaxVisibleItems(10); - audioEncoderCbx->setStyleSheet("QComboBox {combobox-popup:0;}"); - - auto audioEncodercs = Ffmpeg::getCurrentSupportCodecs(AVMEDIA_TYPE_AUDIO, true); - for (auto iter = audioEncodercs.cbegin(); iter != audioEncodercs.cend(); ++iter) { - audioEncoderCbx->addItem(iter.value(), iter.key()); + audioEncoderCbx->setStyleSheet(comboBoxStyleSheet); + auto audioEncodercs = Ffmpeg::getCodecsInfo(AVMEDIA_TYPE_AUDIO, true); + for (const auto &codec : std::as_const(audioEncodercs)) { + auto text = QString("%1 (%2)").arg(codec.longName).arg(codec.name); + audioEncoderCbx->addItem(text, QVariant::fromValue(codec)); + if (codec.codecId == AV_CODEC_ID_AAC) { + audioEncoderCbx->setCurrentText(text); + } } - audioEncoderCbx->setCurrentIndex(audioEncoderCbx->findData(AV_CODEC_ID_AAC)); audioEncoderCbx->model()->sort(0); + + chLayoutCbx = new QComboBox(q_ptr); + chLayoutCbx->setView(new QListView(chLayoutCbx)); + chLayoutCbx->setMaxVisibleItems(10); + chLayoutCbx->setStyleSheet(comboBoxStyleSheet); + + crfSbx = new QSpinBox(q_ptr); + crfSbx->setToolTip( + QCoreApplication::translate("VideoEncoderWidgetPrivate", "smaller -> better")); + + profileCbx = new QComboBox(q_ptr); + profileCbx->setView(new QListView(profileCbx)); + profileCbx->setMaxVisibleItems(10); + profileCbx->setStyleSheet(comboBoxStyleSheet); + + const int defaultBitrate = 512 * 1000; + minBitrateSbx = new QSpinBox(q_ptr); + minBitrateSbx->setRange(0, INT_MAX); + minBitrateSbx->setValue(defaultBitrate); + maxBitrateSbx = new QSpinBox(q_ptr); + maxBitrateSbx->setRange(0, INT_MAX); + maxBitrateSbx->setValue(defaultBitrate); + + init(); + } + + void init() + { + Ffmpeg::EncodeContext encodeParam; + + profileCbx->clear(); + + crfSbx->setRange(Ffmpeg::EncodeLimit::crf_min, Ffmpeg::EncodeLimit::crf_max); + crfSbx->setValue(encodeParam.crf); + } + + [[nodiscard]] auto currentCodecName() const -> QString + { + return audioEncoderCbx->currentData().value().name; } AudioEncoderWidget *q_ptr; QComboBox *audioEncoderCbx; + QComboBox *chLayoutCbx; + QSpinBox *crfSbx; + QComboBox *profileCbx; + + QSpinBox *minBitrateSbx; + QSpinBox *maxBitrateSbx; }; AudioEncoderWidget::AudioEncoderWidget(QWidget *parent) @@ -34,13 +84,15 @@ AudioEncoderWidget::AudioEncoderWidget(QWidget *parent) { setupUI(); buildConnect(); + onEncoderChanged(); } AudioEncoderWidget::~AudioEncoderWidget() = default; auto AudioEncoderWidget::setEncoder(AVCodecID codecId) -> bool { - auto index = d_ptr->audioEncoderCbx->findData(codecId); + Ffmpeg::CodecInfo codec{"", "", codecId}; + auto index = d_ptr->audioEncoderCbx->findData(QVariant::fromValue(codec)); auto finded = (index >= 0); if (finded) { d_ptr->audioEncoderCbx->setCurrentIndex(index); @@ -48,17 +100,67 @@ auto AudioEncoderWidget::setEncoder(AVCodecID codecId) -> bool return finded; } -auto AudioEncoderWidget::encoder() const -> QString +auto AudioEncoderWidget::encodeParam() const -> Ffmpeg::EncodeContext { - return d_ptr->audioEncoderCbx->currentText(); + Ffmpeg::EncodeContext encodeParam; + encodeParam.mediaType = AVMEDIA_TYPE_AUDIO; + encodeParam.encoderName = d_ptr->currentCodecName(); + encodeParam.channel = static_cast(d_ptr->chLayoutCbx->currentData().toLongLong()); + encodeParam.crf = d_ptr->crfSbx->value(); + encodeParam.minBitrate = d_ptr->minBitrateSbx->value(); + encodeParam.maxBitrate = d_ptr->maxBitrateSbx->value(); + encodeParam.bitrate = d_ptr->maxBitrateSbx->value(); + + return encodeParam; +} + +void AudioEncoderWidget::onEncoderChanged() +{ + d_ptr->profileCbx->clear(); + d_ptr->chLayoutCbx->clear(); + + QScopedPointer contextInfoPtr(new Ffmpeg::AVContextInfo); + if (!contextInfoPtr->initEncoder(d_ptr->currentCodecName())) { + return; + } + auto profiles = contextInfoPtr->profiles(); + for (const auto &profile : std::as_const(profiles)) { + d_ptr->profileCbx->addItem(profile.name, profile.profile); + } + auto chLayouts = Ffmpeg::getChLayouts(contextInfoPtr->chLayouts()); + for (const auto &chLayout : std::as_const(chLayouts)) { + d_ptr->chLayoutCbx->addItem(chLayout.channelName, chLayout.channel); + } + auto index = d_ptr->chLayoutCbx->findData(AV_CH_LAYOUT_STEREO); + d_ptr->chLayoutCbx->setCurrentIndex(index >= 0 ? index : 0); } void AudioEncoderWidget::setupUI() { - auto *layout = new QHBoxLayout(this); - layout->addWidget(new QLabel(tr("Encoder:"))); - layout->addWidget(d_ptr->audioEncoderCbx); - layout->addStretch(); + auto *invailedGroupBox = new QGroupBox(tr("Invalid setting"), this); + auto *invailedLayout = new QFormLayout(invailedGroupBox); + invailedLayout->addRow(tr("Crf:"), d_ptr->crfSbx); + invailedLayout->addRow(tr("Profile:"), d_ptr->profileCbx); + + auto *bitrateGroupBox = new QGroupBox(tr("Bitrate"), this); + auto *bitrateLayout = new QFormLayout(bitrateGroupBox); + bitrateLayout->addRow(tr("Min Bitrate:"), d_ptr->minBitrateSbx); + bitrateLayout->addRow(tr("Max Bitrate:"), d_ptr->maxBitrateSbx); + + auto *hLayout = new QHBoxLayout; + hLayout->addWidget(invailedGroupBox); + hLayout->addWidget(bitrateGroupBox); + + auto *layout = new QFormLayout(this); + layout->addRow(tr("Encoder:"), d_ptr->audioEncoderCbx); + layout->addRow(tr("Channel Layout:"), d_ptr->chLayoutCbx); + layout->addRow(hLayout); } -void AudioEncoderWidget::buildConnect() {} +void AudioEncoderWidget::buildConnect() +{ + connect(d_ptr->audioEncoderCbx, + &QComboBox::currentIndexChanged, + this, + &AudioEncoderWidget::onEncoderChanged); +} diff --git a/examples/transcoder/audioencoderwidget.hpp b/examples/transcoder/audioencoderwidget.hpp index da98530..708fa2f 100644 --- a/examples/transcoder/audioencoderwidget.hpp +++ b/examples/transcoder/audioencoderwidget.hpp @@ -1,6 +1,8 @@ #ifndef AUDIOENCODERWIDGET_HPP #define AUDIOENCODERWIDGET_HPP +#include + #include extern "C" { @@ -15,7 +17,11 @@ class AudioEncoderWidget : public QWidget ~AudioEncoderWidget() override; auto setEncoder(AVCodecID codecId) -> bool; - [[nodiscard]] auto encoder() const -> QString; + + [[nodiscard]] auto encodeParam() const -> Ffmpeg::EncodeContext; + +private slots: + void onEncoderChanged(); private: void setupUI(); diff --git a/examples/transcoder/mainwindow.cc b/examples/transcoder/mainwindow.cc index 1295182..fdc138f 100644 --- a/examples/transcoder/mainwindow.cc +++ b/examples/transcoder/mainwindow.cc @@ -7,6 +7,7 @@ #include "videoencoderwidget.hpp" #include +#include #include #include #include @@ -24,15 +25,14 @@ class MainWindow::MainWindowPrivate { Ffmpeg::printFfmpegInfo(); + Ffmpeg::EncodeContext encodeContext; + transcoder = new Ffmpeg::Transcoder(q_ptr); sourceWidget = new SourceWidget(q_ptr); tabWidget = new QTabWidget(q_ptr); previewWidget = new PreviewWidget(q_ptr); videoEncoderWidget = new VideoEncoderWidget(q_ptr); - videoEncoderWidget->setPreset(transcoder->presets(), transcoder->preset()); - videoEncoderWidget->setTune(transcoder->tunes(), transcoder->tune()); - videoEncoderWidget->setProfile(transcoder->profiles(), transcoder->profile()); audioEncoderWidget = new AudioEncoderWidget(q_ptr); tabWidget->addTab(previewWidget, QCoreApplication::translate("MainWindowPrivate", "Preview")); @@ -187,20 +187,12 @@ void MainWindow::onStart() d_ptr->transcoder->setInFilePath(inPath); d_ptr->transcoder->setOutFilePath(outPath); - d_ptr->transcoder->setAudioEncodecName(d_ptr->audioEncoderWidget->encoder()); - d_ptr->transcoder->setVideoEncodecName(d_ptr->videoEncoderWidget->encoder()); - d_ptr->transcoder->setUseGpuDecode(d_ptr->videoEncoderWidget->gpuDecode()); - d_ptr->transcoder->setUseGpuEncode(d_ptr->videoEncoderWidget->gpuEncode()); - d_ptr->transcoder->setSize(d_ptr->videoEncoderWidget->videoSize()); + d_ptr->transcoder->setVideoEncodeContext(d_ptr->videoEncoderWidget->encodeParam()); + d_ptr->transcoder->setAudioEncodeContext(d_ptr->audioEncoderWidget->encodeParam()); + if (QFile::exists(subtitlePath)) { d_ptr->transcoder->setSubtitleFilename(subtitlePath); } - d_ptr->transcoder->setQuailty(d_ptr->videoEncoderWidget->quality()); - d_ptr->transcoder->setMinBitrate(d_ptr->videoEncoderWidget->minBitrate()); - d_ptr->transcoder->setMaxBitrate(d_ptr->videoEncoderWidget->maxBitrate()); - d_ptr->transcoder->setCrf(d_ptr->videoEncoderWidget->crf()); - d_ptr->transcoder->setPreset(d_ptr->videoEncoderWidget->preset()); - d_ptr->transcoder->setProfile(d_ptr->videoEncoderWidget->profile()); d_ptr->transcoder->startTranscode(); d_ptr->statusWidget->setStatus(tr("Stop")); @@ -236,7 +228,7 @@ void MainWindow::onProcessEvents() d_ptr->statusWidget->setProgress(positionEvent->position() * 100.0 / d_ptr->transcoder->duration()); } break; - case Ffmpeg::PropertyChangeEvent::MediaTrack: { + case Ffmpeg::PropertyChangeEvent::EventType::MediaTrack: { bool audioSet = false; bool videoSet = false; auto *mediaTrackEvent = dynamic_cast(eventPtr.data()); @@ -265,13 +257,20 @@ void MainWindow::onProcessEvents() case Ffmpeg::PropertyChangeEvent::EventType::PreviewFramesChanged: d_ptr->previewWidget->setFrames(d_ptr->transcoder->previewFrames()); break; - case Ffmpeg::PropertyChangeEvent::Error: { - auto *errorEvent = dynamic_cast(eventPtr.data()); + case Ffmpeg::PropertyChangeEvent::EventType::AVError: { + auto *errorEvent = dynamic_cast(eventPtr.data()); const auto text = tr("Error[%1]:%2.") .arg(QString::number(errorEvent->error().errorCode()), errorEvent->error().errorString()); qWarning() << text; - } + statusBar()->showMessage(text); + } break; + case Ffmpeg::PropertyChangeEvent::EventType::Error: { + auto *errorEvent = dynamic_cast(eventPtr.data()); + const auto text = tr("Error:%1.").arg(errorEvent->error()); + qWarning() << text; + statusBar()->showMessage(text); + } break; default: break; } } @@ -301,7 +300,7 @@ void MainWindow::setupUI() layout->addWidget(d_ptr->outPutWidget); setCentralWidget(widget); - statusBar()->addWidget(d_ptr->statusWidget); + statusBar()->addPermanentWidget(d_ptr->statusWidget); auto *tempWidget = new QWidget(this); auto *tempLayout = new QVBoxLayout(tempWidget); diff --git a/examples/transcoder/sourcewidget.cc b/examples/transcoder/sourcewidget.cc index a4368b0..e78df60 100644 --- a/examples/transcoder/sourcewidget.cc +++ b/examples/transcoder/sourcewidget.cc @@ -63,6 +63,12 @@ void SourceWidget::setFileInfo(const QString &info) d_ptr->infoLabel->setToolTip(info); } +auto SourceWidget::range() const -> QPair +{ + return {d_ptr->startTimeEdit->time().msecsSinceStartOfDay() * 1000, + d_ptr->endTimeEdit->time().msecsSinceStartOfDay() * 1000}; +} + void SourceWidget::onRangeChanged() { d_ptr->startTimeEdit->setMaximumTime(d_ptr->endTimeEdit->time()); diff --git a/examples/transcoder/sourcewidget.hpp b/examples/transcoder/sourcewidget.hpp index 7fe9034..388a297 100644 --- a/examples/transcoder/sourcewidget.hpp +++ b/examples/transcoder/sourcewidget.hpp @@ -17,6 +17,8 @@ class SourceWidget : public QWidget void setFileInfo(const QString &info); + auto range() const -> QPair; + private slots: void onRangeChanged(); diff --git a/examples/transcoder/stautuswidget.hpp b/examples/transcoder/stautuswidget.hpp index 3288531..ade890f 100644 --- a/examples/transcoder/stautuswidget.hpp +++ b/examples/transcoder/stautuswidget.hpp @@ -11,7 +11,7 @@ class StautusWidget : public QWidget ~StautusWidget() override; void setStatus(const QString &status); - QString status() const; + auto status() const -> QString; void setProgress(int progress); diff --git a/examples/transcoder/videoencoderwidget.cc b/examples/transcoder/videoencoderwidget.cc index fb46794..7857ae3 100644 --- a/examples/transcoder/videoencoderwidget.cc +++ b/examples/transcoder/videoencoderwidget.cc @@ -1,5 +1,6 @@ #include "videoencoderwidget.hpp" +#include #include #include @@ -15,31 +16,25 @@ class VideoEncoderWidget::VideoEncoderWidgetPrivate videoEncoderCbx->setView(new QListView(videoEncoderCbx)); videoEncoderCbx->setMaxVisibleItems(10); videoEncoderCbx->setStyleSheet(comboBoxStyleSheet); - auto videoCodecs = Ffmpeg::getCurrentSupportCodecs(AVMEDIA_TYPE_VIDEO, true); - for (auto iter = videoCodecs.cbegin(); iter != videoCodecs.cend(); ++iter) { - videoEncoderCbx->addItem(iter.value(), iter.key()); + auto videoCodecs = Ffmpeg::getCodecsInfo(AVMEDIA_TYPE_VIDEO, true); + for (const auto &codec : std::as_const(videoCodecs)) { + auto text = QString("%1 (%2)").arg(codec.longName).arg(codec.name); + videoEncoderCbx->addItem(text, QVariant::fromValue(codec)); + if (codec.codecId == AV_CODEC_ID_H264) { + videoEncoderCbx->setCurrentText(text); + } } - videoEncoderCbx->setCurrentIndex(videoEncoderCbx->findData(AV_CODEC_ID_H264)); videoEncoderCbx->model()->sort(0); - gpuEncodeCbx = new QCheckBox(QCoreApplication::translate("VideoEncoderWidgetPrivate", - "Use GPU Encode"), - q_ptr); - gpuEncodeCbx->setChecked(true); gpuDecodeCbx = new QCheckBox(QCoreApplication::translate("VideoEncoderWidgetPrivate", "Use GPU Decode"), q_ptr); gpuDecodeCbx->setChecked(true); - quailtySbx = new QSpinBox(q_ptr); - quailtySbx->setRange(2, 31); - quailtySbx->setToolTip( - QCoreApplication::translate("VideoEncoderWidgetPrivate", "smaller -> better")); crfSbx = new QSpinBox(q_ptr); - crfSbx->setRange(0, 51); crfSbx->setToolTip( QCoreApplication::translate("VideoEncoderWidgetPrivate", "smaller -> better")); - crfSbx->setValue(18); + presetCbx = new QComboBox(q_ptr); presetCbx->setView(new QListView(presetCbx)); tuneCbx = new QComboBox(q_ptr); @@ -60,6 +55,8 @@ class VideoEncoderWidget::VideoEncoderWidgetPrivate minBitrateSbx->setRange(0, INT_MAX); maxBitrateSbx = new QSpinBox(q_ptr); maxBitrateSbx->setRange(0, INT_MAX); + + init(); } void calBitrate() const @@ -70,13 +67,34 @@ class VideoEncoderWidget::VideoEncoderWidgetPrivate maxBitrateSbx->setValue(w * h * 4); } + void init() const + { + Ffmpeg::EncodeContext encodeParam; + + presetCbx->clear(); + presetCbx->addItems(Ffmpeg::EncodeLimit::presets); + presetCbx->setCurrentText(encodeParam.preset); + + tuneCbx->clear(); + tuneCbx->addItems(Ffmpeg::EncodeLimit::tunes); + tuneCbx->setCurrentText(encodeParam.tune); + + profileCbx->clear(); + + crfSbx->setRange(Ffmpeg::EncodeLimit::crf_min, Ffmpeg::EncodeLimit::crf_max); + crfSbx->setValue(encodeParam.crf); + } + + [[nodiscard]] auto currentCodecName() const -> QString + { + return videoEncoderCbx->currentData().value().name; + } + VideoEncoderWidget *q_ptr; QComboBox *videoEncoderCbx; - QCheckBox *gpuEncodeCbx; QCheckBox *gpuDecodeCbx; - QSpinBox *quailtySbx; QSpinBox *crfSbx; QComboBox *presetCbx; QComboBox *tuneCbx; @@ -98,13 +116,15 @@ VideoEncoderWidget::VideoEncoderWidget(QWidget *parent) { setupUI(); buildConnect(); + onEncoderChanged(); } VideoEncoderWidget::~VideoEncoderWidget() = default; auto VideoEncoderWidget::setEncoder(AVCodecID codecId) -> bool { - auto index = d_ptr->videoEncoderCbx->findData(codecId); + Ffmpeg::CodecInfo codec{"", "", codecId}; + auto index = d_ptr->videoEncoderCbx->findData(QVariant::fromValue(codec)); auto finded = (index >= 0); if (finded) { d_ptr->videoEncoderCbx->setCurrentIndex(index); @@ -112,47 +132,6 @@ auto VideoEncoderWidget::setEncoder(AVCodecID codecId) -> bool return finded; } -auto VideoEncoderWidget::encoder() const -> QString -{ - return d_ptr->videoEncoderCbx->currentText(); -} - -void VideoEncoderWidget::setPreset(const QStringList &presets, const QString ¤t) -{ - d_ptr->presetCbx->clear(); - d_ptr->presetCbx->addItems(presets); - d_ptr->presetCbx->setCurrentText(current); -} - -auto VideoEncoderWidget::preset() const -> QString -{ - return d_ptr->presetCbx->currentText(); -} - -void VideoEncoderWidget::setTune(const QStringList &tunes, const QString ¤t) -{ - d_ptr->tuneCbx->clear(); - d_ptr->tuneCbx->addItems(tunes); - d_ptr->tuneCbx->setCurrentText(current); -} - -auto VideoEncoderWidget::tune() const -> QString -{ - return d_ptr->tuneCbx->currentText(); -} - -void VideoEncoderWidget::setProfile(const QStringList &profiles, const QString ¤t) -{ - d_ptr->profileCbx->clear(); - d_ptr->profileCbx->addItems(profiles); - d_ptr->profileCbx->setCurrentText(current); -} - -auto VideoEncoderWidget::profile() const -> QString -{ - return d_ptr->profileCbx->currentText(); -} - void VideoEncoderWidget::setVideoSize(const QSize &size) { d_ptr->widthSbx->blockSignals(true); @@ -166,48 +145,36 @@ void VideoEncoderWidget::setVideoSize(const QSize &size) d_ptr->calBitrate(); } -auto VideoEncoderWidget::videoSize() const -> QSize -{ - return {d_ptr->widthSbx->value(), d_ptr->heightSbx->value()}; -} - -auto VideoEncoderWidget::quality() const -> int -{ - return d_ptr->quailtySbx->value(); -} - -auto VideoEncoderWidget::minBitrate() const -> int -{ - return d_ptr->minBitrateSbx->value(); -} - -auto VideoEncoderWidget::gpuEncode() const -> bool -{ - return d_ptr->gpuEncodeCbx->isChecked(); -} - -auto VideoEncoderWidget::gpuDecode() const -> bool -{ - return d_ptr->gpuDecodeCbx->isChecked(); -} - -auto VideoEncoderWidget::maxBitrate() const -> int -{ - return d_ptr->maxBitrateSbx->value(); -} - -auto VideoEncoderWidget::crf() const -> int +auto VideoEncoderWidget::encodeParam() const -> Ffmpeg::EncodeContext { - return d_ptr->crfSbx->value(); + Ffmpeg::EncodeContext encodeParam; + encodeParam.mediaType = AVMEDIA_TYPE_VIDEO; + encodeParam.encoderName = d_ptr->currentCodecName(); + encodeParam.size = {d_ptr->widthSbx->value(), d_ptr->heightSbx->value()}; + encodeParam.gpuDecode = d_ptr->gpuDecodeCbx->isChecked(); + encodeParam.minBitrate = d_ptr->minBitrateSbx->value(); + encodeParam.maxBitrate = d_ptr->maxBitrateSbx->value(); + encodeParam.bitrate = d_ptr->maxBitrateSbx->value(); + encodeParam.crf = d_ptr->crfSbx->value(); + encodeParam.preset = d_ptr->presetCbx->currentText(); + encodeParam.tune = d_ptr->tuneCbx->currentText(); + // encodeParam.profile = d_ptr->profileCbx->currentText(); + + return encodeParam; } void VideoEncoderWidget::onEncoderChanged() { - auto quantizer = Ffmpeg::getCodecQuantizer(d_ptr->videoEncoderCbx->currentText()); - if (quantizer.first < 0 || quantizer.second < 0) { + d_ptr->profileCbx->clear(); + + QScopedPointer contextInfoPtr(new Ffmpeg::AVContextInfo); + if (!contextInfoPtr->initEncoder(d_ptr->currentCodecName())) { return; } - d_ptr->quailtySbx->setRange(quantizer.first, quantizer.second); + auto profiles = contextInfoPtr->profiles(); + for (const auto &profile : std::as_const(profiles)) { + d_ptr->profileCbx->addItem(profile.name, profile.profile); + } } void VideoEncoderWidget::onVideoWidthChanged() @@ -243,12 +210,10 @@ void VideoEncoderWidget::setupUI() codecLayout->addWidget(new QLabel(tr("Encoder:"), this)); codecLayout->addWidget(d_ptr->videoEncoderCbx); codecLayout->addStretch(); - codecLayout->addWidget(d_ptr->gpuEncodeCbx); codecLayout->addWidget(d_ptr->gpuDecodeCbx); auto *invailedGroupBox = new QGroupBox(tr("Invalid setting"), this); auto *invailedLayout = new QFormLayout(invailedGroupBox); - invailedLayout->addRow(tr("Quality:"), d_ptr->quailtySbx); invailedLayout->addRow(tr("Crf:"), d_ptr->crfSbx); invailedLayout->addRow(tr("Preset:"), d_ptr->presetCbx); invailedLayout->addRow(tr("Tune:"), d_ptr->tuneCbx); @@ -275,7 +240,7 @@ void VideoEncoderWidget::setupUI() void VideoEncoderWidget::buildConnect() { connect(d_ptr->videoEncoderCbx, - &QComboBox::currentTextChanged, + &QComboBox::currentIndexChanged, this, &VideoEncoderWidget::onEncoderChanged); connect(d_ptr->widthSbx, diff --git a/examples/transcoder/videoencoderwidget.hpp b/examples/transcoder/videoencoderwidget.hpp index 9ad128b..3f03c4f 100644 --- a/examples/transcoder/videoencoderwidget.hpp +++ b/examples/transcoder/videoencoderwidget.hpp @@ -1,6 +1,8 @@ #ifndef VIDEOENCODERWIDGET_HPP #define VIDEOENCODERWIDGET_HPP +#include + #include extern "C" { @@ -15,28 +17,10 @@ class VideoEncoderWidget : public QWidget ~VideoEncoderWidget() override; auto setEncoder(AVCodecID codecId) -> bool; - [[nodiscard]] auto encoder() const -> QString; - - void setPreset(const QStringList &presets, const QString ¤t); - [[nodiscard]] auto preset() const -> QString; - - void setTune(const QStringList &tunes, const QString ¤t); - [[nodiscard]] auto tune() const -> QString; - - void setProfile(const QStringList &profiles, const QString ¤t); - [[nodiscard]] auto profile() const -> QString; void setVideoSize(const QSize &size); - [[nodiscard]] auto videoSize() const -> QSize; - - [[nodiscard]] auto quality() const -> int; - [[nodiscard]] auto crf() const -> int; - - [[nodiscard]] auto minBitrate() const -> int; - [[nodiscard]] auto maxBitrate() const -> int; - [[nodiscard]] auto gpuEncode() const -> bool; - [[nodiscard]] auto gpuDecode() const -> bool; + [[nodiscard]] auto encodeParam() const -> Ffmpeg::EncodeContext; private slots: void onEncoderChanged(); diff --git a/ffmpeg/CMakeLists.txt b/ffmpeg/CMakeLists.txt index d96609a..3f427f3 100644 --- a/ffmpeg/CMakeLists.txt +++ b/ffmpeg/CMakeLists.txt @@ -68,6 +68,8 @@ set(PROJECT_SOURCES colorutils.hpp decoder.cc decoder.h + encodecontext.cc + encodecontext.hpp ffmepg_global.h ffmpegutils.cc ffmpegutils.hpp diff --git a/ffmpeg/avcontextinfo.cpp b/ffmpeg/avcontextinfo.cpp index 2967728..7453dae 100644 --- a/ffmpeg/avcontextinfo.cpp +++ b/ffmpeg/avcontextinfo.cpp @@ -255,4 +255,19 @@ auto AVContextInfo::pixfmt() const -> AVPixelFormat return d_ptr->codecCtx->avCodecCtx()->pix_fmt; } +auto AVContextInfo::quantizer() -> QPair +{ + return d_ptr->codecCtx->quantizer(); +} + +auto AVContextInfo::profiles() -> QVector +{ + return d_ptr->codecCtx->supportedProfiles(); +} + +auto AVContextInfo::chLayouts() const -> QVector +{ + return d_ptr->codecCtx->supportedChLayouts(); +} + } // namespace Ffmpeg diff --git a/ffmpeg/avcontextinfo.h b/ffmpeg/avcontextinfo.h index 73eae72..08635ef 100644 --- a/ffmpeg/avcontextinfo.h +++ b/ffmpeg/avcontextinfo.h @@ -6,12 +6,10 @@ #include extern "C" { -#include -#include +#include } struct AVStream; -struct AVRational; namespace Ffmpeg { @@ -60,6 +58,10 @@ class FFMPEG_EXPORT AVContextInfo : public QObject [[nodiscard]] auto gpuType() const -> GpuType; [[nodiscard]] auto pixfmt() const -> AVPixelFormat; + [[nodiscard]] auto quantizer() -> QPair; + [[nodiscard]] auto profiles() -> QVector; + [[nodiscard]] auto chLayouts() const -> QVector; + auto codecCtx() -> CodecContext *; private: diff --git a/ffmpeg/averror.cpp b/ffmpeg/averror.cpp index 8dd8714..7c34f7a 100644 --- a/ffmpeg/averror.cpp +++ b/ffmpeg/averror.cpp @@ -16,6 +16,7 @@ class AVError::AVErrorPrivate AVError *q_ptr; int error = 0; QString errorString; + QString funcInfo; }; AVError::AVError(int error) @@ -29,13 +30,15 @@ AVError::AVError(const AVError &other) { d_ptr->error = other.d_ptr->error; d_ptr->errorString = other.d_ptr->errorString; + d_ptr->funcInfo = other.d_ptr->funcInfo; } AVError::AVError(AVError &&other) noexcept : d_ptr(new AVErrorPrivate(this)) { d_ptr->error = other.d_ptr->error; - d_ptr->errorString = std::move(other.d_ptr->errorString); + d_ptr->errorString = other.d_ptr->errorString; + d_ptr->funcInfo = other.d_ptr->funcInfo; } AVError::~AVError() = default; @@ -44,17 +47,25 @@ auto AVError::operator=(const AVError &other) -> AVError & { d_ptr->error = other.d_ptr->error; d_ptr->errorString = other.d_ptr->errorString; + d_ptr->funcInfo = other.d_ptr->funcInfo; return *this; } auto AVError::operator=(AVError &&other) noexcept -> AVError & { d_ptr->error = other.d_ptr->error; - d_ptr->errorString = std::move(other.d_ptr->errorString); + d_ptr->errorString = other.d_ptr->errorString; + d_ptr->funcInfo = other.d_ptr->funcInfo; + return *this; +} + +auto AVError::setFuncInfo(const QString &funcInfo) -> AVError & +{ + d_ptr->funcInfo = funcInfo; return *this; } -void AVError::setErrorCode(int error) +auto AVError::setErrorCode(int error) -> AVError & { d_ptr->error = error; if (error < 0) { @@ -64,6 +75,7 @@ void AVError::setErrorCode(int error) } else { d_ptr->errorString.clear(); } + return *this; } auto AVError::errorCode() const -> int @@ -73,7 +85,13 @@ auto AVError::errorCode() const -> int auto AVError::errorString() const -> QString { - return d_ptr->errorString; + QString errorString; + if (d_ptr->funcInfo.isEmpty()) { + errorString = d_ptr->errorString; + } else { + errorString = QString("%1: %2").arg(d_ptr->errorString, d_ptr->funcInfo); + } + return errorString; } auto AVError::avErrorString(int error) -> QString diff --git a/ffmpeg/averror.h b/ffmpeg/averror.h index 044600d..6f33abe 100644 --- a/ffmpeg/averror.h +++ b/ffmpeg/averror.h @@ -18,7 +18,9 @@ class FFMPEG_EXPORT AVError auto operator=(const AVError &other) -> AVError &; auto operator=(AVError &&other) noexcept -> AVError &; - void setErrorCode(int error); + auto setFuncInfo(const QString &funcInfo) -> AVError &; + + auto setErrorCode(int error) -> AVError &; [[nodiscard]] auto errorCode() const -> int; [[nodiscard]] auto errorString() const -> QString; diff --git a/ffmpeg/averrormanager.cc b/ffmpeg/averrormanager.cc index aae5f4c..491320d 100644 --- a/ffmpeg/averrormanager.cc +++ b/ffmpeg/averrormanager.cc @@ -29,7 +29,13 @@ void AVErrorManager::setMaxCaches(int max) d_ptr->max = max; } -void AVErrorManager::setErrorCode(int errorCode) +auto AVErrorManager::setFuncInfo(const QString &funcInfo) -> AVErrorManager & +{ + d_ptr->error.setFuncInfo(funcInfo); + return *this; +} + +auto AVErrorManager::setErrorCode(int errorCode) -> AVErrorManager & { d_ptr->errorCodes.append(errorCode); d_ptr->error.setErrorCode(errorCode); @@ -42,6 +48,7 @@ void AVErrorManager::setErrorCode(int errorCode) d_ptr->error.errorString()); } emit error(d_ptr->error); + return *this; } auto AVErrorManager::lastErrorString() const -> QString diff --git a/ffmpeg/averrormanager.hpp b/ffmpeg/averrormanager.hpp index ffc11eb..f60fd77 100644 --- a/ffmpeg/averrormanager.hpp +++ b/ffmpeg/averrormanager.hpp @@ -5,7 +5,8 @@ #include -#define SET_ERROR_CODE(errorCode) AVErrorManager::instance()->setErrorCode(errorCode) +#define SET_ERROR_CODE(errorCode) \ + AVErrorManager::instance()->setFuncInfo(Q_FUNC_INFO).setErrorCode(errorCode) #define ERROR_RETURN(errorCode) \ if ((errorCode) < 0) { \ @@ -25,7 +26,8 @@ class FFMPEG_EXPORT AVErrorManager : public QObject void setPrint(bool print); void setMaxCaches(int max); - void setErrorCode(int errorCode); + auto setFuncInfo(const QString &funcInfo) -> AVErrorManager &; + auto setErrorCode(int errorCode) -> AVErrorManager &; [[nodiscard]] auto lastErrorString() const -> QString; [[nodiscard]] auto errorCodes() const -> QVector; diff --git a/ffmpeg/codeccontext.cpp b/ffmpeg/codeccontext.cpp index f741515..2e183dc 100644 --- a/ffmpeg/codeccontext.cpp +++ b/ffmpeg/codeccontext.cpp @@ -1,5 +1,7 @@ #include "codeccontext.h" #include "averrormanager.hpp" +#include "encodecontext.hpp" +#include "ffmpegutils.hpp" #include "frame.hpp" #include "packet.h" #include "subtitle.h" @@ -20,7 +22,11 @@ class CodecContext::CodecContextPrivate explicit CodecContextPrivate(CodecContext *q) : q_ptr(q) {} - ~CodecContextPrivate() { avcodec_free_context(&codecCtx); } + ~CodecContextPrivate() + { + av_dict_free(&encodeOptions); + avcodec_free_context(&codecCtx); + } void init() { @@ -55,6 +61,119 @@ class CodecContext::CodecContextPrivate ch_layout++; } } + const auto *profile = codec->profiles; + while (profile != nullptr && profile->profile != AV_PROFILE_UNKNOWN) { + supported_profiles.append(*profile); + qDebug() << profile->name << profile->profile; + profile++; + } + } + + // code copy from HandBrake encavcodec.c + void initVideoEncoderOptions(const EncodeContext &encodeContext) + { + double crf = encodeContext.crf; + + Q_ASSERT(codecCtx->codec_type == AVMEDIA_TYPE_VIDEO); + Q_ASSERT(crf >= EncodeLimit::crf_min && crf <= EncodeLimit::crf_max); + + if (codecCtx->codec_id == AV_CODEC_ID_VP8 || codecCtx->codec_id == AV_CODEC_ID_VP9) { + codecCtx->flags |= AV_CODEC_FLAG_QSCALE; + codecCtx->global_quality = FF_QP2LAMBDA * crf + 0.5; + auto quality = QString::number(crf, 'f', 2); + av_dict_set(&encodeOptions, "crf", quality.toUtf8().data(), 0); + } else if (encodeContext.encoderName.contains("nvenc")) { // nvidia编码器 + double adjustedQualityI = crf - 2; + double adjustedQualityB = crf + 2; + if (adjustedQualityB > EncodeLimit::crf_max) { + adjustedQualityB = EncodeLimit::crf_max; + } + + if (adjustedQualityI < EncodeLimit::crf_min) { + adjustedQualityI = EncodeLimit::crf_min; + } + + auto quality = QString::number(crf, 'f', 2); + auto qualityI = QString::number(adjustedQualityI, 'f', 2); + auto qualityB = QString::number(adjustedQualityB, 'f', 2); + + av_dict_set(&encodeOptions, "rc", "vbr", 0); + av_dict_set(&encodeOptions, "cq", quality.toUtf8().data(), 0); + + // further Advanced Quality Settings in Constant Quality Mode + av_dict_set(&encodeOptions, "init_qpP", quality.toUtf8().data(), 0); + av_dict_set(&encodeOptions, "init_qpB", qualityB.toUtf8().data(), 0); + av_dict_set(&encodeOptions, "init_qpI", qualityI.toUtf8().data(), 0); + } else if (encodeContext.encoderName.contains("vce")) { // amd vce编码器 + int maxQuality = EncodeLimit::crf_max; + double qualityOffsetThreshold = 8; + double qualityOffsetP = 2; + double qualityOffsetB; + double adjustedQualityP; + double adjustedQualityB; + + if (codecCtx->codec_id == AV_CODEC_ID_AV1) { + maxQuality = 255; + qualityOffsetThreshold = 32; + qualityOffsetP = 8; + } + + if (crf <= qualityOffsetThreshold) { + qualityOffsetP = crf / qualityOffsetThreshold * qualityOffsetP; + } + qualityOffsetB = qualityOffsetP * 2; + + adjustedQualityP = crf + qualityOffsetP; + adjustedQualityB = crf + qualityOffsetB; + if (adjustedQualityP > maxQuality) { + adjustedQualityP = maxQuality; + } + if (adjustedQualityB > maxQuality) { + adjustedQualityB = maxQuality; + } + + auto quality = QString::number(crf, 'f', 2); + auto qualityP = QString::number(adjustedQualityP, 'f', 2); + auto qualityB = QString::number(adjustedQualityB, 'f', 2); + + av_dict_set(&encodeOptions, "rc", "cqp", 0); + + av_dict_set(&encodeOptions, "qp_i", quality.toUtf8().data(), 0); + av_dict_set(&encodeOptions, "qp_p", qualityP.toUtf8().data(), 0); + // H.265 encoders do not support B frames + if (codecCtx->codec_id != AV_CODEC_ID_H265) { + av_dict_set(&encodeOptions, "qp_b", qualityB.toUtf8().data(), 0); + } + } else if (encodeContext.encoderName.contains("mf")) { // ffmpeg mf编码器 + + auto quality = QString::number(crf, 'f', 2); + av_dict_set(&encodeOptions, "rate_control", "quality", 0); + av_dict_set(&encodeOptions, "quality", quality.toUtf8().data(), 0); + av_dict_set(&encodeOptions, "hw_encoding", "1", 0); + } else { + codecCtx->flags |= AV_CODEC_FLAG_QSCALE; + codecCtx->global_quality = FF_QP2LAMBDA * crf + 0.5; + } + } + + void initAudioEncoderOptions(const EncodeContext &encodeContext) + { + Q_ASSERT(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO); + + codecCtx->global_quality = encodeContext.crf * FF_QP2LAMBDA; + codecCtx->flags |= AV_CODEC_FLAG_QSCALE; + if (encodeContext.encoderName.contains("fdk")) { + auto vbr = QString::asprintf("%.1g", encodeContext.crf); + av_dict_set(&encodeOptions, "vbr", vbr.toUtf8().data(), 0); + } + } + + void printIgnoredOptions() const + { + AVDictionaryEntry *dict = nullptr; + while ((dict = av_dict_get(encodeOptions, "", dict, AV_DICT_IGNORE_SUFFIX)) != nullptr) { + qWarning() << "Unknown avcodec option: " << dict->key; + } } CodecContext *q_ptr; @@ -66,13 +185,16 @@ class CodecContext::CodecContextPrivate QVector supported_samplerates{}; QVector supported_sample_fmts{}; QVector supported_ch_layouts{}; + QVector supported_profiles{}; + + AVDictionary *encodeOptions = nullptr; }; CodecContext::CodecContext(const AVCodec *codec, QObject *parent) : QObject(parent) , d_ptr(new CodecContextPrivate(this)) { - qInfo() << "AVCodec:" << codec->long_name; + qInfo() << "AVCodec:" << codec->name << " long name:" << codec->long_name; d_ptr->codecCtx = avcodec_alloc_context3(codec); d_ptr->init(); Q_ASSERT(d_ptr->codecCtx != nullptr); @@ -95,18 +217,17 @@ void CodecContext::copyToCodecParameters(CodecContext *dst) dstCodecCtx->bits_per_raw_sample = d_ptr->codecCtx->bits_per_raw_sample; dstCodecCtx->compression_level = d_ptr->codecCtx->compression_level; dstCodecCtx->gop_size = d_ptr->codecCtx->gop_size; - dstCodecCtx->profile = d_ptr->codecCtx->profile; + dstCodecCtx->global_quality = d_ptr->codecCtx->global_quality; + dst->setProfile(d_ptr->codecCtx->profile); switch (d_ptr->codecCtx->codec_type) { case AVMEDIA_TYPE_AUDIO: dst->setSampleRate(d_ptr->codecCtx->sample_rate); dst->setChLayout(d_ptr->codecCtx->ch_layout); /* take first format from list of supported formats */ - if (d_ptr->codecCtx->codec->sample_fmts != nullptr) { - dst->setSampleFmt(d_ptr->codecCtx->codec->sample_fmts[0]); - } else { - dst->setSampleFmt(d_ptr->codecCtx->sample_fmt); - } + dst->setSampleFmt(d_ptr->codecCtx->codec->sample_fmts != nullptr + ? d_ptr->codecCtx->codec->sample_fmts[0] + : d_ptr->codecCtx->sample_fmt); dstCodecCtx->time_base = AVRational{1, dstCodecCtx->sample_rate}; break; case AVMEDIA_TYPE_VIDEO: @@ -117,9 +238,9 @@ void CodecContext::copyToCodecParameters(CodecContext *dst) dst->setPixfmt(d_ptr->codecCtx->codec->pix_fmts != nullptr ? d_ptr->codecCtx->codec->pix_fmts[0] : d_ptr->codecCtx->pix_fmt); + dst->setFrameRate(d_ptr->codecCtx->framerate); /* video time_base can be set to whatever is handy and supported by encoder */ - dstCodecCtx->time_base = av_inv_q(d_ptr->codecCtx->framerate); - dstCodecCtx->framerate = d_ptr->codecCtx->framerate; + dstCodecCtx->time_base = av_inv_q(dstCodecCtx->framerate); dstCodecCtx->color_primaries = d_ptr->codecCtx->color_primaries; dstCodecCtx->color_trc = d_ptr->codecCtx->color_trc; dstCodecCtx->colorspace = d_ptr->codecCtx->colorspace; @@ -142,14 +263,30 @@ auto CodecContext::setParameters(const AVCodecParameters *par) -> bool ERROR_RETURN(ret) } +auto CodecContext::supportFrameRates() const -> QVector +{ + return d_ptr->supported_framerates; +} + +void CodecContext::setFrameRate(const AVRational &frameRate) +{ + if (d_ptr->supported_framerates.isEmpty()) { + d_ptr->codecCtx->framerate = frameRate; + return; + } + for (const auto &rate : std::as_const(d_ptr->supported_framerates)) { + if (compareAVRational(rate, frameRate)) { + d_ptr->codecCtx->framerate = rate; + return; + } + } + d_ptr->codecCtx->framerate = d_ptr->supported_framerates.first(); +} + void CodecContext::setThreadCount(int threadCount) { Q_ASSERT(d_ptr->codecCtx != nullptr); - if ((d_ptr->codecCtx->codec->capabilities & AV_CODEC_CAP_FRAME_THREADS) != 0) { - d_ptr->codecCtx->thread_type = FF_THREAD_FRAME; - } else if ((d_ptr->codecCtx->codec->capabilities & AV_CODEC_CAP_SLICE_THREADS) != 0) { - d_ptr->codecCtx->thread_type = FF_THREAD_SLICE; - } + d_ptr->codecCtx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE; d_ptr->codecCtx->thread_count = threadCount; } @@ -162,6 +299,11 @@ void CodecContext::setPixfmt(AVPixelFormat pixfmt) d_ptr->codecCtx->pix_fmt = d_ptr->supported_pix_fmts.first(); } +auto CodecContext::supportSampleRates() const -> QVector +{ + return d_ptr->supported_samplerates; +} + void CodecContext::setSampleRate(int sampleRate) { if (d_ptr->supported_samplerates.isEmpty() @@ -181,6 +323,63 @@ void CodecContext::setSampleFmt(AVSampleFormat sampleFmt) d_ptr->codecCtx->sample_fmt = d_ptr->supported_sample_fmts.first(); } +auto CodecContext::supportedProfiles() const -> QVector +{ + return d_ptr->supported_profiles; +} + +void CodecContext::setProfile(int profile) +{ + if (d_ptr->supported_profiles.isEmpty()) { + d_ptr->codecCtx->profile = profile; + return; + } + for (const auto &prof : std::as_const(d_ptr->supported_profiles)) { + if (prof.profile == profile) { + d_ptr->codecCtx->profile = profile; + return; + } + } + d_ptr->codecCtx->profile = d_ptr->supported_profiles.first().profile; +} + +auto CodecContext::supportedChLayouts() const -> QVector +{ + return d_ptr->supported_ch_layouts; +} + +void CodecContext::setEncodeParameters(const EncodeContext &encodeContext) +{ + setThreadCount(encodeContext.threadCount); + setProfile(encodeContext.profile); + + d_ptr->codecCtx->rc_min_rate = encodeContext.minBitrate; + d_ptr->codecCtx->rc_max_rate = encodeContext.maxBitrate; + d_ptr->codecCtx->bit_rate = encodeContext.bitrate; + + switch (encodeContext.mediaType) { + case AVMEDIA_TYPE_VIDEO: + setSize(encodeContext.size); + d_ptr->initVideoEncoderOptions(encodeContext); + break; + case AVMEDIA_TYPE_AUDIO: { + AVChannelLayout chLayout = {AV_CHANNEL_ORDER_UNSPEC}; + av_channel_layout_from_mask(&chLayout, static_cast(encodeContext.channel)); + setChLayout(chLayout); + d_ptr->initAudioEncoderOptions(encodeContext); + } break; + default: break; + } + // av_opt_set(d_ptr->codecCtx->priv_data, + // "preset", + // encodeContext.preset.toLocal8Bit().constData(), + // AV_OPT_SEARCH_CHILDREN); + // av_opt_set(d_ptr->codecCtx->priv_data, + // "tune", + // encodeContext.tune.toLocal8Bit().constData(), + // AV_OPT_SEARCH_CHILDREN); +} + void CodecContext::setChLayout(const AVChannelLayout &chLayout) { for (const auto &ch : std::as_const(d_ptr->supported_ch_layouts)) { @@ -190,9 +389,8 @@ void CodecContext::setChLayout(const AVChannelLayout &chLayout) } } if (d_ptr->supported_ch_layouts.isEmpty()) { - AVChannelLayout chLayout = {AV_CHANNEL_ORDER_UNSPEC}; - av_channel_layout_from_mask(&chLayout, static_cast(AV_CH_LAYOUT_STEREO)); - av_channel_layout_copy(&d_ptr->codecCtx->ch_layout, &chLayout); + av_channel_layout_from_mask(&d_ptr->codecCtx->ch_layout, + static_cast(AV_CH_LAYOUT_STEREO)); return; } av_channel_layout_copy(&d_ptr->codecCtx->ch_layout, &d_ptr->supported_ch_layouts.first()); @@ -210,11 +408,6 @@ void CodecContext::setSize(const QSize &size) } d_ptr->codecCtx->width = size.width(); d_ptr->codecCtx->height = size.height(); - // fix me to improve image quality - auto bit_rate = d_ptr->codecCtx->width * d_ptr->codecCtx->height * 4; - d_ptr->codecCtx->bit_rate = bit_rate; - d_ptr->codecCtx->rc_min_rate = bit_rate; - d_ptr->codecCtx->rc_max_rate = bit_rate; } auto CodecContext::size() const -> QSize @@ -222,15 +415,6 @@ auto CodecContext::size() const -> QSize return {d_ptr->codecCtx->width, d_ptr->codecCtx->height}; } -void CodecContext::setQuailty(int quailty) -{ - if (quailty < d_ptr->codecCtx->qmin) { - return; - } - d_ptr->codecCtx->flags |= AV_CODEC_FLAG_QSCALE; - d_ptr->codecCtx->global_quality = FF_QP2LAMBDA * quailty; -} - auto CodecContext::quantizer() const -> QPair { return {d_ptr->codecCtx->qmin, d_ptr->codecCtx->qmax}; @@ -246,46 +430,11 @@ auto CodecContext::supportSampleFmts() const -> QVector return d_ptr->supported_sample_fmts; } -void CodecContext::setMinBitrate(int64_t bitrate) -{ - Q_ASSERT(bitrate > 0); - d_ptr->codecCtx->rc_min_rate = bitrate; -} - -void CodecContext::setMaxBitrate(int64_t bitrate) -{ - qDebug() << bitrate << d_ptr->codecCtx->rc_min_rate; - Q_ASSERT(bitrate >= d_ptr->codecCtx->rc_min_rate); - d_ptr->codecCtx->rc_max_rate = bitrate; - d_ptr->codecCtx->bit_rate = bitrate; -} - -void CodecContext::setCrf(int crf) -{ - // 设置crf参数,范围是0-51,0是无损,23是默认值,51是最差质量 - Q_ASSERT(crf >= 0 && crf <= 51); - av_opt_set_double(d_ptr->codecCtx->priv_data, "crf", crf, 0); -} - -void CodecContext::setPreset(const QString &preset) -{ - av_opt_set(d_ptr->codecCtx->priv_data, "preset", preset.toLocal8Bit().constData(), 0); -} - -void CodecContext::setTune(const QString &tune) -{ - av_opt_set(d_ptr->codecCtx->priv_data, "tune", tune.toLocal8Bit().constData(), 0); -} - -void CodecContext::setProfile(const QString &profile) -{ - av_opt_set(d_ptr->codecCtx->priv_data, "profile", profile.toLocal8Bit().constData(), 0); -} - auto CodecContext::open() -> bool { Q_ASSERT(d_ptr->codecCtx != nullptr); - auto ret = avcodec_open2(d_ptr->codecCtx, nullptr, nullptr); + auto ret = avcodec_open2(d_ptr->codecCtx, nullptr, &d_ptr->encodeOptions); + d_ptr->printIgnoredOptions(); ERROR_RETURN(ret) } diff --git a/ffmpeg/codeccontext.h b/ffmpeg/codeccontext.h index c3c0a92..a7c5fa6 100644 --- a/ffmpeg/codeccontext.h +++ b/ffmpeg/codeccontext.h @@ -4,18 +4,15 @@ #include extern "C" { -#include -#include +#include } -struct AVCodecContext; struct AVCodecParameters; -struct AVRational; -struct AVCodec; -struct AVChannelLayout; +struct AVCodecContext; namespace Ffmpeg { +struct EncodeContext; class Subtitle; class Packet; class Frame; @@ -28,29 +25,33 @@ class CodecContext : public QObject void copyToCodecParameters(CodecContext *dst); auto setParameters(const AVCodecParameters *par) -> bool; + + [[nodiscard]] auto supportFrameRates() const -> QVector; + void setFrameRate(const AVRational &frameRate); + + [[nodiscard]] auto supportPixFmts() const -> QVector; void setPixfmt(AVPixelFormat pixfmt); + + [[nodiscard]] auto supportSampleRates() const -> QVector; void setSampleRate(int sampleRate); + + [[nodiscard]] auto supportSampleFmts() const -> QVector; void setSampleFmt(AVSampleFormat sampleFmt); - void setMinBitrate(int64_t bitrate); - void setMaxBitrate(int64_t bitrate); - void setCrf(int crf); - void setPreset(const QString &preset); - void setTune(const QString &tune); - void setProfile(const QString &profile); + [[nodiscard]] auto supportedProfiles() const -> QVector; + void setProfile(int profile); - void setChLayout(const AVChannelLayout &chLayout); + [[nodiscard]] auto supportedChLayouts() const -> QVector; [[nodiscard]] auto chLayout() const -> AVChannelLayout; + void setChLayout(const AVChannelLayout &chLayout); + + void setEncodeParameters(const EncodeContext &encodeContext); void setSize(const QSize &size); [[nodiscard]] auto size() const -> QSize; - void setQuailty(int quailty); [[nodiscard]] auto quantizer() const -> QPair; - [[nodiscard]] auto supportPixFmts() const -> QVector; - [[nodiscard]] auto supportSampleFmts() const -> QVector; - // Set before open, Soft solution is effective void setThreadCount(int threadCount); auto open() -> bool; diff --git a/ffmpeg/encodecontext.cc b/ffmpeg/encodecontext.cc new file mode 100644 index 0000000..495b98e --- /dev/null +++ b/ffmpeg/encodecontext.cc @@ -0,0 +1,3 @@ +#include "encodecontext.hpp" + +namespace Ffmpeg {} // namespace Ffmpeg diff --git a/ffmpeg/encodecontext.hpp b/ffmpeg/encodecontext.hpp new file mode 100644 index 0000000..3840c2b --- /dev/null +++ b/ffmpeg/encodecontext.hpp @@ -0,0 +1,67 @@ +#ifndef ENCODECONTEXT_HPP +#define ENCODECONTEXT_HPP + +#include "ffmepg_global.h" + +#include + +extern "C" { +#include +#include +} + +namespace Ffmpeg { + +namespace EncodeLimit { + +static const int crf_min = 0; +static const int crf_max = 51; + +static const QStringList presets = QStringList{"ultrafast", + "superfast", + "veryfast", + "faster", + "fast", + "medium", + "slow", + "slower", + "veryslow", + "placebo"}; +static const QStringList tunes = QStringList{"film", + "animation", + "grain", + "stillimage", + "psnr", + "ssim", + "fastdecode", + "zerolatency"}; + +} // namespace EncodeLimit +struct EncodeContext +{ + AVMediaType mediaType; + + QString encoderName; + + qint64 minBitrate = -1; + qint64 maxBitrate = -1; + qint64 bitrate = -1; + + int threadCount = -1; + + bool gpuDecode = true; + + int crf = 18; + + QString preset = "slow"; + QString tune = "film"; + int profile = 0; + + QSize size = {-1, -1}; + + AVChannel channel; +}; + +} // namespace Ffmpeg + +#endif // ENCODECONTEXT_HPP diff --git a/ffmpeg/event/errorevent.hpp b/ffmpeg/event/errorevent.hpp index 87dd008..54604cf 100644 --- a/ffmpeg/event/errorevent.hpp +++ b/ffmpeg/event/errorevent.hpp @@ -6,15 +6,15 @@ namespace Ffmpeg { -class FFMPEG_EXPORT ErrorEvent : public PropertyChangeEvent +class FFMPEG_EXPORT AVErrorEvent : public PropertyChangeEvent { public: - explicit ErrorEvent(const AVError &error, QObject *parent = nullptr) + explicit AVErrorEvent(const AVError &error, QObject *parent = nullptr) : PropertyChangeEvent(parent) , m_error(error) {} - [[nodiscard]] auto type() const -> EventType override { return Error; } + [[nodiscard]] auto type() const -> EventType override { return EventType::AVError; } void setError(const AVError &error) { m_error = error; } [[nodiscard]] auto error() const -> AVError { return m_error; } @@ -23,4 +23,21 @@ class FFMPEG_EXPORT ErrorEvent : public PropertyChangeEvent AVError m_error; }; +class FFMPEG_EXPORT ErrorEvent : public PropertyChangeEvent +{ +public: + explicit ErrorEvent(const QString &error, QObject *parent = nullptr) + : PropertyChangeEvent(parent) + , m_error(error) + {} + + [[nodiscard]] auto type() const -> EventType override { return EventType::Error; } + + void setError(const QString &error) { m_error = error; } + [[nodiscard]] auto error() const -> QString { return m_error; } + +private: + QString m_error; +}; + } // namespace Ffmpeg diff --git a/ffmpeg/event/event.hpp b/ffmpeg/event/event.hpp index 7682e85..a4a6694 100644 --- a/ffmpeg/event/event.hpp +++ b/ffmpeg/event/event.hpp @@ -10,7 +10,7 @@ class FFMPEG_EXPORT PropertyChangeEvent : public QObject { Q_OBJECT public: - enum EventType { + enum class EventType { None, Duration, Position, @@ -19,6 +19,7 @@ class FFMPEG_EXPORT PropertyChangeEvent : public QObject CacheSpeed, SeekChanged, PreviewFramesChanged, + AVError, Error }; Q_ENUM(EventType); @@ -35,7 +36,7 @@ class FFMPEG_EXPORT PropertyChangeEvent : public QObject [[nodiscard]] virtual auto type() const -> EventType { return m_type; } private: - EventType m_type = None; + EventType m_type = EventType::None; }; using PropertyChangeEventPtr = QSharedPointer; @@ -44,7 +45,7 @@ class FFMPEG_EXPORT Event : public QObject { Q_OBJECT public: - enum EventType { + enum class EventType { None, OpenMedia, CloseMedia, @@ -66,7 +67,7 @@ class FFMPEG_EXPORT Event : public QObject auto operator==(const Event &other) const -> bool { return type() == other.type(); } auto operator!=(const Event &other) const -> bool { return !(*this == other); } - [[nodiscard]] virtual auto type() const -> EventType { return None; } + [[nodiscard]] virtual auto type() const -> EventType { return EventType::None; } }; using EventPtr = QSharedPointer; diff --git a/ffmpeg/event/trackevent.hpp b/ffmpeg/event/trackevent.hpp index b6e535a..c105a7e 100644 --- a/ffmpeg/event/trackevent.hpp +++ b/ffmpeg/event/trackevent.hpp @@ -14,7 +14,7 @@ class FFMPEG_EXPORT MediaTrackEvent : public PropertyChangeEvent , m_tracks(tracks) {} - [[nodiscard]] auto type() const -> EventType override { return MediaTrack; } + [[nodiscard]] auto type() const -> EventType override { return EventType::MediaTrack; } void setTracks(const QVector &tracks) { m_tracks = tracks; } [[nodiscard]] auto tracks() const -> QVector { return m_tracks; } @@ -31,7 +31,8 @@ class FFMPEG_EXPORT SelectedMediaTrackEvent : public Event , m_index(index) , m_type(type) { - Q_ASSERT(type == AudioTarck || type == VideoTrack || type == SubtitleTrack); + Q_ASSERT(type == EventType::AudioTarck || type == EventType::VideoTrack + || type == EventType::SubtitleTrack); } [[nodiscard]] auto type() const -> EventType override { return m_type; } diff --git a/ffmpeg/ffmpeg.pro b/ffmpeg/ffmpeg.pro index 52b6ae0..1bbc9bc 100644 --- a/ffmpeg/ffmpeg.pro +++ b/ffmpeg/ffmpeg.pro @@ -27,6 +27,7 @@ SOURCES += \ codeccontext.cpp \ colorutils.cc \ decoder.cc \ + encodecontext.cc \ ffmpegutils.cc \ formatcontext.cpp \ frame.cc \ @@ -57,6 +58,7 @@ HEADERS += \ codeccontext.h \ colorutils.hpp \ decoder.h \ + encodecontext.hpp \ ffmepg_global.h \ ffmpegutils.hpp \ formatcontext.h \ diff --git a/ffmpeg/ffmpegutils.cc b/ffmpeg/ffmpegutils.cc index ccc43a0..0046a5b 100644 --- a/ffmpeg/ffmpegutils.cc +++ b/ffmpeg/ffmpegutils.cc @@ -1,4 +1,5 @@ #include "ffmpegutils.hpp" +#include "audioframeconverter.h" #include "avcontextinfo.h" #include "codeccontext.h" #include "formatcontext.h" @@ -151,19 +152,9 @@ auto compareAVRational(const AVRational &a, const AVRational &b) -> bool return a.den == b.den && a.num == b.num; } -auto getCodecQuantizer(const QString &codecName) -> QPair +auto getCodecsInfo(AVMediaType mediaType, bool encoder) -> CodecInfos { - QScopedPointer contextInfoPtr(new AVContextInfo); - if (!contextInfoPtr->initEncoder(codecName)) { - return {-1, -1}; - } - auto quantizer = contextInfoPtr->codecCtx()->quantizer(); - return quantizer; -} - -auto getCurrentSupportCodecs(AVMediaType mediaType, bool encoder) -> QMap -{ - QMap codecnames; + CodecInfos codecInfos; const AVCodecDescriptor **codecs{}; auto nb_codecs = get_codecs_sorted(&codecs); @@ -182,7 +173,8 @@ auto getCurrentSupportCodecs(AVMediaType mediaType, bool encoder) -> QMapname; - codecnames.insert(codec->id, codec->name); + codecInfos.append( + {QString::fromUtf8(name), QString::fromUtf8(desc->long_name), codec->id}); auto str = QString::asprintf("%-20s %s", name, codec->long_name != nullptr ? codec->long_name : ""); @@ -193,7 +185,7 @@ auto getCurrentSupportCodecs(AVMediaType mediaType, bool encoder) -> QMap Metadatas @@ -206,4 +198,37 @@ auto getMetaDatas(AVDictionary *metadata) -> Metadatas return metadatas; } +ChLayouts getChLayouts(const QVector &channelLayout) +{ + static ChLayouts s_chLayouts; + if (s_chLayouts.isEmpty()) { + QVector channels{AV_CH_LAYOUT_MONO, + AV_CH_LAYOUT_STEREO, + AV_CH_LAYOUT_2_1, + AV_CH_LAYOUT_QUAD, + AV_CH_LAYOUT_5POINT0_BACK, + AV_CH_LAYOUT_6POINT0_FRONT, + AV_CH_LAYOUT_6POINT1, + AV_CH_LAYOUT_7POINT1}; + + for (const auto &channel : std::as_const(channels)) { + auto ch = static_cast(channel); + // char name[64] = {0}; + // char description[128] = {0}; + // av_channel_name(name, sizeof(name), ch); + // av_channel_description(description, sizeof(description), ch); + // auto text = QString("%1 (%2)").arg(name).arg(description); + AVChannelLayout chLayout = {AV_CHANNEL_ORDER_UNSPEC}; + av_channel_layout_from_mask(&chLayout, channel); + s_chLayouts.append({ch, getAVChannelLayoutDescribe(chLayout)}); + } + } + + ChLayouts chLayouts; + for (const auto &ch : std::as_const(channelLayout)) { + chLayouts.append({static_cast(ch.u.mask), getAVChannelLayoutDescribe(ch)}); + } + return chLayouts.isEmpty() ? s_chLayouts : chLayouts; +} + } // namespace Ffmpeg diff --git a/ffmpeg/ffmpegutils.hpp b/ffmpeg/ffmpegutils.hpp index 707dac9..e95e7ec 100644 --- a/ffmpeg/ffmpegutils.hpp +++ b/ffmpeg/ffmpegutils.hpp @@ -3,10 +3,11 @@ #include "ffmepg_global.h" +#include #include extern "C" { -#include +#include #include } @@ -34,11 +35,31 @@ auto compareAVRational(const AVRational &a, const AVRational &b) -> bool; auto getMetaDatas(AVDictionary *metadata) -> Metadatas; -auto FFMPEG_EXPORT getCodecQuantizer(const QString &codecname) -> QPair; +struct CodecInfo +{ + auto operator==(const CodecInfo &other) const -> bool { return codecId == other.codecId; } + auto operator!=(const CodecInfo &other) const -> bool { return !(*this == other); } -auto FFMPEG_EXPORT getCurrentSupportCodecs(AVMediaType mediaType, bool encoder) - -> QMap; + QString name; + QString longName; + enum AVCodecID codecId; +}; + +using CodecInfos = QVector; + +auto FFMPEG_EXPORT getCodecsInfo(AVMediaType mediaType, bool encoder) -> CodecInfos; + +struct FFMPEG_EXPORT ChLayout +{ + AVChannel channel; + QString channelName; +}; +using ChLayouts = QVector; + +auto FFMPEG_EXPORT getChLayouts(const QVector &channelLayout) -> ChLayouts; } // namespace Ffmpeg +Q_DECLARE_METATYPE(Ffmpeg::CodecInfo); + #endif // FFMPEGUTILS_HPP diff --git a/ffmpeg/filter/filtergraph.cc b/ffmpeg/filter/filtergraph.cc index 1e7d637..7b46148 100644 --- a/ffmpeg/filter/filtergraph.cc +++ b/ffmpeg/filter/filtergraph.cc @@ -3,6 +3,8 @@ #include +#include + extern "C" { #include } diff --git a/ffmpeg/frame.cc b/ffmpeg/frame.cc index ab7862b..97ffddc 100644 --- a/ffmpeg/frame.cc +++ b/ffmpeg/frame.cc @@ -3,6 +3,7 @@ #include "ffmpegutils.hpp" #include "videoformat.hpp" +#include #include extern "C" { diff --git a/ffmpeg/mediainfo.cc b/ffmpeg/mediainfo.cc index d6530d9..3b50d4d 100644 --- a/ffmpeg/mediainfo.cc +++ b/ffmpeg/mediainfo.cc @@ -123,13 +123,19 @@ auto StreamInfo::info() const -> QString if (bitRate > 0) { text += "-" + QString::number(bitRate); } - text += "-" + format; + if (!format.isEmpty()) { + text += "-" + format; + } switch (mediaType) { - case AVMEDIA_TYPE_AUDIO: text += "-" + chLayout + "-" + QString::number(sampleRate); break; + case AVMEDIA_TYPE_AUDIO: + text += QString("-%1-%2hz").arg(chLayout, QString::number(sampleRate)); + break; case AVMEDIA_TYPE_VIDEO: - text += "-" + QString::number(frameRate) + "-" + QString::number(size.width()) + "x" - + QString::number(size.height()); + text += QString("-%1fps-[%2x%3]") + .arg(QString::number(frameRate), + QString::number(size.width()), + QString::number(size.height())); break; default: break; } @@ -175,9 +181,10 @@ MediaInfo::MediaInfo(AVFormatContext *formatCtx) url = formatCtx->url; startTime = formatCtx->start_time; startTimeText = QTime::fromMSecsSinceStartOfDay(formatCtx->start_time / 1000) - .toString("hh:mm:ss.zzz"); + .toString("hh:mm:ss.zzz"); duration = formatCtx->duration; - durationText = QTime::fromMSecsSinceStartOfDay(formatCtx->duration / 1000).toString("hh:mm:ss.zzz"); + durationText = QTime::fromMSecsSinceStartOfDay(formatCtx->duration / 1000) + .toString("hh:mm:ss.zzz"); bitRate = formatCtx->bit_rate; size = duration / AV_TIME_BASE * bitRate / 8; diff --git a/ffmpeg/player.cpp b/ffmpeg/player.cpp index 2a66578..d1fa127 100644 --- a/ffmpeg/player.cpp +++ b/ffmpeg/player.cpp @@ -47,7 +47,7 @@ class Player::PlayerPrivate &AVErrorManager::error, q_ptr, [this](const AVError &error) { - addPropertyChangeEvent(new ErrorEvent(error)); + addPropertyChangeEvent(new AVErrorEvent(error)); }); } diff --git a/ffmpeg/transcoder.cc b/ffmpeg/transcoder.cc index 002a7ec..ea848e0 100644 --- a/ffmpeg/transcoder.cc +++ b/ffmpeg/transcoder.cc @@ -4,6 +4,7 @@ #include "avcontextinfo.h" #include "averrormanager.hpp" #include "codeccontext.h" +#include "encodecontext.hpp" #include "ffmpegutils.hpp" #include "formatcontext.h" #include "frame.hpp" @@ -40,17 +41,20 @@ class Transcoder::TranscoderPrivate threadPool = new QThreadPool(q_ptr); threadPool->setMaxThreadCount(2); + videoEncodeContext.mediaType = AVMEDIA_TYPE_VIDEO; + audioEncodeContext.mediaType = AVMEDIA_TYPE_AUDIO; + QObject::connect(AVErrorManager::instance(), &AVErrorManager::error, q_ptr, [this](const AVError &error) { - addPropertyChangeEvent(new ErrorEvent(error)); + addPropertyChangeEvent(new AVErrorEvent(error)); }); } ~TranscoderPrivate() { reset(); } - auto openInputFile() -> bool + auto openInputFile(bool eventChanged) -> bool { Q_ASSERT(!inFilePath.isEmpty()); auto ret = inFormatContext->openFilePath(inFilePath); @@ -81,19 +85,26 @@ class Transcoder::TranscoderPrivate return false; } if (codec_type == AVMEDIA_TYPE_VIDEO || codec_type == AVMEDIA_TYPE_AUDIO) { - contextInfoPtr->openCodec(useGpuDecode ? AVContextInfo::GpuDecode - : AVContextInfo::NotUseGpu); + if (!contextInfoPtr->openCodec(videoEncodeContext.gpuDecode + ? AVContextInfo::GpuDecode + : AVContextInfo::NotUseGpu)) { + return false; + } } transContext->decContextInfoPtr = contextInfoPtr; } inFormatContext->dumpFormat(); - auto tracks = inFormatContext->audioTracks(); - tracks.append(inFormatContext->videoTracks()); - tracks.append(inFormatContext->subtitleTracks()); - tracks.append(inFormatContext->attachmentTracks()); - addPropertyChangeEvent(new MediaTrackEvent(tracks)); - addPropertyChangeEvent(new DurationEvent(inFormatContext->duration())); + range = {0, inFormatContext->duration()}; + + if (eventChanged) { + auto tracks = inFormatContext->audioTracks(); + tracks.append(inFormatContext->videoTracks()); + tracks.append(inFormatContext->subtitleTracks()); + tracks.append(inFormatContext->attachmentTracks()); + addPropertyChangeEvent(new MediaTrackEvent(tracks)); + addPropertyChangeEvent(new DurationEvent(inFormatContext->duration())); + } return true; } @@ -127,11 +138,12 @@ class Transcoder::TranscoderPrivate return ret != 0; } stream->time_base = inStream->time_base; - stream->codecpar->width = inStream->codecpar->width > 0 ? inStream->codecpar->width - : size.width(); + stream->codecpar->width = inStream->codecpar->width > 0 + ? inStream->codecpar->width + : videoEncodeContext.size.width(); stream->codecpar->height = inStream->codecpar->height > 0 ? inStream->codecpar->height - : size.height(); + : videoEncodeContext.size.width(); continue; } stream->codecpar->codec_type = decContextInfo->mediaType(); @@ -142,30 +154,24 @@ class Transcoder::TranscoderPrivate contextInfoPtr->setIndex(i); contextInfoPtr->setStream(stream); contextInfoPtr->initEncoder(decContextInfo->mediaType() == AVMEDIA_TYPE_AUDIO - ? audioEncoderName - : videoEncoderName); + ? audioEncodeContext.encoderName + : videoEncodeContext.encoderName); //contextInfoPtr->initEncoder(decContextInfo->codecCtx()->avCodecCtx()->codec_id); auto *codecCtx = contextInfoPtr->codecCtx(); auto *avCodecCtx = codecCtx->avCodecCtx(); decContextInfo->codecCtx()->copyToCodecParameters(codecCtx); // ffmpeg example transcoding.c ? framerate, sample_rate codecCtx->avCodecCtx()->time_base = decContextInfo->timebase(); - codecCtx->setQuailty(quailty); - codecCtx->setCrf(crf); - codecCtx->setPreset(preset); - codecCtx->setTune(tune); - if (decContextInfo->mediaType() == AVMEDIA_TYPE_VIDEO) { - codecCtx->setSize(size); - codecCtx->setMinBitrate(minBitrate); - codecCtx->setMaxBitrate(maxBitrate); - codecCtx->setProfile(profile); - } + codecCtx->setEncodeParameters(decContextInfo->mediaType() == AVMEDIA_TYPE_AUDIO + ? audioEncodeContext + : videoEncodeContext); if ((outFormatContext->avFormatContext()->oformat->flags & AVFMT_GLOBALHEADER) != 0) { avCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } - contextInfoPtr->openCodec(useGpuEncode ? AVContextInfo::GpuEncode - : AVContextInfo::NotUseGpu); + if (!contextInfoPtr->openCodec(AVContextInfo::GpuEncode)) { + return false; + } auto ret = avcodec_parameters_from_context(stream->codecpar, contextInfoPtr->codecCtx()->avCodecCtx()); if (ret < 0) { @@ -205,10 +211,10 @@ class Transcoder::TranscoderPrivate } QString filter_spec; if (codec_type == AVMEDIA_TYPE_VIDEO) { - if (size.isValid()) { // "scale=320:240" + if (videoEncodeContext.size.isValid()) { // "scale=320:240" filter_spec = QString("scale=%1:%2") - .arg(QString::number(size.width()), - QString::number(size.height())); + .arg(QString::number(videoEncodeContext.size.width()), + QString::number(videoEncodeContext.size.height())); } if (!subtitleFilename.isEmpty()) { // "subtitles=filename=..." burn subtitle into video @@ -217,8 +223,8 @@ class Transcoder::TranscoderPrivate } filter_spec += QString("subtitles=filename='%1':original_size=%2x%3") .arg(subtitleFilename, - QString::number(size.width()), - QString::number(size.height())); + QString::number(videoEncodeContext.size.width()), + QString::number(videoEncodeContext.size.height())); } if (filter_spec.isEmpty()) { filter_spec = "null"; @@ -227,7 +233,7 @@ class Transcoder::TranscoderPrivate } else { filter_spec = "anull"; } - transcodeCtx->init_filter(filter_spec, frame); + transcodeCtx->initFilter(filter_spec, frame); } void initAudioFifo() const @@ -388,7 +394,6 @@ class Transcoder::TranscoderPrivate void loop() { - auto duration = inFormatContext->duration(); while (runing.load()) { PacketPtr packetPtr(new Packet); if (!inFormatContext->readFrame(packetPtr.get())) { @@ -481,39 +486,11 @@ class Transcoder::TranscoderPrivate FormatContext *inFormatContext; FormatContext *outFormatContext; QVector transcodeContexts{}; - QString audioEncoderName; - QString videoEncoderName; - QSize size = QSize(-1, -1); QString subtitleFilename; - int quailty = -1; - int64_t minBitrate = -1; - int64_t maxBitrate = -1; - int crf = 18; - QStringList presets{"ultrafast", - "superfast", - "veryfast", - "faster", - "fast", - "medium", - "slow", - "slower", - "veryslow", - "placebo"}; - QString preset = "medium"; - QStringList tunes{"film", - "animation", - "grain", - "stillimage", - "psnr", - "ssim", - "fastdecode", - "zerolatency"}; - QString tune = "film"; - QStringList profiles{"baseline", "extended", "main", "high"}; - QString profile = "main"; - - bool useGpuDecode = false; - bool useGpuEncode = false; + + EncodeContext videoEncodeContext; + EncodeContext audioEncodeContext; + QPair range; std::atomic_bool runing = true; QScopedPointer fpsPtr; @@ -538,16 +515,6 @@ Transcoder::~Transcoder() stopTranscode(); } -void Transcoder::setUseGpuDecode(bool use) -{ - d_ptr->useGpuDecode = use; -} - -void Transcoder::setUseGpuEncode(bool use) -{ - d_ptr->useGpuEncode = use; -} - void Transcoder::setInFilePath(const QString &filePath) { d_ptr->inFilePath = filePath; @@ -556,7 +523,7 @@ void Transcoder::setInFilePath(const QString &filePath) auto Transcoder::parseInputFile() -> bool { d_ptr->reset(); - return d_ptr->openInputFile(); + return d_ptr->openInputFile(true); } auto Transcoder::duration() const -> qint64 @@ -581,7 +548,7 @@ void Transcoder::setPreviewFrames(const std::vector> &fram [=] { d_ptr->previewFrames = framePtrs; auto *propertChangeEvent = new PropertyChangeEvent; - propertChangeEvent->setType(PropertyChangeEvent::PreviewFramesChanged); + propertChangeEvent->setType(PropertyChangeEvent::EventType::PreviewFramesChanged); d_ptr->addPropertyChangeEvent(propertChangeEvent); }, Qt::QueuedConnection); @@ -597,29 +564,19 @@ void Transcoder::setOutFilePath(const QString &filepath) d_ptr->outFilepath = filepath; } -void Transcoder::setAudioEncodecID(AVCodecID codecID) -{ - d_ptr->audioEncoderName = avcodec_get_name(codecID); -} - -void Transcoder::setVideoEnCodecID(AVCodecID codecID) -{ - d_ptr->videoEncoderName = avcodec_get_name(codecID); -} - -void Transcoder::setAudioEncodecName(const QString &name) +void Transcoder::setVideoEncodeContext(const EncodeContext &encodeContext) { - d_ptr->audioEncoderName = name; + d_ptr->videoEncodeContext = encodeContext; } -void Transcoder::setVideoEncodecName(const QString &name) +void Transcoder::setAudioEncodeContext(const EncodeContext &encodeContext) { - d_ptr->videoEncoderName = name; + d_ptr->audioEncodeContext = encodeContext; } -void Transcoder::setSize(const QSize &size) +void Transcoder::setRange(const QPair &range) { - d_ptr->size = size; + d_ptr->range = range; } void Transcoder::setSubtitleFilename(const QString &filename) @@ -634,74 +591,6 @@ void Transcoder::setSubtitleFilename(const QString &filename) } } -void Transcoder::setQuailty(int quailty) -{ - d_ptr->quailty = quailty; -} - -void Transcoder::setMinBitrate(int64_t bitrate) -{ - d_ptr->minBitrate = bitrate; -} - -void Transcoder::setMaxBitrate(int64_t bitrate) -{ - d_ptr->maxBitrate = bitrate; -} - -void Transcoder::setCrf(int crf) -{ - d_ptr->crf = crf; -} - -void Transcoder::setPreset(const QString &preset) -{ - Q_ASSERT(d_ptr->presets.contains(preset)); - d_ptr->preset = preset; -} - -auto Transcoder::preset() const -> QString -{ - return d_ptr->preset; -} - -auto Transcoder::presets() const -> QStringList -{ - return d_ptr->presets; -} - -void Transcoder::setTune(const QString &tune) -{ - Q_ASSERT(d_ptr->tunes.contains(tune)); - d_ptr->tune = tune; -} - -auto Transcoder::tune() const -> QString -{ - return d_ptr->tune; -} - -auto Transcoder::tunes() const -> QStringList -{ - return d_ptr->tunes; -} - -void Transcoder::setProfile(const QString &profile) -{ - Q_ASSERT(d_ptr->profiles.contains(profile)); - d_ptr->profile = profile; -} - -auto Transcoder::profile() const -> QString -{ - return d_ptr->profile; -} - -auto Transcoder::profiles() const -> QStringList -{ - return d_ptr->profiles; -} - void Transcoder::startTranscode() { stopTranscode(); @@ -750,19 +639,21 @@ void Transcoder::run() timer.start(); qInfo() << "Start Transcoding"; d_ptr->reset(); - if (!d_ptr->openInputFile()) { - qWarning() << "Open input file failed!"; + if (!d_ptr->openInputFile(false)) { + d_ptr->addPropertyChangeEvent(new ErrorEvent(tr("Open input file failed!"))); return; } if (!d_ptr->openOutputFile()) { - qWarning() << "Open ouput file failed!"; + d_ptr->addPropertyChangeEvent(new ErrorEvent(tr("Open ouput file failed!"))); return; } d_ptr->initAudioFifo(); d_ptr->loop(); d_ptr->cleanup(); - qInfo() << "Finish Transcoding: " - << QTime::fromMSecsSinceStartOfDay(timer.elapsed()).toString("hh:mm:ss.zzz"); + + auto text = tr("Finish Transcoding: %1.") + .arg(QTime::fromMSecsSinceStartOfDay(timer.elapsed()).toString("hh:mm:ss.zzz")); + qInfo() << text; } } // namespace Ffmpeg diff --git a/ffmpeg/transcoder.hpp b/ffmpeg/transcoder.hpp index 556529b..bda9fd2 100644 --- a/ffmpeg/transcoder.hpp +++ b/ffmpeg/transcoder.hpp @@ -13,6 +13,7 @@ extern "C" { namespace Ffmpeg { +struct EncodeContext; class AVError; class Frame; class FFMPEG_EXPORT Transcoder : public QThread @@ -22,9 +23,6 @@ class FFMPEG_EXPORT Transcoder : public QThread explicit Transcoder(QObject *parent = nullptr); ~Transcoder() override; - void setUseGpuDecode(bool use); - void setUseGpuEncode(bool use); - void setInFilePath(const QString &filePath); auto parseInputFile() -> bool; @@ -34,32 +32,14 @@ class FFMPEG_EXPORT Transcoder : public QThread void setPreviewFrames(const std::vector> &framePtrs); [[nodiscard]] auto previewFrames() const -> std::vector>; - void setOutFilePath(const QString &filepath); - - void setAudioEncodecID(AVCodecID codecID); - void setVideoEnCodecID(AVCodecID codecID); - void setAudioEncodecName(const QString &name); - void setVideoEncodecName(const QString &name); - - void setSize(const QSize &size); - void setSubtitleFilename(const QString &filename); - - void setQuailty(int quailty); - void setMinBitrate(int64_t bitrate); - void setMaxBitrate(int64_t bitrate); - void setCrf(int crf); + void setVideoEncodeContext(const EncodeContext &encodeContext); + void setAudioEncodeContext(const EncodeContext &encodeContext); - void setPreset(const QString &preset); - [[nodiscard]] auto preset() const -> QString; - [[nodiscard]] auto presets() const -> QStringList; + void setRange(const QPair &range); - void setTune(const QString &tune); - [[nodiscard]] auto tune() const -> QString; - [[nodiscard]] auto tunes() const -> QStringList; + void setOutFilePath(const QString &filepath); - void setProfile(const QString &profile); - [[nodiscard]] auto profile() const -> QString; - [[nodiscard]] auto profiles() const -> QStringList; + void setSubtitleFilename(const QString &filename); void startTranscode(); void stopTranscode(); diff --git a/ffmpeg/transcodercontext.cc b/ffmpeg/transcodercontext.cc index f795476..dfa1ba1 100644 --- a/ffmpeg/transcodercontext.cc +++ b/ffmpeg/transcodercontext.cc @@ -15,7 +15,7 @@ extern "C" { namespace Ffmpeg { -auto TranscoderContext::init_filter(const QString &filter_spec, Frame *frame) const -> bool +auto TranscoderContext::initFilter(const QString &filter_spec, Frame *frame) const -> bool { if (filterPtr->isInitialized()) { return true; diff --git a/ffmpeg/transcodercontext.hpp b/ffmpeg/transcodercontext.hpp index a4ac714..6b5a6b2 100644 --- a/ffmpeg/transcodercontext.hpp +++ b/ffmpeg/transcodercontext.hpp @@ -12,7 +12,7 @@ class AudioFifo; struct TranscoderContext { - auto init_filter(const QString &filter_spec, Frame *frame) const -> bool; + auto initFilter(const QString &filter_spec, Frame *frame) const -> bool; QSharedPointer decContextInfoPtr; QSharedPointer encContextInfoPtr;