avmedia/source/qt6/QtFrameGrabber.cxx |    7 ---
 avmedia/source/qt6/QtPlayer.cxx       |   79 +++++++++++++++++++++++++++++-----
 avmedia/source/qt6/QtPlayer.hxx       |    7 +++
 3 files changed, 77 insertions(+), 16 deletions(-)

New commits:
commit c69b0bbe94047a2edee1b1b1e10b0f9baf1a35f6
Author:     Michael Weghorn <m.wegh...@posteo.de>
AuthorDate: Mon Jun 17 17:37:49 2024 +0200
Commit:     Michael Weghorn <m.wegh...@posteo.de>
CommitDate: Tue Jun 18 07:10:33 2024 +0200

    tdf#145735 qt avmedia: Don't deadlock with QGstreamerMediaPlayer
    
    While opending a slide with a video worked fine for
    me with the qt6 VCL plugin and a local Qt development
    build on Debian testing (qtbase as of
    8915ae3a75c4a356d94962dd9b31e1458f2a506f,
    qtwayland as of deae8b9ce9f551b29ef98d0bb827a8543af2797e,
    qtmultimedia as of 235ba5f273fbb7dfed8ba3736e4444a85aee5770),
    this resulted in a freeze when using Debian's system-provided
    Qt packages instead (libqt6multimedia6:amd64 6.4.2-11+b2).
    
    While the self-compiled Qt dev is using `QFFmpegMediaPlayer`
    which asynchronously loads media, the system QtMultimedia
    is using `QGstreamerMediaPlayer` (s. frame 37 in below backtrace)
    that apparently doesn't use multiple threads.
    
    Therefore, using `Qt::BlockingQueuedConnection` is problematic,
    as its documentation [1] says:
    
    > Same as Qt::QueuedConnection, except that the signalling thread blocks
    > until the slot returns. This connection must not be used if the receiver
    > lives in the signalling thread, or else the application will deadlock.
    
    Use `Qt::AutoConnection` (= 0, the default) instead and specify the
    `Qt::SingleShotConnection` flag in addition to ensure the slot
    gets called only once:
    
    > This is a flag that can be combined with any one of the above connection
    > types, using a bitwise OR. When Qt::SingleShotConnection is set, the
    > slot is going to be called only once; the connection will be
    > automatically broken when the signal is emitted. This flag was
    > introduced in Qt 6.0.
    
    Drop the now no longer needed manual disconnect.
    
    This makes the scenario work with both, the custom-compiled
    Qt dev using `QFFmpegMediaPlayer` and the system-provided
    Qt 6.4.2 using `QGstreamerMediaPlayer`.
    
    Side note: Unrelated to the issue addressed here, using the
    system-provided Qt with `QGstreamerMediaPlayer` results
    in a crash when started via the soffice shell script wrapper
    with a LibreOffice debug build or when using soffice.bin directly
    and manually setting `MALLOC_PERTURB_=153`, which indicates
    some memory issue. That could be within Qt, though, haven't
    analyzed that further.
    
    Backtrace of deadlock:
    
        1 syscall syscall.S 38 0x7f40a83249f9
        2 QSemaphore::acquire(int) 0x7f40948714e2
        3 ?? 0x7f409477fede
        4 QVideoSink::videoFrameChanged(QVideoFrame const&) const 0x7f4095195376
        5 ?? 0x7f405c219f5c
        6 ?? 0x7f405c21a25b
        7 QApplicationPrivate::notify_helper(QObject *, QEvent *) 0x7f4093782d62
        8 QCoreApplication::notifyInternal2(QObject *, QEvent *) 0x7f40947356d8
        9 QCoreApplicationPrivate::sendPostedEvents(QObject *, int, QThreadData 
*) 0x7f40947358b7
        10 ?? 0x7f4094925257
        11 ?? 0x7f409ab0de3f
        12 ?? 0x7f409ab0fec7
        13 g_main_context_iteration 0x7f409ab104e0
        14 
QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) 
0x7f4094922f60
        15 QtInstance::ImplYield QtInstance.cxx 455 0x7f4094e534e0
        16 QtInstance::DoYield QtInstance.cxx 464 0x7f4094e568a5
        17 ImplYield svapp.cxx 384 0x7f409f3c48cc
        18 Scheduler::ProcessEventsToIdle svapp.cxx 419 0x7f409f3c4bc8
        19 avmedia::qt::QtFrameGrabber::grabFrame QtFrameGrabber.cxx 106 
