Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Crash when deleting QMediaPlayer



  • I have a custom QAbstractVideoSurface attached to using QMediaPlayer using QMediaPlayer::setVideoOutput. I setup the media player like this:

      m_MediaPlayer = new QMediaPlayer(this, QMediaPlayer::VideoSurface);
      m_MediaPlayer->setMedia(url);
      m_MediaPlayer->setVideoOutput(m_VideoSurface);
    

    When my class containing these objects is deleted, the video surface gets deleted with it (since the video surface is a child QObject). This causes a crash in dsengined.dll!EVRCustomPresenter::flush() Line 1146 at c:\users\qt\work\qt\qtmultimedia\src\plugins\common\evr\evrcustompresenter.cpp(1146)

        if (m_renderState == RenderStopped && m_surface->isActive()) {
    

    since m_surface is junk at that point.

    To work around this, I have to unparent the video surface from my parent object and then in the destructor do a m_videoSurface->deleteLater()

    Feels like the dsengined.dll!EVRCustomPresenter::flush() should check to make sure m_surface is not null before dereferencing it.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    How are you building that object ?
    What version of Qt are you using ?
    On what version of Windows ?



  • Hi and welcome to devnet,

    Thanks :)

    How are you building that object ?

    class VideoFileVideoSource : public AbstractVideoSource
    {
      friend class VideoFileVideoSurface;
      Q_OBJECT
    
    public:
      VideoFileVideoSource(const QUrl &url, QObject *parent);
      ~VideoFileVideoSource();
    
      // Overrides
      virtual bool CanEditSettings() const;
      virtual void EditSettings(QWidget *parent);
    
    signals:
      void FrameAvailable(const QVideoFrame &frame);
    
    private slots:
      void MediaPlayerError(QMediaPlayer::Error error);
      void MediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus status);
      void MediaPlayerStateChanged(QMediaPlayer::State newState);
    
    private:
      // AbstractVideoSource overrides
      void DoStart();
      void DoStop();
    
      // Called by VideoFileVideoSurface when a new video frame is available
      void VideoSurfaceFrameAvailable(const QVideoFrame &frame);
    
      void LoadSettings();
      void SaveSettings();
    
      QMediaPlayer *m_MediaPlayer;
      VideoFileVideoSurface *m_VideoSurface;
    };
    
    
    class VideoFileVideoSurface : public QAbstractVideoSurface
    {
      VideoFileVideoSource *m_Parent;
    public:
    
      VideoFileVideoSurface(VideoFileVideoSource *parent)
      : QAbstractVideoSurface(nullptr) // Don't pass parent in here since we want to control when things get deleted
      , m_Parent(parent)
      {
      }
    
      QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const
      {
        // Return the formats you will support
        Q_UNUSED(handleType);
    
        return AppUtils::GetSupportedPixelFormats();
      }
    
      bool present(const QVideoFrame &frame)
      {
        // Handle the frame and do your processing
        Q_UNUSED(frame);
    
    //    qDebug() << "VideoSurface::present pixelFormat: " << frame.pixelFormat();
    
        m_Parent->VideoSurfaceFrameAvailable(frame);
    
        return true;
      }
    };
    
    ///////////////////////////////////////////////////////////////////////////////////////////////////
    VideoFileVideoSource::VideoFileVideoSource(const QUrl &url, QObject *parent)
    : AbstractVideoSource(parent)
    , m_MediaPlayer(nullptr)
    , m_VideoSurface(nullptr)
    {
      m_VideoSurface = new VideoFileVideoSurface(this);
    
      m_MediaPlayer = new QMediaPlayer(this, QMediaPlayer::VideoSurface);
      m_MediaPlayer->setMedia(url);
      m_MediaPlayer->setVideoOutput(m_VideoSurface);
    
      // This will set the playback rate directly onto the media player object
      LoadSettings();
    
      connect(m_MediaPlayer, SIGNAL(error(QMediaPlayer::Error)),
                             SLOT(MediaPlayerError(QMediaPlayer::Error)));
      connect(m_MediaPlayer, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
                             SLOT(MediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus)));
      connect(m_MediaPlayer, SIGNAL(stateChanged(QMediaPlayer::State)),
                             SLOT(MediaPlayerStateChanged(QMediaPlayer::State)));
    }
    
    VideoFileVideoSource::~VideoFileVideoSource()
    {
      // Can't delete it now since there is a crash deep in Qt's directshow relatd code that assumes
      // that the surface still exists (only on windows).
      m_VideoSurface->deleteLater();
      m_VideoSurface = nullptr;
    }
    
    bool VideoFileVideoSource::CanEditSettings() const
    {
      return m_MediaPlayer->playbackRate() > 0.0;
    }
    
    void VideoFileVideoSource::EditSettings(QWidget *parent)
    {
      double playbackRate = m_MediaPlayer->playbackRate();
      VideoFilesSettingsDialog dlg(playbackRate, parent);
      int result = dlg.exec();
      if (result == QDialog::Accepted)
      {
        double playbackRate = dlg.GetPlaybackRate();
        m_MediaPlayer->setPlaybackRate(playbackRate);
    
        SaveSettings();
      }
    }
    
    void VideoFileVideoSource::MediaPlayerError(QMediaPlayer::Error error)
    {
      qCritical() << "MediaPlayerError: " << error;
    }
    
    void VideoFileVideoSource::MediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus status)
    {
      qDebug() << "MediaPlayerMediaStatusChanged: " << status;
    }
    
    void VideoFileVideoSource::MediaPlayerStateChanged(QMediaPlayer::State newState)
    {
      qDebug() << "MediaPlayerStateChanged: " << newState;
    
      // Loop
      if (newState == QMediaPlayer::StoppedState)
      {
        m_MediaPlayer->setPosition(0);
        m_MediaPlayer->play();
      }
    }
    
    void VideoFileVideoSource::DoStart()
    {
      m_MediaPlayer->play();
    }
    
    void VideoFileVideoSource::DoStop()
    {
      m_MediaPlayer->pause();
    }
    
    void VideoFileVideoSource::VideoSurfaceFrameAvailable(const QVideoFrame &frame)
    {
      // So, present on the QAbstractVideoSurface gets called even when we call pause and stop
      // so let's only forward the frame when the players state is actually playing
      if (m_MediaPlayer->state() == QMediaPlayer::PlayingState)
      {
        emit FrameAvailable(frame);
      }
    }
    
    void VideoFileVideoSource::LoadSettings()
    {
      QSettings settings(AppUtils::GetSettingsPath(), QSettings::IniFormat);
    
      settings.beginGroup("VideoFileVideoSource");
      double playbackRate = settings.value("PlaybackRate", m_MediaPlayer->playbackRate()).toDouble();
      if (playbackRate > 0.001)
      {
        m_MediaPlayer->setPlaybackRate(playbackRate);
      }
      settings.endGroup();
    }
    
    void VideoFileVideoSource::SaveSettings()
    {
      QSettings settings(AppUtils::GetSettingsPath(), QSettings::IniFormat);
    
      settings.beginGroup("VideoFileVideoSource");
      settings.setValue("PlaybackRate", m_MediaPlayer->playbackRate());
      settings.endGroup();
    }
    
    
    

    What version of Qt are you using ?

    5.13.1

    On what version of Windows ?

    Windows 10 Pro Version: 1903


  • Lifetime Qt Champion

    Since you seem to be properly parenting your object, why are you trying to delete them in the destructor ?



  • @sgaist This is the code with the workaround. The original code didn't do anything in the destructor and was relying on the QObject child lifecycle management for the object to be destroyed.

    The original code (which I guess I should have posted) passed in the VideoFileVideoSource as the parent to the QAbstractVideoSurface constructor:

    VideoFileVideoSurface(VideoFileVideoSource *parent)
      : QAbstractVideoSurface(parent) // Code above passes in nullptr
    

    And the destructor of VideoFileVideoSource was just this:

    VideoFileVideoSource::~VideoFileVideoSource()
    {
    }
    

    One tick after that destructor executed was the crash, as the surface was automatically deleted by QObject (but the direct show code, which was running on some system callback or something, still assumed that the surface was alive). If I set the video surface to nullptr in the destructor m_MediaPlayer->setVideoOutput((QAbstractVideoSurface *)nullptr);, the crash would be immediate (i.e. right in the destructor call, as all over the directshow code, the surface is assumed to be non-null and still alive.


  • Lifetime Qt Champion

    Do you have the full stack trace ?



  • @sgaist ![Here's the Visual Studio call stack](0_1568809382430_75d15e07-94ca-453d-bd89-163aca23f18d-image.png image url)
    That's when I pass the VideoFileVideoSource parent to the QAbstractVideoSurface constructor and empty the constructor, as in the last code snippet I sent.

    The line numbers all correspond to source line numbers of Qt 5.13.1


  • Lifetime Qt Champion

    Do you get the same crash with Qt 5.12 ?



  • @sgaist I haven't tried any other versions of Qt. First time working with Qt in about 8 years and this is a brand new project


  • Lifetime Qt Champion

    Then I recommend testing the latest Qt 5.12 to see if you found a new issue or if there's something specific to your machine.


Log in to reply