diff options
Diffstat (limited to 'src/plugins/multimedia/windows/player')
10 files changed, 2896 insertions, 0 deletions
diff --git a/src/plugins/multimedia/windows/player/mfactivate.cpp b/src/plugins/multimedia/windows/player/mfactivate.cpp new file mode 100644 index 000000000..644c96529 --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfactivate.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "mfactivate_p.h" + +#include <mfapi.h> + +MFAbstractActivate::MFAbstractActivate() +{ + MFCreateAttributes(&m_attributes, 0); +} + +MFAbstractActivate::~MFAbstractActivate() +{ + if (m_attributes) + m_attributes->Release(); +} diff --git a/src/plugins/multimedia/windows/player/mfactivate_p.h b/src/plugins/multimedia/windows/player/mfactivate_p.h new file mode 100644 index 000000000..efe75474b --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfactivate_p.h @@ -0,0 +1,202 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef MFACTIVATE_H +#define MFACTIVATE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <mfidl.h> +#include <private/qcomobject_p.h> + +QT_BEGIN_NAMESPACE + +namespace QtPrivate { + +template <> +struct QComObjectTraits<IMFActivate> +{ + static constexpr bool isGuidOf(REFIID riid) noexcept + { + return QComObjectTraits<IMFActivate, IMFAttributes>::isGuidOf(riid); + } +}; + +} // namespace QtPrivate + +class MFAbstractActivate : public QComObject<IMFActivate> +{ +public: + explicit MFAbstractActivate(); + + //from IMFAttributes + STDMETHODIMP GetItem(REFGUID guidKey, PROPVARIANT *pValue) override + { + return m_attributes->GetItem(guidKey, pValue); + } + + STDMETHODIMP GetItemType(REFGUID guidKey, MF_ATTRIBUTE_TYPE *pType) override + { + return m_attributes->GetItemType(guidKey, pType); + } + + STDMETHODIMP CompareItem(REFGUID guidKey, REFPROPVARIANT Value, BOOL *pbResult) override + { + return m_attributes->CompareItem(guidKey, Value, pbResult); + } + + STDMETHODIMP Compare(IMFAttributes *pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, BOOL *pbResult) override + { + return m_attributes->Compare(pTheirs, MatchType, pbResult); + } + + STDMETHODIMP GetUINT32(REFGUID guidKey, UINT32 *punValue) override + { + return m_attributes->GetUINT32(guidKey, punValue); + } + + STDMETHODIMP GetUINT64(REFGUID guidKey, UINT64 *punValue) override + { + return m_attributes->GetUINT64(guidKey, punValue); + } + + STDMETHODIMP GetDouble(REFGUID guidKey, double *pfValue) override + { + return m_attributes->GetDouble(guidKey, pfValue); + } + + STDMETHODIMP GetGUID(REFGUID guidKey, GUID *pguidValue) override + { + return m_attributes->GetGUID(guidKey, pguidValue); + } + + STDMETHODIMP GetStringLength(REFGUID guidKey, UINT32 *pcchLength) override + { + return m_attributes->GetStringLength(guidKey, pcchLength); + } + + STDMETHODIMP GetString(REFGUID guidKey, LPWSTR pwszValue, UINT32 cchBufSize, UINT32 *pcchLength) override + { + return m_attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength); + } + + STDMETHODIMP GetAllocatedString(REFGUID guidKey, LPWSTR *ppwszValue, UINT32 *pcchLength) override + { + return m_attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength); + } + + STDMETHODIMP GetBlobSize(REFGUID guidKey, UINT32 *pcbBlobSize) override + { + return m_attributes->GetBlobSize(guidKey, pcbBlobSize); + } + + STDMETHODIMP GetBlob(REFGUID guidKey, UINT8 *pBuf, UINT32 cbBufSize, UINT32 *pcbBlobSize) override + { + return m_attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize); + } + + STDMETHODIMP GetAllocatedBlob(REFGUID guidKey, UINT8 **ppBuf, UINT32 *pcbSize) override + { + return m_attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize); + } + + STDMETHODIMP GetUnknown(REFGUID guidKey, REFIID riid, LPVOID *ppv) override + { + return m_attributes->GetUnknown(guidKey, riid, ppv); + } + + STDMETHODIMP SetItem(REFGUID guidKey, REFPROPVARIANT Value) override + { + return m_attributes->SetItem(guidKey, Value); + } + + STDMETHODIMP DeleteItem(REFGUID guidKey) override + { + return m_attributes->DeleteItem(guidKey); + } + + STDMETHODIMP DeleteAllItems() override + { + return m_attributes->DeleteAllItems(); + } + + STDMETHODIMP SetUINT32(REFGUID guidKey, UINT32 unValue) override + { + return m_attributes->SetUINT32(guidKey, unValue); + } + + STDMETHODIMP SetUINT64(REFGUID guidKey, UINT64 unValue) override + { + return m_attributes->SetUINT64(guidKey, unValue); + } + + STDMETHODIMP SetDouble(REFGUID guidKey, double fValue) override + { + return m_attributes->SetDouble(guidKey, fValue); + } + + STDMETHODIMP SetGUID(REFGUID guidKey, REFGUID guidValue) override + { + return m_attributes->SetGUID(guidKey, guidValue); + } + + STDMETHODIMP SetString(REFGUID guidKey, LPCWSTR wszValue) override + { + return m_attributes->SetString(guidKey, wszValue); + } + + STDMETHODIMP SetBlob(REFGUID guidKey, const UINT8 *pBuf, UINT32 cbBufSize) override + { + return m_attributes->SetBlob(guidKey, pBuf, cbBufSize); + } + + STDMETHODIMP SetUnknown(REFGUID guidKey, IUnknown *pUnknown) override + { + return m_attributes->SetUnknown(guidKey, pUnknown); + } + + STDMETHODIMP LockStore() override + { + return m_attributes->LockStore(); + } + + STDMETHODIMP UnlockStore() override + { + return m_attributes->UnlockStore(); + } + + STDMETHODIMP GetCount(UINT32 *pcItems) override + { + return m_attributes->GetCount(pcItems); + } + + STDMETHODIMP GetItemByIndex(UINT32 unIndex, GUID *pguidKey, PROPVARIANT *pValue) override + { + return m_attributes->GetItemByIndex(unIndex, pguidKey, pValue); + } + + STDMETHODIMP CopyAllItems(IMFAttributes *pDest) override + { + return m_attributes->CopyAllItems(pDest); + } + +protected: + // Destructor is not public. Caller should call Release. + ~MFAbstractActivate() override; + +private: + IMFAttributes *m_attributes = nullptr; +}; + +QT_END_NAMESPACE + +#endif // MFACTIVATE_H diff --git a/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol.cpp b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol.cpp new file mode 100644 index 000000000..109f7964b --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol.cpp @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "mfevrvideowindowcontrol_p.h" + +#include <qdebug.h> + +MFEvrVideoWindowControl::MFEvrVideoWindowControl(QVideoSink *parent) + : EvrVideoWindowControl(parent) + , m_currentActivate(NULL) + , m_evrSink(NULL) +{ +} + +MFEvrVideoWindowControl::~MFEvrVideoWindowControl() +{ + clear(); +} + +void MFEvrVideoWindowControl::clear() +{ + setEvr(NULL); + + if (m_evrSink) + m_evrSink->Release(); + if (m_currentActivate) { + m_currentActivate->ShutdownObject(); + m_currentActivate->Release(); + } + m_evrSink = NULL; + m_currentActivate = NULL; +} + +IMFActivate* MFEvrVideoWindowControl::createActivate() +{ + clear(); + + if (FAILED(MFCreateVideoRendererActivate(0, &m_currentActivate))) { + qWarning() << "Failed to create evr video renderer activate!"; + return NULL; + } + if (FAILED(m_currentActivate->ActivateObject(IID_IMFMediaSink, (LPVOID*)(&m_evrSink)))) { + qWarning() << "Failed to activate evr media sink!"; + return NULL; + } + if (!setEvr(m_evrSink)) + return NULL; + + return m_currentActivate; +} + +void MFEvrVideoWindowControl::releaseActivate() +{ + clear(); +} diff --git a/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol_p.h b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol_p.h new file mode 100644 index 000000000..1ac90e8ce --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfevrvideowindowcontrol_p.h @@ -0,0 +1,38 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef MFEVRVIDEOWINDOWCONTROL_H +#define MFEVRVIDEOWINDOWCONTROL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "evrvideowindowcontrol_p.h" + +QT_USE_NAMESPACE + +class MFEvrVideoWindowControl : public EvrVideoWindowControl +{ +public: + MFEvrVideoWindowControl(QVideoSink *parent = 0); + ~MFEvrVideoWindowControl(); + + IMFActivate* createActivate(); + void releaseActivate(); + +private: + void clear(); + + IMFActivate *m_currentActivate; + IMFMediaSink *m_evrSink; +}; + +#endif // MFEVRVIDEOWINDOWCONTROL_H diff --git a/src/plugins/multimedia/windows/player/mfplayercontrol.cpp b/src/plugins/multimedia/windows/player/mfplayercontrol.cpp new file mode 100644 index 000000000..ae0022773 --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfplayercontrol.cpp @@ -0,0 +1,306 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "mfplayercontrol_p.h" +#include "mfplayersession_p.h" +#include "mfvideorenderercontrol_p.h" +#include <qdebug.h> + +//#define DEBUG_MEDIAFOUNDATION + +MFPlayerControl::MFPlayerControl(QMediaPlayer *player) + : QPlatformMediaPlayer(player) + , m_state(QMediaPlayer::StoppedState) + , m_stateDirty(false) + , m_videoAvailable(false) + , m_audioAvailable(false) + , m_duration(0) + , m_seekable(false) +{ + m_session = new MFPlayerSession(this); +} + +MFPlayerControl::~MFPlayerControl() +{ + m_session->close(); + m_session->setPlayerControl(nullptr); + m_session->Release(); +} + +void MFPlayerControl::setMedia(const QUrl &media, QIODevice *stream) +{ + if (m_state != QMediaPlayer::StoppedState) { + changeState(QMediaPlayer::StoppedState); + m_session->stop(true); + refreshState(); + } + + m_media = media; + m_stream = stream; + resetAudioVideoAvailable(); + handleDurationUpdate(0); + handleSeekableUpdate(false); + m_session->load(media, stream); +} + +void MFPlayerControl::play() +{ + if (m_state == QMediaPlayer::PlayingState) + return; + resetCurrentLoop(); + if (QMediaPlayer::InvalidMedia == m_session->status()) + m_session->load(m_media, m_stream); + + switch (m_session->status()) { + case QMediaPlayer::NoMedia: + case QMediaPlayer::InvalidMedia: + return; + case QMediaPlayer::LoadedMedia: + case QMediaPlayer::BufferingMedia: + case QMediaPlayer::BufferedMedia: + case QMediaPlayer::EndOfMedia: + changeState(QMediaPlayer::PlayingState); + m_session->start(); + break; + default: //Loading/Stalled + changeState(QMediaPlayer::PlayingState); + break; + } + refreshState(); +} + +void MFPlayerControl::pause() +{ + if (m_state == QMediaPlayer::PausedState) + return; + + if (m_session->status() == QMediaPlayer::NoMedia || + m_session->status() == QMediaPlayer::InvalidMedia) + return; + + changeState(QMediaPlayer::PausedState); + m_session->pause(); + refreshState(); +} + +void MFPlayerControl::stop() +{ + if (m_state == QMediaPlayer::StoppedState) + return; + changeState(QMediaPlayer::StoppedState); + m_session->stop(); + refreshState(); +} + +QMediaMetaData MFPlayerControl::metaData() const +{ + return m_session->metaData(); +} + +void MFPlayerControl::setAudioOutput(QPlatformAudioOutput *output) +{ + m_session->setAudioOutput(output); +} + +void MFPlayerControl::setVideoSink(QVideoSink *sink) +{ + m_session->setVideoSink(sink); +} + +void MFPlayerControl::changeState(QMediaPlayer::PlaybackState state) +{ + if (m_state == state) + return; + m_state = state; + m_stateDirty = true; +} + +void MFPlayerControl::refreshState() +{ + if (!m_stateDirty) + return; + m_stateDirty = false; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MFPlayerControl::emit stateChanged" << m_state; +#endif + stateChanged(m_state); +} + +void MFPlayerControl::handleStatusChanged() +{ + QMediaPlayer::MediaStatus status = m_session->status(); + switch (status) { + case QMediaPlayer::EndOfMedia: + if (doLoop()) { + setPosition(0); + m_session->start(); + } else { + changeState(QMediaPlayer::StoppedState); + } + break; + case QMediaPlayer::InvalidMedia: + break; + case QMediaPlayer::LoadedMedia: + case QMediaPlayer::BufferingMedia: + case QMediaPlayer::BufferedMedia: + if (m_state == QMediaPlayer::PlayingState) + m_session->start(); + break; + default: + break; + } + mediaStatusChanged(m_session->status()); + refreshState(); +} + +void MFPlayerControl::handleTracksChanged() +{ + tracksChanged(); +} + +void MFPlayerControl::handleVideoAvailable() +{ + if (m_videoAvailable) + return; + m_videoAvailable = true; + videoAvailableChanged(m_videoAvailable); +} + +void MFPlayerControl::handleAudioAvailable() +{ + if (m_audioAvailable) + return; + m_audioAvailable = true; + audioAvailableChanged(m_audioAvailable); +} + +void MFPlayerControl::resetAudioVideoAvailable() +{ + bool videoDirty = false; + if (m_videoAvailable) { + m_videoAvailable = false; + videoDirty = true; + } + if (m_audioAvailable) { + m_audioAvailable = false; + audioAvailableChanged(m_audioAvailable); + } + if (videoDirty) + videoAvailableChanged(m_videoAvailable); +} + +void MFPlayerControl::handleDurationUpdate(qint64 duration) +{ + if (m_duration == duration) + return; + m_duration = duration; + durationChanged(m_duration); +} + +void MFPlayerControl::handleSeekableUpdate(bool seekable) +{ + if (m_seekable == seekable) + return; + m_seekable = seekable; + seekableChanged(m_seekable); +} + +QMediaPlayer::PlaybackState MFPlayerControl::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus MFPlayerControl::mediaStatus() const +{ + return m_session->status(); +} + +qint64 MFPlayerControl::duration() const +{ + return m_duration; +} + +qint64 MFPlayerControl::position() const +{ + return m_session->position(); +} + +void MFPlayerControl::setPosition(qint64 position) +{ + if (!m_seekable || position == m_session->position()) + return; + m_session->setPosition(position); +} + +float MFPlayerControl::bufferProgress() const +{ + return m_session->bufferProgress() / 100.; +} + +bool MFPlayerControl::isAudioAvailable() const +{ + return m_audioAvailable; +} + +bool MFPlayerControl::isVideoAvailable() const +{ + return m_videoAvailable; +} + +bool MFPlayerControl::isSeekable() const +{ + return m_seekable; +} + +QMediaTimeRange MFPlayerControl::availablePlaybackRanges() const +{ + return m_session->availablePlaybackRanges(); +} + +qreal MFPlayerControl::playbackRate() const +{ + return m_session->playbackRate(); +} + +void MFPlayerControl::setPlaybackRate(qreal rate) +{ + m_session->setPlaybackRate(rate); +} + +QUrl MFPlayerControl::media() const +{ + return m_media; +} + +const QIODevice* MFPlayerControl::mediaStream() const +{ + return m_stream; +} + +void MFPlayerControl::handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal) +{ + if (isFatal) + stop(); + error(int(errorCode), errorString); +} + +void MFPlayerControl::setActiveTrack(TrackType type, int index) +{ + m_session->setActiveTrack(type, index); +} + +int MFPlayerControl::activeTrack(TrackType type) +{ + return m_session->activeTrack(type); +} + +int MFPlayerControl::trackCount(TrackType type) +{ + return m_session->trackCount(type); +} + +QMediaMetaData MFPlayerControl::trackMetaData(TrackType type, int trackNumber) +{ + return m_session->trackMetaData(type, trackNumber); +} + diff --git a/src/plugins/multimedia/windows/player/mfplayercontrol_p.h b/src/plugins/multimedia/windows/player/mfplayercontrol_p.h new file mode 100644 index 000000000..db863afaa --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfplayercontrol_p.h @@ -0,0 +1,103 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef MFPLAYERCONTROL_H +#define MFPLAYERCONTROL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qurl.h" +#include "private/qplatformmediaplayer_p.h" + +#include <QtCore/qcoreevent.h> + +QT_BEGIN_NAMESPACE + +class MFPlayerSession; + +class MFPlayerControl : public QPlatformMediaPlayer +{ +public: + MFPlayerControl(QMediaPlayer *player); + ~MFPlayerControl(); + + QMediaPlayer::PlaybackState state() const override; + + QMediaPlayer::MediaStatus mediaStatus() const override; + + qint64 duration() const override; + + qint64 position() const override; + void setPosition(qint64 position) override; + + float bufferProgress() const override; + + bool isAudioAvailable() const override; + bool isVideoAvailable() const override; + + bool isSeekable() const override; + + QMediaTimeRange availablePlaybackRanges() const override; + + qreal playbackRate() const override; + void setPlaybackRate(qreal rate) override; + + QUrl media() const override; + const QIODevice *mediaStream() const override; + void setMedia(const QUrl &media, QIODevice *stream) override; + + void play() override; + void pause() override; + void stop() override; + + bool streamPlaybackSupported() const override { return true; } + + QMediaMetaData metaData() const override; + + void setAudioOutput(QPlatformAudioOutput *output) override; + + void setVideoSink(QVideoSink *sink) override; + + void setActiveTrack(TrackType type, int index) override; + int activeTrack(TrackType type) override; + int trackCount(TrackType type) override; + QMediaMetaData trackMetaData(TrackType type, int trackNumber) override; + + void handleStatusChanged(); + void handleTracksChanged(); + void handleVideoAvailable(); + void handleAudioAvailable(); + void handleDurationUpdate(qint64 duration); + void handleSeekableUpdate(bool seekable); + void handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal); + +private: + void changeState(QMediaPlayer::PlaybackState state); + void resetAudioVideoAvailable(); + void refreshState(); + + QMediaPlayer::PlaybackState m_state; + bool m_stateDirty; + + bool m_videoAvailable; + bool m_audioAvailable; + qint64 m_duration; + bool m_seekable; + + QIODevice *m_stream; + QUrl m_media; + MFPlayerSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/multimedia/windows/player/mfplayersession.cpp b/src/plugins/multimedia/windows/player/mfplayersession.cpp new file mode 100644 index 000000000..996ce35d8 --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfplayersession.cpp @@ -0,0 +1,1736 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "private/qplatformmediaplayer_p.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qthread.h> +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qbuffer.h> + +#include "private/qplatformaudiooutput_p.h" +#include "qaudiooutput.h" + +#include "mfplayercontrol_p.h" +#include "mfvideorenderercontrol_p.h" +#include <mfmetadata_p.h> +#include <private/qwindowsmfdefs_p.h> +#include <private/qwindowsaudioutils_p.h> + +#include "mfplayersession_p.h" +#include <mferror.h> +#include <nserror.h> +#include <winerror.h> +#include "sourceresolver_p.h" +#include <wmcodecdsp.h> + +#include <mmdeviceapi.h> +#include <propvarutil.h> +#include <functiondiscoverykeys_devpkey.h> + +//#define DEBUG_MEDIAFOUNDATION + +QT_BEGIN_NAMESPACE + +MFPlayerSession::MFPlayerSession(MFPlayerControl *playerControl) + : m_cRef(1), + m_playerControl(playerControl), + m_scrubbing(false), + m_restoreRate(1), + m_closing(false), + m_mediaTypes(0), + m_pendingRate(1), + m_status(QMediaPlayer::NoMedia) + +{ + connect(this, &MFPlayerSession::sessionEvent, this, &MFPlayerSession::handleSessionEvent); + + m_signalPositionChangeTimer.setInterval(10); + m_signalPositionChangeTimer.setTimerType(Qt::PreciseTimer); + m_signalPositionChangeTimer.callOnTimeout(this, &MFPlayerSession::timeout); + + m_pendingState = NoPending; + ZeroMemory(&m_state, sizeof(m_state)); + m_state.command = CmdStop; + m_state.prevCmd = CmdNone; + m_state.rate = 1.0f; + ZeroMemory(&m_request, sizeof(m_request)); + m_request.command = CmdNone; + m_request.prevCmd = CmdNone; + m_request.rate = 1.0f; + + m_videoRendererControl = new MFVideoRendererControl(this); +} + +void MFPlayerSession::timeout() +{ + const qint64 pos = position(); + + if (pos != m_lastPosition) { + const bool updatePos = m_timeCounter++ % 10 == 0; + if (pos >= qint64(m_duration / 10000 - 20)) { + if (m_playerControl->doLoop()) { + m_session->Pause(); + setPosition(0); + positionChanged(0); + } else { + if (updatePos) + positionChanged(pos); + } + } else { + if (updatePos) + positionChanged(pos); + } + m_lastPosition = pos; + } +} + +void MFPlayerSession::close() +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "close"; +#endif + + m_signalPositionChangeTimer.stop(); + clear(); + if (!m_session) + return; + + HRESULT hr = S_OK; + if (m_session) { + m_closing = true; + hr = m_session->Close(); + if (SUCCEEDED(hr)) { + DWORD dwWaitResult = WaitForSingleObject(m_hCloseEvent.get(), 2000); + if (dwWaitResult == WAIT_TIMEOUT) { + qWarning() << "session close time out!"; + } + } + m_closing = false; + } + + if (SUCCEEDED(hr)) { + if (m_session) + m_session->Shutdown(); + if (m_sourceResolver) + m_sourceResolver->shutdown(); + } + m_sourceResolver.Reset(); + + m_videoRendererControl->releaseActivate(); +// } else if (m_playerService->videoWindowControl()) { +// m_playerService->videoWindowControl()->releaseActivate(); +// } + + m_session.Reset(); + m_hCloseEvent = {}; + m_lastPosition = -1; + m_position = 0; +} + +void MFPlayerSession::load(const QUrl &url, QIODevice *stream) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "load"; +#endif + clear(); + + if (m_status == QMediaPlayer::LoadingMedia && m_sourceResolver) + m_sourceResolver->cancel(); + + if (url.isEmpty() && !stream) { + close(); + changeStatus(QMediaPlayer::NoMedia); + } else if (stream && (!stream->isReadable())) { + close(); + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Invalid stream source."), true); + } else if (createSession()) { + changeStatus(QMediaPlayer::LoadingMedia); + m_sourceResolver->load(url, stream); + if (url.isLocalFile()) + m_updateRoutingOnStart = true; + } + positionChanged(position()); +} + +void MFPlayerSession::handleSourceError(long hr) +{ + QString errorString; + QMediaPlayer::Error errorCode = QMediaPlayer::ResourceError; + switch (hr) { + case QMediaPlayer::FormatError: + errorCode = QMediaPlayer::FormatError; + errorString = tr("Attempting to play invalid Qt resource."); + break; + case NS_E_FILE_NOT_FOUND: + errorString = tr("The system cannot find the file specified."); + break; + case NS_E_SERVER_NOT_FOUND: + errorString = tr("The specified server could not be found."); + break; + case MF_E_UNSUPPORTED_BYTESTREAM_TYPE: + errorCode = QMediaPlayer::FormatError; + errorString = tr("Unsupported media type."); + break; + case MF_E_UNSUPPORTED_SCHEME: + errorCode = QMediaPlayer::ResourceError; + errorString = tr("Unsupported URL scheme."); + break; + case QMM_WININET_E_CANNOT_CONNECT: + errorCode = QMediaPlayer::NetworkError; + errorString = tr("Connection to server could not be established."); + break; + default: + qWarning() << "handleSourceError:" + << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hr); + errorString = tr("Failed to load source."); + break; + } + changeStatus(QMediaPlayer::InvalidMedia); + error(errorCode, errorString, true); +} + +void MFPlayerSession::handleMediaSourceReady() +{ + if (QMediaPlayer::LoadingMedia != m_status || !m_sourceResolver + || m_sourceResolver.Get() != sender()) + return; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "handleMediaSourceReady"; +#endif + HRESULT hr = S_OK; + IMFMediaSource* mediaSource = m_sourceResolver->mediaSource(); + + DWORD dwCharacteristics = 0; + mediaSource->GetCharacteristics(&dwCharacteristics); + seekableUpdate(MFMEDIASOURCE_CAN_SEEK & dwCharacteristics); + + ComPtr<IMFPresentationDescriptor> sourcePD; + hr = mediaSource->CreatePresentationDescriptor(&sourcePD); + if (SUCCEEDED(hr)) { + m_duration = 0; + m_metaData = MFMetaData::fromNative(mediaSource); + metaDataChanged(); + sourcePD->GetUINT64(MF_PD_DURATION, &m_duration); + //convert from 100 nanosecond to milisecond + durationUpdate(qint64(m_duration / 10000)); + setupPlaybackTopology(mediaSource, sourcePD.Get()); + tracksChanged(); + } else { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Cannot create presentation descriptor."), true); + } +} + +bool MFPlayerSession::getStreamInfo(IMFStreamDescriptor *stream, + MFPlayerSession::MediaType *type, + QString *name, + QString *language, + GUID *format) const +{ + if (!stream || !type || !name || !language || !format) + return false; + + *type = Unknown; + *name = QString(); + *language = QString(); + + ComPtr<IMFMediaTypeHandler> typeHandler; + + if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler))) { + + UINT32 len = 0; + if (SUCCEEDED(stream->GetStringLength(QMM_MF_SD_STREAM_NAME, &len)) && len > 0) { + WCHAR *wstr = new WCHAR[len+1]; + if (SUCCEEDED(stream->GetString(QMM_MF_SD_STREAM_NAME, wstr, len+1, &len))) { + *name = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr)); + } + delete []wstr; + } + if (SUCCEEDED(stream->GetStringLength(QMM_MF_SD_LANGUAGE, &len)) && len > 0) { + WCHAR *wstr = new WCHAR[len+1]; + if (SUCCEEDED(stream->GetString(QMM_MF_SD_LANGUAGE, wstr, len+1, &len))) { + *language = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr)); + } + delete []wstr; + } + + GUID guidMajorType; + if (SUCCEEDED(typeHandler->GetMajorType(&guidMajorType))) { + if (guidMajorType == MFMediaType_Audio) + *type = Audio; + else if (guidMajorType == MFMediaType_Video) + *type = Video; + } + + ComPtr<IMFMediaType> mediaType; + if (SUCCEEDED(typeHandler->GetCurrentMediaType(&mediaType))) { + mediaType->GetGUID(MF_MT_SUBTYPE, format); + } + } + + return *type != Unknown; +} + +void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD) +{ + HRESULT hr = S_OK; + // Get the number of streams in the media source. + DWORD cSourceStreams = 0; + hr = sourcePD->GetStreamDescriptorCount(&cSourceStreams); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Failed to get stream count."), true); + return; + } + + ComPtr<IMFTopology> topology; + hr = MFCreateTopology(&topology); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Failed to create topology."), true); + return; + } + + // For each stream, create the topology nodes and add them to the topology. + DWORD succeededCount = 0; + for (DWORD i = 0; i < cSourceStreams; i++) { + BOOL selected = FALSE; + bool streamAdded = false; + ComPtr<IMFStreamDescriptor> streamDesc; + + HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &selected, &streamDesc); + if (SUCCEEDED(hr)) { + // The media might have multiple audio and video streams, + // only use one of each kind, and only if it is selected by default. + MediaType mediaType = Unknown; + QString streamName; + QString streamLanguage; + GUID format = GUID_NULL; + + if (getStreamInfo(streamDesc.Get(), &mediaType, &streamName, &streamLanguage, + &format)) { + + QPlatformMediaPlayer::TrackType trackType = (mediaType == Audio) ? + QPlatformMediaPlayer::AudioStream : QPlatformMediaPlayer::VideoStream; + + QLocale::Language lang = streamLanguage.isEmpty() ? + QLocale::Language::AnyLanguage : QLocale(streamLanguage).language(); + + QMediaMetaData metaData; + metaData.insert(QMediaMetaData::Title, streamName); + metaData.insert(QMediaMetaData::Language, lang); + + m_trackInfo[trackType].metaData.append(metaData); + m_trackInfo[trackType].nativeIndexes.append(i); + m_trackInfo[trackType].format = format; + + if (((m_mediaTypes & mediaType) == 0) && selected) { // Check if this type isn't already added + ComPtr<IMFTopologyNode> sourceNode = + addSourceNode(topology.Get(), source, sourcePD, streamDesc.Get()); + if (sourceNode) { + ComPtr<IMFTopologyNode> outputNode = + addOutputNode(mediaType, topology.Get(), 0); + if (outputNode) { + sourceNode->GetTopoNodeID(&m_trackInfo[trackType].sourceNodeId); + outputNode->GetTopoNodeID(&m_trackInfo[trackType].outputNodeId); + + hr = sourceNode->ConnectOutput(0, outputNode.Get(), 0); + + if (FAILED(hr)) { + error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false); + } else { + m_trackInfo[trackType].currentIndex = m_trackInfo[trackType].nativeIndexes.count() - 1; + streamAdded = true; + succeededCount++; + m_mediaTypes |= mediaType; + switch (mediaType) { + case Audio: + audioAvailable(); + break; + case Video: + videoAvailable(); + break; + default: + break; + } + } + } else { + // remove the source node if the output node cannot be created + topology->RemoveNode(sourceNode.Get()); + } + } + } + } + + if (selected && !streamAdded) + sourcePD->DeselectStream(i); + } + } + + if (succeededCount == 0) { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Unable to play."), true); + } else { + if (m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId != TOPOID(-1)) + topology = insertMFT(topology, m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId); + + hr = m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology.Get()); + if (SUCCEEDED(hr)) { + m_updatingTopology = true; + } else { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Failed to set topology."), true); + } + } +} + +ComPtr<IMFTopologyNode> MFPlayerSession::addSourceNode(IMFTopology *topology, + IMFMediaSource *source, + IMFPresentationDescriptor *presentationDesc, + IMFStreamDescriptor *streamDesc) +{ + ComPtr<IMFTopologyNode> node; + HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node); + if (SUCCEEDED(hr)) { + hr = node->SetUnknown(MF_TOPONODE_SOURCE, source); + if (SUCCEEDED(hr)) { + hr = node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, presentationDesc); + if (SUCCEEDED(hr)) { + hr = node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, streamDesc); + if (SUCCEEDED(hr)) { + hr = topology->AddNode(node.Get()); + if (SUCCEEDED(hr)) + return node; + } + } + } + } + return NULL; +} + +ComPtr<IMFTopologyNode> MFPlayerSession::addOutputNode(MediaType mediaType, IMFTopology *topology, + DWORD sinkID) +{ + ComPtr<IMFTopologyNode> node; + if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node))) + return NULL; + + ComPtr<IMFActivate> activate; + if (mediaType == Audio) { + if (m_audioOutput) { + auto id = m_audioOutput->device.id(); + if (id.isEmpty()) { + qInfo() << "No audio output"; + return NULL; + } + + HRESULT hr = MFCreateAudioRendererActivate(&activate); + if (FAILED(hr)) { + qWarning() << "Failed to create audio renderer activate"; + return NULL; + } + + QString s = QString::fromUtf8(id); + hr = activate->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, (LPCWSTR)s.utf16()); + if (FAILED(hr)) { + qWarning() << "Failed to set attribute for audio device" + << m_audioOutput->device.description(); + return NULL; + } + } + } else if (mediaType == Video) { + activate = m_videoRendererControl->createActivate(); + + QSize resolution = m_metaData.value(QMediaMetaData::Resolution).toSize(); + + if (resolution.isValid()) + m_videoRendererControl->setCropRect(QRect(QPoint(), resolution)); + + } else { + // Unknown stream type. + error(QMediaPlayer::FormatError, tr("Unknown stream type."), false); + } + + if (!activate || FAILED(node->SetObject(activate.Get())) + || FAILED(node->SetUINT32(MF_TOPONODE_STREAMID, sinkID)) + || FAILED(node->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE)) + || FAILED(topology->AddNode(node.Get()))) { + node.Reset(); + } + + if (activate && mediaType == Audio) + activate.Reset(); + + return node; +} + +// BindOutputNode +// Sets the IMFStreamSink pointer on an output node. +// IMFActivate pointer in the output node must be converted to an +// IMFStreamSink pointer before the topology loader resolves the topology. +HRESULT BindOutputNode(IMFTopologyNode *pNode) +{ + ComPtr<IUnknown> nodeObject; + ComPtr<IMFActivate> activate; + ComPtr<IMFStreamSink> stream; + ComPtr<IMFMediaSink> sink; + + HRESULT hr = pNode->GetObject(&nodeObject); + if (FAILED(hr)) + return hr; + + hr = nodeObject->QueryInterface(IID_PPV_ARGS(&activate)); + if (SUCCEEDED(hr)) { + DWORD dwStreamID = 0; + + // Try to create the media sink. + hr = activate->ActivateObject(IID_PPV_ARGS(&sink)); + if (SUCCEEDED(hr)) + dwStreamID = MFGetAttributeUINT32(pNode, MF_TOPONODE_STREAMID, 0); + + if (SUCCEEDED(hr)) { + // First check if the media sink already has a stream sink with the requested ID. + hr = sink->GetStreamSinkById(dwStreamID, &stream); + if (FAILED(hr)) { + // Create the stream sink. + hr = sink->AddStreamSink(dwStreamID, NULL, &stream); + } + } + + // Replace the node's object pointer with the stream sink. + if (SUCCEEDED(hr)) { + hr = pNode->SetObject(stream.Get()); + } + } else { + hr = nodeObject->QueryInterface(IID_PPV_ARGS(&stream)); + } + + return hr; +} + +// BindOutputNodes +// Sets the IMFStreamSink pointers on all of the output nodes in a topology. +HRESULT BindOutputNodes(IMFTopology *pTopology) +{ + ComPtr<IMFCollection> collection; + + // Get the collection of output nodes. + HRESULT hr = pTopology->GetOutputNodeCollection(&collection); + + // Enumerate all of the nodes in the collection. + if (SUCCEEDED(hr)) { + DWORD cNodes; + hr = collection->GetElementCount(&cNodes); + + if (SUCCEEDED(hr)) { + for (DWORD i = 0; i < cNodes; i++) { + ComPtr<IUnknown> element; + hr = collection->GetElement(i, &element); + if (FAILED(hr)) + break; + + ComPtr<IMFTopologyNode> node; + hr = element->QueryInterface(IID_IMFTopologyNode, &node); + if (FAILED(hr)) + break; + + // Bind this node. + hr = BindOutputNode(node.Get()); + if (FAILED(hr)) + break; + } + } + } + + return hr; +} + +// This method binds output nodes to complete the topology, +// then loads the topology and inserts MFT between the output node +// and a filter connected to the output node. +ComPtr<IMFTopology> MFPlayerSession::insertMFT(const ComPtr<IMFTopology> &topology, + TOPOID outputNodeId) +{ + bool isNewTopology = false; + + ComPtr<IMFTopoLoader> topoLoader; + ComPtr<IMFTopology> resolvedTopology; + ComPtr<IMFCollection> outputNodes; + + do { + if (FAILED(BindOutputNodes(topology.Get()))) + break; + + if (FAILED(MFCreateTopoLoader(&topoLoader))) + break; + + if (FAILED(topoLoader->Load(topology.Get(), &resolvedTopology, NULL))) { + // Topology could not be resolved, adding ourselves a color converter + // to the topology might solve the problem + insertColorConverter(topology.Get(), outputNodeId); + if (FAILED(topoLoader->Load(topology.Get(), &resolvedTopology, NULL))) + break; + } + + if (insertResizer(resolvedTopology.Get())) + isNewTopology = true; + } while (false); + + if (isNewTopology) { + return resolvedTopology; + } + + return topology; +} + +// This method checks if the topology contains a color converter transform (CColorConvertDMO), +// if it does it inserts a resizer transform (CResizerDMO) to handle dynamic frame size change +// of the video stream. +// Returns true if it inserted a resizer +bool MFPlayerSession::insertResizer(IMFTopology *topology) +{ + bool inserted = false; + WORD elementCount = 0; + ComPtr<IMFTopologyNode> node; + ComPtr<IUnknown> object; + ComPtr<IWMColorConvProps> colorConv; + ComPtr<IMFTransform> resizer; + ComPtr<IMFTopologyNode> resizerNode; + ComPtr<IMFTopologyNode> inputNode; + + HRESULT hr = topology->GetNodeCount(&elementCount); + if (FAILED(hr)) + return false; + + for (WORD i = 0; i < elementCount; ++i) { + node.Reset(); + object.Reset(); + + if (FAILED(topology->GetNode(i, &node))) + break; + + MF_TOPOLOGY_TYPE nodeType; + if (FAILED(node->GetNodeType(&nodeType))) + break; + + if (nodeType != MF_TOPOLOGY_TRANSFORM_NODE) + continue; + + if (FAILED(node->GetObject(&object))) + break; + + if (FAILED(object->QueryInterface(IID_PPV_ARGS(&colorConv)))) + continue; + + if (FAILED(CoCreateInstance(CLSID_CResizerDMO, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, + &resizer))) + break; + + if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &resizerNode))) + break; + + if (FAILED(resizerNode->SetObject(resizer.Get()))) + break; + + if (FAILED(topology->AddNode(resizerNode.Get()))) + break; + + DWORD outputIndex = 0; + if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) { + topology->RemoveNode(resizerNode.Get()); + break; + } + + if (FAILED(inputNode->ConnectOutput(0, resizerNode.Get(), 0))) { + topology->RemoveNode(resizerNode.Get()); + break; + } + + if (FAILED(resizerNode->ConnectOutput(0, node.Get(), 0))) { + inputNode->ConnectOutput(0, node.Get(), 0); + topology->RemoveNode(resizerNode.Get()); + break; + } + + inserted = true; + break; + } + + return inserted; +} + +// This method inserts a color converter (CColorConvertDMO) in the topology, +// typically to convert to RGB format. +// Usually this converter is automatically inserted when the topology is resolved but +// for some reason it fails to do so in some cases, we then do it ourselves. +void MFPlayerSession::insertColorConverter(IMFTopology *topology, TOPOID outputNodeId) +{ + ComPtr<IMFCollection> outputNodes; + + if (FAILED(topology->GetOutputNodeCollection(&outputNodes))) + return; + + DWORD elementCount = 0; + if (FAILED(outputNodes->GetElementCount(&elementCount))) + return; + + for (DWORD n = 0; n < elementCount; n++) { + ComPtr<IUnknown> element; + ComPtr<IMFTopologyNode> node; + ComPtr<IMFTopologyNode> inputNode; + ComPtr<IMFTopologyNode> mftNode; + ComPtr<IMFTransform> converter; + + do { + if (FAILED(outputNodes->GetElement(n, &element))) + break; + + if (FAILED(element->QueryInterface(IID_IMFTopologyNode, &node))) + break; + + TOPOID id; + if (FAILED(node->GetTopoNodeID(&id))) + break; + + if (id != outputNodeId) + break; + + DWORD outputIndex = 0; + if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) + break; + + if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &mftNode))) + break; + + if (FAILED(CoCreateInstance(CLSID_CColorConvertDMO, NULL, CLSCTX_INPROC_SERVER, + IID_IMFTransform, &converter))) + break; + + if (FAILED(mftNode->SetObject(converter.Get()))) + break; + + if (FAILED(topology->AddNode(mftNode.Get()))) + break; + + if (FAILED(inputNode->ConnectOutput(0, mftNode.Get(), 0))) + break; + + if (FAILED(mftNode->ConnectOutput(0, node.Get(), 0))) + break; + + } while (false); + } +} + +void MFPlayerSession::stop(bool immediate) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "stop"; +#endif + if (!immediate && m_pendingState != NoPending) { + m_request.setCommand(CmdStop); + } else { + if (m_state.command == CmdStop) + return; + + if (m_scrubbing) + scrub(false); + + if (SUCCEEDED(m_session->Stop())) { + + m_state.setCommand(CmdStop); + m_pendingState = CmdPending; + if (m_status != QMediaPlayer::EndOfMedia) { + m_position = 0; + positionChanged(0); + } + } else { + error(QMediaPlayer::ResourceError, tr("Failed to stop."), true); + } + } +} + +void MFPlayerSession::start() +{ + if (status() == QMediaPlayer::LoadedMedia && m_updateRoutingOnStart) { + m_updateRoutingOnStart = false; + updateOutputRouting(); + } + + if (m_status == QMediaPlayer::EndOfMedia) { + m_position = 0; // restart from the beginning + positionChanged(0); + } + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "start"; +#endif + + if (m_pendingState != NoPending) { + m_request.setCommand(CmdStart); + } else { + if (m_state.command == CmdStart) + return; + + if (m_scrubbing) { + scrub(false); + m_position = position() * 10000; + } + + if (m_restorePosition >= 0) { + m_position = m_restorePosition; + if (!m_updatingTopology) + m_restorePosition = -1; + } + + PROPVARIANT varStart; + InitPropVariantFromInt64(m_position, &varStart); + + if (SUCCEEDED(m_session->Start(&GUID_NULL, &varStart))) { + m_state.setCommand(CmdStart); + m_pendingState = CmdPending; + } else { + error(QMediaPlayer::ResourceError, tr("failed to start playback"), true); + } + PropVariantClear(&varStart); + } +} + +void MFPlayerSession::pause() +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "pause"; +#endif + if (m_pendingState != NoPending) { + m_request.setCommand(CmdPause); + } else { + if (m_state.command == CmdPause) + return; + + if (SUCCEEDED(m_session->Pause())) { + m_state.setCommand(CmdPause); + m_pendingState = CmdPending; + } else { + error(QMediaPlayer::ResourceError, tr("Failed to pause."), false); + } + if (m_status == QMediaPlayer::EndOfMedia) { + setPosition(0); + positionChanged(0); + } + } +} + +void MFPlayerSession::changeStatus(QMediaPlayer::MediaStatus newStatus) +{ + if (m_status == newStatus) + return; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MFPlayerSession::changeStatus" << newStatus; +#endif + m_status = newStatus; + statusChanged(); +} + +QMediaPlayer::MediaStatus MFPlayerSession::status() const +{ + return m_status; +} + +bool MFPlayerSession::createSession() +{ + close(); + + Q_ASSERT(m_session == NULL); + + HRESULT hr = MFCreateMediaSession(NULL, &m_session); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Unable to create mediasession."), true); + return false; + } + + m_hCloseEvent = EventHandle{ CreateEvent(NULL, FALSE, FALSE, NULL) }; + + hr = m_session->BeginGetEvent(this, m_session.Get()); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::ResourceError, tr("Unable to pull session events."), false); + close(); + return false; + } + + m_sourceResolver = makeComObject<SourceResolver>(); + QObject::connect(m_sourceResolver.Get(), &SourceResolver::mediaSourceReady, this, + &MFPlayerSession::handleMediaSourceReady); + QObject::connect(m_sourceResolver.Get(), &SourceResolver::error, this, + &MFPlayerSession::handleSourceError); + + m_position = 0; + return true; +} + +qint64 MFPlayerSession::position() +{ + if (m_request.command == CmdSeek || m_request.command == CmdSeekResume) + return m_request.start; + + if (m_pendingState == SeekPending) + return m_state.start; + + if (m_state.command == CmdStop) + return m_position / 10000; + + if (m_presentationClock) { + MFTIME time, sysTime; + if (FAILED(m_presentationClock->GetCorrelatedTime(0, &time, &sysTime))) + return m_position / 10000; + return qint64(time / 10000); + } + return m_position / 10000; +} + +void MFPlayerSession::setPosition(qint64 position) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "setPosition"; +#endif + if (m_pendingState != NoPending) { + m_request.setCommand(CmdSeek); + m_request.start = position; + } else { + setPositionInternal(position, CmdNone); + } +} + +void MFPlayerSession::setPositionInternal(qint64 position, Command requestCmd) +{ + if (m_status == QMediaPlayer::EndOfMedia) + changeStatus(QMediaPlayer::LoadedMedia); + if (m_state.command == CmdStop && requestCmd != CmdSeekResume) { + m_position = position * 10000; + // Even though the position is not actually set on the session yet, + // report it to have changed anyway for UI controls to be updated + positionChanged(this->position()); + return; + } + + if (m_state.command == CmdPause) + scrub(true); + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "setPositionInternal"; +#endif + + PROPVARIANT varStart; + varStart.vt = VT_I8; + varStart.hVal.QuadPart = LONGLONG(position * 10000); + if (SUCCEEDED(m_session->Start(NULL, &varStart))) { + PropVariantClear(&varStart); + // Store the pending state. + m_state.setCommand(CmdStart); + m_state.start = position; + m_pendingState = SeekPending; + } else { + error(QMediaPlayer::ResourceError, tr("Failed to seek."), true); + } +} + +qreal MFPlayerSession::playbackRate() const +{ + if (m_scrubbing) + return m_restoreRate; + return m_state.rate; +} + +void MFPlayerSession::setPlaybackRate(qreal rate) +{ + if (m_scrubbing) { + m_restoreRate = rate; + playbackRateChanged(rate); + return; + } + setPlaybackRateInternal(rate); +} + +void MFPlayerSession::setPlaybackRateInternal(qreal rate) +{ + if (rate == m_request.rate) + return; + + m_pendingRate = rate; + if (!m_rateSupport) + return; + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "setPlaybackRate"; +#endif + BOOL isThin = FALSE; + + //from MSDN http://msdn.microsoft.com/en-us/library/aa965220%28v=vs.85%29.aspx + //Thinning applies primarily to video streams. + //In thinned mode, the source drops delta frames and deliver only key frames. + //At very high playback rates, the source might skip some key frames (for example, deliver every other key frame). + + if (FAILED(m_rateSupport->IsRateSupported(FALSE, rate, NULL))) { + isThin = TRUE; + if (FAILED(m_rateSupport->IsRateSupported(isThin, rate, NULL))) { + qWarning() << "unable to set playbackrate = " << rate; + m_pendingRate = m_request.rate = m_state.rate; + return; + } + } + if (m_pendingState != NoPending) { + m_request.rate = rate; + m_request.isThin = isThin; + // Remember the current transport state (play, paused, etc), so that we + // can restore it after the rate change, if necessary. However, if + // anothercommand is already pending, that one takes precedent. + if (m_request.command == CmdNone) + m_request.setCommand(m_state.command); + } else { + //No pending operation. Commit the new rate. + commitRateChange(rate, isThin); + } +} + +void MFPlayerSession::commitRateChange(qreal rate, BOOL isThin) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "commitRateChange"; +#endif + Q_ASSERT(m_pendingState == NoPending); + MFTIME hnsSystemTime = 0; + MFTIME hnsClockTime = 0; + Command cmdNow = m_state.command; + bool resetPosition = false; + // Allowed rate transitions: + // Positive <-> negative: Stopped + // Negative <-> zero: Stopped + // Postive <-> zero: Paused or stopped + if ((rate > 0 && m_state.rate <= 0) || (rate < 0 && m_state.rate >= 0)) { + if (cmdNow == CmdStart) { + // Get the current clock position. This will be the restart time. + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + Q_ASSERT(hnsSystemTime != 0); + + if (rate < 0 || m_state.rate < 0) + m_request.setCommand(CmdSeekResume); + else if (isThin || m_state.isThin) + m_request.setCommand(CmdStartAndSeek); + else + m_request.setCommand(CmdStart); + + // We need to stop only when dealing with negative rates + if (rate >= 0 && m_state.rate >= 0) + pause(); + else + stop(); + + // If we deal with negative rates, we stopped the session and consequently + // reset the position to zero. We then need to resume to the current position. + m_request.start = hnsClockTime / 10000; + } else if (cmdNow == CmdPause) { + if (rate < 0 || m_state.rate < 0) { + // The current state is paused. + // For this rate change, the session must be stopped. However, the + // session cannot transition back from stopped to paused. + // Therefore, this rate transition is not supported while paused. + qWarning() << "Unable to change rate from positive to negative or vice versa in paused state"; + rate = m_state.rate; + isThin = m_state.isThin; + goto done; + } + + // This happens when resuming playback after scrubbing in pause mode. + // This transition requires the session to be paused. Even though our + // internal state is set to paused, the session might not be so we need + // to enforce it + if (rate > 0 && m_state.rate == 0) { + m_state.setCommand(CmdNone); + pause(); + } + } + } else if (rate == 0 && m_state.rate > 0) { + if (cmdNow != CmdPause) { + // Transition to paused. + // This transisition requires the paused state. + // Pause and set the rate. + pause(); + + // Request: Switch back to current state. + m_request.setCommand(cmdNow); + } + } else if (rate == 0 && m_state.rate < 0) { + // Changing rate from negative to zero requires to stop the session + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + + m_request.setCommand(CmdSeekResume); + + stop(); + + // Resume to the current position (stop() will reset the position to 0) + m_request.start = hnsClockTime / 10000; + } else if (!isThin && m_state.isThin) { + if (cmdNow == CmdStart) { + // When thinning, only key frames are read and presented. Going back + // to normal playback requires to reset the current position to force + // the pipeline to decode the actual frame at the current position + // (which might be earlier than the last decoded key frame) + resetPosition = true; + } else if (cmdNow == CmdPause) { + // If paused, don't reset the position until we resume, otherwise + // a new frame will be rendered + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + m_request.setCommand(CmdSeekResume); + m_request.start = hnsClockTime / 10000; + } + + } + + // Set the rate. + if (FAILED(m_rateControl->SetRate(isThin, rate))) { + qWarning() << "failed to set playbackrate = " << rate; + rate = m_state.rate; + isThin = m_state.isThin; + goto done; + } + + if (resetPosition) { + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + setPosition(hnsClockTime / 10000); + } + +done: + // Adjust our current rate and requested rate. + m_pendingRate = m_request.rate = m_state.rate = rate; + if (rate != 0) + m_state.isThin = isThin; + playbackRateChanged(rate); +} + +void MFPlayerSession::scrub(bool enableScrub) +{ + if (m_scrubbing == enableScrub) + return; + + m_scrubbing = enableScrub; + + if (!canScrub()) { + if (!enableScrub) + m_pendingRate = m_restoreRate; + return; + } + + if (enableScrub) { + // Enter scrubbing mode. Cache the rate. + m_restoreRate = m_request.rate; + setPlaybackRateInternal(0.0f); + } else { + // Leaving scrubbing mode. Restore the old rate. + setPlaybackRateInternal(m_restoreRate); + } +} + +void MFPlayerSession::setVolume(float volume) +{ + if (m_volume == volume) + return; + m_volume = volume; + + if (!m_muted) + setVolumeInternal(volume); +} + +void MFPlayerSession::setMuted(bool muted) +{ + if (m_muted == muted) + return; + m_muted = muted; + + setVolumeInternal(muted ? 0 : m_volume); +} + +void MFPlayerSession::setVolumeInternal(float volume) +{ + if (m_volumeControl) { + quint32 channelCount = 0; + if (!SUCCEEDED(m_volumeControl->GetChannelCount(&channelCount)) + || channelCount == 0) + return; + + for (quint32 i = 0; i < channelCount; ++i) + m_volumeControl->SetChannelVolume(i, volume); + } +} + +float MFPlayerSession::bufferProgress() +{ + if (!m_netsourceStatistics) + return 0; + PROPVARIANT var; + PropVariantInit(&var); + PROPERTYKEY key; + key.fmtid = MFNETSOURCE_STATISTICS; + key.pid = MFNETSOURCE_BUFFERPROGRESS_ID; + int progress = -1; + // GetValue returns S_FALSE if the property is not available, which has + // a value > 0. We therefore can't use the SUCCEEDED macro here. + if (m_netsourceStatistics->GetValue(key, &var) == S_OK) { + progress = var.lVal; + PropVariantClear(&var); + } + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "bufferProgress: progress = " << progress; +#endif + + return progress/100.; +} + +QMediaTimeRange MFPlayerSession::availablePlaybackRanges() +{ + // defaults to the whole media + qint64 start = 0; + qint64 end = qint64(m_duration / 10000); + + if (m_netsourceStatistics) { + PROPVARIANT var; + PropVariantInit(&var); + PROPERTYKEY key; + key.fmtid = MFNETSOURCE_STATISTICS; + key.pid = MFNETSOURCE_SEEKRANGESTART_ID; + // GetValue returns S_FALSE if the property is not available, which has + // a value > 0. We therefore can't use the SUCCEEDED macro here. + if (m_netsourceStatistics->GetValue(key, &var) == S_OK) { + start = qint64(var.uhVal.QuadPart / 10000); + PropVariantClear(&var); + PropVariantInit(&var); + key.pid = MFNETSOURCE_SEEKRANGEEND_ID; + if (m_netsourceStatistics->GetValue(key, &var) == S_OK) { + end = qint64(var.uhVal.QuadPart / 10000); + PropVariantClear(&var); + } + } + } + + return QMediaTimeRange(start, end); +} + +HRESULT MFPlayerSession::QueryInterface(REFIID riid, void** ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFAsyncCallback) { + *ppvObject = static_cast<IMFAsyncCallback*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +ULONG MFPlayerSession::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +ULONG MFPlayerSession::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + deleteLater(); + + // In rare cases the session has queued events to be run between deleteLater and deleting, + // so we set the parent control to nullptr in order to prevent crashes in the cases. + m_playerControl = nullptr; + } + return cRef; +} + +HRESULT MFPlayerSession::Invoke(IMFAsyncResult *pResult) +{ + if (pResult->GetStateNoAddRef() != m_session.Get()) + return S_OK; + + ComPtr<IMFMediaEvent> pEvent; + // Get the event from the event queue. + HRESULT hr = m_session->EndGetEvent(pResult, &pEvent); + if (FAILED(hr)) { + return S_OK; + } + + MediaEventType meType = MEUnknown; + hr = pEvent->GetType(&meType); + if (FAILED(hr)) { + return S_OK; + } + + if (meType == MESessionClosed) { + SetEvent(m_hCloseEvent.get()); + return S_OK; + } else { + hr = m_session->BeginGetEvent(this, m_session.Get()); + if (FAILED(hr)) { + return S_OK; + } + } + + if (!m_closing) { + emit sessionEvent(pEvent); + } + return S_OK; +} + +void MFPlayerSession::handleSessionEvent(const ComPtr<IMFMediaEvent> &sessionEvent) +{ + HRESULT hrStatus = S_OK; + HRESULT hr = sessionEvent->GetStatus(&hrStatus); + if (FAILED(hr) || !m_session) { + return; + } + + MediaEventType meType = MEUnknown; + hr = sessionEvent->GetType(&meType); +#ifdef DEBUG_MEDIAFOUNDATION + if (FAILED(hrStatus)) + qDebug() << "handleSessionEvent: MediaEventType = " << meType << "Failed"; + else + qDebug() << "handleSessionEvent: MediaEventType = " << meType; +#endif + + switch (meType) { + case MENonFatalError: { + PROPVARIANT var; + PropVariantInit(&var); + sessionEvent->GetValue(&var); + qWarning() << "handleSessionEvent: non fatal error = " << var.ulVal; + PropVariantClear(&var); + error(QMediaPlayer::ResourceError, tr("Media session non-fatal error."), false); + } + break; + case MESourceUnknown: + changeStatus(QMediaPlayer::InvalidMedia); + break; + case MEError: + if (hrStatus == MF_E_ALREADY_INITIALIZED) { + // Workaround for a possible WMF issue that causes an error + // with some specific videos, which play fine otherwise. +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "handleSessionEvent: ignoring MF_E_ALREADY_INITIALIZED"; +#endif + break; + } + changeStatus(QMediaPlayer::InvalidMedia); + qWarning() << "handleSessionEvent: serious error = " + << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hrStatus); + switch (hrStatus) { + case MF_E_NET_READ: + error(QMediaPlayer::NetworkError, tr("Error reading from the network."), true); + break; + case MF_E_NET_WRITE: + error(QMediaPlayer::NetworkError, tr("Error writing to the network."), true); + break; + case NS_E_FIREWALL: + error(QMediaPlayer::NetworkError, tr("Network packets might be blocked by a firewall."), true); + break; + case MF_E_MEDIAPROC_WRONGSTATE: + error(QMediaPlayer::ResourceError, tr("Media session state error."), true); + break; + case MF_E_INVALID_STREAM_DATA: + error(QMediaPlayer::ResourceError, tr("Invalid stream data."), true); + break; + default: + error(QMediaPlayer::ResourceError, tr("Media session serious error."), true); + break; + } + break; + case MESessionRateChanged: + // If the rate change succeeded, we've already got the rate + // cached. If it failed, try to get the actual rate. + if (FAILED(hrStatus)) { + PROPVARIANT var; + PropVariantInit(&var); + if (SUCCEEDED(sessionEvent->GetValue(&var)) && (var.vt == VT_R4)) { + m_state.rate = var.fltVal; + } + playbackRateChanged(playbackRate()); + } + break; + case MESessionScrubSampleComplete : + if (m_scrubbing) + updatePendingCommands(CmdStart); + break; + case MESessionStarted: + if (m_status == QMediaPlayer::EndOfMedia + || m_status == QMediaPlayer::LoadedMedia) { + // If the session started, then enough data is buffered to play + changeStatus(QMediaPlayer::BufferedMedia); + } + + updatePendingCommands(CmdStart); + // playback started, we can now set again the procAmpValues if they have been + // changed previously (these are lost when loading a new media) +// if (m_playerService->videoWindowControl()) { +// m_playerService->videoWindowControl()->applyImageControls(); +// } + m_signalPositionChangeTimer.start(); + break; + case MESessionStopped: + if (m_status != QMediaPlayer::EndOfMedia) { + m_position = 0; + + // Reset to Loaded status unless we are loading a new media + // or changing the playback rate to negative values (stop required) + if (m_status != QMediaPlayer::LoadingMedia && m_request.command != CmdSeekResume) + changeStatus(QMediaPlayer::LoadedMedia); + } + updatePendingCommands(CmdStop); + m_signalPositionChangeTimer.stop(); + break; + case MESessionPaused: + m_position = position() * 10000; + updatePendingCommands(CmdPause); + m_signalPositionChangeTimer.stop(); + if (m_status == QMediaPlayer::LoadedMedia) + setPosition(position()); + break; + case MEReconnectStart: +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MEReconnectStart" << ((hrStatus == S_OK) ? "OK" : "Failed"); +#endif + break; + case MEReconnectEnd: +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MEReconnectEnd" << ((hrStatus == S_OK) ? "OK" : "Failed"); +#endif + break; + case MESessionTopologySet: + if (FAILED(hrStatus)) { + changeStatus(QMediaPlayer::InvalidMedia); + error(QMediaPlayer::FormatError, tr("Unsupported media, a codec is missing."), true); + } else { + // Topology is resolved and successfuly set, this happens only after loading a new media. + // Make sure we always start the media from the beginning + m_lastPosition = -1; + m_position = 0; + positionChanged(0); + changeStatus(QMediaPlayer::LoadedMedia); + } + break; + } + + if (FAILED(hrStatus)) { + return; + } + + switch (meType) { + case MEBufferingStarted: + changeStatus(QMediaPlayer::StalledMedia); + bufferProgressChanged(bufferProgress()); + break; + case MEBufferingStopped: + changeStatus(QMediaPlayer::BufferedMedia); + bufferProgressChanged(bufferProgress()); + break; + case MESessionEnded: + m_pendingState = NoPending; + m_state.command = CmdStop; + m_state.prevCmd = CmdNone; + m_request.command = CmdNone; + m_request.prevCmd = CmdNone; + + //keep reporting the final position after end of media + m_position = qint64(m_duration); + positionChanged(position()); + + changeStatus(QMediaPlayer::EndOfMedia); + break; + case MEEndOfPresentationSegment: + break; + case MESessionTopologyStatus: { + UINT32 status; + if (SUCCEEDED(sessionEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status))) { + if (status == MF_TOPOSTATUS_READY) { + ComPtr<IMFClock> clock; + if (SUCCEEDED(m_session->GetClock(&clock))) { + clock->QueryInterface(IID_IMFPresentationClock, &m_presentationClock); + } + + if (SUCCEEDED(MFGetService(m_session.Get(), MF_RATE_CONTROL_SERVICE, + IID_PPV_ARGS(&m_rateControl)))) { + if (SUCCEEDED(MFGetService(m_session.Get(), MF_RATE_CONTROL_SERVICE, + IID_PPV_ARGS(&m_rateSupport)))) { + if (SUCCEEDED(m_rateSupport->IsRateSupported(TRUE, 0, NULL))) + m_canScrub = true; + } + BOOL isThin = FALSE; + float rate = 1; + if (SUCCEEDED(m_rateControl->GetRate(&isThin, &rate))) { + if (m_pendingRate != rate) { + m_state.rate = m_request.rate = rate; + setPlaybackRate(m_pendingRate); + } + } + } + MFGetService(m_session.Get(), MFNETSOURCE_STATISTICS_SERVICE, + IID_PPV_ARGS(&m_netsourceStatistics)); + + if (SUCCEEDED(MFGetService(m_session.Get(), MR_STREAM_VOLUME_SERVICE, + IID_PPV_ARGS(&m_volumeControl)))) + setVolumeInternal(m_muted ? 0 : m_volume); + + m_updatingTopology = false; + stop(); + } + } + } + break; + default: + break; + } +} + +void MFPlayerSession::updatePendingCommands(Command command) +{ + positionChanged(position()); + if (m_state.command != command || m_pendingState == NoPending) + return; + + // Seek while paused completed + if (m_pendingState == SeekPending && m_state.prevCmd == CmdPause) { + m_pendingState = NoPending; + // A seek operation actually restarts playback. If scrubbing is possible, playback rate + // is set to 0.0 at this point and we just need to reset the current state to Pause. + // If scrubbing is not possible, the playback rate was not changed and we explicitly need + // to re-pause playback. + if (!canScrub()) + pause(); + else + m_state.setCommand(CmdPause); + } + + m_pendingState = NoPending; + + //First look for rate changes. + if (m_request.rate != m_state.rate) { + commitRateChange(m_request.rate, m_request.isThin); + } + + // Now look for new requests. + if (m_pendingState == NoPending) { + switch (m_request.command) { + case CmdStart: + start(); + break; + case CmdPause: + pause(); + break; + case CmdStop: + stop(); + break; + case CmdSeek: + case CmdSeekResume: + setPositionInternal(m_request.start, m_request.command); + break; + case CmdStartAndSeek: + start(); + setPositionInternal(m_request.start, m_request.command); + break; + default: + break; + } + m_request.setCommand(CmdNone); + } + +} + +bool MFPlayerSession::canScrub() const +{ + return m_canScrub && m_rateSupport && m_rateControl; +} + +void MFPlayerSession::clear() +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MFPlayerSession::clear"; +#endif + m_mediaTypes = 0; + m_canScrub = false; + + m_pendingState = NoPending; + m_state.command = CmdStop; + m_state.prevCmd = CmdNone; + m_request.command = CmdNone; + m_request.prevCmd = CmdNone; + + for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) { + m_trackInfo[i].metaData.clear(); + m_trackInfo[i].nativeIndexes.clear(); + m_trackInfo[i].currentIndex = -1; + m_trackInfo[i].sourceNodeId = TOPOID(-1); + m_trackInfo[i].outputNodeId = TOPOID(-1); + m_trackInfo[i].format = GUID_NULL; + } + + if (!m_metaData.isEmpty()) { + m_metaData.clear(); + metaDataChanged(); + } + + m_presentationClock.Reset(); + m_rateControl.Reset(); + m_rateSupport.Reset(); + m_volumeControl.Reset(); + m_netsourceStatistics.Reset(); +} + +void MFPlayerSession::setAudioOutput(QPlatformAudioOutput *device) +{ + if (m_audioOutput == device) + return; + + if (m_audioOutput) + m_audioOutput->q->disconnect(this); + + m_audioOutput = device; + if (m_audioOutput) { + setMuted(m_audioOutput->q->isMuted()); + setVolume(m_audioOutput->q->volume()); + updateOutputRouting(); + connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &MFPlayerSession::updateOutputRouting); + connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this, &MFPlayerSession::setVolume); + connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this, &MFPlayerSession::setMuted); + } +} + +void MFPlayerSession::updateOutputRouting() +{ + int currentAudioTrack = m_trackInfo[QPlatformMediaPlayer::AudioStream].currentIndex; + if (currentAudioTrack > -1) + setActiveTrack(QPlatformMediaPlayer::AudioStream, currentAudioTrack); +} + +void MFPlayerSession::setVideoSink(QVideoSink *sink) +{ + m_videoRendererControl->setSink(sink); +} + +void MFPlayerSession::setActiveTrack(QPlatformMediaPlayer::TrackType type, int index) +{ + if (!m_session) + return; + + // Only audio track selection is currently supported. + if (type != QPlatformMediaPlayer::AudioStream) + return; + + const auto &nativeIndexes = m_trackInfo[type].nativeIndexes; + + if (index < -1 || index >= nativeIndexes.count()) + return; + + // Updating the topology fails if there is a HEVC video stream, + // which causes other issues. Ignoring the change, for now. + if (m_trackInfo[QPlatformMediaPlayer::VideoStream].format == MFVideoFormat_HEVC) + return; + + ComPtr<IMFTopology> topology; + + if (SUCCEEDED(m_session->GetFullTopology(QMM_MFSESSION_GETFULLTOPOLOGY_CURRENT, 0, &topology))) { + + m_restorePosition = position() * 10000; + + if (m_state.command == CmdStart) + stop(); + + if (m_trackInfo[type].outputNodeId != TOPOID(-1)) { + ComPtr<IMFTopologyNode> node; + if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].outputNodeId, &node))) { + topology->RemoveNode(node.Get()); + m_trackInfo[type].outputNodeId = TOPOID(-1); + } + } + if (m_trackInfo[type].sourceNodeId != TOPOID(-1)) { + ComPtr<IMFTopologyNode> node; + if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].sourceNodeId, &node))) { + topology->RemoveNode(node.Get()); + m_trackInfo[type].sourceNodeId = TOPOID(-1); + } + } + + IMFMediaSource *mediaSource = m_sourceResolver->mediaSource(); + + ComPtr<IMFPresentationDescriptor> sourcePD; + if (SUCCEEDED(mediaSource->CreatePresentationDescriptor(&sourcePD))) { + + if (m_trackInfo[type].currentIndex >= 0 && m_trackInfo[type].currentIndex < nativeIndexes.count()) + sourcePD->DeselectStream(nativeIndexes.at(m_trackInfo[type].currentIndex)); + + m_trackInfo[type].currentIndex = index; + + if (index == -1) { + m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology.Get()); + } else { + int nativeIndex = nativeIndexes.at(index); + sourcePD->SelectStream(nativeIndex); + + ComPtr<IMFStreamDescriptor> streamDesc; + BOOL selected = FALSE; + + if (SUCCEEDED(sourcePD->GetStreamDescriptorByIndex(nativeIndex, &selected, &streamDesc))) { + ComPtr<IMFTopologyNode> sourceNode = addSourceNode( + topology.Get(), mediaSource, sourcePD.Get(), streamDesc.Get()); + if (sourceNode) { + ComPtr<IMFTopologyNode> outputNode = + addOutputNode(MFPlayerSession::Audio, topology.Get(), 0); + if (outputNode) { + if (SUCCEEDED(sourceNode->ConnectOutput(0, outputNode.Get(), 0))) { + sourceNode->GetTopoNodeID(&m_trackInfo[type].sourceNodeId); + outputNode->GetTopoNodeID(&m_trackInfo[type].outputNodeId); + m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, + topology.Get()); + } + } + } + } + } + m_updatingTopology = true; + } + } +} + +int MFPlayerSession::activeTrack(QPlatformMediaPlayer::TrackType type) +{ + if (type >= QPlatformMediaPlayer::NTrackTypes) + return -1; + return m_trackInfo[type].currentIndex; +} + +int MFPlayerSession::trackCount(QPlatformMediaPlayer::TrackType type) +{ + if (type >= QPlatformMediaPlayer::NTrackTypes) + return -1; + return m_trackInfo[type].metaData.count(); +} + +QMediaMetaData MFPlayerSession::trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber) +{ + if (type >= QPlatformMediaPlayer::NTrackTypes) + return {}; + + if (trackNumber < 0 || trackNumber >= m_trackInfo[type].metaData.count()) + return {}; + + return m_trackInfo[type].metaData.at(trackNumber); +} + +QT_END_NAMESPACE + +#include "moc_mfplayersession_p.cpp" diff --git a/src/plugins/multimedia/windows/player/mfplayersession_p.h b/src/plugins/multimedia/windows/player/mfplayersession_p.h new file mode 100644 index 000000000..50141a7fb --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfplayersession_p.h @@ -0,0 +1,240 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef MFPLAYERSESSION_H +#define MFPLAYERSESSION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <mfapi.h> +#include <mfidl.h> + +#include "qmediaplayer.h" +#include "qmediatimerange.h" + +#include <QtCore/qcoreevent.h> +#include <QtCore/qmutex.h> +#include <QtCore/qurl.h> +#include <QtCore/qwaitcondition.h> +#include <QtMultimedia/qaudioformat.h> +#include <QtMultimedia/qvideoframeformat.h> +#include <qaudiodevice.h> +#include <qtimer.h> +#include "mfplayercontrol_p.h" +#include <private/qcomptr_p.h> +#include <evrhelpers_p.h> + +QT_BEGIN_NAMESPACE + +class QUrl; + +class SourceResolver; +class MFVideoRendererControl; +class MFPlayerControl; +class MFPlayerService; +class AudioSampleGrabberCallback; +class MFTransform; + +class MFPlayerSession : public QObject, public IMFAsyncCallback +{ + Q_OBJECT + friend class SourceResolver; +public: + MFPlayerSession(MFPlayerControl *playerControl = 0); + + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) override; + + STDMETHODIMP_(ULONG) AddRef(void) override; + + STDMETHODIMP_(ULONG) Release(void) override; + + STDMETHODIMP Invoke(IMFAsyncResult *pResult) override; + + STDMETHODIMP GetParameters(DWORD *pdwFlags, DWORD *pdwQueue) override + { + Q_UNUSED(pdwFlags); + Q_UNUSED(pdwQueue); + return E_NOTIMPL; + } + + void load(const QUrl &media, QIODevice *stream); + void stop(bool immediate = false); + void start(); + void pause(); + + QMediaPlayer::MediaStatus status() const; + qint64 position(); + void setPosition(qint64 position); + qreal playbackRate() const; + void setPlaybackRate(qreal rate); + float bufferProgress(); + QMediaTimeRange availablePlaybackRanges(); + + void changeStatus(QMediaPlayer::MediaStatus newStatus); + + void close(); + + void setAudioOutput(QPlatformAudioOutput *device); + + QMediaMetaData metaData() const { return m_metaData; } + + void setVideoSink(QVideoSink *sink); + + void setActiveTrack(QPlatformMediaPlayer::TrackType type, int index); + int activeTrack(QPlatformMediaPlayer::TrackType type); + int trackCount(QPlatformMediaPlayer::TrackType); + QMediaMetaData trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber); + + void setPlayerControl(MFPlayerControl *playerControl) { m_playerControl = playerControl; } + + void statusChanged() { if (m_playerControl) m_playerControl->handleStatusChanged(); } + void tracksChanged() { if (m_playerControl) m_playerControl->handleTracksChanged(); } + void audioAvailable() { if (m_playerControl) m_playerControl->handleAudioAvailable(); } + void videoAvailable() { if (m_playerControl) m_playerControl->handleVideoAvailable(); } + void durationUpdate(qint64 duration) { if (m_playerControl) m_playerControl->handleDurationUpdate(duration); } + void seekableUpdate(bool seekable) { if (m_playerControl) m_playerControl->handleSeekableUpdate(seekable); } + void error(QMediaPlayer::Error error, QString errorString, bool isFatal) { if (m_playerControl) m_playerControl->handleError(error, errorString, isFatal); } + void playbackRateChanged(qreal rate) { if (m_playerControl) m_playerControl->playbackRateChanged(rate); } + void bufferProgressChanged(float percentFilled) { if (m_playerControl) m_playerControl->bufferProgressChanged(percentFilled); } + void metaDataChanged() { if (m_playerControl) m_playerControl->metaDataChanged(); } + void positionChanged(qint64 position) { if (m_playerControl) m_playerControl->positionChanged(position); } + +public Q_SLOTS: + void setVolume(float volume); + void setMuted(bool muted); + void updateOutputRouting(); + +Q_SIGNALS: + void sessionEvent(const ComPtr<IMFMediaEvent> &sessionEvent); + +private Q_SLOTS: + void handleMediaSourceReady(); + void handleSessionEvent(const ComPtr<IMFMediaEvent> &sessionEvent); + void handleSourceError(long hr); + void timeout(); + +private: + long m_cRef; + MFPlayerControl *m_playerControl = nullptr; + MFVideoRendererControl *m_videoRendererControl = nullptr; + ComPtr<IMFMediaSession> m_session; + ComPtr<IMFPresentationClock> m_presentationClock; + ComPtr<IMFRateControl> m_rateControl; + ComPtr<IMFRateSupport> m_rateSupport; + ComPtr<IMFAudioStreamVolume> m_volumeControl; + ComPtr<IPropertyStore> m_netsourceStatistics; + qint64 m_position = 0; + qint64 m_restorePosition = -1; + qint64 m_timeCounter = 0; + UINT64 m_duration = 0; + bool m_updatingTopology = false; + bool m_updateRoutingOnStart = false; + + enum Command + { + CmdNone = 0, + CmdStop, + CmdStart, + CmdPause, + CmdSeek, + CmdSeekResume, + CmdStartAndSeek + }; + + void clear(); + void setPositionInternal(qint64 position, Command requestCmd); + void setPlaybackRateInternal(qreal rate); + void commitRateChange(qreal rate, BOOL isThin); + bool canScrub() const; + void scrub(bool enableScrub); + bool m_scrubbing; + float m_restoreRate; + + ComPtr<SourceResolver> m_sourceResolver; + EventHandle m_hCloseEvent; + bool m_closing; + + enum MediaType + { + Unknown = 0, + Audio = 1, + Video = 2, + }; + DWORD m_mediaTypes; + + enum PendingState + { + NoPending = 0, + CmdPending, + SeekPending, + }; + + struct SeekState + { + void setCommand(Command cmd) { + prevCmd = command; + command = cmd; + } + Command command; + Command prevCmd; + float rate; // Playback rate + BOOL isThin; // Thinned playback? + qint64 start; // Start position + }; + SeekState m_state; // Current nominal state. + SeekState m_request; // Pending request. + PendingState m_pendingState; + float m_pendingRate; + void updatePendingCommands(Command command); + + struct TrackInfo + { + QList<QMediaMetaData> metaData; + QList<int> nativeIndexes; + int currentIndex = -1; + TOPOID sourceNodeId = -1; + TOPOID outputNodeId = -1; + GUID format = GUID_NULL; + }; + TrackInfo m_trackInfo[QPlatformMediaPlayer::NTrackTypes]; + + QMediaPlayer::MediaStatus m_status; + bool m_canScrub; + float m_volume = 1.; + bool m_muted = false; + + QPlatformAudioOutput *m_audioOutput = nullptr; + QMediaMetaData m_metaData; + + void setVolumeInternal(float volume); + + bool createSession(); + void setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD); + bool getStreamInfo(IMFStreamDescriptor *stream, MFPlayerSession::MediaType *type, QString *name, QString *language, GUID *format) const; + ComPtr<IMFTopologyNode> addSourceNode(IMFTopology *topology, IMFMediaSource *source, + IMFPresentationDescriptor *presentationDesc, + IMFStreamDescriptor *streamDesc); + ComPtr<IMFTopologyNode> addOutputNode(MediaType mediaType, IMFTopology *topology, DWORD sinkID); + + QAudioFormat audioFormatForMFMediaType(IMFMediaType *mediaType) const; + + ComPtr<IMFTopology> insertMFT(const ComPtr<IMFTopology> &topology, TOPOID outputNodeId); + bool insertResizer(IMFTopology *topology); + void insertColorConverter(IMFTopology *topology, TOPOID outputNodeId); + + QTimer m_signalPositionChangeTimer; + qint64 m_lastPosition = -1; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/multimedia/windows/player/mfvideorenderercontrol.cpp b/src/plugins/multimedia/windows/player/mfvideorenderercontrol.cpp new file mode 100644 index 000000000..7c79c3a8a --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfvideorenderercontrol.cpp @@ -0,0 +1,152 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "mfvideorenderercontrol_p.h" +#include "mfactivate_p.h" + +#include "evrcustompresenter_p.h" + +#include <private/qplatformvideosink_p.h> + +QT_BEGIN_NAMESPACE + +class EVRCustomPresenterActivate : public MFAbstractActivate +{ +public: + EVRCustomPresenterActivate(QVideoSink *sink); + + STDMETHODIMP ActivateObject(REFIID riid, void **ppv) override; + STDMETHODIMP ShutdownObject() override; + STDMETHODIMP DetachObject() override; + + void setSink(QVideoSink *sink); + void setCropRect(QRect cropRect); + +private: + // Destructor is not public. Caller should call Release. + ~EVRCustomPresenterActivate() override { } + + EVRCustomPresenter *m_presenter; + QVideoSink *m_videoSink; + QRect m_cropRect; + QMutex m_mutex; +}; + + +MFVideoRendererControl::MFVideoRendererControl(QObject *parent) + : QObject(parent) +{ +} + +MFVideoRendererControl::~MFVideoRendererControl() +{ + releaseActivate(); +} + +void MFVideoRendererControl::releaseActivate() +{ + if (m_sink) + m_sink->platformVideoSink()->setVideoFrame(QVideoFrame()); + + if (m_presenterActivate) { + m_presenterActivate->ShutdownObject(); + m_presenterActivate->Release(); + m_presenterActivate = NULL; + } + + if (m_currentActivate) { + m_currentActivate->ShutdownObject(); + m_currentActivate->Release(); + } + m_currentActivate = NULL; +} + +void MFVideoRendererControl::setSink(QVideoSink *sink) +{ + m_sink = sink; + + if (m_presenterActivate) + m_presenterActivate->setSink(m_sink); +} + +void MFVideoRendererControl::setCropRect(const QRect &cropRect) +{ + if (m_presenterActivate) + m_presenterActivate->setCropRect(cropRect); +} + +IMFActivate* MFVideoRendererControl::createActivate() +{ + releaseActivate(); + + if (m_sink) { + // Create the EVR media sink, but replace the presenter with our own + if (SUCCEEDED(MFCreateVideoRendererActivate(::GetShellWindow(), &m_currentActivate))) { + m_presenterActivate = new EVRCustomPresenterActivate(m_sink); + m_currentActivate->SetUnknown(MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE, m_presenterActivate); + } + } + + return m_currentActivate; +} + +EVRCustomPresenterActivate::EVRCustomPresenterActivate(QVideoSink *sink) + : MFAbstractActivate() + , m_presenter(0) + , m_videoSink(sink) +{ } + +HRESULT EVRCustomPresenterActivate::ActivateObject(REFIID riid, void **ppv) +{ + if (!ppv) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + if (!m_presenter) { + m_presenter = new EVRCustomPresenter(m_videoSink); + m_presenter->setCropRect(m_cropRect); + } + return m_presenter->QueryInterface(riid, ppv); +} + +HRESULT EVRCustomPresenterActivate::ShutdownObject() +{ + // The presenter does not implement IMFShutdown so + // this function is the same as DetachObject() + return DetachObject(); +} + +HRESULT EVRCustomPresenterActivate::DetachObject() +{ + QMutexLocker locker(&m_mutex); + if (m_presenter) { + m_presenter->Release(); + m_presenter = 0; + } + return S_OK; +} + +void EVRCustomPresenterActivate::setSink(QVideoSink *sink) +{ + QMutexLocker locker(&m_mutex); + if (m_videoSink == sink) + return; + + m_videoSink = sink; + + if (m_presenter) + m_presenter->setSink(sink); +} + +void EVRCustomPresenterActivate::setCropRect(QRect cropRect) +{ + QMutexLocker locker(&m_mutex); + if (m_cropRect == cropRect) + return; + + m_cropRect = cropRect; + + if (m_presenter) + m_presenter->setCropRect(cropRect); +} + +QT_END_NAMESPACE diff --git a/src/plugins/multimedia/windows/player/mfvideorenderercontrol_p.h b/src/plugins/multimedia/windows/player/mfvideorenderercontrol_p.h new file mode 100644 index 000000000..ed5195240 --- /dev/null +++ b/src/plugins/multimedia/windows/player/mfvideorenderercontrol_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef MFVIDEORENDERERCONTROL_H +#define MFVIDEORENDERERCONTROL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qobject.h> +#include <qpointer.h> +#include <qrect.h> +#include <mfobjects.h> + +QT_BEGIN_NAMESPACE +class EVRCustomPresenterActivate; +class QVideoSink; + +class MFVideoRendererControl : public QObject +{ +public: + MFVideoRendererControl(QObject *parent = 0); + ~MFVideoRendererControl(); + + void setSink(QVideoSink *surface); + void setCropRect(const QRect &cropRect); + + IMFActivate* createActivate(); + void releaseActivate(); + +private: + QPointer<QVideoSink> m_sink; + IMFActivate *m_currentActivate = nullptr; + EVRCustomPresenterActivate *m_presenterActivate = nullptr; +}; + +QT_END_NAMESPACE + +#endif |