0x7f405cac5524
        20 non-virtual thunk to avmedia::qt::QtFrameGrabber::grabFrame(double) 
0x7f405cac564e
        21 avmedia::MediaWindow::grabFrame mediawindow.cxx 385 0x7f40a03e41ad
        22 SdrMediaObj::getSnapshot() 
const::$_0::operator()(com::sun::star::uno::Reference<com::sun::star::media::XPlayer>
 const&) const svdomedia.cxx 195 0x7f40a249e5b5
        23 std::__invoke_impl<void, SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> 
const&>(std::__invoke_other, SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&) invoke.h 
61 0x7f40a249e52d
        24 std::__invoke_r<void, SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> 
const&>(SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&) invoke.h 
111 0x7f40a249e4dd
        25 std::_Function_handler<void 
(com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&), 
SdrMediaObj::getSnapshot() const::$_0>::_M_invoke(std::_Any_data const&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&) 
std_function.h 290 0x7f40a249e345
        26 std::function<void 
(com::sun::star::uno::Reference<com::sun::star::media::XPlayer> 
const&)>::operator()(com::sun::star::uno::Reference<com::sun::star::media::XPlayer>
 const&) const std_function.h 591 0x7f40a03e96cd
        27 avmedia::PlayerListener::callPlayerWindowSizeAvailable 
mediawindow.hxx 76 0x7f40a03e6bf1
        28 avmedia::PlayerListener::preferredPlayerWindowSizeAvailable 
mediawindow.cxx 496 0x7f40a03e5055
        29 avmedia::qt::QtPlayer::notifyListeners QtPlayer.cxx 395 
0x7f405cacfd53
        30 avmedia::qt::QtPlayer::notifyIfReady QtPlayer.cxx 326 0x7f405cacfb42
        31 QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, 
QtPrivate::List<QMediaPlayer::MediaStatus>, void, void (avmedia::qt::QtPlayer:: 
*)(QMediaPlayer::MediaStatus)>::call qobjectdefs_impl.h 135 0x7f405cad477b
        32 QtPrivate::FunctionPointer<void (avmedia::qt::QtPlayer:: 
*)(QMediaPlayer::MediaStatus)>::call<QtPrivate::List<QMediaPlayer::MediaStatus>,
 void> qobjectdefs_impl.h 172 0x7f405cad46cd
        33 QtPrivate::QSlotObject<void (avmedia::qt::QtPlayer:: 
*)(QMediaPlayer::MediaStatus), QtPrivate::List<QMediaPlayer::MediaStatus>, 
void>::impl qobjectdefs_impl.h 383 0x7f405cad4612
        34 ?? 0x7f409477fbbe
        35 QMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus) 
0x7f4095184a05
        36 ?? 0x7f405c210740
        37 non-virtual thunk to 
QGstreamerMediaPlayer::processBusMessage(QGstreamerMessage const&) 
0x7f405c2009f7
        38 ?? 0x7f405c21aa94
        39 QObject::event(QEvent *) 0x7f40947723e0
        40 QApplicationPrivate::notify_helper(QObject *, QEvent *) 
0x7f4093782d62
        41 QCoreApplication::notifyInternal2(QObject *, QEvent *) 0x7f40947356d8
        42 QCoreApplicationPrivate::sendPostedEvents(QObject *, int, 
QThreadData *) 0x7f40947358b7
        43 ?? 0x7f4094925257
        44 ?? 0x7f409ab0de3f
        45 ?? 0x7f409ab0fec7
        46 g_main_context_iteration 0x7f409ab104e0
        47 
QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) 
0x7f4094922f60
        48 QtInstance::ImplYield QtInstance.cxx 455 0x7f4094e534e0
        49 QtInstance::DoYield QtInstance.cxx 464 0x7f4094e568a5
        50 ImplYield svapp.cxx 384 0x7f409f3c48cc
        51 Scheduler::ProcessEventsToIdle svapp.cxx 419 0x7f409f3c4bc8
        52 avmedia::qt::QtFrameGrabber::grabFrame QtFrameGrabber.cxx 106 
0x7f405cac5524
        53 non-virtual thunk to avmedia::qt::QtFrameGrabber::grabFrame(double) 
0x7f405cac564e
        54 avmedia::MediaWindow::grabFrame mediawindow.cxx 385 0x7f40a03e41ad
        55 SdrMediaObj::getSnapshot() 
const::$_0::operator()(com::sun::star::uno::Reference<com::sun::star::media::XPlayer>
 const&) const svdomedia.cxx 195 0x7f40a249e5b5
        56 std::__invoke_impl<void, SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> 
