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