File indexing completed on 2025-01-05 03:57:27

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2024-01-14
0007  * Description : QtAVPlayer video integration class.
0008  *
0009  * SPDX-FileCopyrightText: 2023-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "dvideowidget.h"
0016 
0017 // Qt includes
0018 
0019 #include <QList>
0020 #include <QMutex>
0021 #include <QMutexLocker>
0022 #include <QVBoxLayout>
0023 #include <QAbstractVideoSurface>
0024 #include <QVideoSurfaceFormat>
0025 #include <QGraphicsScene>
0026 #include <QGraphicsVideoItem>
0027 
0028 // Local includes
0029 
0030 #include "digikam_debug.h"
0031 
0032 namespace Digikam
0033 {
0034 
0035 class Q_DECL_HIDDEN DVideoWidget::Private
0036 {
0037 
0038 public:
0039 
0040     Private() = default;
0041 
0042     QGraphicsScene*      videoScene         = nullptr;
0043     QGraphicsView*       videoView          = nullptr;
0044     QGraphicsVideoItem*  videoItem          = nullptr;
0045 
0046     QAVPlayer*           player             = nullptr;
0047     QAVAudioOutput*      audioOutput        = nullptr;
0048 
0049     QVideoFrame          videoFrame;
0050     QMutex               mutex;
0051 
0052     int                  videoOrientation   = 0;
0053 };
0054 
0055 DVideoWidget::DVideoWidget(QWidget* const parent)
0056     : QFrame(parent),
0057       d     (new Private)
0058 {
0059     setMouseTracking(true);
0060     setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
0061     setLineWidth(1);
0062 
0063     d->videoScene  = new QGraphicsScene(parent);
0064     d->videoView   = new QGraphicsView(d->videoScene);
0065     d->videoView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0066     d->videoView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0067     d->videoView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
0068     d->videoItem   = new QGraphicsVideoItem();
0069     d->videoScene->addItem(d->videoItem);
0070     d->videoItem->setAspectRatioMode(Qt::IgnoreAspectRatio);
0071     d->videoView->setMouseTracking(true);
0072 
0073     QVBoxLayout* const vbox2 = new QVBoxLayout(this);
0074     vbox2->addWidget(d->videoView);
0075     vbox2->setContentsMargins(QMargins(0, 0, 0, 9));
0076     vbox2->setSpacing(0);
0077 
0078     d->player      = new QAVPlayer(this);
0079 
0080     d->audioOutput = new QAVAudioOutput(this);
0081 
0082     connect(d->player, &QAVPlayer::audioFrame,
0083             this, &DVideoWidget::slotAudioFrame,
0084             Qt::DirectConnection);
0085 
0086     connect(d->player, &QAVPlayer::videoFrame,
0087             this, &DVideoWidget::slotVideoFrame,
0088             Qt::DirectConnection);
0089 }
0090 
0091 DVideoWidget::~DVideoWidget()
0092 {
0093     delete d;
0094 }
0095 
0096 QAVPlayer* DVideoWidget::player() const
0097 {
0098     return d->player;
0099 }
0100 
0101 QGraphicsView* DVideoWidget::view() const
0102 {
0103     return d->videoView;
0104 }
0105 
0106 QAVAudioOutput* DVideoWidget::audioOutput() const
0107 {
0108     return d->audioOutput;
0109 }
0110 
0111 QVideoFrame DVideoWidget::videoFrame() const
0112 {
0113     QMutexLocker lock(&d->mutex);
0114 
0115     return d->videoFrame;
0116 }
0117 
0118 void DVideoWidget::slotAudioFrame(const QAVAudioFrame& frame)
0119 {
0120     d->audioOutput->play(frame);
0121 }
0122 
0123 void DVideoWidget::slotVideoFrame(const QAVVideoFrame& frame)
0124 {
0125     if (!d->videoItem->videoSurface())
0126     {
0127         return;
0128     }
0129 
0130     {
0131         QMutexLocker lock(&d->mutex);
0132 
0133         d->videoFrame = frame.convertTo(AV_PIX_FMT_RGB32);
0134     }
0135 
0136     if (
0137         !d->videoItem->videoSurface()->isActive() ||
0138         (d->videoItem->videoSurface()->surfaceFormat().frameSize() != d->videoFrame.size())
0139        )
0140     {
0141         QVideoSurfaceFormat f(d->videoFrame.size(), d->videoFrame.pixelFormat(), d->videoFrame.handleType());
0142         d->videoItem->videoSurface()->start(f);
0143     }
0144 
0145     if (d->videoItem->videoSurface()->isActive())
0146     {
0147          d->videoItem->videoSurface()->present(d->videoFrame);
0148     }
0149 
0150     Q_EMIT positionChanged(d->player->position());
0151 }
0152 
0153 int DVideoWidget::videoMediaOrientation() const
0154 {
0155     int orientation = 0;
0156 
0157     if (d->player->availableVideoStreams().count() > 0)
0158     {
0159         qCDebug(DIGIKAM_GENERAL_LOG) << "Video Metadata from" << d->player->source();
0160         qCDebug(DIGIKAM_GENERAL_LOG) << "---";
0161         qCDebug(DIGIKAM_GENERAL_LOG) << "Video Streams Available:" << d->player->availableVideoStreams().count();
0162 
0163         Q_FOREACH (const QAVStream& vstream, d->player->availableVideoStreams())
0164         {
0165             QMap<QString, QString> vals = vstream.metadata();
0166 
0167             for (QMap<QString, QString>::const_iterator it = vals.constBegin() ; it != vals.constEnd() ; ++it)
0168             {
0169                 qCDebug(DIGIKAM_GENERAL_LOG) << it.key() << it.value();
0170             }
0171         }
0172 
0173         qCDebug(DIGIKAM_GENERAL_LOG) << "---";
0174 
0175         QList<QAVStream> vstream = d->player->currentVideoStreams();
0176 
0177         if (!vstream.isEmpty())
0178         {
0179             QMap<QString, QString> vals = vstream.first().metadata();
0180 
0181             if (!vals.isEmpty())
0182             {
0183                 if (vals.contains(QLatin1String("rotate")))
0184                 {
0185                     orientation = vals[QLatin1String("rotate")].toInt();
0186                 }
0187             }
0188         }
0189     }
0190 
0191     return orientation;
0192 }
0193 
0194 void DVideoWidget::adjustVideoSize()
0195 {
0196     d->videoItem->resetTransform();
0197 
0198     QSizeF nativeSize    = d->videoItem->nativeSize();
0199     int mediaOrientation = videoMediaOrientation();
0200 
0201     if ((nativeSize.width()  < 1.0) ||
0202         (nativeSize.height() < 1.0))
0203     {
0204         return;
0205     }
0206 
0207     if ((mediaOrientation == 90) ||
0208         (mediaOrientation == 270))
0209     {
0210         nativeSize.transpose();
0211     }
0212 
0213     double ratio = (nativeSize.width() /
0214                     nativeSize.height());
0215 
0216     if (d->videoView->width() > d->videoView->height())
0217     {
0218         QSizeF vsize(d->videoView->height() * ratio,
0219                      d->videoView->height());
0220         d->videoItem->setSize(vsize);
0221     }
0222     else
0223     {
0224         QSizeF vsize(d->videoView->width(),
0225                      d->videoView->width() / ratio);
0226         d->videoItem->setSize(vsize);
0227     }
0228 
0229     d->videoView->setSceneRect(0, 0, d->videoItem->size().width(),
0230                                      d->videoItem->size().height());
0231 
0232     QPointF center = d->videoItem->boundingRect().center();
0233     d->videoItem->setTransformOriginPoint(center);
0234     d->videoItem->setRotation(d->videoOrientation);
0235 
0236     d->videoView->fitInView(d->videoItem, Qt::KeepAspectRatio);
0237     d->videoView->centerOn(d->videoItem);
0238     d->videoView->raise();
0239 }
0240 
0241 void DVideoWidget::setVideoItemOrientation(int orientation)
0242 {
0243     d->videoOrientation = orientation;
0244     adjustVideoSize();
0245 }
0246 
0247 int DVideoWidget::videoItemOrientation() const
0248 {
0249     return d->videoOrientation;
0250 }
0251 
0252 } // namespace Digikam
0253 
0254 #include "moc_dvideowidget.cpp"