const&>(std::__invoke_other, SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&) invoke.h 
61 0x7f40a249e52d
        57 std::__invoke_r<void, SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> 
const&>(SdrMediaObj::getSnapshot() const::$_0&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&) invoke.h 
111 0x7f40a249e4dd
        58 std::_Function_handler<void 
(com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&), 
SdrMediaObj::getSnapshot() const::$_0>::_M_invoke(std::_Any_data const&, 
com::sun::star::uno::Reference<com::sun::star::media::XPlayer> const&) 
std_function.h 290 0x7f40a249e345
        59 std::function<void 
(com::sun::star::uno::Reference<com::sun::star::media::XPlayer> 
const&)>::operator()(com::sun::star::uno::Reference<com::sun::star::media::XPlayer>
 const&) const std_function.h 591 0x7f40a03e96cd
        60 avmedia::PlayerListener::callPlayerWindowSizeAvailable 
mediawindow.hxx 76 0x7f40a03e6bf1
        61 avmedia::PlayerListener::preferredPlayerWindowSizeAvailable 
mediawindow.cxx 496 0x7f40a03e5055
        62 avmedia::qt::QtPlayer::notifyListeners QtPlayer.cxx 395 
0x7f405cacfd53
        63 avmedia::qt::QtPlayer::notifyIfReady QtPlayer.cxx 326 0x7f405cacfb42
        64 QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, 
QtPrivate::List<QMediaPlayer::MediaStatus>, void, void (avmedia::qt::QtPlayer:: 
*)(QMediaPlayer::MediaStatus)>::call qobjectdefs_impl.h 135 0x7f405cad477b
        65 QtPrivate::FunctionPointer<void (avmedia::qt::QtPlayer:: 
*)(QMediaPlayer::MediaStatus)>::call<QtPrivate::List<QMediaPlayer::MediaStatus>,
 void> qobjectdefs_impl.h 172 0x7f405cad46cd
        66 QtPrivate::QSlotObject<void (avmedia::qt::QtPlayer:: 
*)(QMediaPlayer::MediaStatus), QtPrivate::List<QMediaPlayer::MediaStatus>, 
void>::impl qobjectdefs_impl.h 383 0x7f405cad4612
    
    [1] https://doc.qt.io/qt-6/qt.html#ConnectionType-enum
    
    Change-Id: Ia8bfd19b0c0c4f970a5eb200c2a0b45784ef25fd
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169036
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.wegh...@posteo.de>
    (cherry picked from commit 4df2a30c57c150d30d34e4cd1641a076cf3010f6)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169097

