Revision control
Copy as Markdown
// 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/qwindowsaudioutils_p.h>
#include <private/qwindows_scopedpropvariant_p.h>
#include "mfplayersession_p.h"
#include <mferror.h>
#include <nserror.h>
#include <winerror.h>
#include "sourceresolver_p.h"
#include <wmcodecdsp.h>
#include <mfidl.h>
#include <mmdeviceapi.h>
#include <propvarutil.h>
#include <wininet.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)
{
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 (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 INET_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 != status() || !m_sourceResolver
|| m_sourceResolver.Get() != sender())
return;
#ifdef DEBUG_MEDIAFOUNDATION
qDebug() << "handleMediaSourceReady";
#endif
HRESULT hr = S_OK;
ComPtr<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.Get());
metaDataChanged();
sourcePD->GetUINT64(MF_PD_DURATION, &m_duration);
//convert from 100 nanosecond to milisecond
durationUpdate(qint64(m_duration / 10000));
setupPlaybackTopology(mediaSource.Get(), 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(MF_SD_STREAM_NAME, &len)) && len > 0) {
WCHAR *wstr = new WCHAR[len+1];
if (SUCCEEDED(stream->GetString(MF_SD_STREAM_NAME, wstr, len + 1, &len))) {
*name = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr));
}
delete []wstr;
}
if (SUCCEEDED(stream->GetStringLength(MF_SD_LANGUAGE, &len)) && len > 0) {
WCHAR *wstr = new WCHAR[len+1];
if (SUCCEEDED(stream->GetString(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)) {
TrackType trackType = (mediaType == Audio) ?
TrackType::AudioStream
: TrackType::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[TrackType::VideoStream].outputNodeId != TOPOID(-1))
topology = insertMFT(
topology,
m_trackInfo[TrackType::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 (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 (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;
}
QtMultimediaPrivate::ScopedPropVariant varStart;
InitPropVariantFromInt64(m_position, varStart.get());
if (SUCCEEDED(m_session->Start(&GUID_NULL, varStart.get()))) {
m_state.setCommand(CmdStart);
m_pendingState = CmdPending;
} else {
error(QMediaPlayer::ResourceError, tr("failed to start playback"), true);
}
}
}
void MFPlayerSession::pause()
{
#ifdef DEBUG_MEDIAFOUNDATION
qDebug() << "pause";
#endif
// Pause() may technically succeed during loading, but the session
// is not yet fully initialized (no topology/clock), so the pause
// has no real effect. Defer it until the topology is fully resolved
// (MF_TOPOSTATUS_READY), when the session is in a stable stopped state.
if (status() == QMediaPlayer::LoadingMedia) {
m_deferredPause = true;
return;
}
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 (status() == QMediaPlayer::EndOfMedia) {
setPosition(0);
positionChanged(0);
}
}
}
void MFPlayerSession::changeStatus(QMediaPlayer::MediaStatus newStatus)
{
if (!m_playerControl)
return;
#ifdef DEBUG_MEDIAFOUNDATION
qDebug() << "MFPlayerSession::changeStatus" << newStatus;
#endif
// notify the control to run its session-specific handling
statusChanged(newStatus);
}
QMediaPlayer::MediaStatus MFPlayerSession::status() const
{
if (!m_playerControl)
return QMediaPlayer::NoMedia;
return m_playerControl->mediaStatus();
}
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 (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
QtMultimediaPrivate::ScopedPropVariant varStart;
varStart->vt = VT_I8;
varStart->hVal.QuadPart = LONGLONG(position * 10000);
if (SUCCEEDED(m_session->Start(NULL, varStart.get()))) {
// 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;
//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;
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.
QtMultimediaPrivate::ScopedPropVariant var;
if (m_netsourceStatistics->GetValue(key, var.get()) == S_OK)
progress = var->lVal;
#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) {
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.
QtMultimediaPrivate::ScopedPropVariant startVar;
if (m_netsourceStatistics->GetValue(key, startVar.get()) == S_OK) {
start = qint64(startVar->uhVal.QuadPart / 10000);
key.pid = MFNETSOURCE_SEEKRANGEEND_ID;
QtMultimediaPrivate::ScopedPropVariant endVar;
if (m_netsourceStatistics->GetValue(key, endVar.get()) == S_OK)
end = qint64(endVar->uhVal.QuadPart / 10000);
}
}
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) {
Q_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: {
QtMultimediaPrivate::ScopedPropVariant var;
sessionEvent->GetValue(var.get());
qWarning() << "handleSessionEvent: non fatal error = " << var->ulVal;
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)) {
QtMultimediaPrivate::ScopedPropVariant var;
if (SUCCEEDED(sessionEvent->GetValue(var.get())) && (var->vt == VT_R4))
m_state.rate = var->fltVal;
playbackRateChanged(playbackRate());
}
break;
case MESessionScrubSampleComplete :
if (m_scrubbing)
updatePendingCommands(CmdStart);
break;
case MESessionStarted:
if (status() == QMediaPlayer::EndOfMedia
|| 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 (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 (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 (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();
if (m_deferredPause) {
m_deferredPause = false;
pause();
}
}
}
}
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_deferredPause = false;
m_pendingState = NoPending;
m_state.command = CmdStop;
m_state.prevCmd = CmdNone;
m_request.command = CmdNone;
m_request.prevCmd = CmdNone;
for (size_t 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[TrackType::AudioStream].currentIndex;
if (currentAudioTrack > -1)
setActiveTrack(TrackType::AudioStream, currentAudioTrack);
}
void MFPlayerSession::setVideoSink(QVideoSink *sink)
{
m_videoRendererControl->setSink(sink);
}
void MFPlayerSession::setActiveTrack(TrackType type, int index)
{
if (!m_session)
return;
// Only audio track selection is currently supported.
if (type != TrackType::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[TrackType::VideoStream].format == MFVideoFormat_HEVC)
return;
ComPtr<IMFTopology> topology;
if (SUCCEEDED(m_session->GetFullTopology(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);
}
}
ComPtr<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.Get(), 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(TrackType type)
{
return m_trackInfo[type].currentIndex;
}
int MFPlayerSession::trackCount(TrackType type)
{
return m_trackInfo[type].metaData.count();
}
QMediaMetaData MFPlayerSession::trackMetaData(TrackType type, int trackNumber)
{
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"