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