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