diff --git a/avmedia/source/qt6/QtFrameGrabber.cxx 
b/avmedia/source/qt6/QtFrameGrabber.cxx
index d58c274520ef..a9cf99fadc7f 100644
--- a/avmedia/source/qt6/QtFrameGrabber.cxx
+++ b/avmedia/source/qt6/QtFrameGrabber.cxx
@@ -55,7 +55,7 @@ QtFrameGrabber::QtFrameGrabber(const QUrl& rSourceUrl)
     m_xMediaPlayer->setVideoSink(m_xVideoSink.get());
 
     connect(m_xMediaPlayer.get(), &QMediaPlayer::errorOccurred, this,
-            &QtFrameGrabber::onErrorOccured, Qt::BlockingQueuedConnection);
+            &QtFrameGrabber::onErrorOccured, Qt::SingleShotConnection);
 }
 
 void QtFrameGrabber::onErrorOccured(QMediaPlayer::Error eError, const QString& 
rErrorString)
@@ -72,9 +72,6 @@ void QtFrameGrabber::onVideoFrameChanged(const QVideoFrame& 
rFrame)
 {
     std::lock_guard aLock(m_aMutex);
 
-    disconnect(m_xVideoSink.get(), &QVideoSink::videoFrameChanged, this,
-               &QtFrameGrabber::onVideoFrameChanged);
-
     const QImage aImage = rFrame.toImage();
     m_xGraphic = toXGraphic(aImage);
     m_bWaitingForFrame = false;
@@ -90,7 +87,7 @@ css::uno::Reference<css::graphic::XGraphic> SAL_CALL 
QtFrameGrabber::grabFrame(d
     // until the first frame has been received
     m_bWaitingForFrame = true;
     connect(m_xVideoSink.get(), &QVideoSink::videoFrameChanged, this,
-            &QtFrameGrabber::onVideoFrameChanged, 
Qt::BlockingQueuedConnection);
+            &QtFrameGrabber::onVideoFrameChanged, Qt::SingleShotConnection);
     m_xMediaPlayer->play();
     while (m_bWaitingForFrame)
     {
commit 266620c7822ed000b9983cb3a0068be4fd2ed3fb
Author:     Michael Weghorn <m.wegh...@posteo.de>
AuthorDate: Mon Jun 17 16:27:56 2024 +0200
Commit:     Michael Weghorn <m.wegh...@posteo.de>
CommitDate: Tue Jun 18 07:10:25 2024 +0200

    tdf#145735 qt avmedia: Show audio placeholder for audio files
    
    So far, `QtPlayer::createPlayerWindow` was unconditionally
    creating a video widget.
    
    However, audio-only media files can be used as well,
    therefore handle that case, too and create a widget
    holding an audio icon as a placeholder instead if
    the media doesn't contain video.
    (This is the same icon shown when using qt5 that
    doesn't use QtMultimedia).
    
    As described in
    
        commit a99575f04fa9c858bfcd996f037444135810d43f
        Author: Michael Weghorn <m.wegh...@posteo.de>
        Date:   Sat Jun 1 07:32:22 2024 +0200
    
            tdf#194504 qt avmedia: Don't wait for video frame if there's none
    
    , `QMediaPlayer::hasVideo()` only returns a useful
    result once loading media has finished. Therefore,
    if the player is still in that state in
    `QtPlayer::createPlayerWindow`, defer
    creating the widget to when the media status
    changes.
    
    With this commit in place, opening an Impress presentation
    that contains an audio file (like attachment 194504
    from tdf#145735) and starting presentation mode now shows an
    "audio icon" as placeholder as expected when using
    the qt6 VCL plugin.
    
    (This commit here makes that work for presentation
    mode, while the above-mentioned commit already made
    the icon show as expected for non-presentation mode.)
    
    Change-Id: I1ff7e8b8659162a748abc3f97a8d2181375c0e7c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169009
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.wegh...@posteo.de>
    (cherry picked from commit 12c4b7ee91c6fb1e2a1e4a5c8828372ddfad5a9f)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169096

diff --git a/avmedia/source/qt6/QtPlayer.cxx b/avmedia/source/qt6/QtPlayer.cxx
index d5291d5b0d5a..5cd19a3c2df5 100644
--- a/avmedia/source/qt6/QtPlayer.cxx
+++ b/avmedia/source/qt6/QtPlayer.cxx
@@ -13,6 +13,7 @@
 #include <QtMultimedia/QAudioOutput>
 #include <QtMultimedia/QMediaMetaData>
 #include <QtMultimediaWidgets/QVideoWidget>
+#include <QtWidgets/QLabel>
 #include <QtWidgets/QLayout>
 
 #include <cppuhelper/supportsservice.hxx>
@@ -20,6 +21,7 @@
 #include <rtl/string.hxx>
 #include <tools/link.hxx>
 #include <vcl/BitmapTools.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
 #include <vcl/graph.hxx>
 #include <vcl/svapp.hxx>
 #include <vcl/syschild.hxx>
@@ -47,6 +49,7 @@ namespace avmedia::qt
 QtPlayer::QtPlayer()
     : QtPlayer_BASE(m_aMutex)
     , m_lListener(m_aMutex)
+    , m_pMediaWidgetParent(nullptr)
 {
 }
 
@@ -197,6 +200,9 @@ uno::Reference<::media::XPlayerWindow>
 {
     osl::MutexGuard aGuard(m_aMutex);
 
+    if (rArguments.getLength() > 1)
+        rArguments[1] >>= m_aPlayerWidgetRect;
+
     if (rArguments.getLength() <= 2)
     {
         uno::Reference<::media::XPlayerWindow> xRet = new 
::avmedia::gstreamer::Window;
@@ -213,18 +219,20 @@ uno::Reference<::media::XPlayerWindow>
     if (!pParentEnvData)
         return nullptr;
 
-    QWidget* pParent = static_cast<QWidget*>(pParentEnvData->pWidget);
-    QVideoWidget* pVideoWidget = new QVideoWidget(pParent);
-    pVideoWidget->setAspectRatioMode(Qt::IgnoreAspectRatio);
-    pVideoWidget->setSizePolicy(QSizePolicy::Expanding, 
QSizePolicy::Expanding);
-
-    assert(!m_xMediaPlayer->videoOutput() && "Video widget already set.");
-    m_xMediaPlayer->setVideoOutput(pVideoWidget);
+    m_pMediaWidgetParent = static_cast<QWidget*>(pParentEnvData->pWidget);
+    assert(m_pMediaWidgetParent);
 
-    // retrieve the layout (which is set in the QtObjectWidget ctor)
-    QLayout* pLayout = pParent->layout();
-    assert(pLayout);
-    pLayout->addWidget(pVideoWidget);
+    // while media is loading, QMediaPlayer::hasVideo doesn't yet return
+    // whether media actually has video; defer creating audio/video widget
+    if (m_xMediaPlayer->mediaStatus() == QMediaPlayer::LoadingMedia)
+    {
+        connect(m_xMediaPlayer.get(), &QMediaPlayer::mediaStatusChanged, this,
+                &QtPlayer::createMediaPlayerWidget, Qt::SingleShotConnection);
+    }
+    else
+    {
+        createMediaPlayerWidget();
+    }
 
     uno::Reference<::media::XPlayerWindow> xRet = new 
::avmedia::gstreamer::Window;
     return xRet;
@@ -320,6 +328,55 @@ void QtPlayer::notifyIfReady(QMediaPlayer::MediaStatus)
     }
 }
 
+void QtPlayer::createMediaPlayerWidget()
+{
+    assert(m_xMediaPlayer);
+    assert(m_xMediaPlayer->mediaStatus() != QMediaPlayer::LoadingMedia
+           && "Media is still loading, detecting video availability not 
possible.");
+
+    assert(m_pMediaWidgetParent && "Parent for media widget not set");
+
+    // if media contains video, show the video output,
+    // otherwise show an audio icon as a placeholder
+    QWidget* pWidget;
+    if (m_xMediaPlayer->hasVideo())
+    {
+        QVideoWidget* pVideoWidget = new QVideoWidget(m_pMediaWidgetParent);
+        pVideoWidget->setAspectRatioMode(Qt::IgnoreAspectRatio);
+
+        assert(!m_xMediaPlayer->videoOutput() && "Video output already set.");
+        m_xMediaPlayer->setVideoOutput(pVideoWidget);
+
+        pWidget = pVideoWidget;
+    }
+    else
+    {
+        BitmapEx aPlaceholderIcon(u"avmedia/res/avaudiologo.png"_ustr);
+        SvMemoryStream aMemoryStream;
+        vcl::PngImageWriter aWriter(aMemoryStream);
+        aWriter.write(aPlaceholderIcon);
+        QPixmap aAudioPixmap;
+        aAudioPixmap.loadFromData(static_cast<const 
uchar*>(aMemoryStream.GetData()),
+                                  aMemoryStream.TellEnd());
+        assert(!aAudioPixmap.isNull() && "Failed to load audio logo");
+        aAudioPixmap
+            = aAudioPixmap.scaled(QSize(m_aPlayerWidgetRect.Width, 
m_aPlayerWidgetRect.Height));
+
+        QLabel* pLabel = new QLabel;
+        pLabel->setPixmap(aAudioPixmap);
+        pWidget = pLabel;
+    }
+
+    assert(pWidget);
+    pWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+    // retrieve the layout (which is set in the QtObjectWidget ctor)
+    QLayout* pLayout = m_pMediaWidgetParent->layout();
+    assert(pLayout);
+    assert(pLayout->count() == 0 && "Layout already has a widget set");
+    pLayout->addWidget(pWidget);
+}
+
 void QtPlayer::notifyListeners()
 {
     comphelper::OInterfaceContainerHelper2* pContainer
diff --git a/avmedia/source/qt6/QtPlayer.hxx b/avmedia/source/qt6/QtPlayer.hxx
index 212f297fdc8b..07d1de183c46 100644
--- a/avmedia/source/qt6/QtPlayer.hxx
+++ b/avmedia/source/qt6/QtPlayer.hxx
@@ -12,6 +12,7 @@
 #include <sal/config.h>
 
 #include <QtMultimedia/QMediaPlayer>
+#include <QtWidgets/QWidget>
 
 #include <com/sun/star/lang/XServiceInfo.hpp>
 #include <com/sun/star/media/XPlayer.hpp>
@@ -71,6 +72,12 @@ private:
     std::unique_ptr<QMediaPlayer> m_xMediaPlayer;
     comphelper::OMultiTypeInterfaceContainerHelper2 m_lListener;
 
+    // area to use for the player widget
+    css::awt::Rectangle m_aPlayerWidgetRect;
+
+    QWidget* m_pMediaWidgetParent;
+
+    void createMediaPlayerWidget();
     bool isReadyToPlay();
 
     void installNotify();

Reply via email to