Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
mfplayersession.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "private/qplatformmediaplayer_p.h"
5
6#include <QtCore/qcoreapplication.h>
7#include <QtCore/qdatetime.h>
8#include <QtCore/qthread.h>
9#include <QtCore/qvarlengtharray.h>
10#include <QtCore/qdebug.h>
11#include <QtCore/qfile.h>
12#include <QtCore/qbuffer.h>
13
14#include "private/qplatformaudiooutput_p.h"
15#include "qaudiooutput.h"
16
19#include <mfmetadata_p.h>
20#include <private/qwindowsaudioutils_p.h>
21#include <private/qwindows_scopedpropvariant_p.h>
22
24#include <mferror.h>
25#include <nserror.h>
26#include <winerror.h>
28#include <wmcodecdsp.h>
29
30#include <mfidl.h>
31#include <mmdeviceapi.h>
32#include <propvarutil.h>
33#include <wininet.h>
34#include <functiondiscoverykeys_devpkey.h>
35
36//#define DEBUG_MEDIAFOUNDATION
37
39
40MFPlayerSession::MFPlayerSession(MFPlayerControl *playerControl)
41 : m_cRef(1),
42 m_playerControl(playerControl),
43 m_scrubbing(false),
44 m_restoreRate(1),
45 m_closing(false),
46 m_mediaTypes(0),
47 m_pendingRate(1)
48
49{
50 connect(this, &MFPlayerSession::sessionEvent, this, &MFPlayerSession::handleSessionEvent);
51
52 m_signalPositionChangeTimer.setInterval(10);
53 m_signalPositionChangeTimer.setTimerType(Qt::PreciseTimer);
54 m_signalPositionChangeTimer.callOnTimeout(this, &MFPlayerSession::timeout);
55
56 m_pendingState = NoPending;
57 ZeroMemory(&m_state, sizeof(m_state));
58 m_state.command = CmdStop;
59 m_state.prevCmd = CmdNone;
60 m_state.rate = 1.0f;
61 ZeroMemory(&m_request, sizeof(m_request));
62 m_request.command = CmdNone;
63 m_request.prevCmd = CmdNone;
64 m_request.rate = 1.0f;
65
66 m_videoRendererControl = new MFVideoRendererControl(this);
67}
68
69void MFPlayerSession::timeout()
70{
71 const qint64 pos = position();
72
73 if (pos != m_lastPosition) {
74 const bool updatePos = m_timeCounter++ % 10 == 0;
75 if (pos >= qint64(m_duration / 10000 - 20)) {
76 if (m_playerControl->doLoop()) {
77 m_session->Pause();
78 setPosition(0);
79 positionChanged(0);
80 } else {
81 if (updatePos)
82 positionChanged(pos);
83 }
84 } else {
85 if (updatePos)
86 positionChanged(pos);
87 }
88 m_lastPosition = pos;
89 }
90}
91
93{
94#ifdef DEBUG_MEDIAFOUNDATION
95 qDebug() << "close";
96#endif
97
98 m_signalPositionChangeTimer.stop();
99 clear();
100 if (!m_session)
101 return;
102
103 HRESULT hr = S_OK;
104 if (m_session) {
105 m_closing = true;
106 hr = m_session->Close();
107 if (SUCCEEDED(hr)) {
108 DWORD dwWaitResult = WaitForSingleObject(m_hCloseEvent.get(), 2000);
109 if (dwWaitResult == WAIT_TIMEOUT) {
110 qWarning() << "session close time out!";
111 }
112 }
113 m_closing = false;
114 }
115
116 if (SUCCEEDED(hr)) {
117 if (m_session)
118 m_session->Shutdown();
119 if (m_sourceResolver)
120 m_sourceResolver->shutdown();
121 }
122 m_sourceResolver.Reset();
123
124 m_videoRendererControl->releaseActivate();
125// } else if (m_playerService->videoWindowControl()) {
126// m_playerService->videoWindowControl()->releaseActivate();
127// }
128
129 m_session.Reset();
130 m_hCloseEvent = {};
131 m_lastPosition = -1;
132 m_position = 0;
133}
134
135void MFPlayerSession::load(const QUrl &url, QIODevice *stream)
136{
137#ifdef DEBUG_MEDIAFOUNDATION
138 qDebug() << "load";
139#endif
140 clear();
141
142 if (status() == QMediaPlayer::LoadingMedia && m_sourceResolver)
143 m_sourceResolver->cancel();
144
145 if (url.isEmpty() && !stream) {
146 close();
147 changeStatus(QMediaPlayer::NoMedia);
148 } else if (stream && (!stream->isReadable())) {
149 close();
150 changeStatus(QMediaPlayer::InvalidMedia);
151 error(QMediaPlayer::ResourceError, tr("Invalid stream source."), true);
152 } else if (createSession()) {
153 changeStatus(QMediaPlayer::LoadingMedia);
154 m_sourceResolver->load(url, stream);
155 if (url.isLocalFile())
156 m_updateRoutingOnStart = true;
157 }
158 positionChanged(position());
159}
160
161void MFPlayerSession::handleSourceError(long hr)
162{
163 QString errorString;
164 QMediaPlayer::Error errorCode = QMediaPlayer::ResourceError;
165 switch (hr) {
166 case QMediaPlayer::FormatError:
167 errorCode = QMediaPlayer::FormatError;
168 errorString = tr("Attempting to play invalid Qt resource.");
169 break;
170 case NS_E_FILE_NOT_FOUND:
171 errorString = tr("The system cannot find the file specified.");
172 break;
173 case NS_E_SERVER_NOT_FOUND:
174 errorString = tr("The specified server could not be found.");
175 break;
176 case MF_E_UNSUPPORTED_BYTESTREAM_TYPE:
177 errorCode = QMediaPlayer::FormatError;
178 errorString = tr("Unsupported media type.");
179 break;
180 case MF_E_UNSUPPORTED_SCHEME:
181 errorCode = QMediaPlayer::ResourceError;
182 errorString = tr("Unsupported URL scheme.");
183 break;
184 case INET_E_CANNOT_CONNECT:
185 errorCode = QMediaPlayer::NetworkError;
186 errorString = tr("Connection to server could not be established.");
187 break;
188 default:
189 qWarning() << "handleSourceError:"
190 << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hr);
191 errorString = tr("Failed to load source.");
192 break;
193 }
194 changeStatus(QMediaPlayer::InvalidMedia);
195 error(errorCode, errorString, true);
196}
197
198void MFPlayerSession::handleMediaSourceReady()
199{
200 if (QMediaPlayer::LoadingMedia != status() || !m_sourceResolver
201 || m_sourceResolver.Get() != sender())
202 return;
203#ifdef DEBUG_MEDIAFOUNDATION
204 qDebug() << "handleMediaSourceReady";
205#endif
206 HRESULT hr = S_OK;
207 IMFMediaSource* mediaSource = m_sourceResolver->mediaSource();
208
209 DWORD dwCharacteristics = 0;
210 mediaSource->GetCharacteristics(&dwCharacteristics);
211 seekableUpdate(MFMEDIASOURCE_CAN_SEEK & dwCharacteristics);
212
213 ComPtr<IMFPresentationDescriptor> sourcePD;
214 hr = mediaSource->CreatePresentationDescriptor(&sourcePD);
215 if (SUCCEEDED(hr)) {
216 m_duration = 0;
217 m_metaData = MFMetaData::fromNative(mediaSource);
219 sourcePD->GetUINT64(MF_PD_DURATION, &m_duration);
220 //convert from 100 nanosecond to milisecond
221 durationUpdate(qint64(m_duration / 10000));
222 setupPlaybackTopology(mediaSource, sourcePD.Get());
224 } else {
225 changeStatus(QMediaPlayer::InvalidMedia);
226 error(QMediaPlayer::ResourceError, tr("Cannot create presentation descriptor."), true);
227 }
228}
229
230bool MFPlayerSession::getStreamInfo(IMFStreamDescriptor *stream,
231 MFPlayerSession::MediaType *type,
232 QString *name,
233 QString *language,
234 GUID *format) const
235{
236 if (!stream || !type || !name || !language || !format)
237 return false;
238
239 *type = Unknown;
240 *name = QString();
241 *language = QString();
242
243 ComPtr<IMFMediaTypeHandler> typeHandler;
244
245 if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler))) {
246
247 UINT32 len = 0;
248 if (SUCCEEDED(stream->GetStringLength(MF_SD_STREAM_NAME, &len)) && len > 0) {
249 WCHAR *wstr = new WCHAR[len+1];
250 if (SUCCEEDED(stream->GetString(MF_SD_STREAM_NAME, wstr, len + 1, &len))) {
251 *name = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr));
252 }
253 delete []wstr;
254 }
255 if (SUCCEEDED(stream->GetStringLength(MF_SD_LANGUAGE, &len)) && len > 0) {
256 WCHAR *wstr = new WCHAR[len+1];
257 if (SUCCEEDED(stream->GetString(MF_SD_LANGUAGE, wstr, len + 1, &len))) {
258 *language = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr));
259 }
260 delete []wstr;
261 }
262
263 GUID guidMajorType;
264 if (SUCCEEDED(typeHandler->GetMajorType(&guidMajorType))) {
265 if (guidMajorType == MFMediaType_Audio)
266 *type = Audio;
267 else if (guidMajorType == MFMediaType_Video)
268 *type = Video;
269 }
270
271 ComPtr<IMFMediaType> mediaType;
272 if (SUCCEEDED(typeHandler->GetCurrentMediaType(&mediaType))) {
273 mediaType->GetGUID(MF_MT_SUBTYPE, format);
274 }
275 }
276
277 return *type != Unknown;
278}
279
280void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD)
281{
282 HRESULT hr = S_OK;
283 // Get the number of streams in the media source.
284 DWORD cSourceStreams = 0;
285 hr = sourcePD->GetStreamDescriptorCount(&cSourceStreams);
286 if (FAILED(hr)) {
287 changeStatus(QMediaPlayer::InvalidMedia);
288 error(QMediaPlayer::ResourceError, tr("Failed to get stream count."), true);
289 return;
290 }
291
292 ComPtr<IMFTopology> topology;
293 hr = MFCreateTopology(&topology);
294 if (FAILED(hr)) {
295 changeStatus(QMediaPlayer::InvalidMedia);
296 error(QMediaPlayer::ResourceError, tr("Failed to create topology."), true);
297 return;
298 }
299
300 // For each stream, create the topology nodes and add them to the topology.
301 DWORD succeededCount = 0;
302 for (DWORD i = 0; i < cSourceStreams; i++) {
303 BOOL selected = FALSE;
304 bool streamAdded = false;
305 ComPtr<IMFStreamDescriptor> streamDesc;
306
307 HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &selected, &streamDesc);
308 if (SUCCEEDED(hr)) {
309 // The media might have multiple audio and video streams,
310 // only use one of each kind, and only if it is selected by default.
311 MediaType mediaType = Unknown;
312 QString streamName;
313 QString streamLanguage;
314 GUID format = GUID_NULL;
315
316 if (getStreamInfo(streamDesc.Get(), &mediaType, &streamName, &streamLanguage,
317 &format)) {
318
319 QPlatformMediaPlayer::TrackType trackType = (mediaType == Audio) ?
320 QPlatformMediaPlayer::AudioStream : QPlatformMediaPlayer::VideoStream;
321
322 QLocale::Language lang = streamLanguage.isEmpty() ?
323 QLocale::Language::AnyLanguage : QLocale(streamLanguage).language();
324
325 QMediaMetaData metaData;
326 metaData.insert(QMediaMetaData::Title, streamName);
327 metaData.insert(QMediaMetaData::Language, lang);
328
329 m_trackInfo[trackType].metaData.append(metaData);
330 m_trackInfo[trackType].nativeIndexes.append(i);
331 m_trackInfo[trackType].format = format;
332
333 if (((m_mediaTypes & mediaType) == 0) && selected) { // Check if this type isn't already added
334 ComPtr<IMFTopologyNode> sourceNode =
335 addSourceNode(topology.Get(), source, sourcePD, streamDesc.Get());
336 if (sourceNode) {
337 ComPtr<IMFTopologyNode> outputNode =
338 addOutputNode(mediaType, topology.Get(), 0);
339 if (outputNode) {
340 sourceNode->GetTopoNodeID(&m_trackInfo[trackType].sourceNodeId);
341 outputNode->GetTopoNodeID(&m_trackInfo[trackType].outputNodeId);
342
343 hr = sourceNode->ConnectOutput(0, outputNode.Get(), 0);
344
345 if (FAILED(hr)) {
346 error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false);
347 } else {
348 m_trackInfo[trackType].currentIndex = m_trackInfo[trackType].nativeIndexes.count() - 1;
349 streamAdded = true;
350 succeededCount++;
351 m_mediaTypes |= mediaType;
352 switch (mediaType) {
353 case Audio:
355 break;
356 case Video:
358 break;
359 default:
360 break;
361 }
362 }
363 } else {
364 // remove the source node if the output node cannot be created
365 topology->RemoveNode(sourceNode.Get());
366 }
367 }
368 }
369 }
370
371 if (selected && !streamAdded)
372 sourcePD->DeselectStream(i);
373 }
374 }
375
376 if (succeededCount == 0) {
377 changeStatus(QMediaPlayer::InvalidMedia);
378 error(QMediaPlayer::ResourceError, tr("Unable to play."), true);
379 } else {
380 if (m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId != TOPOID(-1))
381 topology = insertMFT(topology, m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId);
382
383 hr = m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology.Get());
384 if (SUCCEEDED(hr)) {
385 m_updatingTopology = true;
386 } else {
387 changeStatus(QMediaPlayer::InvalidMedia);
388 error(QMediaPlayer::ResourceError, tr("Failed to set topology."), true);
389 }
390 }
391}
392
393ComPtr<IMFTopologyNode> MFPlayerSession::addSourceNode(IMFTopology *topology,
394 IMFMediaSource *source,
395 IMFPresentationDescriptor *presentationDesc,
396 IMFStreamDescriptor *streamDesc)
397{
398 ComPtr<IMFTopologyNode> node;
399 HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node);
400 if (SUCCEEDED(hr)) {
401 hr = node->SetUnknown(MF_TOPONODE_SOURCE, source);
402 if (SUCCEEDED(hr)) {
403 hr = node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, presentationDesc);
404 if (SUCCEEDED(hr)) {
405 hr = node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, streamDesc);
406 if (SUCCEEDED(hr)) {
407 hr = topology->AddNode(node.Get());
408 if (SUCCEEDED(hr))
409 return node;
410 }
411 }
412 }
413 }
414 return NULL;
415}
416
417ComPtr<IMFTopologyNode> MFPlayerSession::addOutputNode(MediaType mediaType, IMFTopology *topology,
418 DWORD sinkID)
419{
420 ComPtr<IMFTopologyNode> node;
421 if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node)))
422 return NULL;
423
424 ComPtr<IMFActivate> activate;
425 if (mediaType == Audio) {
426 if (m_audioOutput) {
427 auto id = m_audioOutput->device.id();
428 if (id.isEmpty()) {
429 qInfo() << "No audio output";
430 return NULL;
431 }
432
433 HRESULT hr = MFCreateAudioRendererActivate(&activate);
434 if (FAILED(hr)) {
435 qWarning() << "Failed to create audio renderer activate";
436 return NULL;
437 }
438
439 QString s = QString::fromUtf8(id);
440 hr = activate->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, (LPCWSTR)s.utf16());
441 if (FAILED(hr)) {
442 qWarning() << "Failed to set attribute for audio device"
443 << m_audioOutput->device.description();
444 return NULL;
445 }
446 }
447 } else if (mediaType == Video) {
448 activate = m_videoRendererControl->createActivate();
449
450 QSize resolution = m_metaData.value(QMediaMetaData::Resolution).toSize();
451
452 if (resolution.isValid())
453 m_videoRendererControl->setCropRect(QRect(QPoint(), resolution));
454
455 } else {
456 // Unknown stream type.
457 error(QMediaPlayer::FormatError, tr("Unknown stream type."), false);
458 }
459
460 if (!activate || FAILED(node->SetObject(activate.Get()))
461 || FAILED(node->SetUINT32(MF_TOPONODE_STREAMID, sinkID))
462 || FAILED(node->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE))
463 || FAILED(topology->AddNode(node.Get()))) {
464 node.Reset();
465 }
466
467 if (activate && mediaType == Audio)
468 activate.Reset();
469
470 return node;
471}
472
473// BindOutputNode
474// Sets the IMFStreamSink pointer on an output node.
475// IMFActivate pointer in the output node must be converted to an
476// IMFStreamSink pointer before the topology loader resolves the topology.
477HRESULT BindOutputNode(IMFTopologyNode *pNode)
478{
479 ComPtr<IUnknown> nodeObject;
480 ComPtr<IMFActivate> activate;
481 ComPtr<IMFStreamSink> stream;
482 ComPtr<IMFMediaSink> sink;
483
484 HRESULT hr = pNode->GetObject(&nodeObject);
485 if (FAILED(hr))
486 return hr;
487
488 hr = nodeObject->QueryInterface(IID_PPV_ARGS(&activate));
489 if (SUCCEEDED(hr)) {
490 DWORD dwStreamID = 0;
491
492 // Try to create the media sink.
493 hr = activate->ActivateObject(IID_PPV_ARGS(&sink));
494 if (SUCCEEDED(hr))
495 dwStreamID = MFGetAttributeUINT32(pNode, MF_TOPONODE_STREAMID, 0);
496
497 if (SUCCEEDED(hr)) {
498 // First check if the media sink already has a stream sink with the requested ID.
499 hr = sink->GetStreamSinkById(dwStreamID, &stream);
500 if (FAILED(hr)) {
501 // Create the stream sink.
502 hr = sink->AddStreamSink(dwStreamID, NULL, &stream);
503 }
504 }
505
506 // Replace the node's object pointer with the stream sink.
507 if (SUCCEEDED(hr)) {
508 hr = pNode->SetObject(stream.Get());
509 }
510 } else {
511 hr = nodeObject->QueryInterface(IID_PPV_ARGS(&stream));
512 }
513
514 return hr;
515}
516
517// BindOutputNodes
518// Sets the IMFStreamSink pointers on all of the output nodes in a topology.
519HRESULT BindOutputNodes(IMFTopology *pTopology)
520{
521 ComPtr<IMFCollection> collection;
522
523 // Get the collection of output nodes.
524 HRESULT hr = pTopology->GetOutputNodeCollection(&collection);
525
526 // Enumerate all of the nodes in the collection.
527 if (SUCCEEDED(hr)) {
528 DWORD cNodes;
529 hr = collection->GetElementCount(&cNodes);
530
531 if (SUCCEEDED(hr)) {
532 for (DWORD i = 0; i < cNodes; i++) {
533 ComPtr<IUnknown> element;
534 hr = collection->GetElement(i, &element);
535 if (FAILED(hr))
536 break;
537
538 ComPtr<IMFTopologyNode> node;
539 hr = element->QueryInterface(IID_IMFTopologyNode, &node);
540 if (FAILED(hr))
541 break;
542
543 // Bind this node.
544 hr = BindOutputNode(node.Get());
545 if (FAILED(hr))
546 break;
547 }
548 }
549 }
550
551 return hr;
552}
553
554// This method binds output nodes to complete the topology,
555// then loads the topology and inserts MFT between the output node
556// and a filter connected to the output node.
557ComPtr<IMFTopology> MFPlayerSession::insertMFT(const ComPtr<IMFTopology> &topology,
558 TOPOID outputNodeId)
559{
560 bool isNewTopology = false;
561
562 ComPtr<IMFTopoLoader> topoLoader;
563 ComPtr<IMFTopology> resolvedTopology;
564 ComPtr<IMFCollection> outputNodes;
565
566 do {
567 if (FAILED(BindOutputNodes(topology.Get())))
568 break;
569
570 if (FAILED(MFCreateTopoLoader(&topoLoader)))
571 break;
572
573 if (FAILED(topoLoader->Load(topology.Get(), &resolvedTopology, NULL))) {
574 // Topology could not be resolved, adding ourselves a color converter
575 // to the topology might solve the problem
576 insertColorConverter(topology.Get(), outputNodeId);
577 if (FAILED(topoLoader->Load(topology.Get(), &resolvedTopology, NULL)))
578 break;
579 }
580
581 if (insertResizer(resolvedTopology.Get()))
582 isNewTopology = true;
583 } while (false);
584
585 if (isNewTopology) {
586 return resolvedTopology;
587 }
588
589 return topology;
590}
591
592// This method checks if the topology contains a color converter transform (CColorConvertDMO),
593// if it does it inserts a resizer transform (CResizerDMO) to handle dynamic frame size change
594// of the video stream.
595// Returns true if it inserted a resizer
596bool MFPlayerSession::insertResizer(IMFTopology *topology)
597{
598 bool inserted = false;
599 WORD elementCount = 0;
600 ComPtr<IMFTopologyNode> node;
601 ComPtr<IUnknown> object;
602 ComPtr<IWMColorConvProps> colorConv;
603 ComPtr<IMFTransform> resizer;
604 ComPtr<IMFTopologyNode> resizerNode;
605 ComPtr<IMFTopologyNode> inputNode;
606
607 HRESULT hr = topology->GetNodeCount(&elementCount);
608 if (FAILED(hr))
609 return false;
610
611 for (WORD i = 0; i < elementCount; ++i) {
612 node.Reset();
613 object.Reset();
614
615 if (FAILED(topology->GetNode(i, &node)))
616 break;
617
618 MF_TOPOLOGY_TYPE nodeType;
619 if (FAILED(node->GetNodeType(&nodeType)))
620 break;
621
622 if (nodeType != MF_TOPOLOGY_TRANSFORM_NODE)
623 continue;
624
625 if (FAILED(node->GetObject(&object)))
626 break;
627
628 if (FAILED(object->QueryInterface(IID_PPV_ARGS(&colorConv))))
629 continue;
630
631 if (FAILED(CoCreateInstance(CLSID_CResizerDMO, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform,
632 &resizer)))
633 break;
634
635 if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &resizerNode)))
636 break;
637
638 if (FAILED(resizerNode->SetObject(resizer.Get())))
639 break;
640
641 if (FAILED(topology->AddNode(resizerNode.Get())))
642 break;
643
644 DWORD outputIndex = 0;
645 if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) {
646 topology->RemoveNode(resizerNode.Get());
647 break;
648 }
649
650 if (FAILED(inputNode->ConnectOutput(0, resizerNode.Get(), 0))) {
651 topology->RemoveNode(resizerNode.Get());
652 break;
653 }
654
655 if (FAILED(resizerNode->ConnectOutput(0, node.Get(), 0))) {
656 inputNode->ConnectOutput(0, node.Get(), 0);
657 topology->RemoveNode(resizerNode.Get());
658 break;
659 }
660
661 inserted = true;
662 break;
663 }
664
665 return inserted;
666}
667
668// This method inserts a color converter (CColorConvertDMO) in the topology,
669// typically to convert to RGB format.
670// Usually this converter is automatically inserted when the topology is resolved but
671// for some reason it fails to do so in some cases, we then do it ourselves.
672void MFPlayerSession::insertColorConverter(IMFTopology *topology, TOPOID outputNodeId)
673{
674 ComPtr<IMFCollection> outputNodes;
675
676 if (FAILED(topology->GetOutputNodeCollection(&outputNodes)))
677 return;
678
679 DWORD elementCount = 0;
680 if (FAILED(outputNodes->GetElementCount(&elementCount)))
681 return;
682
683 for (DWORD n = 0; n < elementCount; n++) {
684 ComPtr<IUnknown> element;
685 ComPtr<IMFTopologyNode> node;
686 ComPtr<IMFTopologyNode> inputNode;
687 ComPtr<IMFTopologyNode> mftNode;
688 ComPtr<IMFTransform> converter;
689
690 do {
691 if (FAILED(outputNodes->GetElement(n, &element)))
692 break;
693
694 if (FAILED(element->QueryInterface(IID_IMFTopologyNode, &node)))
695 break;
696
697 TOPOID id;
698 if (FAILED(node->GetTopoNodeID(&id)))
699 break;
700
701 if (id != outputNodeId)
702 break;
703
704 DWORD outputIndex = 0;
705 if (FAILED(node->GetInput(0, &inputNode, &outputIndex)))
706 break;
707
708 if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &mftNode)))
709 break;
710
711 if (FAILED(CoCreateInstance(CLSID_CColorConvertDMO, NULL, CLSCTX_INPROC_SERVER,
712 IID_IMFTransform, &converter)))
713 break;
714
715 if (FAILED(mftNode->SetObject(converter.Get())))
716 break;
717
718 if (FAILED(topology->AddNode(mftNode.Get())))
719 break;
720
721 if (FAILED(inputNode->ConnectOutput(0, mftNode.Get(), 0)))
722 break;
723
724 if (FAILED(mftNode->ConnectOutput(0, node.Get(), 0)))
725 break;
726
727 } while (false);
728 }
729}
730
731void MFPlayerSession::stop(bool immediate)
732{
733#ifdef DEBUG_MEDIAFOUNDATION
734 qDebug() << "stop";
735#endif
736 if (!immediate && m_pendingState != NoPending) {
737 m_request.setCommand(CmdStop);
738 } else {
739 if (m_state.command == CmdStop)
740 return;
741
742 if (m_scrubbing)
743 scrub(false);
744
745 if (SUCCEEDED(m_session->Stop())) {
746
747 m_state.setCommand(CmdStop);
748 m_pendingState = CmdPending;
749 if (status() != QMediaPlayer::EndOfMedia) {
750 m_position = 0;
751 positionChanged(0);
752 }
753 } else {
754 error(QMediaPlayer::ResourceError, tr("Failed to stop."), true);
755 }
756 }
757}
758
760{
761 if (status() == QMediaPlayer::LoadedMedia && m_updateRoutingOnStart) {
762 m_updateRoutingOnStart = false;
764 }
765
766 if (status() == QMediaPlayer::EndOfMedia) {
767 m_position = 0; // restart from the beginning
768 positionChanged(0);
769 }
770
771#ifdef DEBUG_MEDIAFOUNDATION
772 qDebug() << "start";
773#endif
774
775 if (m_pendingState != NoPending) {
776 m_request.setCommand(CmdStart);
777 } else {
778 if (m_state.command == CmdStart)
779 return;
780
781 if (m_scrubbing) {
782 scrub(false);
783 m_position = position() * 10000;
784 }
785
786 if (m_restorePosition >= 0) {
787 m_position = m_restorePosition;
788 if (!m_updatingTopology)
789 m_restorePosition = -1;
790 }
791
792 QtMultimediaPrivate::ScopedPropVariant varStart;
793 InitPropVariantFromInt64(m_position, varStart.get());
794
795 if (SUCCEEDED(m_session->Start(&GUID_NULL, varStart.get()))) {
796 m_state.setCommand(CmdStart);
797 m_pendingState = CmdPending;
798 } else {
799 error(QMediaPlayer::ResourceError, tr("failed to start playback"), true);
800 }
801 }
802}
803
805{
806#ifdef DEBUG_MEDIAFOUNDATION
807 qDebug() << "pause";
808#endif
809
810 // Pause() may technically succeed during loading, but the session
811 // is not yet fully initialized (no topology/clock), so the pause
812 // has no real effect. Defer it until the topology is fully resolved
813 // (MF_TOPOSTATUS_READY), when the session is in a stable stopped state.
814 if (status() == QMediaPlayer::LoadingMedia) {
815 m_deferredPause = true;
816 return;
817 }
818
819 if (m_pendingState != NoPending) {
820 m_request.setCommand(CmdPause);
821 } else {
822 if (m_state.command == CmdPause)
823 return;
824
825 if (SUCCEEDED(m_session->Pause())) {
826 m_state.setCommand(CmdPause);
827 m_pendingState = CmdPending;
828 } else {
829 error(QMediaPlayer::ResourceError, tr("Failed to pause."), false);
830 }
831 if (status() == QMediaPlayer::EndOfMedia) {
832 setPosition(0);
833 positionChanged(0);
834 }
835 }
836}
837
838void MFPlayerSession::changeStatus(QMediaPlayer::MediaStatus newStatus)
839{
840 if (!m_playerControl)
841 return;
842#ifdef DEBUG_MEDIAFOUNDATION
843 qDebug() << "MFPlayerSession::changeStatus" << newStatus;
844#endif
845 // notify the control to run its session-specific handling
846 statusChanged(newStatus);
847}
848
849QMediaPlayer::MediaStatus MFPlayerSession::status() const
850{
851 if (!m_playerControl)
852 return QMediaPlayer::NoMedia;
853 return m_playerControl->mediaStatus();
854}
855
856bool MFPlayerSession::createSession()
857{
858 close();
859
860 Q_ASSERT(m_session == NULL);
861
862 HRESULT hr = MFCreateMediaSession(NULL, &m_session);
863 if (FAILED(hr)) {
864 changeStatus(QMediaPlayer::InvalidMedia);
865 error(QMediaPlayer::ResourceError, tr("Unable to create mediasession."), true);
866 return false;
867 }
868
869 m_hCloseEvent = EventHandle{ CreateEvent(NULL, FALSE, FALSE, NULL) };
870
871 hr = m_session->BeginGetEvent(this, m_session.Get());
872 if (FAILED(hr)) {
873 changeStatus(QMediaPlayer::InvalidMedia);
874 error(QMediaPlayer::ResourceError, tr("Unable to pull session events."), false);
875 close();
876 return false;
877 }
878
879 m_sourceResolver = makeComObject<SourceResolver>();
880 QObject::connect(m_sourceResolver.Get(), &SourceResolver::mediaSourceReady, this,
881 &MFPlayerSession::handleMediaSourceReady);
882 QObject::connect(m_sourceResolver.Get(), &SourceResolver::error, this,
883 &MFPlayerSession::handleSourceError);
884
885 m_position = 0;
886 return true;
887}
888
890{
891 if (m_request.command == CmdSeek || m_request.command == CmdSeekResume)
892 return m_request.start;
893
894 if (m_pendingState == SeekPending)
895 return m_state.start;
896
897 if (m_state.command == CmdStop)
898 return m_position / 10000;
899
900 if (m_presentationClock) {
901 MFTIME time, sysTime;
902 if (FAILED(m_presentationClock->GetCorrelatedTime(0, &time, &sysTime)))
903 return m_position / 10000;
904 return qint64(time / 10000);
905 }
906 return m_position / 10000;
907}
908
909void MFPlayerSession::setPosition(qint64 position)
910{
911#ifdef DEBUG_MEDIAFOUNDATION
912 qDebug() << "setPosition";
913#endif
914 if (m_pendingState != NoPending) {
915 m_request.setCommand(CmdSeek);
916 m_request.start = position;
917 } else {
918 setPositionInternal(position, CmdNone);
919 }
920}
921
922void MFPlayerSession::setPositionInternal(qint64 position, Command requestCmd)
923{
924 if (status() == QMediaPlayer::EndOfMedia)
925 changeStatus(QMediaPlayer::LoadedMedia);
926 if (m_state.command == CmdStop && requestCmd != CmdSeekResume) {
927 m_position = position * 10000;
928 // Even though the position is not actually set on the session yet,
929 // report it to have changed anyway for UI controls to be updated
930 positionChanged(this->position());
931 return;
932 }
933
934 if (m_state.command == CmdPause)
935 scrub(true);
936
937#ifdef DEBUG_MEDIAFOUNDATION
938 qDebug() << "setPositionInternal";
939#endif
940
941 QtMultimediaPrivate::ScopedPropVariant varStart;
942 varStart->vt = VT_I8;
943 varStart->hVal.QuadPart = LONGLONG(position * 10000);
944 if (SUCCEEDED(m_session->Start(NULL, varStart.get()))) {
945 // Store the pending state.
946 m_state.setCommand(CmdStart);
947 m_state.start = position;
948 m_pendingState = SeekPending;
949 } else {
950 error(QMediaPlayer::ResourceError, tr("Failed to seek."), true);
951 }
952}
953
955{
956 if (m_scrubbing)
957 return m_restoreRate;
958 return m_state.rate;
959}
960
962{
963 if (m_scrubbing) {
964 m_restoreRate = rate;
965 playbackRateChanged(rate);
966 return;
967 }
968 setPlaybackRateInternal(rate);
969}
970
971void MFPlayerSession::setPlaybackRateInternal(qreal rate)
972{
973 if (rate == m_request.rate)
974 return;
975
976 m_pendingRate = rate;
977 if (!m_rateSupport)
978 return;
979
980#ifdef DEBUG_MEDIAFOUNDATION
981 qDebug() << "setPlaybackRate";
982#endif
983 BOOL isThin = FALSE;
984
985 //from MSDN http://msdn.microsoft.com/en-us/library/aa965220%28v=vs.85%29.aspx
986 //Thinning applies primarily to video streams.
987 //In thinned mode, the source drops delta frames and deliver only key frames.
988 //At very high playback rates, the source might skip some key frames (for example, deliver every other key frame).
989
990 if (FAILED(m_rateSupport->IsRateSupported(FALSE, rate, NULL))) {
991 isThin = TRUE;
992 if (FAILED(m_rateSupport->IsRateSupported(isThin, rate, NULL))) {
993 qWarning() << "unable to set playbackrate = " << rate;
994 m_pendingRate = m_request.rate = m_state.rate;
995 return;
996 }
997 }
998 if (m_pendingState != NoPending) {
999 m_request.rate = rate;
1000 m_request.isThin = isThin;
1001 // Remember the current transport state (play, paused, etc), so that we
1002 // can restore it after the rate change, if necessary. However, if
1003 // anothercommand is already pending, that one takes precedent.
1004 if (m_request.command == CmdNone)
1005 m_request.setCommand(m_state.command);
1006 } else {
1007 //No pending operation. Commit the new rate.
1008 commitRateChange(rate, isThin);
1009 }
1010}
1011
1012void MFPlayerSession::commitRateChange(qreal rate, BOOL isThin)
1013{
1014#ifdef DEBUG_MEDIAFOUNDATION
1015 qDebug() << "commitRateChange";
1016#endif
1017 Q_ASSERT(m_pendingState == NoPending);
1018 MFTIME hnsSystemTime = 0;
1019 MFTIME hnsClockTime = 0;
1020 Command cmdNow = m_state.command;
1021 bool resetPosition = false;
1022 // Allowed rate transitions:
1023 // Positive <-> negative: Stopped
1024 // Negative <-> zero: Stopped
1025 // Postive <-> zero: Paused or stopped
1026 if ((rate > 0 && m_state.rate <= 0) || (rate < 0 && m_state.rate >= 0)) {
1027 if (cmdNow == CmdStart) {
1028 // Get the current clock position. This will be the restart time.
1029 m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
1030 Q_ASSERT(hnsSystemTime != 0);
1031
1032 if (rate < 0 || m_state.rate < 0)
1033 m_request.setCommand(CmdSeekResume);
1034 else if (isThin || m_state.isThin)
1035 m_request.setCommand(CmdStartAndSeek);
1036 else
1037 m_request.setCommand(CmdStart);
1038
1039 // We need to stop only when dealing with negative rates
1040 if (rate >= 0 && m_state.rate >= 0)
1041 pause();
1042 else
1043 stop();
1044
1045 // If we deal with negative rates, we stopped the session and consequently
1046 // reset the position to zero. We then need to resume to the current position.
1047 m_request.start = hnsClockTime / 10000;
1048 } else if (cmdNow == CmdPause) {
1049 if (rate < 0 || m_state.rate < 0) {
1050 // The current state is paused.
1051 // For this rate change, the session must be stopped. However, the
1052 // session cannot transition back from stopped to paused.
1053 // Therefore, this rate transition is not supported while paused.
1054 qWarning() << "Unable to change rate from positive to negative or vice versa in paused state";
1055 rate = m_state.rate;
1056 isThin = m_state.isThin;
1057 goto done;
1058 }
1059
1060 // This happens when resuming playback after scrubbing in pause mode.
1061 // This transition requires the session to be paused. Even though our
1062 // internal state is set to paused, the session might not be so we need
1063 // to enforce it
1064 if (rate > 0 && m_state.rate == 0) {
1065 m_state.setCommand(CmdNone);
1066 pause();
1067 }
1068 }
1069 } else if (rate == 0 && m_state.rate > 0) {
1070 if (cmdNow != CmdPause) {
1071 // Transition to paused.
1072 // This transisition requires the paused state.
1073 // Pause and set the rate.
1074 pause();
1075
1076 // Request: Switch back to current state.
1077 m_request.setCommand(cmdNow);
1078 }
1079 } else if (rate == 0 && m_state.rate < 0) {
1080 // Changing rate from negative to zero requires to stop the session
1081 m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
1082
1083 m_request.setCommand(CmdSeekResume);
1084
1085 stop();
1086
1087 // Resume to the current position (stop() will reset the position to 0)
1088 m_request.start = hnsClockTime / 10000;
1089 } else if (!isThin && m_state.isThin) {
1090 if (cmdNow == CmdStart) {
1091 // When thinning, only key frames are read and presented. Going back
1092 // to normal playback requires to reset the current position to force
1093 // the pipeline to decode the actual frame at the current position
1094 // (which might be earlier than the last decoded key frame)
1095 resetPosition = true;
1096 } else if (cmdNow == CmdPause) {
1097 // If paused, don't reset the position until we resume, otherwise
1098 // a new frame will be rendered
1099 m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
1100 m_request.setCommand(CmdSeekResume);
1101 m_request.start = hnsClockTime / 10000;
1102 }
1103
1104 }
1105
1106 // Set the rate.
1107 if (FAILED(m_rateControl->SetRate(isThin, rate))) {
1108 qWarning() << "failed to set playbackrate = " << rate;
1109 rate = m_state.rate;
1110 isThin = m_state.isThin;
1111 goto done;
1112 }
1113
1114 if (resetPosition) {
1115 m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
1116 setPosition(hnsClockTime / 10000);
1117 }
1118
1119done:
1120 // Adjust our current rate and requested rate.
1121 m_pendingRate = m_request.rate = m_state.rate = rate;
1122 if (rate != 0)
1123 m_state.isThin = isThin;
1124 playbackRateChanged(rate);
1125}
1126
1127void MFPlayerSession::scrub(bool enableScrub)
1128{
1129 if (m_scrubbing == enableScrub)
1130 return;
1131
1132 m_scrubbing = enableScrub;
1133
1134 if (!canScrub()) {
1135 if (!enableScrub)
1136 m_pendingRate = m_restoreRate;
1137 return;
1138 }
1139
1140 if (enableScrub) {
1141 // Enter scrubbing mode. Cache the rate.
1142 m_restoreRate = m_request.rate;
1143 setPlaybackRateInternal(0.0f);
1144 } else {
1145 // Leaving scrubbing mode. Restore the old rate.
1146 setPlaybackRateInternal(m_restoreRate);
1147 }
1148}
1149
1150void MFPlayerSession::setVolume(float volume)
1151{
1152 if (m_volume == volume)
1153 return;
1154 m_volume = volume;
1155
1156 if (!m_muted)
1157 setVolumeInternal(volume);
1158}
1159
1160void MFPlayerSession::setMuted(bool muted)
1161{
1162 if (m_muted == muted)
1163 return;
1164 m_muted = muted;
1165
1166 setVolumeInternal(muted ? 0 : m_volume);
1167}
1168
1169void MFPlayerSession::setVolumeInternal(float volume)
1170{
1171 if (m_volumeControl) {
1172 quint32 channelCount = 0;
1173 if (!SUCCEEDED(m_volumeControl->GetChannelCount(&channelCount))
1174 || channelCount == 0)
1175 return;
1176
1177 for (quint32 i = 0; i < channelCount; ++i)
1178 m_volumeControl->SetChannelVolume(i, volume);
1179 }
1180}
1181
1183{
1184 if (!m_netsourceStatistics)
1185 return 0;
1186
1187 PROPERTYKEY key;
1188 key.fmtid = MFNETSOURCE_STATISTICS;
1189 key.pid = MFNETSOURCE_BUFFERPROGRESS_ID;
1190 int progress = -1;
1191 // GetValue returns S_FALSE if the property is not available, which has
1192 // a value > 0. We therefore can't use the SUCCEEDED macro here.
1193 QtMultimediaPrivate::ScopedPropVariant var;
1194 if (m_netsourceStatistics->GetValue(key, var.get()) == S_OK)
1195 progress = var->lVal;
1196
1197#ifdef DEBUG_MEDIAFOUNDATION
1198 qDebug() << "bufferProgress: progress = " << progress;
1199#endif
1200
1201 return progress/100.;
1202}
1203
1205{
1206 // defaults to the whole media
1207 qint64 start = 0;
1208 qint64 end = qint64(m_duration / 10000);
1209
1210 if (m_netsourceStatistics) {
1211 PROPERTYKEY key;
1212 key.fmtid = MFNETSOURCE_STATISTICS;
1213 key.pid = MFNETSOURCE_SEEKRANGESTART_ID;
1214 // GetValue returns S_FALSE if the property is not available, which has
1215 // a value > 0. We therefore can't use the SUCCEEDED macro here.
1216 QtMultimediaPrivate::ScopedPropVariant startVar;
1217 if (m_netsourceStatistics->GetValue(key, startVar.get()) == S_OK) {
1218 start = qint64(startVar->uhVal.QuadPart / 10000);
1219 key.pid = MFNETSOURCE_SEEKRANGEEND_ID;
1220 QtMultimediaPrivate::ScopedPropVariant endVar;
1221 if (m_netsourceStatistics->GetValue(key, endVar.get()) == S_OK)
1222 end = qint64(endVar->uhVal.QuadPart / 10000);
1223 }
1224 }
1225
1226 return QMediaTimeRange(start, end);
1227}
1228
1229HRESULT MFPlayerSession::QueryInterface(REFIID riid, void** ppvObject)
1230{
1231 if (!ppvObject)
1232 return E_POINTER;
1233 if (riid == IID_IMFAsyncCallback) {
1234 *ppvObject = static_cast<IMFAsyncCallback*>(this);
1235 } else if (riid == IID_IUnknown) {
1236 *ppvObject = static_cast<IUnknown*>(this);
1237 } else {
1238 *ppvObject = NULL;
1239 return E_NOINTERFACE;
1240 }
1241 return S_OK;
1242}
1243
1244ULONG MFPlayerSession::AddRef(void)
1245{
1246 return InterlockedIncrement(&m_cRef);
1247}
1248
1249ULONG MFPlayerSession::Release(void)
1250{
1251 LONG cRef = InterlockedDecrement(&m_cRef);
1252 if (cRef == 0) {
1253 deleteLater();
1254
1255 // In rare cases the session has queued events to be run between deleteLater and deleting,
1256 // so we set the parent control to nullptr in order to prevent crashes in the cases.
1257 m_playerControl = nullptr;
1258 }
1259 return cRef;
1260}
1261
1262HRESULT MFPlayerSession::Invoke(IMFAsyncResult *pResult)
1263{
1264 if (pResult->GetStateNoAddRef() != m_session.Get())
1265 return S_OK;
1266
1267 ComPtr<IMFMediaEvent> pEvent;
1268 // Get the event from the event queue.
1269 HRESULT hr = m_session->EndGetEvent(pResult, &pEvent);
1270 if (FAILED(hr)) {
1271 return S_OK;
1272 }
1273
1274 MediaEventType meType = MEUnknown;
1275 hr = pEvent->GetType(&meType);
1276 if (FAILED(hr)) {
1277 return S_OK;
1278 }
1279
1280 if (meType == MESessionClosed) {
1281 SetEvent(m_hCloseEvent.get());
1282 return S_OK;
1283 } else {
1284 hr = m_session->BeginGetEvent(this, m_session.Get());
1285 if (FAILED(hr)) {
1286 return S_OK;
1287 }
1288 }
1289
1290 if (!m_closing) {
1291 emit sessionEvent(pEvent);
1292 }
1293 return S_OK;
1294}
1295
1296void MFPlayerSession::handleSessionEvent(const ComPtr<IMFMediaEvent> &sessionEvent)
1297{
1298 HRESULT hrStatus = S_OK;
1299 HRESULT hr = sessionEvent->GetStatus(&hrStatus);
1300 if (FAILED(hr) || !m_session) {
1301 return;
1302 }
1303
1304 MediaEventType meType = MEUnknown;
1305 hr = sessionEvent->GetType(&meType);
1306#ifdef DEBUG_MEDIAFOUNDATION
1307 if (FAILED(hrStatus))
1308 qDebug() << "handleSessionEvent: MediaEventType = " << meType << "Failed";
1309 else
1310 qDebug() << "handleSessionEvent: MediaEventType = " << meType;
1311#endif
1312
1313 switch (meType) {
1314 case MENonFatalError: {
1315 QtMultimediaPrivate::ScopedPropVariant var;
1316 sessionEvent->GetValue(var.get());
1317 qWarning() << "handleSessionEvent: non fatal error = " << var->ulVal;
1318 error(QMediaPlayer::ResourceError, tr("Media session non-fatal error."), false);
1319 }
1320 break;
1321 case MESourceUnknown:
1322 changeStatus(QMediaPlayer::InvalidMedia);
1323 break;
1324 case MEError:
1325 if (hrStatus == MF_E_ALREADY_INITIALIZED) {
1326 // Workaround for a possible WMF issue that causes an error
1327 // with some specific videos, which play fine otherwise.
1328#ifdef DEBUG_MEDIAFOUNDATION
1329 qDebug() << "handleSessionEvent: ignoring MF_E_ALREADY_INITIALIZED";
1330#endif
1331 break;
1332 }
1333 changeStatus(QMediaPlayer::InvalidMedia);
1334 qWarning() << "handleSessionEvent: serious error = "
1335 << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hrStatus);
1336 switch (hrStatus) {
1337 case MF_E_NET_READ:
1338 error(QMediaPlayer::NetworkError, tr("Error reading from the network."), true);
1339 break;
1340 case MF_E_NET_WRITE:
1341 error(QMediaPlayer::NetworkError, tr("Error writing to the network."), true);
1342 break;
1343 case NS_E_FIREWALL:
1344 error(QMediaPlayer::NetworkError, tr("Network packets might be blocked by a firewall."), true);
1345 break;
1346 case MF_E_MEDIAPROC_WRONGSTATE:
1347 error(QMediaPlayer::ResourceError, tr("Media session state error."), true);
1348 break;
1349 case MF_E_INVALID_STREAM_DATA:
1350 error(QMediaPlayer::ResourceError, tr("Invalid stream data."), true);
1351 break;
1352 default:
1353 error(QMediaPlayer::ResourceError, tr("Media session serious error."), true);
1354 break;
1355 }
1356 break;
1357 case MESessionRateChanged:
1358 // If the rate change succeeded, we've already got the rate
1359 // cached. If it failed, try to get the actual rate.
1360 if (FAILED(hrStatus)) {
1361 QtMultimediaPrivate::ScopedPropVariant var;
1362 if (SUCCEEDED(sessionEvent->GetValue(var.get())) && (var->vt == VT_R4))
1363 m_state.rate = var->fltVal;
1364 playbackRateChanged(playbackRate());
1365 }
1366 break;
1367 case MESessionScrubSampleComplete :
1368 if (m_scrubbing)
1369 updatePendingCommands(CmdStart);
1370 break;
1371 case MESessionStarted:
1372 if (status() == QMediaPlayer::EndOfMedia
1373 || status() == QMediaPlayer::LoadedMedia) {
1374 // If the session started, then enough data is buffered to play
1375 changeStatus(QMediaPlayer::BufferedMedia);
1376 }
1377
1378 updatePendingCommands(CmdStart);
1379 // playback started, we can now set again the procAmpValues if they have been
1380 // changed previously (these are lost when loading a new media)
1381// if (m_playerService->videoWindowControl()) {
1382// m_playerService->videoWindowControl()->applyImageControls();
1383// }
1384 m_signalPositionChangeTimer.start();
1385 break;
1386 case MESessionStopped:
1387 if (status() != QMediaPlayer::EndOfMedia) {
1388 m_position = 0;
1389
1390 // Reset to Loaded status unless we are loading a new media
1391 // or changing the playback rate to negative values (stop required)
1392 if (status() != QMediaPlayer::LoadingMedia && m_request.command != CmdSeekResume)
1393 changeStatus(QMediaPlayer::LoadedMedia);
1394 }
1395 updatePendingCommands(CmdStop);
1396 m_signalPositionChangeTimer.stop();
1397 break;
1398 case MESessionPaused:
1399 m_position = position() * 10000;
1400 updatePendingCommands(CmdPause);
1401 m_signalPositionChangeTimer.stop();
1402 if (status() == QMediaPlayer::LoadedMedia)
1403 setPosition(position());
1404 break;
1405 case MEReconnectStart:
1406#ifdef DEBUG_MEDIAFOUNDATION
1407 qDebug() << "MEReconnectStart" << ((hrStatus == S_OK) ? "OK" : "Failed");
1408#endif
1409 break;
1410 case MEReconnectEnd:
1411#ifdef DEBUG_MEDIAFOUNDATION
1412 qDebug() << "MEReconnectEnd" << ((hrStatus == S_OK) ? "OK" : "Failed");
1413#endif
1414 break;
1415 case MESessionTopologySet:
1416 if (FAILED(hrStatus)) {
1417 changeStatus(QMediaPlayer::InvalidMedia);
1418 error(QMediaPlayer::FormatError, tr("Unsupported media, a codec is missing."), true);
1419 } else {
1420 // Topology is resolved and successfuly set, this happens only after loading a new media.
1421 // Make sure we always start the media from the beginning
1422 m_lastPosition = -1;
1423 m_position = 0;
1424 positionChanged(0);
1425 changeStatus(QMediaPlayer::LoadedMedia);
1426 }
1427 break;
1428 }
1429
1430 if (FAILED(hrStatus)) {
1431 return;
1432 }
1433
1434 switch (meType) {
1435 case MEBufferingStarted:
1436 changeStatus(QMediaPlayer::StalledMedia);
1438 break;
1439 case MEBufferingStopped:
1440 changeStatus(QMediaPlayer::BufferedMedia);
1442 break;
1443 case MESessionEnded:
1444 m_pendingState = NoPending;
1445 m_state.command = CmdStop;
1446 m_state.prevCmd = CmdNone;
1447 m_request.command = CmdNone;
1448 m_request.prevCmd = CmdNone;
1449
1450 //keep reporting the final position after end of media
1451 m_position = qint64(m_duration);
1452 positionChanged(position());
1453
1454 changeStatus(QMediaPlayer::EndOfMedia);
1455 break;
1456 case MEEndOfPresentationSegment:
1457 break;
1458 case MESessionTopologyStatus: {
1459 UINT32 status;
1460 if (SUCCEEDED(sessionEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status))) {
1461 if (status == MF_TOPOSTATUS_READY) {
1462 ComPtr<IMFClock> clock;
1463 if (SUCCEEDED(m_session->GetClock(&clock))) {
1464 clock->QueryInterface(IID_IMFPresentationClock, &m_presentationClock);
1465 }
1466
1467 if (SUCCEEDED(MFGetService(m_session.Get(), MF_RATE_CONTROL_SERVICE,
1468 IID_PPV_ARGS(&m_rateControl)))) {
1469 if (SUCCEEDED(MFGetService(m_session.Get(), MF_RATE_CONTROL_SERVICE,
1470 IID_PPV_ARGS(&m_rateSupport)))) {
1471 if (SUCCEEDED(m_rateSupport->IsRateSupported(TRUE, 0, NULL)))
1472 m_canScrub = true;
1473 }
1474 BOOL isThin = FALSE;
1475 float rate = 1;
1476 if (SUCCEEDED(m_rateControl->GetRate(&isThin, &rate))) {
1477 if (m_pendingRate != rate) {
1478 m_state.rate = m_request.rate = rate;
1479 setPlaybackRate(m_pendingRate);
1480 }
1481 }
1482 }
1483 MFGetService(m_session.Get(), MFNETSOURCE_STATISTICS_SERVICE,
1484 IID_PPV_ARGS(&m_netsourceStatistics));
1485
1486 if (SUCCEEDED(MFGetService(m_session.Get(), MR_STREAM_VOLUME_SERVICE,
1487 IID_PPV_ARGS(&m_volumeControl))))
1488 setVolumeInternal(m_muted ? 0 : m_volume);
1489
1490 m_updatingTopology = false;
1491 stop();
1492
1493 if (m_deferredPause) {
1494 m_deferredPause = false;
1495 pause();
1496 }
1497 }
1498 }
1499 }
1500 break;
1501 default:
1502 break;
1503 }
1504}
1505
1506void MFPlayerSession::updatePendingCommands(Command command)
1507{
1508 positionChanged(position());
1509 if (m_state.command != command || m_pendingState == NoPending)
1510 return;
1511
1512 // Seek while paused completed
1513 if (m_pendingState == SeekPending && m_state.prevCmd == CmdPause) {
1514 m_pendingState = NoPending;
1515 // A seek operation actually restarts playback. If scrubbing is possible, playback rate
1516 // is set to 0.0 at this point and we just need to reset the current state to Pause.
1517 // If scrubbing is not possible, the playback rate was not changed and we explicitly need
1518 // to re-pause playback.
1519 if (!canScrub())
1520 pause();
1521 else
1522 m_state.setCommand(CmdPause);
1523 }
1524
1525 m_pendingState = NoPending;
1526
1527 //First look for rate changes.
1528 if (m_request.rate != m_state.rate) {
1529 commitRateChange(m_request.rate, m_request.isThin);
1530 }
1531
1532 // Now look for new requests.
1533 if (m_pendingState == NoPending) {
1534 switch (m_request.command) {
1535 case CmdStart:
1536 start();
1537 break;
1538 case CmdPause:
1539 pause();
1540 break;
1541 case CmdStop:
1542 stop();
1543 break;
1544 case CmdSeek:
1545 case CmdSeekResume:
1546 setPositionInternal(m_request.start, m_request.command);
1547 break;
1548 case CmdStartAndSeek:
1549 start();
1550 setPositionInternal(m_request.start, m_request.command);
1551 break;
1552 default:
1553 break;
1554 }
1555 m_request.setCommand(CmdNone);
1556 }
1557
1558}
1559
1560bool MFPlayerSession::canScrub() const
1561{
1562 return m_canScrub && m_rateSupport && m_rateControl;
1563}
1564
1565void MFPlayerSession::clear()
1566{
1567#ifdef DEBUG_MEDIAFOUNDATION
1568 qDebug() << "MFPlayerSession::clear";
1569#endif
1570 m_mediaTypes = 0;
1571 m_canScrub = false;
1572 m_deferredPause = false;
1573
1574 m_pendingState = NoPending;
1575 m_state.command = CmdStop;
1576 m_state.prevCmd = CmdNone;
1577 m_request.command = CmdNone;
1578 m_request.prevCmd = CmdNone;
1579
1580 for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1581 m_trackInfo[i].metaData.clear();
1582 m_trackInfo[i].nativeIndexes.clear();
1583 m_trackInfo[i].currentIndex = -1;
1584 m_trackInfo[i].sourceNodeId = TOPOID(-1);
1585 m_trackInfo[i].outputNodeId = TOPOID(-1);
1586 m_trackInfo[i].format = GUID_NULL;
1587 }
1588
1589 if (!m_metaData.isEmpty()) {
1590 m_metaData.clear();
1592 }
1593
1594 m_presentationClock.Reset();
1595 m_rateControl.Reset();
1596 m_rateSupport.Reset();
1597 m_volumeControl.Reset();
1598 m_netsourceStatistics.Reset();
1599}
1600
1601void MFPlayerSession::setAudioOutput(QPlatformAudioOutput *device)
1602{
1603 if (m_audioOutput == device)
1604 return;
1605
1606 if (m_audioOutput)
1607 m_audioOutput->q->disconnect(this);
1608
1609 m_audioOutput = device;
1610 if (m_audioOutput) {
1611 setMuted(m_audioOutput->q->isMuted());
1612 setVolume(m_audioOutput->q->volume());
1614 connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &MFPlayerSession::updateOutputRouting);
1615 connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this, &MFPlayerSession::setVolume);
1616 connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this, &MFPlayerSession::setMuted);
1617 }
1618}
1619
1621{
1622 int currentAudioTrack = m_trackInfo[QPlatformMediaPlayer::AudioStream].currentIndex;
1623 if (currentAudioTrack > -1)
1624 setActiveTrack(QPlatformMediaPlayer::AudioStream, currentAudioTrack);
1625}
1626
1627void MFPlayerSession::setVideoSink(QVideoSink *sink)
1628{
1629 m_videoRendererControl->setSink(sink);
1630}
1631
1632void MFPlayerSession::setActiveTrack(QPlatformMediaPlayer::TrackType type, int index)
1633{
1634 if (!m_session)
1635 return;
1636
1637 // Only audio track selection is currently supported.
1638 if (type != QPlatformMediaPlayer::AudioStream)
1639 return;
1640
1641 const auto &nativeIndexes = m_trackInfo[type].nativeIndexes;
1642
1643 if (index < -1 || index >= nativeIndexes.count())
1644 return;
1645
1646 // Updating the topology fails if there is a HEVC video stream,
1647 // which causes other issues. Ignoring the change, for now.
1648 if (m_trackInfo[QPlatformMediaPlayer::VideoStream].format == MFVideoFormat_HEVC)
1649 return;
1650
1651 ComPtr<IMFTopology> topology;
1652
1653 if (SUCCEEDED(m_session->GetFullTopology(MFSESSION_GETFULLTOPOLOGY_CURRENT, 0, &topology))) {
1654
1655 m_restorePosition = position() * 10000;
1656
1657 if (m_state.command == CmdStart)
1658 stop();
1659
1660 if (m_trackInfo[type].outputNodeId != TOPOID(-1)) {
1661 ComPtr<IMFTopologyNode> node;
1662 if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].outputNodeId, &node))) {
1663 topology->RemoveNode(node.Get());
1664 m_trackInfo[type].outputNodeId = TOPOID(-1);
1665 }
1666 }
1667 if (m_trackInfo[type].sourceNodeId != TOPOID(-1)) {
1668 ComPtr<IMFTopologyNode> node;
1669 if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].sourceNodeId, &node))) {
1670 topology->RemoveNode(node.Get());
1671 m_trackInfo[type].sourceNodeId = TOPOID(-1);
1672 }
1673 }
1674
1675 IMFMediaSource *mediaSource = m_sourceResolver->mediaSource();
1676
1677 ComPtr<IMFPresentationDescriptor> sourcePD;
1678 if (SUCCEEDED(mediaSource->CreatePresentationDescriptor(&sourcePD))) {
1679
1680 if (m_trackInfo[type].currentIndex >= 0 && m_trackInfo[type].currentIndex < nativeIndexes.count())
1681 sourcePD->DeselectStream(nativeIndexes.at(m_trackInfo[type].currentIndex));
1682
1683 m_trackInfo[type].currentIndex = index;
1684
1685 if (index == -1) {
1686 m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology.Get());
1687 } else {
1688 int nativeIndex = nativeIndexes.at(index);
1689 sourcePD->SelectStream(nativeIndex);
1690
1691 ComPtr<IMFStreamDescriptor> streamDesc;
1692 BOOL selected = FALSE;
1693
1694 if (SUCCEEDED(sourcePD->GetStreamDescriptorByIndex(nativeIndex, &selected, &streamDesc))) {
1695 ComPtr<IMFTopologyNode> sourceNode = addSourceNode(
1696 topology.Get(), mediaSource, sourcePD.Get(), streamDesc.Get());
1697 if (sourceNode) {
1698 ComPtr<IMFTopologyNode> outputNode =
1699 addOutputNode(MFPlayerSession::Audio, topology.Get(), 0);
1700 if (outputNode) {
1701 if (SUCCEEDED(sourceNode->ConnectOutput(0, outputNode.Get(), 0))) {
1702 sourceNode->GetTopoNodeID(&m_trackInfo[type].sourceNodeId);
1703 outputNode->GetTopoNodeID(&m_trackInfo[type].outputNodeId);
1704 m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE,
1705 topology.Get());
1706 }
1707 }
1708 }
1709 }
1710 }
1711 m_updatingTopology = true;
1712 }
1713 }
1714}
1715
1716int MFPlayerSession::activeTrack(QPlatformMediaPlayer::TrackType type)
1717{
1718 if (type >= QPlatformMediaPlayer::NTrackTypes)
1719 return -1;
1720 return m_trackInfo[type].currentIndex;
1721}
1722
1723int MFPlayerSession::trackCount(QPlatformMediaPlayer::TrackType type)
1724{
1725 if (type >= QPlatformMediaPlayer::NTrackTypes)
1726 return -1;
1727 return m_trackInfo[type].metaData.count();
1728}
1729
1730QMediaMetaData MFPlayerSession::trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber)
1731{
1732 if (type >= QPlatformMediaPlayer::NTrackTypes)
1733 return {};
1734
1735 if (trackNumber < 0 || trackNumber >= m_trackInfo[type].metaData.count())
1736 return {};
1737
1738 return m_trackInfo[type].metaData.at(trackNumber);
1739}
1740
1741QT_END_NAMESPACE
1742
1743#include "moc_mfplayersession_p.cpp"
void seekableUpdate(bool seekable)
void setPlaybackRate(qreal rate)
void setPosition(qint64 position)
void setAudioOutput(QPlatformAudioOutput *device)
void setVideoSink(QVideoSink *sink)
void bufferProgressChanged(float percentFilled)
qreal playbackRate() const
void stop(bool immediate=false)
STDMETHODIMP Invoke(IMFAsyncResult *pResult) override
void load(const QUrl &media, QIODevice *stream)
void changeStatus(QMediaPlayer::MediaStatus newStatus)
QMediaTimeRange availablePlaybackRanges()
void setMuted(bool muted)
void setSink(QVideoSink *surface)
HRESULT BindOutputNode(IMFTopologyNode *pNode)
HRESULT BindOutputNodes(IMFTopology *pTopology)
Combined button and popup list for selecting options.