diff --git a/README.md b/README.md index ad538c8..b8f21d9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ### 需要一个强大的opengl和vulkan yuv渲染模块 -1. Opengl中的shader有太多if else导致GPU空跑,影响GPU解码和av_hwframe_transfer_data速度,这个现象在4K视频图像上尤为明显; +1. Opengl的片段着色器目前支持的图像格式有限; 2. 在WidgetRender中,尽可能使用QImage::Format_RGB32和QImage::Format_ARGB32_Premultiplied图像格式。如下原因: 1. Avoid most rendering directly to most of these formats using QPainter. Rendering is best optimized to the Format_RGB32 and Format_ARGB32_Premultiplied formats, and secondarily for rendering to the Format_RGB16, Format_RGBX8888, Format_RGBA8888_Premultiplied, Format_RGBX64 and Format_RGBA64_Premultiplied formats. @@ -19,22 +19,62 @@ #### 2. 非opengl渲染的情况下,又该怎么样添加filter实现图像补偿? -1. 现在对AVCOL_TRC_SMPTE2084调整了对比度,饱和度,亮度;效果并不是很好。 -2. 如果不调整,就跟ffplay输出图像效果一致,整体颜色偏暗。Netflix的视频,视频开头的N,显示的颜色偏暗黄色。 - -```bash -contrast = 1.4; -saturation = 0.9; -brightness = 0; -``` - -3. 参考[MPV video_shaders](https://github.com/mpv-player/mpv/blob/master/video/out/gpu/video_shaders.c#L341),效果也不是很好;应该是哪里有遗漏。 +1. 参考[MPV video_shaders](https://github.com/mpv-player/mpv/blob/master/video/out/gpu/video_shaders.c#L341),效果也不是很好;应该是哪里有遗漏。 ```cpp void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); ``` +2. 在NV12的shader上,参考MPV实现的对SMPTE2084进行图像调整shader + +```glsl +#version 330 core + +in vec2 TexCord; // 纹理坐标 +out vec4 FragColor; // 输出颜色 + +uniform sampler2D tex_y; +uniform sampler2D tex_uv; + +uniform vec3 offset; +uniform mat3 colorConversion; + +const float PQ_M1 = 2610. / 4096 * 1. / 4, PQ_M2 = 2523. / 4096 * 128, PQ_C1 = 3424. / 4096, + PQ_C2 = 2413. / 4096 * 32, PQ_C3 = 2392. / 4096 * 32; + +#define MP_REF_WHITE 203.0 +#define MP_REF_WHITE_HLG 3.17955 + +void main() +{ + vec3 yuv; + vec3 rgb; + + yuv.x = texture(tex_y, TexCord).r; + yuv.yz = texture(tex_uv, TexCord).rg; + + yuv += offset; + rgb = yuv * colorConversion; + + vec4 color = vec4(rgb, 1.0); + // ------------------ + color.rgb = clamp(color.rgb, 0.0, 1.0); + color.rgb = pow(color.rgb, vec3(1.0 / PQ_M2)); + color.rgb = max(color.rgb - vec3(PQ_C1), vec3(0.0)) / (vec3(PQ_C2) - vec3(PQ_C3) * color.rgb); + color.rgb = pow(color.rgb, vec3(1.0 / PQ_M1)); + // ------------------ + color.rgb = clamp(color.rgb, 0.0, 1.0); + color.rgb = pow(color.rgb, vec3(PQ_M1)); + color.rgb = (vec3(PQ_C1) + vec3(PQ_C2) * color.rgb) / (vec3(1.0) + vec3(PQ_C3) * color.rgb); + color.rgb = pow(color.rgb, vec3(PQ_M2)); + // ------------------ + rgb = color.rgb; + + FragColor = vec4(rgb, 1.0); +} +``` + ### OpenGL 渲染图像,怎么实现画质增强的效果? ### Ffmpeg(5.0)在解码字幕与4.4.3不太一样 diff --git a/examples/player/CMakeLists.txt b/examples/player/CMakeLists.txt index 3ddccad..392ac7e 100644 --- a/examples/player/CMakeLists.txt +++ b/examples/player/CMakeLists.txt @@ -1,4 +1,6 @@ set(PROJECT_SOURCES + colorspacedialog.cc + colorspacedialog.hpp controlwidget.cc controlwidget.hpp main.cpp diff --git a/examples/player/colorspacedialog.cc b/examples/player/colorspacedialog.cc new file mode 100644 index 0000000..b4fb357 --- /dev/null +++ b/examples/player/colorspacedialog.cc @@ -0,0 +1,194 @@ +#include "colorspacedialog.hpp" +#include "slider.h" + +#include + +void setBlockValue(QSpinBox *spinBox, int value) +{ + spinBox->blockSignals(true); + spinBox->setValue(value); + spinBox->blockSignals(false); +} + +void setBlockValue(QSlider *slider, int value) +{ + slider->blockSignals(true); + slider->setValue(value); + slider->blockSignals(false); +} + +class ColorSpaceDialog::ColorSpaceDialogPrivate +{ +public: + explicit ColorSpaceDialogPrivate(ColorSpaceDialog *q) + : q_ptr(q) + { + Ffmpeg::ColorSpaceTrc colorTrc; + + contrastSlider = new Slider(q_ptr); + contrastSlider->setRange(colorTrc.contrast_min * multiple, colorTrc.contrast_max * multiple); + contrastSlider->setValue(colorTrc.contrast * multiple); + saturationSlider = new Slider(q_ptr); + saturationSlider->setRange(colorTrc.saturation_min * multiple, + colorTrc.saturation_max * multiple); + saturationSlider->setValue(colorTrc.saturation * multiple); + brightnessSlider = new Slider(q_ptr); + brightnessSlider->setRange(colorTrc.brightness_min * multiple, + colorTrc.brightness_max * multiple); + brightnessSlider->setValue(colorTrc.brightness * multiple); + + contrastSpinBox = new QSpinBox(q_ptr); + contrastSpinBox->setKeyboardTracking(false); + contrastSpinBox->setRange(colorTrc.contrast_min * multiple, + colorTrc.contrast_max * multiple); + contrastSpinBox->setValue(colorTrc.contrast * multiple); + saturationSpinBox = new QSpinBox(q_ptr); + saturationSpinBox->setKeyboardTracking(false); + saturationSpinBox->setRange(colorTrc.saturation_min * multiple, + colorTrc.saturation_max * multiple); + saturationSpinBox->setValue(colorTrc.saturation * multiple); + brightnessSpinBox = new QSpinBox(q_ptr); + brightnessSpinBox->setKeyboardTracking(false); + brightnessSpinBox->setRange(colorTrc.brightness_min * multiple, + colorTrc.brightness_max * multiple); + brightnessSpinBox->setValue(colorTrc.brightness * multiple); + + resetButton = new QToolButton(q_ptr); + resetButton->setText("Reset"); + } + + void setupUI() + { + auto *layout = new QGridLayout(q_ptr); + layout->addWidget(new QLabel(QObject::tr("Contrast:"), q_ptr), 0, 0); + layout->addWidget(contrastSlider, 0, 1); + layout->addWidget(contrastSpinBox, 0, 2); + layout->addWidget(new QLabel(QObject::tr("Saturation:"), q_ptr), 1, 0); + layout->addWidget(saturationSlider, 1, 1); + layout->addWidget(saturationSpinBox, 1, 2); + layout->addWidget(new QLabel(QObject::tr("Brightness:"), q_ptr), 2, 0); + layout->addWidget(brightnessSlider, 2, 1); + layout->addWidget(brightnessSpinBox, 2, 2); + layout->addWidget(resetButton, 3, 2, Qt::AlignRight); + } + + ColorSpaceDialog *q_ptr; + + Slider *contrastSlider; + Slider *saturationSlider; + Slider *brightnessSlider; + QSpinBox *contrastSpinBox; + QSpinBox *saturationSpinBox; + QSpinBox *brightnessSpinBox; + QToolButton *resetButton; + + const float multiple = 100.0; +}; + +ColorSpaceDialog::ColorSpaceDialog(QWidget *parent) + : QDialog(parent) + , d_ptr(new ColorSpaceDialogPrivate(this)) +{ + d_ptr->setupUI(); + buildConnect(); + resize(400, 250); +} + +ColorSpaceDialog::~ColorSpaceDialog() = default; + +void ColorSpaceDialog::setColorSpace(const Ffmpeg::ColorSpaceTrc &colorTrc) +{ + setBlockValue(d_ptr->contrastSpinBox, colorTrc.contrast * d_ptr->multiple); + setBlockValue(d_ptr->saturationSpinBox, colorTrc.saturation * d_ptr->multiple); + setBlockValue(d_ptr->brightnessSpinBox, colorTrc.brightness * d_ptr->multiple); + setBlockValue(d_ptr->contrastSlider, colorTrc.contrast * d_ptr->multiple); + setBlockValue(d_ptr->saturationSlider, colorTrc.saturation * d_ptr->multiple); + setBlockValue(d_ptr->brightnessSlider, colorTrc.brightness * d_ptr->multiple); +} + +Ffmpeg::ColorSpaceTrc ColorSpaceDialog::colorSpace() const +{ + Ffmpeg::ColorSpaceTrc colorTrc; + colorTrc.contrast = d_ptr->contrastSlider->value() / d_ptr->multiple; + colorTrc.saturation = d_ptr->saturationSlider->value() / d_ptr->multiple; + colorTrc.brightness = d_ptr->brightnessSlider->value() / d_ptr->multiple; + return colorTrc; +} + +void ColorSpaceDialog::onContrastSliderChanged(int value) +{ + setBlockValue(d_ptr->contrastSpinBox, value); + emit d_ptr->q_ptr->colorSpaceChanged(); +} + +void ColorSpaceDialog::onSaturationSliderChanged(int value) +{ + setBlockValue(d_ptr->saturationSpinBox, value); + emit d_ptr->q_ptr->colorSpaceChanged(); +} + +void ColorSpaceDialog::onBrightnessSliderChanged(int value) +{ + setBlockValue(d_ptr->brightnessSpinBox, value); + emit d_ptr->q_ptr->colorSpaceChanged(); +} + +void ColorSpaceDialog::onContrastSpinBoxChanged(int value) +{ + setBlockValue(d_ptr->contrastSlider, value); + emit d_ptr->q_ptr->colorSpaceChanged(); +} + +void ColorSpaceDialog::onSaturationSpinBoxChanged(int value) +{ + setBlockValue(d_ptr->saturationSlider, value); + emit d_ptr->q_ptr->colorSpaceChanged(); +} + +void ColorSpaceDialog::onBrightnessSpinBoxChanged(int value) +{ + setBlockValue(d_ptr->brightnessSlider, value); + emit d_ptr->q_ptr->colorSpaceChanged(); +} + +void ColorSpaceDialog::onReset() +{ + Ffmpeg::ColorSpaceTrc colorTrc; + setBlockValue(d_ptr->contrastSlider, colorTrc.contrast_default * d_ptr->multiple); + setBlockValue(d_ptr->saturationSlider, colorTrc.saturation_default * d_ptr->multiple); + setBlockValue(d_ptr->brightnessSlider, colorTrc.brightness_default * d_ptr->multiple); + setBlockValue(d_ptr->contrastSpinBox, colorTrc.contrast_default * d_ptr->multiple); + setBlockValue(d_ptr->saturationSpinBox, colorTrc.saturation_default * d_ptr->multiple); + setBlockValue(d_ptr->brightnessSpinBox, colorTrc.brightness_default * d_ptr->multiple); + emit d_ptr->q_ptr->colorSpaceChanged(); +} + +void ColorSpaceDialog::buildConnect() +{ + connect(d_ptr->contrastSlider, + &Slider::valueChanged, + this, + &ColorSpaceDialog::onContrastSliderChanged); + connect(d_ptr->saturationSlider, + &Slider::valueChanged, + this, + &ColorSpaceDialog::onSaturationSliderChanged); + connect(d_ptr->brightnessSlider, + &Slider::valueChanged, + this, + &ColorSpaceDialog::onBrightnessSliderChanged); + connect(d_ptr->contrastSpinBox, + &QSpinBox::valueChanged, + this, + &ColorSpaceDialog::onContrastSpinBoxChanged); + connect(d_ptr->saturationSpinBox, + &QSpinBox::valueChanged, + this, + &ColorSpaceDialog::onSaturationSpinBoxChanged); + connect(d_ptr->brightnessSpinBox, + &QSpinBox::valueChanged, + this, + &ColorSpaceDialog::onBrightnessSpinBoxChanged); + + connect(d_ptr->resetButton, &QToolButton::clicked, this, &ColorSpaceDialog::onReset); +} diff --git a/examples/player/colorspacedialog.hpp b/examples/player/colorspacedialog.hpp new file mode 100644 index 0000000..dd95442 --- /dev/null +++ b/examples/player/colorspacedialog.hpp @@ -0,0 +1,37 @@ +#ifndef COLORSPACEDIALOG_HPP +#define COLORSPACEDIALOG_HPP + +#include + +#include + +class ColorSpaceDialog : public QDialog +{ + Q_OBJECT +public: + explicit ColorSpaceDialog(QWidget *parent = nullptr); + ~ColorSpaceDialog() override; + + void setColorSpace(const Ffmpeg::ColorSpaceTrc &colorTrc); + [[nodiscard]] Ffmpeg::ColorSpaceTrc colorSpace() const; + +signals: + void colorSpaceChanged(); + +private slots: + void onContrastSliderChanged(int value); + void onSaturationSliderChanged(int value); + void onBrightnessSliderChanged(int value); + void onContrastSpinBoxChanged(int value); + void onSaturationSpinBoxChanged(int value); + void onBrightnessSpinBoxChanged(int value); + void onReset(); + +private: + void buildConnect(); + + class ColorSpaceDialogPrivate; + QScopedPointer d_ptr; +}; + +#endif // COLORSPACEDIALOG_HPP diff --git a/examples/player/controlwidget.cc b/examples/player/controlwidget.cc index 31c50be..720448d 100644 --- a/examples/player/controlwidget.cc +++ b/examples/player/controlwidget.cc @@ -29,7 +29,7 @@ class ControlWidget::ControlWidgetPrivate volumeSlider->setRange(0, 100); speedCbx = new QComboBox(q_ptr); - auto speedCbxView = new QListView(speedCbx); + auto *speedCbxView = new QListView(speedCbx); speedCbxView->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); speedCbxView->setTextElideMode(Qt::ElideRight); speedCbxView->setAlternatingRowColors(true); @@ -47,31 +47,38 @@ class ControlWidget::ControlWidgetPrivate void setupUI() { - auto processLayout = new QHBoxLayout; + auto *processLayout = new QHBoxLayout; processLayout->setSpacing(10); processLayout->addWidget(slider); processLayout->addWidget(positionLabel); processLayout->addWidget(new QLabel("/", q_ptr)); processLayout->addWidget(durationLabel); - auto skipBackwardButton = new QToolButton(q_ptr); + auto *skipBackwardButton = new QToolButton(q_ptr); skipBackwardButton->setToolTip(QObject::tr("Previous")); skipBackwardButton->setIcon(q_ptr->style()->standardIcon(QStyle::SP_MediaSkipBackward)); QObject::connect(skipBackwardButton, &QToolButton::clicked, q_ptr, &ControlWidget::previous); - auto skipForwardButton = new QToolButton(q_ptr); + auto *skipForwardButton = new QToolButton(q_ptr); skipForwardButton->setToolTip(QObject::tr("Next")); skipForwardButton->setIcon(q_ptr->style()->standardIcon(QStyle::SP_MediaSkipForward)); QObject::connect(skipForwardButton, &QToolButton::clicked, q_ptr, &ControlWidget::next); - auto volumeBotton = new QToolButton(q_ptr); + auto *volumeBotton = new QToolButton(q_ptr); volumeBotton->setIcon(q_ptr->style()->standardIcon(QStyle::SP_MediaVolume)); - auto listButton = new QToolButton(q_ptr); + auto *colorSpaceButton = new QToolButton(q_ptr); + colorSpaceButton->setText(tr("Color Space")); + QObject::connect(colorSpaceButton, + &QToolButton::clicked, + q_ptr, + &ControlWidget::showColorSpace); + + auto *listButton = new QToolButton(q_ptr); listButton->setText(tr("List")); QObject::connect(listButton, &QToolButton::clicked, q_ptr, &ControlWidget::showList); - auto controlLayout = new QHBoxLayout; + auto *controlLayout = new QHBoxLayout; controlLayout->setSpacing(10); controlLayout->addWidget(skipBackwardButton); controlLayout->addWidget(playButton); @@ -86,18 +93,19 @@ class ControlWidget::ControlWidgetPrivate controlLayout->addWidget(new QLabel("->", q_ptr)); controlLayout->addWidget(currentFPSLabel); controlLayout->addWidget(modelButton); + controlLayout->addWidget(colorSpaceButton); controlLayout->addWidget(listButton); - auto widget = new QWidget(q_ptr); + auto *widget = new QWidget(q_ptr); widget->setObjectName("wid"); widget->setStyleSheet("QWidget#wid{background: rgba(255,255,255,0.3); border-radius:5px;}" "QLabel{ color: white; }"); - auto layout = new QVBoxLayout(widget); + auto *layout = new QVBoxLayout(widget); layout->setSpacing(15); layout->addLayout(processLayout); layout->addLayout(controlLayout); - auto l = new QHBoxLayout(q_ptr); + auto *l = new QHBoxLayout(q_ptr); l->addWidget(widget); } @@ -237,7 +245,7 @@ void ControlWidget::onModelChanged() if (model > QMediaPlaylist::Random) { model = QMediaPlaylist::CurrentItemOnce; } - auto text = metaEnum.valueToKey(model); + const auto *text = metaEnum.valueToKey(model); d_ptr->modelButton->setText(text); d_ptr->modelButton->setToolTip(text); d_ptr->modelButton->setProperty("model", model); diff --git a/examples/player/controlwidget.hpp b/examples/player/controlwidget.hpp index c1f3ada..56d57c7 100644 --- a/examples/player/controlwidget.hpp +++ b/examples/player/controlwidget.hpp @@ -39,6 +39,7 @@ class ControlWidget : public QWidget void volumeChanged(int value); void speedChanged(double value); void modelChanged(int model); + void showColorSpace(); void showList(); private slots: diff --git a/examples/player/mainwindow.cpp b/examples/player/mainwindow.cpp index 6660181..c27411c 100644 --- a/examples/player/mainwindow.cpp +++ b/examples/player/mainwindow.cpp @@ -1,4 +1,5 @@ #include "mainwindow.h" +#include "colorspacedialog.hpp" #include "controlwidget.hpp" #include "openwebmediadialog.hpp" #include "playlistmodel.h" @@ -31,7 +32,7 @@ static auto isPlaylist(const QUrl &url) -> bool // Check for ".m3u" playlists. class MainWindow::MainWindowPrivate { public: - MainWindowPrivate(MainWindow *q) + explicit MainWindowPrivate(MainWindow *q) : q_ptr(q) , playerPtr(new Ffmpeg::Player) { @@ -40,7 +41,7 @@ class MainWindow::MainWindowPrivate titleWidget = new TitleWidget(q_ptr); titleWidget->setMinimumHeight(80); titleWidget->setVisible(false); - auto bottomLayout = new QHBoxLayout; + auto *bottomLayout = new QHBoxLayout; bottomLayout->addStretch(); bottomLayout->addWidget(controlWidget); bottomLayout->addStretch(); @@ -79,17 +80,17 @@ class MainWindow::MainWindowPrivate void resetTrackMenu() { auto actions = audioTracksGroup->actions(); - for (auto action : actions) { + for (auto *action : actions) { audioTracksGroup->removeAction(action); delete action; } actions = videoTracksGroup->actions(); - for (auto action : actions) { + for (auto *action : actions) { videoTracksGroup->removeAction(action); delete action; } actions = subTracksGroup->actions(); - for (auto action : actions) { + for (auto *action : actions) { subTracksGroup->removeAction(action); delete action; } @@ -312,14 +313,13 @@ void MainWindow::onRenderChanged(QAction *action) case 1: renderType = Ffmpeg::VideoRenderCreate::Widget; break; default: renderType = Ffmpeg::VideoRenderCreate::Opengl; break; } - auto videoRender = Ffmpeg::VideoRenderCreate::create(renderType); + auto *videoRender = Ffmpeg::VideoRenderCreate::create(renderType); videoRender->widget()->setLayout(d_ptr->controlLayout); videoRender->widget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); videoRender->widget()->setAcceptDrops(true); videoRender->widget()->installEventFilter(this); d_ptr->playerPtr->setVideoRenders({videoRender}); d_ptr->splitter->insertWidget(0, videoRender->widget()); - // 为什么切换成widget还是有使用GPU 0-3D,而且使用量是切换为opengl的两倍!!! d_ptr->videoRender.reset(videoRender); } @@ -343,21 +343,34 @@ void MainWindow::jump(const QModelIndex &index) } } +void MainWindow::onShowColorSpace() +{ + if (d_ptr->videoRender.isNull()) { + return; + } + ColorSpaceDialog dialog(this); + dialog.setColorSpace(d_ptr->videoRender->colorSpaceTrc()); + connect(&dialog, &ColorSpaceDialog::colorSpaceChanged, this, [&] { + d_ptr->videoRender->setColorSpaceTrc(dialog.colorSpace()); + }); + dialog.exec(); +} + void MainWindow::onProcessEvents() { while (d_ptr->playerPtr->propertyChangeEventSize() > 0) { auto eventPtr = d_ptr->playerPtr->takePropertyChangeEvent(); switch (eventPtr->type()) { case Ffmpeg::PropertyChangeEvent::EventType::Duration: { - auto durationEvent = dynamic_cast(eventPtr.data()); + auto *durationEvent = dynamic_cast(eventPtr.data()); d_ptr->controlWidget->setDuration(durationEvent->duration() / AV_TIME_BASE); } break; case Ffmpeg::PropertyChangeEvent::EventType::Position: { - auto positionEvent = dynamic_cast(eventPtr.data()); + auto *positionEvent = dynamic_cast(eventPtr.data()); d_ptr->controlWidget->setPosition(positionEvent->position() / AV_TIME_BASE); } break; case Ffmpeg::PropertyChangeEvent::EventType::MediaState: { - auto stateEvent = dynamic_cast(eventPtr.data()); + auto *stateEvent = dynamic_cast(eventPtr.data()); switch (stateEvent->state()) { case Ffmpeg::MediaState::Stopped: d_ptr->controlWidget->setPlayButtonChecked(false); @@ -377,13 +390,13 @@ void MainWindow::onProcessEvents() } } break; case Ffmpeg::PropertyChangeEvent::EventType::CacheSpeed: { - auto speedEvent = dynamic_cast(eventPtr.data()); + auto *speedEvent = dynamic_cast(eventPtr.data()); d_ptr->controlWidget->setCacheSpeed(speedEvent->speed()); } break; case Ffmpeg::PropertyChangeEvent::MediaTrack: { d_ptr->resetTrackMenu(); - auto tracksEvent = dynamic_cast(eventPtr.data()); + auto *tracksEvent = dynamic_cast(eventPtr.data()); auto tracks = tracksEvent->tracks(); for (const auto &track : qAsConst(tracks)) { std::unique_ptr actionPtr(new QAction(track.info(), this)); @@ -394,17 +407,17 @@ void MainWindow::onProcessEvents() } switch (track.type) { case AVMEDIA_TYPE_AUDIO: { - auto action = actionPtr.release(); + auto *action = actionPtr.release(); d_ptr->audioTracksMenuPtr->addAction(action); d_ptr->audioTracksGroup->addAction(action); } break; case AVMEDIA_TYPE_VIDEO: { - auto action = actionPtr.release(); + auto *action = actionPtr.release(); d_ptr->videoTracksMenuPtr->addAction(action); d_ptr->videoTracksGroup->addAction(action); } break; case AVMEDIA_TYPE_SUBTITLE: { - auto action = actionPtr.release(); + auto *action = actionPtr.release(); d_ptr->subTracksMenuPtr->addAction(action); d_ptr->subTracksGroup->addAction(action); } break; @@ -413,7 +426,7 @@ void MainWindow::onProcessEvents() } } break; case Ffmpeg::PropertyChangeEvent::SeekChanged: { - auto seekEvent = dynamic_cast(eventPtr.data()); + 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%)") .arg(QTime::fromMSecsSinceStartOfDay(seekEvent->position() / 1000) @@ -424,7 +437,7 @@ void MainWindow::onProcessEvents() d_ptr->setTitleWidgetText(text); } break; case Ffmpeg::PropertyChangeEvent::Error: { - auto errorEvent = dynamic_cast(eventPtr.data()); + auto *errorEvent = dynamic_cast(eventPtr.data()); const auto text = tr("Error[%1]:%2.") .arg(QString::number(errorEvent->error().errorCode()), errorEvent->error().errorString()); @@ -442,22 +455,22 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) if (!d_ptr->videoRender.isNull() && watched == d_ptr->videoRender->widget()) { switch (event->type()) { case QEvent::DragEnter: { - auto e = dynamic_cast(event); + auto *e = dynamic_cast(event); e->acceptProposedAction(); } break; case QEvent::DragMove: { - auto e = dynamic_cast(event); + auto *e = dynamic_cast(event); e->acceptProposedAction(); } break; case QEvent::Drop: { - auto e = dynamic_cast(event); + auto *e = dynamic_cast(event); auto urls = e->mimeData()->urls(); if (!urls.isEmpty()) { addToPlaylist(urls); } } break; case QEvent::ContextMenu: { - auto e = dynamic_cast(event); + auto *e = dynamic_cast(event); d_ptr->menu->exec(e->globalPos()); } break; case QEvent::MouseButtonDblClick: @@ -473,7 +486,7 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) } else if (watched == d_ptr->playlistView) { switch (event->type()) { case QEvent::ContextMenu: { - auto e = dynamic_cast(event); + auto *e = dynamic_cast(event); d_ptr->playListMenu->exec(e->globalPos()); } break; default: break; @@ -484,7 +497,7 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) d_ptr->controlWidget->show(); d_ptr->controlWidget->hide(); auto controlWidgetGeometry = d_ptr->controlWidget->geometry(); - auto e = dynamic_cast(event); + auto *e = dynamic_cast(event); bool contain = controlWidgetGeometry.contains(e->position().toPoint()); d_ptr->setControlWidgetVisible(contain); if (isFullScreen()) { @@ -575,6 +588,10 @@ void MainWindow::buildConnect() d_ptr->playlistModel->playlist()->setPlaybackMode( QMediaPlaylist::PlaybackMode(model)); }); + connect(d_ptr->controlWidget, + &ControlWidget::showColorSpace, + this, + &MainWindow::onShowColorSpace); connect(d_ptr->controlWidget, &ControlWidget::showList, d_ptr->playlistView, [this] { d_ptr->playlistView->setVisible(!d_ptr->playlistView->isVisible()); }); @@ -593,7 +610,7 @@ void MainWindow::initMenu() d_ptr->menu->addAction(tr("Open Local Media"), this, &MainWindow::onOpenLocalMedia); d_ptr->menu->addAction(tr("Open Web Media"), this, &MainWindow::onOpenWebMedia); - auto hwAction = new QAction("H/W", this); + auto *hwAction = new QAction("H/W", this); hwAction->setCheckable(true); connect(hwAction, &QAction::toggled, this, [this](bool checked) { d_ptr->playerPtr->addEvent(Ffmpeg::EventPtr(new Ffmpeg::GpuEvent(checked))); @@ -601,14 +618,14 @@ void MainWindow::initMenu() hwAction->setChecked(true); d_ptr->menu->addAction(hwAction); - auto widgetAction = new QAction(tr("Widget"), this); + auto *widgetAction = new QAction(tr("Widget"), this); widgetAction->setCheckable(true); widgetAction->setData(Ffmpeg::VideoRenderCreate::Widget); - auto openglAction = new QAction(tr("Opengl"), this); + auto *openglAction = new QAction(tr("Opengl"), this); openglAction->setCheckable(true); openglAction->setData(Ffmpeg::VideoRenderCreate::Opengl); openglAction->setChecked(true); - auto actionGroup = new QActionGroup(this); + auto *actionGroup = new QActionGroup(this); actionGroup->setExclusive(true); actionGroup->addAction(widgetAction); actionGroup->addAction(openglAction); @@ -619,7 +636,7 @@ void MainWindow::initMenu() Qt::QueuedConnection); openglAction->trigger(); - auto renderMenu = new QMenu(tr("Video Render"), this); + auto *renderMenu = new QMenu(tr("Video Render"), this); renderMenu->addAction(widgetAction); renderMenu->addAction(openglAction); d_ptr->menu->addMenu(renderMenu); @@ -662,7 +679,7 @@ void MainWindow::initPlayListMenu() void MainWindow::addToPlaylist(const QList &urls) { - auto playlist = d_ptr->playlistModel->playlist(); + auto *playlist = d_ptr->playlistModel->playlist(); const int previousMediaCount = playlist->mediaCount(); for (auto &url : urls) { if (isPlaylist(url)) { diff --git a/examples/player/mainwindow.h b/examples/player/mainwindow.h index faa6428..8e0a8dd 100644 --- a/examples/player/mainwindow.h +++ b/examples/player/mainwindow.h @@ -26,6 +26,8 @@ private slots: void playlistPositionChanged(int); void jump(const QModelIndex &index); + void onShowColorSpace(); + void onProcessEvents(); protected: diff --git a/examples/player/player.pro b/examples/player/player.pro index 2d072c9..f923a5d 100644 --- a/examples/player/player.pro +++ b/examples/player/player.pro @@ -15,6 +15,7 @@ LIBS += \ include(../../3rdparty/3rdparty.pri) SOURCES += \ + colorspacedialog.cc \ controlwidget.cc \ main.cpp \ mainwindow.cpp \ @@ -27,6 +28,7 @@ SOURCES += \ titlewidget.cc HEADERS += \ + colorspacedialog.hpp \ controlwidget.hpp \ mainwindow.h \ openwebmediadialog.hpp \ diff --git a/ffmpeg/CMakeLists.txt b/ffmpeg/CMakeLists.txt index b82a580..76b554e 100644 --- a/ffmpeg/CMakeLists.txt +++ b/ffmpeg/CMakeLists.txt @@ -26,6 +26,8 @@ set(PROJECT_SOURCES subtitle/assdata.hpp videorender/openglrender.cc videorender/openglrender.hpp + videorender/openglshader.cc + videorender/openglshader.hpp videorender/openglshaderprogram.cc videorender/openglshaderprogram.hpp videorender/videopreviewwidget.cc diff --git a/ffmpeg/videoframeconverter.cc b/ffmpeg/videoframeconverter.cc index dcd62f2..26635bc 100644 --- a/ffmpeg/videoframeconverter.cc +++ b/ffmpeg/videoframeconverter.cc @@ -53,7 +53,7 @@ VideoFrameConverter::VideoFrameConverter(CodecContext *codecCtx, : QObject(parent) , d_ptr(new VideoFrameConverterPrivate(this)) { - auto ctx = codecCtx->avCodecCtx(); + auto *ctx = codecCtx->avCodecCtx(); d_ptr->src_pix_fmt = ctx->pix_fmt; d_ptr->dst_pix_fmt = pix_fmt; d_ptr->debugMessage(); @@ -89,9 +89,30 @@ VideoFrameConverter::~VideoFrameConverter() sws_freeContext(d_ptr->swsContext); } +void VideoFrameConverter::setColorspaceDetails(Frame *frame, + float brightness, + float contrast, + float saturation) +{ + auto *avFrame = frame->avFrame(); + int srcRange = 0; + switch (avFrame->color_range) { + case AVCOL_RANGE_MPEG: srcRange = 0; break; + default: srcRange = 1; break; + } + sws_setColorspaceDetails(d_ptr->swsContext, + sws_getCoefficients(avFrame->colorspace), + srcRange, + sws_getCoefficients(SWS_CS_DEFAULT), + 1, + static_cast(brightness * (1 << 16)), + static_cast(contrast * (1 << 16)), + static_cast(saturation * (1 << 16))); +} + void VideoFrameConverter::flush(Frame *frame, const QSize &dstSize, AVPixelFormat pix_fmt) { - auto avFrame = frame->avFrame(); + auto *avFrame = frame->avFrame(); d_ptr->src_pix_fmt = static_cast(avFrame->format); d_ptr->dst_pix_fmt = pix_fmt; d_ptr->debugMessage(); @@ -112,11 +133,11 @@ void VideoFrameConverter::flush(Frame *frame, const QSize &dstSize, AVPixelForma Q_ASSERT(d_ptr->swsContext != nullptr); } -int VideoFrameConverter::scale(Frame *in, Frame *out) +auto VideoFrameConverter::scale(Frame *in, Frame *out) -> int { Q_ASSERT(d_ptr->swsContext != nullptr); - auto inFrame = in->avFrame(); - auto outFrame = out->avFrame(); + auto *inFrame = in->avFrame(); + auto *outFrame = out->avFrame(); auto ret = sws_scale(d_ptr->swsContext, static_cast(inFrame->data), inFrame->linesize, @@ -136,12 +157,12 @@ int VideoFrameConverter::scale(Frame *in, Frame *out) return ret; } -bool VideoFrameConverter::isSupportedInput_pix_fmt(AVPixelFormat pix_fmt) +auto VideoFrameConverter::isSupportedInput_pix_fmt(AVPixelFormat pix_fmt) -> bool { return sws_isSupportedInput(pix_fmt); } -bool VideoFrameConverter::isSupportedOutput_pix_fmt(AVPixelFormat pix_fmt) +auto VideoFrameConverter::isSupportedOutput_pix_fmt(AVPixelFormat pix_fmt) -> bool { return sws_isSupportedOutput(pix_fmt); } diff --git a/ffmpeg/videoframeconverter.hpp b/ffmpeg/videoframeconverter.hpp index 1b76526..361c9b4 100644 --- a/ffmpeg/videoframeconverter.hpp +++ b/ffmpeg/videoframeconverter.hpp @@ -25,6 +25,7 @@ class VideoFrameConverter : public QObject QObject *parent = nullptr); ~VideoFrameConverter() override; + void setColorspaceDetails(Frame *frame, float brightness, float contrast, float saturation); void flush(Frame *frame, const QSize &dstSize = QSize(-1, -1), AVPixelFormat pix_fmt = AV_PIX_FMT_RGBA); diff --git a/ffmpeg/videorender/openglrender.cc b/ffmpeg/videorender/openglrender.cc index b170e9f..fc907df 100644 --- a/ffmpeg/videorender/openglrender.cc +++ b/ffmpeg/videorender/openglrender.cc @@ -1,4 +1,5 @@ #include "openglrender.hpp" +#include "openglshader.hpp" #include "openglshaderprogram.hpp" #include @@ -209,21 +210,11 @@ void OpenglRender::setColorSpace() void OpenglRender::setColorTrc() { - GLfloat contrast = 1.0; - GLfloat saturation = 0; - GLfloat brightness = 0; auto *avFrame = d_ptr->framePtr->avFrame(); switch (avFrame->color_trc) { - case AVCOL_TRC_SMPTE2084: // fake hdr - contrast = 1.4; - saturation = 0.9; - brightness = 0; - break; + case AVCOL_TRC_SMPTE2084: break; default: break; } - d_ptr->programPtr->setUniformValue("contrast", contrast); - d_ptr->programPtr->setUniformValue("saturation", saturation); - d_ptr->programPtr->setUniformValue("brightness", brightness); } auto OpenglRender::fitToScreen(const QSize &size) -> QMatrix4x4 @@ -256,60 +247,8 @@ void OpenglRender::resetShader(int format) makeCurrent(); cleanup(); d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader/video.vert"); - switch (format) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: - case AV_PIX_FMT_YUV410P: - case AV_PIX_FMT_YUV411P: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_yuv420p.frag"); - break; - case AV_PIX_FMT_YUYV422: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_yuyv422.frag"); - break; - case AV_PIX_FMT_RGB24: - case AV_PIX_FMT_BGR8: - case AV_PIX_FMT_RGB8: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_rgb24.frag"); - break; - case AV_PIX_FMT_BGR24: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_bgr24.frag"); - break; - case AV_PIX_FMT_UYVY422: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_uyvy422.frag"); - break; - case AV_PIX_FMT_NV12: - case AV_PIX_FMT_P010LE: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_nv12.frag"); - break; - case AV_PIX_FMT_NV21: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_nv21.frag"); - break; - case AV_PIX_FMT_ARGB: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_argb.frag"); - break; - case AV_PIX_FMT_RGBA: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_rgba.frag"); - break; - case AV_PIX_FMT_ABGR: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_abgr.frag"); - break; - case AV_PIX_FMT_BGRA: - d_ptr->programPtr->addShaderFromSourceFile(QOpenGLShader::Fragment, - ":/shader/video_bgra.frag"); - break; - default: break; - } + OpenglShader shader; + d_ptr->programPtr->addShaderFromSourceCode(QOpenGLShader::Fragment, shader.generate(format)); glBindVertexArray(d_ptr->vao); d_ptr->programPtr->link(); d_ptr->programPtr->bind(); @@ -376,6 +315,9 @@ void OpenglRender::paintVideoFrame() } d_ptr->programPtr->bind(); // 绑定着色器 d_ptr->programPtr->setUniformValue("transform", fitToScreen({avFrame->width, avFrame->height})); + d_ptr->programPtr->setUniformValue("contrast", m_colorSpaceTrc.contrast); + d_ptr->programPtr->setUniformValue("saturation", m_colorSpaceTrc.saturation); + d_ptr->programPtr->setUniformValue("brightness", m_colorSpaceTrc.brightness); setColorSpace(); setColorTrc(); draw(); diff --git a/ffmpeg/videorender/openglshader.cc b/ffmpeg/videorender/openglshader.cc new file mode 100644 index 0000000..b5c134f --- /dev/null +++ b/ffmpeg/videorender/openglshader.cc @@ -0,0 +1,62 @@ +#include "openglshader.hpp" + +#include + +#include + +extern "C" { +#include +} + +namespace Ffmpeg { + +class OpenglShader::OpenglShaderPrivate +{ +public: + explicit OpenglShaderPrivate(OpenglShader *q) + : q_ptr(q) + {} + + OpenglShader *q_ptr; +}; + +OpenglShader::OpenglShader(QObject *parent) + : QObject{parent} + , d_ptr(new OpenglShaderPrivate(this)) +{} + +OpenglShader::~OpenglShader() = default; + +QByteArray OpenglShader::generate(int format) +{ + qInfo() << "Generate Shader:" << format; + auto frag = Utils::readAllFile(":/shader/video_header.frag"); + frag.append("\n"); + frag.append(Utils::readAllFile(":/shader/video_color.frag")); + frag.append("\n"); + switch (format) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUV410P: + case AV_PIX_FMT_YUV411P: frag.append(Utils::readAllFile(":/shader/video_yuv420p.frag")); break; + case AV_PIX_FMT_YUYV422: frag.append(Utils::readAllFile(":/shader/video_yuyv422.frag")); break; + case AV_PIX_FMT_RGB24: + case AV_PIX_FMT_BGR8: + case AV_PIX_FMT_RGB8: frag.append(Utils::readAllFile(":/shader/video_rgb24.frag")); break; + case AV_PIX_FMT_BGR24: frag.append(Utils::readAllFile(":/shader/video_bgr24.frag")); break; + case AV_PIX_FMT_UYVY422: frag.append(Utils::readAllFile(":/shader/video_uyvy422.frag")); break; + case AV_PIX_FMT_NV12: + case AV_PIX_FMT_P010LE: frag.append(Utils::readAllFile(":/shader/video_nv12.frag")); break; + case AV_PIX_FMT_NV21: frag.append(Utils::readAllFile(":/shader/video_nv21.frag")); break; + case AV_PIX_FMT_ARGB: frag.append(Utils::readAllFile(":/shader/video_argb.frag")); break; + case AV_PIX_FMT_RGBA: frag.append(Utils::readAllFile(":/shader/video_rgba.frag")); break; + case AV_PIX_FMT_ABGR: frag.append(Utils::readAllFile(":/shader/video_abgr.frag")); break; + case AV_PIX_FMT_BGRA: frag.append(Utils::readAllFile(":/shader/video_bgra.frag")); break; + default: qWarning() << "UnSupported format:" << format; break; + } + frag.append("\n"); + return frag; +} + +} // namespace Ffmpeg diff --git a/ffmpeg/videorender/openglshader.hpp b/ffmpeg/videorender/openglshader.hpp new file mode 100644 index 0000000..64a17f4 --- /dev/null +++ b/ffmpeg/videorender/openglshader.hpp @@ -0,0 +1,23 @@ +#ifndef OPENGLSHADER_HPP +#define OPENGLSHADER_HPP + +#include + +namespace Ffmpeg { + +class OpenglShader : public QObject +{ +public: + explicit OpenglShader(QObject *parent = nullptr); + ~OpenglShader() override; + + QByteArray generate(int format); + +private: + class OpenglShaderPrivate; + QScopedPointer d_ptr; +}; + +} // namespace Ffmpeg + +#endif // OPENGLSHADER_HPP diff --git a/ffmpeg/videorender/shader/video_abgr.frag b/ffmpeg/videorender/shader/video_abgr.frag index 156f679..dc18b70 100644 --- a/ffmpeg/videorender/shader/video_abgr.frag +++ b/ffmpeg/videorender/shader/video_abgr.frag @@ -1,14 +1,10 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { - FragColor = texture(tex_y, TexCord).abgr; + vec4 color = texture(tex_y, TexCord).abgr; + + color.rgb = adjustContrast(color.rgb, contrast); + color.rgb = adjustSaturation(color.rgb, saturation); + color.rgb = adjustBrightness(color.rgb, brightness); + + FragColor = color; } diff --git a/ffmpeg/videorender/shader/video_argb.frag b/ffmpeg/videorender/shader/video_argb.frag index b8df276..f968029 100644 --- a/ffmpeg/videorender/shader/video_argb.frag +++ b/ffmpeg/videorender/shader/video_argb.frag @@ -1,14 +1,10 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { - FragColor = texture(tex_y, TexCord).gbar; + vec4 color = texture(tex_y, TexCord).gbar; + + color.rgb = adjustContrast(color.rgb, contrast); + color.rgb = adjustSaturation(color.rgb, saturation); + color.rgb = adjustBrightness(color.rgb, brightness); + + FragColor = color; } diff --git a/ffmpeg/videorender/shader/video_bgr24.frag b/ffmpeg/videorender/shader/video_bgr24.frag index 081be7f..3c10b04 100644 --- a/ffmpeg/videorender/shader/video_bgr24.frag +++ b/ffmpeg/videorender/shader/video_bgr24.frag @@ -1,14 +1,10 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { - FragColor = vec4(texture(tex_y, TexCord).bgr, 1); + vec3 rgb = texture(tex_y, TexCord).bgr; + + rgb = adjustContrast(rgb, contrast); + rgb = adjustSaturation(rgb, saturation); + rgb = adjustBrightness(rgb, brightness); + + FragColor = vec4(rgb, 1.0); } diff --git a/ffmpeg/videorender/shader/video_bgra.frag b/ffmpeg/videorender/shader/video_bgra.frag index 753e222..5b3214b 100644 --- a/ffmpeg/videorender/shader/video_bgra.frag +++ b/ffmpeg/videorender/shader/video_bgra.frag @@ -1,14 +1,10 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { - FragColor = texture(tex_y, TexCord).bgra; + vec4 color = texture(tex_y, TexCord).bgra; + + color.rgb = adjustContrast(color.rgb, contrast); + color.rgb = adjustSaturation(color.rgb, saturation); + color.rgb = adjustBrightness(color.rgb, brightness); + + FragColor = color; } diff --git a/ffmpeg/videorender/shader/video_color.frag b/ffmpeg/videorender/shader/video_color.frag new file mode 100644 index 0000000..54af954 --- /dev/null +++ b/ffmpeg/videorender/shader/video_color.frag @@ -0,0 +1,42 @@ +uniform float contrast; +uniform float saturation; +uniform float brightness; + +vec3 adjustBrightness(vec3 rgb, float brightness) // 调整亮度 +{ + brightness = clamp(brightness, -1.0, 1.0); + return clamp(rgb + brightness, 0.0, 1.0); +} + +vec3 adjustContrast(vec3 rgb, float contrast) // 调整对比度 和使用sws_setColorspaceDetails效果不太一样 +{ + contrast = clamp(contrast, 0.0, 2.0); + vec3 contrastColor = (rgb - vec3(0.5)) * contrast + vec3(0.5); + return clamp(contrastColor, vec3(0.0), vec3(1.0)); +} + +vec3 rgb2Hsv(vec3 rgb) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(rgb.bg, K.wz), vec4(rgb.gb, K.xy), step(rgb.b, rgb.g)); + vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2Rgb(vec3 hsv) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www); + return hsv.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsv.y); +} + +vec3 adjustSaturation(vec3 rgb, float saturation) // 调整饱和度 +{ + saturation = clamp(saturation, 0.0, 2.0); + vec3 hsv = rgb2Hsv(rgb); + hsv.y = hsv.y * saturation; + return hsv2Rgb(hsv); +} \ No newline at end of file diff --git a/ffmpeg/videorender/shader/video_header.frag b/ffmpeg/videorender/shader/video_header.frag new file mode 100644 index 0000000..cbd9a28 --- /dev/null +++ b/ffmpeg/videorender/shader/video_header.frag @@ -0,0 +1,11 @@ +#version 330 core + +in vec2 TexCord; // 纹理坐标 +out vec4 FragColor; // 输出颜色 + +uniform sampler2D tex_y; +uniform sampler2D tex_u; +uniform sampler2D tex_v; + +uniform vec3 offset; +uniform mat3 colorConversion; diff --git a/ffmpeg/videorender/shader/video_nv12.frag b/ffmpeg/videorender/shader/video_nv12.frag index def5a1b..f9b64a8 100644 --- a/ffmpeg/videorender/shader/video_nv12.frag +++ b/ffmpeg/videorender/shader/video_nv12.frag @@ -1,33 +1,3 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; -uniform sampler2D tex_u; - -uniform vec3 offset; -uniform mat3 colorConversion; - -uniform float contrast; -uniform float saturation; -uniform float brightness; - -vec3 adjustContrast(vec3 rgb, float contrast) // 调整对比度 -{ - return clamp((rgb - vec3(0.5)) * contrast + vec3(0.5), vec3(0.0), vec3(1.0)); -} - -vec3 adjustSaturation(vec3 rgb, float saturation) // 调整饱和度 -{ - return mix(vec3(dot(rgb, vec3(0.2126, 0.7152, 0.0722))), rgb, 1.0 + saturation); -} - -vec3 adjustBrightness(vec3 rgb, float brightness) // 调整亮度 -{ - return clamp(rgb + vec3(brightness), vec3(0.0), vec3(1.0)); -} - void main() { vec3 yuv; diff --git a/ffmpeg/videorender/shader/video_nv21.frag b/ffmpeg/videorender/shader/video_nv21.frag index a724b04..65e673b 100644 --- a/ffmpeg/videorender/shader/video_nv21.frag +++ b/ffmpeg/videorender/shader/video_nv21.frag @@ -1,14 +1,3 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; -uniform sampler2D tex_u; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { vec3 yuv; @@ -20,5 +9,9 @@ void main() yuv += offset; rgb = yuv * colorConversion; + rgb = adjustContrast(rgb, contrast); + rgb = adjustSaturation(rgb, saturation); + rgb = adjustBrightness(rgb, brightness); + FragColor = vec4(rgb, 1.0); } diff --git a/ffmpeg/videorender/shader/video_p010le.frag b/ffmpeg/videorender/shader/video_p010le.frag index d60d3c8..f9b64a8 100644 --- a/ffmpeg/videorender/shader/video_p010le.frag +++ b/ffmpeg/videorender/shader/video_p010le.frag @@ -1,14 +1,3 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; -uniform sampler2D tex_u; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { vec3 yuv; @@ -20,5 +9,9 @@ void main() yuv += offset; rgb = yuv * colorConversion; + rgb = adjustContrast(rgb, contrast); + rgb = adjustSaturation(rgb, saturation); + rgb = adjustBrightness(rgb, brightness); + FragColor = vec4(rgb, 1.0); } diff --git a/ffmpeg/videorender/shader/video_rgb24.frag b/ffmpeg/videorender/shader/video_rgb24.frag index bc0a47b..7ce40f0 100644 --- a/ffmpeg/videorender/shader/video_rgb24.frag +++ b/ffmpeg/videorender/shader/video_rgb24.frag @@ -1,14 +1,10 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { - FragColor = vec4(texture(tex_y, TexCord).rgb, 1); + vec3 rgb = texture(tex_y, TexCord).rgb; + + rgb = adjustContrast(rgb, contrast); + rgb = adjustSaturation(rgb, saturation); + rgb = adjustBrightness(rgb, brightness); + + FragColor = vec4(rgb, 1.0); } diff --git a/ffmpeg/videorender/shader/video_rgba.frag b/ffmpeg/videorender/shader/video_rgba.frag index 04d6620..f253c98 100644 --- a/ffmpeg/videorender/shader/video_rgba.frag +++ b/ffmpeg/videorender/shader/video_rgba.frag @@ -1,14 +1,10 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { - FragColor = texture(tex_y, TexCord); + FragColor = texture(tex_y, TexCord).rgba; + + color.rgb = adjustContrast(color.rgb, contrast); + color.rgb = adjustSaturation(color.rgb, saturation); + color.rgb = adjustBrightness(color.rgb, brightness); + + FragColor = color; } diff --git a/ffmpeg/videorender/shader/video_uyvy422.frag b/ffmpeg/videorender/shader/video_uyvy422.frag index 9837dbe..3ee2e90 100644 --- a/ffmpeg/videorender/shader/video_uyvy422.frag +++ b/ffmpeg/videorender/shader/video_uyvy422.frag @@ -1,13 +1,3 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { vec3 yuv; @@ -27,5 +17,9 @@ void main() yuv += offset; rgb = yuv * colorConversion; + rgb = adjustContrast(rgb, contrast); + rgb = adjustSaturation(rgb, saturation); + rgb = adjustBrightness(rgb, brightness); + FragColor = vec4(rgb, 1.0); } diff --git a/ffmpeg/videorender/shader/video_yuv420p.frag b/ffmpeg/videorender/shader/video_yuv420p.frag index 0997028..3d29719 100644 --- a/ffmpeg/videorender/shader/video_yuv420p.frag +++ b/ffmpeg/videorender/shader/video_yuv420p.frag @@ -1,15 +1,3 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; -uniform sampler2D tex_u; -uniform sampler2D tex_v; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { vec3 yuv; @@ -22,5 +10,9 @@ void main() yuv += offset; rgb = yuv * colorConversion; + rgb = adjustContrast(rgb, contrast); + rgb = adjustSaturation(rgb, saturation); + rgb = adjustBrightness(rgb, brightness); + FragColor = vec4(rgb, 1.0); } diff --git a/ffmpeg/videorender/shader/video_yuyv422.frag b/ffmpeg/videorender/shader/video_yuyv422.frag index a7f1828..92be751 100644 --- a/ffmpeg/videorender/shader/video_yuyv422.frag +++ b/ffmpeg/videorender/shader/video_yuyv422.frag @@ -1,13 +1,3 @@ -#version 330 core - -in vec2 TexCord; // 纹理坐标 -out vec4 FragColor; // 输出颜色 - -uniform sampler2D tex_y; - -uniform vec3 offset; -uniform mat3 colorConversion; - void main() { vec3 yuv; @@ -18,5 +8,9 @@ void main() yuv += offset; rgb = yuv * colorConversion; + rgb = adjustContrast(rgb, contrast); + rgb = adjustSaturation(rgb, saturation); + rgb = adjustBrightness(rgb, brightness); + FragColor = vec4(rgb, 1.0); } diff --git a/ffmpeg/videorender/shaders.qrc b/ffmpeg/videorender/shaders.qrc index 2163ef8..483815e 100644 --- a/ffmpeg/videorender/shaders.qrc +++ b/ffmpeg/videorender/shaders.qrc @@ -16,5 +16,8 @@ shader/video_rgba.frag shader/video_abgr.frag shader/video_bgra.frag + shader/video_color.frag + shader/video_header.frag + shader/video_p010le.frag diff --git a/ffmpeg/videorender/videorender.cc b/ffmpeg/videorender/videorender.cc index 065bbcc..ab9f3ff 100644 --- a/ffmpeg/videorender/videorender.cc +++ b/ffmpeg/videorender/videorender.cc @@ -13,6 +13,14 @@ extern "C" { namespace Ffmpeg { +ColorSpaceTrc &ColorSpaceTrc::operator=(const ColorSpaceTrc &other) +{ + contrast = other.contrast; + saturation = other.saturation; + brightness = other.brightness; + return *this; +} + class VideoRender::VideoRenderPrivate { public: diff --git a/ffmpeg/videorender/videorender.hpp b/ffmpeg/videorender/videorender.hpp index 23ffdf8..2fd0a5f 100644 --- a/ffmpeg/videorender/videorender.hpp +++ b/ffmpeg/videorender/videorender.hpp @@ -3,19 +3,37 @@ #include -#include +#include extern "C" { #include } -class QWidget; - namespace Ffmpeg { class Frame; class Subtitle; +struct ColorSpaceTrc +{ + auto operator=(const ColorSpaceTrc &other) -> ColorSpaceTrc &; + + const float contrast_min = 0.0; + const float contrast_max = 2.0; + const float contrast_default = 1.0; + float contrast = 1.0; + + const float saturation_min = 0.0; + const float saturation_max = 2.0; + const float saturation_default = 1.0; + float saturation = 1.0; + + const float brightness_min = -1.0; + const float brightness_max = 1.0; + const float brightness_default = 0.0; + float brightness = 0.0; +}; + class FFMPEG_EXPORT VideoRender { Q_DISABLE_COPY_MOVE(VideoRender) @@ -32,6 +50,9 @@ class FFMPEG_EXPORT VideoRender void setSubTitleFrame(QSharedPointer framePtr); virtual void resetAllFrame() = 0; + void setColorSpaceTrc(const ColorSpaceTrc &colorTrc) { m_colorSpaceTrc = colorTrc; } + auto colorSpaceTrc() const -> ColorSpaceTrc { return m_colorSpaceTrc; } + virtual auto widget() -> QWidget * = 0; auto fps() -> float; @@ -42,6 +63,8 @@ class FFMPEG_EXPORT VideoRender virtual void updateFrame(QSharedPointer frame) = 0; virtual void updateSubTitleFrame(QSharedPointer frame) = 0; + ColorSpaceTrc m_colorSpaceTrc; + private: class VideoRenderPrivate; QScopedPointer d_ptr; diff --git a/ffmpeg/videorender/videorender.pri b/ffmpeg/videorender/videorender.pri index 09139ab..a70abcb 100644 --- a/ffmpeg/videorender/videorender.pri +++ b/ffmpeg/videorender/videorender.pri @@ -3,6 +3,7 @@ RESOURCES += \ HEADERS += \ $$PWD/openglrender.hpp \ + $$PWD/openglshader.hpp \ $$PWD/openglshaderprogram.hpp \ $$PWD/videopreviewwidget.hpp \ $$PWD/videorender.hpp \ @@ -11,6 +12,7 @@ HEADERS += \ SOURCES += \ $$PWD/openglrender.cc \ + $$PWD/openglshader.cc \ $$PWD/openglshaderprogram.cc \ $$PWD/videopreviewwidget.cc \ $$PWD/videorender.cc \ diff --git a/ffmpeg/videorender/widgetrender.cc b/ffmpeg/videorender/widgetrender.cc index 2315c92..d2a89d8 100644 --- a/ffmpeg/videorender/widgetrender.cc +++ b/ffmpeg/videorender/widgetrender.cc @@ -44,15 +44,15 @@ WidgetRender::WidgetRender(QWidget *parent) WidgetRender::~WidgetRender() = default; -bool WidgetRender::isSupportedOutput_pix_fmt(AVPixelFormat pix_fmt) +auto WidgetRender::isSupportedOutput_pix_fmt(AVPixelFormat pix_fmt) -> bool { return d_ptr->supportFormats.contains(pix_fmt); } -QSharedPointer WidgetRender::convertSupported_pix_fmt(QSharedPointer frame) +auto WidgetRender::convertSupported_pix_fmt(QSharedPointer frame) -> QSharedPointer { auto dst_pix_fmt = AV_PIX_FMT_RGB32; - auto avframe = frame->avFrame(); + auto *avframe = frame->avFrame(); auto size = QSize(avframe->width, avframe->height); size.scale(this->size() * devicePixelRatio(), Qt::KeepAspectRatio); if (d_ptr->frameConverterPtr.isNull()) { @@ -60,6 +60,10 @@ QSharedPointer WidgetRender::convertSupported_pix_fmt(QSharedPointerframeConverterPtr->flush(frame.data(), size, dst_pix_fmt); } + d_ptr->frameConverterPtr->setColorspaceDetails(frame.data(), + m_colorSpaceTrc.brightness, + m_colorSpaceTrc.contrast, + m_colorSpaceTrc.saturation); QSharedPointer frameRgbPtr(new Frame); frameRgbPtr->imageAlloc(size, dst_pix_fmt); d_ptr->frameConverterPtr->scale(frame.data(), frameRgbPtr.data()); @@ -68,7 +72,7 @@ QSharedPointer WidgetRender::convertSupported_pix_fmt(QSharedPointer WidgetRender::supportedOutput_pix_fmt() +auto WidgetRender::supportedOutput_pix_fmt() -> QVector { return d_ptr->supportFormats; } @@ -81,7 +85,7 @@ void WidgetRender::resetAllFrame() d_ptr->subTitleFramePtr.reset(); } -QWidget *WidgetRender::widget() +auto WidgetRender::widget() -> QWidget * { return this; } diff --git a/ffmpeg/videorender/widgetrender.hpp b/ffmpeg/videorender/widgetrender.hpp index a7d6cfd..eb74802 100644 --- a/ffmpeg/videorender/widgetrender.hpp +++ b/ffmpeg/videorender/widgetrender.hpp @@ -15,7 +15,7 @@ class FFMPEG_EXPORT WidgetRender : public VideoRender, public QWidget auto isSupportedOutput_pix_fmt(AVPixelFormat pix_fmt) -> bool override; auto convertSupported_pix_fmt(QSharedPointer frame) -> QSharedPointer override; - QVector supportedOutput_pix_fmt() override; + auto supportedOutput_pix_fmt() -> QVector override; void resetAllFrame() override; diff --git a/utils/utils.cpp b/utils/utils.cpp index 5da4c15..f6c0874 100644 --- a/utils/utils.cpp +++ b/utils/utils.cpp @@ -188,7 +188,7 @@ static auto errnoToQString(int error) -> QString char msg[128]; if (strerror_s(msg, sizeof msg, error) != 0) return QString::fromLocal8Bit(msg); - return QString(); + return {}; #else return QString::fromLocal8Bit(strerror(error)); #endif @@ -238,13 +238,7 @@ auto Utils::convertBytesToString(qint64 bytes) -> QString QJsonObject Utils::jsonFromFile(const QString &filePath) { - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << QObject::tr("Cannot open the file: %1").arg(filePath); - return QJsonObject(); - } - const QByteArray buf(file.readAll()); - file.close(); + const QByteArray buf(readAllFile(filePath)); return jsonFromBytes(buf); } @@ -255,14 +249,14 @@ QJsonObject Utils::jsonFromBytes(const QByteArray &bytes) if (QJsonParseError::NoError != jsonParseError.error) { qWarning() << QObject::tr("%1\nOffset: %2") .arg(jsonParseError.errorString(), jsonParseError.offset); - return QJsonObject(); + return {}; } return jsonDocument.object(); } void Utils::setGlobalThreadPoolMaxSize(int maxSize) { - auto instance = QThreadPool::globalInstance(); + auto *instance = QThreadPool::globalInstance(); if (maxSize > 0) { instance->setMaxThreadCount(maxSize); return; @@ -286,3 +280,15 @@ auto Utils::getConfigPath() -> QString Utils::generateDirectorys(path); return path; } + +QByteArray Utils::readAllFile(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << QObject::tr("Cannot open the file: %1").arg(filePath); + return {}; + } + auto buf = file.readAll(); + file.close(); + return buf; +} diff --git a/utils/utils.h b/utils/utils.h index 7784ff2..694ac6d 100644 --- a/utils/utils.h +++ b/utils/utils.h @@ -26,6 +26,7 @@ UTILS_EXPORT auto convertBytesToString(qint64 bytes) -> QString; UTILS_EXPORT auto jsonFromFile(const QString &filePath) -> QJsonObject; UTILS_EXPORT auto jsonFromBytes(const QByteArray &bytes) -> QJsonObject; UTILS_EXPORT auto getConfigPath() -> QString; +UTILS_EXPORT auto readAllFile(const QString &filePath) -> QByteArray; } // namespace Utils