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 4df2a30c57c150d30d34e4cd1641a076cf3010f6 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 05:27:53 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> 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 12c4b7ee91c6fb1e2a1e4a5c8828372ddfad5a9f 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 05:27:47 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> 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();