6#include <common/mfmetadata_p.h>
8#include <player/qmediaenginenotify_p.h>
9#include <player/qmediaenginetimedtextnotify_p.h>
10#include <player/qmediaenginevideobuffer_p.h>
12#include <QtMultimedia/qaudiooutput.h>
13#include <QtMultimedia/qvideosink.h>
14#include <QtMultimedia/private/qcomtaskresource_p.h>
15#include <QtMultimedia/private/qplatformaudiooutput_p.h>
16#include <QtMultimedia/private/qplatformvideosink_p.h>
17#include <QtMultimedia/private/qvideoframe_p.h>
18#include <QtMultimedia/private/qwindows_scopedpropvariant_p.h>
19#include <QtMultimedia/private/qwindowsd3d11rhitextureimporter_p.h>
20#include <QtMultimedia/private/qwmf_support_p.h>
21#include <QtConcurrent/qtconcurrentrun.h>
22#include <QtCore/qfile.h>
23#include <QtCore/qlocale.h>
24#include <QtCore/qloggingcategory.h>
25#include <QtCore/qscopeguard.h>
26#include <QtCore/qtimer.h>
27#include <QtCore/private/qbstr_p.h>
28#include <QtCore/private/qsystemerror_p.h>
38using namespace Qt::StringLiterals;
39using namespace std::chrono_literals;
41using reference_time = QWMF::reference_time;
45QVariant languageVariant(
const QString &langStr)
47 const QLocale::Language lang = QLocale::codeToLanguage(langStr, QLocale::AnyLanguageCode);
48 return lang != QLocale::AnyLanguage ? QVariant::fromValue(lang) : QVariant(langStr);
51QMediaPlayer::Error mapMediaEngineError(MF_MEDIA_ENGINE_ERR err)
54 case MF_MEDIA_ENGINE_ERR_NOERROR:
55 return QMediaPlayer::NoError;
56 case MF_MEDIA_ENGINE_ERR_ABORTED:
57 return QMediaPlayer::ResourceError;
58 case MF_MEDIA_ENGINE_ERR_NETWORK:
59 return QMediaPlayer::NetworkError;
60 case MF_MEDIA_ENGINE_ERR_DECODE:
61 return QMediaPlayer::FormatError;
62 case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED:
63 return QMediaPlayer::ResourceError;
64 case MF_MEDIA_ENGINE_ERR_ENCRYPTED:
65 return QMediaPlayer::AccessDeniedError;
67 return QMediaPlayer::ResourceError;
71std::chrono::microseconds videoFrameDurationUs(IMFMediaEngineEx *engine)
76 DWORD streamCount = 0;
77 if (FAILED(engine->GetNumberOfStreams(&streamCount)))
80 for (DWORD i = 0; i < streamCount; ++i) {
81 ScopedPropVariant majorType;
82 if (FAILED(engine->GetStreamAttribute(i, MF_MT_MAJOR_TYPE, majorType.get()))
83 || majorType->vt != VT_CLSID || *majorType->puuid != MFMediaType_Video) {
87 ScopedPropVariant rate;
88 if (FAILED(engine->GetStreamAttribute(i, MF_MT_FRAME_RATE, rate.get()))
89 || rate->vt != VT_UI8) {
93 UINT64 avgTimePerFrame = 0;
94 if (SUCCEEDED(MFFrameRateToAverageTimePerFrame(
95 HI32(rate->uhVal.QuadPart), LO32(rate->uhVal.QuadPart), &avgTimePerFrame)))
96 return std::chrono::round<
std::chrono::microseconds>(reference_time(avgTimePerFrame));
107 m_textureImporter = std::make_shared<QtMultimediaPrivate::QWindowsD3D11RhiTextureImporter>();
109 const D3D_FEATURE_LEVEL featureLevels[] = {
110 D3D_FEATURE_LEVEL_11_1,
111 D3D_FEATURE_LEVEL_11_0,
114 UINT flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT | D3D11_CREATE_DEVICE_BGRA_SUPPORT;
116 HRESULT hr = D3D11CreateDevice(
nullptr, D3D_DRIVER_TYPE_HARDWARE,
nullptr, flags, featureLevels,
117 ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &m_d3dDevice,
121 qCWarning(lcMediaEngine) <<
"D3D11CreateDevice:" << QSystemError::windowsComString(hr);
125 ComPtr<ID3D10Multithread> multithread;
126 if (SUCCEEDED(m_d3dDevice.As(&multithread)))
127 multithread->SetMultithreadProtected(TRUE);
134 HRESULT hr = MFCreateDXGIDeviceManager(&m_resetToken, &m_dxgiManager);
136 qCWarning(lcMediaEngine) <<
"MFCreateDXGIDeviceManager:"
137 << QSystemError::windowsComString(hr);
141 hr = m_dxgiManager->ResetDevice(m_d3dDevice.Get(), m_resetToken);
143 qCWarning(lcMediaEngine) <<
"ResetDevice:" << QSystemError::windowsComString(hr);
147 m_notify = makeComObject<QMediaEngineNotify>();
150 QObject::connect(m_notify.Get(), &QMediaEngineNotify::eventNotified, context(),
151 [
this](DWORD event, DWORD_PTR param1, DWORD param2) {
152 handleMediaEngineEvent(event, param1, param2);
153 }, Qt::QueuedConnection);
155 ComPtr<IMFAttributes> attrs;
156 hr = MFCreateAttributes(&attrs, 4);
158 qCWarning(lcMediaEngine) <<
"MFCreateAttributes:" << QSystemError::windowsComString(hr);
162 attrs->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, m_notify.Get());
163 attrs->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER, m_dxgiManager.Get());
164 attrs->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM);
166 ComPtr<IMFMediaEngineClassFactory> factory;
167 hr = CoCreateInstance(CLSID_MFMediaEngineClassFactory,
nullptr, CLSCTX_INPROC_SERVER,
168 IID_PPV_ARGS(&factory));
170 qCWarning(lcMediaEngine) <<
"CoCreateInstance(MFMediaEngineClassFactory):"
171 << QSystemError::windowsComString(hr);
175 hr = factory->CreateInstance(0, attrs.Get(), &m_mediaEngine);
177 qCWarning(lcMediaEngine) <<
"IMFMediaEngine::CreateInstance:"
178 << QSystemError::windowsComString(hr);
182 hr = m_mediaEngine.As(&m_mediaEngineEx);
184 qCWarning(lcMediaEngine) <<
"IMFMediaEngineEx:" << QSystemError::windowsComString(hr);
190 ComPtr<IMFGetService> getService;
191 if (SUCCEEDED(m_mediaEngine.As(&getService))) {
192 getService->GetService(MF_MEDIA_ENGINE_TIMEDTEXT, IID_PPV_ARGS(&m_timedText));
196 m_timedText->SetInBandEnabled(TRUE);
198 m_timedTextNotify = makeComObject<QMediaEngineTimedTextNotify>();
200 QObject::connect(m_timedTextNotify.Get(), &QMediaEngineTimedTextNotify::trackAdded,
202 updateSubtitleTracks();
205 QObject::connect(m_timedTextNotify.Get(), &QMediaEngineTimedTextNotify::subtitleTextChanged,
206 context(), [
this](
const QString &text) {
208 m_videoSink->setSubtitleText(text);
211 m_timedText->RegisterNotifications(m_timedTextNotify.Get());
215 if (!qFuzzyCompare(m_playbackRate, 1.0)) {
216 m_mediaEngine->SetDefaultPlaybackRate(m_playbackRate);
217 m_mediaEngine->SetPlaybackRate(m_playbackRate);
226 m_mediaEngine->Shutdown();
227 m_mediaEngine.Reset();
229 m_mediaEngineEx.Reset();
231 m_timedTextNotify.Reset();
233 m_dxgiManager.Reset();
239 m_positionUpdateTimer.start();
240 m_videoRenderTimer.start();
245 m_positionUpdateTimer.stop();
246 m_videoRenderTimer.stop();
252 error(QMediaPlayer::ResourceError, u"Failed to initialize D3D11"_s);
254 m_positionUpdateTimer.callOnTimeout(context(), [
this] {
255 if (m_mediaEngine && !m_seekTarget)
256 positionChanged(currentPositionMs());
259 m_videoRenderTimer.callOnTimeout(context(), [
this] {
263 m_seekTimeoutTimer.setSingleShot(
true);
264 m_seekTimeoutTimer.callOnTimeout(context(), [
this] {
266 qCWarning(lcMediaEngine) <<
"Seek timeout — SEEKED event not received, resetting";
267 m_seekTarget.reset();
274 stopPlaybackTimers();
275 m_seekTimeoutTimer.stop();
277 m_metadataFuture.cancel();
279 releaseMediaEngine();
282void QMediaEnginePlayer::handleMediaEngineEvent(DWORD event, DWORD_PTR param1, DWORD param2)
290 case MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA:
291 qCDebug(lcMediaEngine) <<
"LOADEDMETADATA";
297 case MF_MEDIA_ENGINE_EVENT_LOADEDDATA: {
298 qCDebug(lcMediaEngine) <<
"LOADEDDATA";
299 audioAvailableChanged(m_mediaEngine->HasAudio());
300 videoAvailableChanged(m_mediaEngine->HasVideo());
301 updateVideoFrameFormat();
303 seekableChanged(!m_stream || !m_stream->isSequential());
304 mediaStatusChanged(QMediaPlayer::LoadedMedia);
307 bufferProgressChanged(bp);
310 m_pendingPlay =
false;
316 case MF_MEDIA_ENGINE_EVENT_CANPLAY:
317 qCDebug(lcMediaEngine) <<
"CANPLAY";
318 if (state() == QMediaPlayer::PlayingState || mediaStatus() == QMediaPlayer::BufferingMedia
320 mediaStatusChanged(QMediaPlayer::BufferedMedia);
322 bufferProgressChanged(1.0f);
325 case MF_MEDIA_ENGINE_EVENT_PLAYING:
326 qCDebug(lcMediaEngine) <<
"PLAYING";
327 if (state() != QMediaPlayer::PlayingState)
328 stateChanged(QMediaPlayer::PlayingState);
329 if (mediaStatus() == QMediaPlayer::BufferingMedia)
330 mediaStatusChanged(QMediaPlayer::BufferedMedia);
331 startPlaybackTimers();
334 case MF_MEDIA_ENGINE_EVENT_PAUSE:
335 qCDebug(lcMediaEngine) <<
"PAUSE";
337 if (!m_mediaEngine->IsPaused())
339 if (state() != QMediaPlayer::StoppedState && state() != QMediaPlayer::PausedState) {
340 stateChanged(QMediaPlayer::PausedState);
341 stopPlaybackTimers();
345 case MF_MEDIA_ENGINE_EVENT_ENDED: {
346 qCDebug(lcMediaEngine) <<
"ENDED";
347 stopPlaybackTimers();
349 if (mediaStatus() == QMediaPlayer::BufferingMedia)
350 mediaStatusChanged(QMediaPlayer::BufferedMedia);
353 m_mediaEngine->SetCurrentTime(0);
354 m_mediaEngine->Play();
357 m_videoSink->setVideoFrame({});
358 positionChanged(m_duration.count());
359 stateChanged(QMediaPlayer::StoppedState);
360 mediaStatusChanged(QMediaPlayer::EndOfMedia);
361 bufferProgressChanged(0.f);
367 case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE:
369 positionChanged(currentPositionMs());
372 case MF_MEDIA_ENGINE_EVENT_ERROR: {
373 const auto mfErr =
static_cast<MF_MEDIA_ENGINE_ERR>(param1);
377 case MF_MEDIA_ENGINE_ERR_ABORTED:
378 detail = u"Playback aborted"_s;
380 case MF_MEDIA_ENGINE_ERR_NETWORK:
381 detail = u"Network error"_s;
383 case MF_MEDIA_ENGINE_ERR_DECODE:
384 detail = u"Unsupported or corrupt media"_s;
386 case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED:
387 detail = u"Source not supported"_s;
389 case MF_MEDIA_ENGINE_ERR_ENCRYPTED:
390 detail = u"Encrypted content"_s;
393 detail = u"Unknown error %1"_s.arg(param1);
397 qCWarning(lcMediaEngine) <<
"Error:" << detail;
398 error(mapMediaEngineError(mfErr), detail);
399 mediaStatusChanged(QMediaPlayer::InvalidMedia);
400 stopPlaybackTimers();
404 case MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED:
405 qCDebug(lcMediaEngine) <<
"BUFFERINGSTARTED";
406 mediaStatusChanged(QMediaPlayer::BufferingMedia);
410 case MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED:
411 qCDebug(lcMediaEngine) <<
"BUFFERINGENDED";
412 mediaStatusChanged(QMediaPlayer::BufferedMedia);
416 case MF_MEDIA_ENGINE_EVENT_DURATIONCHANGE:
417 qCDebug(lcMediaEngine) <<
"DURATIONCHANGE";
421 case MF_MEDIA_ENGINE_EVENT_FORMATCHANGE:
422 qCDebug(lcMediaEngine) <<
"FORMATCHANGE";
423 updateVideoFrameFormat();
426 case MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY:
427 qCDebug(lcMediaEngine) <<
"FIRSTFRAMEREADY";
428 if (state() != QMediaPlayer::StoppedState)
432 case MF_MEDIA_ENGINE_EVENT_SEEKED:
433 qCDebug(lcMediaEngine) <<
"SEEKED";
434 m_seekTimeoutTimer.stop();
435 m_seekTarget.reset();
445 qCDebug(lcMediaEngine) <<
"setMedia:" << media <<
"stream:" << stream;
447 if (state() != QMediaPlayer::StoppedState)
454 m_pendingPlay =
false;
455 m_seekTarget.reset();
456 m_seekTimeoutTimer.stop();
457 seekableChanged(
false);
459 m_transferTexture.Reset();
461 m_metadataFuture.cancel();
464 m_videoSink->setVideoFrame({});
468 if (media.isEmpty() && !stream) {
469 audioAvailableChanged(
false);
470 videoAvailableChanged(
false);
474 releaseMediaEngine();
476 mediaStatusChanged(QMediaPlayer::NoMedia);
482 if (!m_mediaEngine) {
483 if (!initMediaEngine()) {
484 error(QMediaPlayer::ResourceError, u"MediaEngine not initialized"_s);
485 mediaStatusChanged(QMediaPlayer::InvalidMedia);
493 ComPtr<MFStream> mfStream = makeComObject<MFStream>(stream,
false);
494 QString urlStr = media.toString();
498 if (urlStr.isEmpty()) {
499 if (
auto *file = qobject_cast<QFile *>(stream))
500 urlStr = u"file:///"_s + file->fileName();
502 urlStr = u"stream:///media"_s;
504 const QBStr bstrUrl(urlStr);
505 hr = m_mediaEngineEx->SetSourceFromByteStream(mfStream.Get(), bstrUrl.bstr());
507 const QBStr bstrUrl(media.toString());
508 hr = m_mediaEngine->SetSource(bstrUrl.bstr());
512 qCWarning(lcMediaEngine) <<
"SetSource:" << QSystemError::windowsComString(hr);
513 error(QMediaPlayer::ResourceError, u"Failed to set media source"_s);
514 mediaStatusChanged(QMediaPlayer::InvalidMedia);
518 mediaStatusChanged(QMediaPlayer::LoadingMedia);
527 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia) {
531 if (mediaStatus() == QMediaPlayer::LoadingMedia) {
532 m_pendingPlay =
true;
536 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
540 if (!m_mediaEngine->IsEnded())
541 m_mediaEngine->SetCurrentTime(0);
545 mediaStatusChanged(QMediaPlayer::BufferingMedia);
546 }
else if (mediaStatus() == QMediaPlayer::LoadedMedia
547 || mediaStatus() == QMediaPlayer::BufferedMedia) {
548 mediaStatusChanged(QMediaPlayer::BufferingMedia);
551 HRESULT hr = m_mediaEngine->Play();
553 qCWarning(lcMediaEngine) <<
"Play:" << QSystemError::windowsComString(hr);
557 stateChanged(QMediaPlayer::PlayingState);
558 startPlaybackTimers();
566 m_pendingPlay =
false;
569 const bool fromEndOfMedia = mediaStatus() == QMediaPlayer::EndOfMedia;
570 if (fromEndOfMedia) {
576 HRESULT hr = m_mediaEngine->Pause();
578 qCWarning(lcMediaEngine) <<
"Pause:" << QSystemError::windowsComString(hr);
583 mediaStatusChanged(QMediaPlayer::BufferedMedia);
585 if (mediaStatus() != QMediaPlayer::NoMedia && mediaStatus() != QMediaPlayer::InvalidMedia) {
586 stateChanged(QMediaPlayer::PausedState);
587 m_positionUpdateTimer.stop();
591 if (isVideoAvailable() && !m_videoRenderTimer.isActive())
592 m_videoRenderTimer.start();
600 m_pendingPlay =
false;
601 m_seekTarget.reset();
602 m_seekTimeoutTimer.stop();
603 stopPlaybackTimers();
608 if (mediaStatus() != QMediaPlayer::EndOfMedia) {
609 HRESULT hr = m_mediaEngine->Pause();
611 qCWarning(lcMediaEngine) <<
"Pause (in stop):" << QSystemError::windowsComString(hr);
616 positionChanged(currentPositionMs());
618 m_mediaEngine->SetCurrentTime(0);
621 m_videoSink->setVideoFrame({});
623 stateChanged(QMediaPlayer::StoppedState);
627 if (mediaStatus() == QMediaPlayer::BufferedMedia
628 || mediaStatus() == QMediaPlayer::BufferingMedia
629 || mediaStatus() == QMediaPlayer::EndOfMedia) {
630 mediaStatusChanged(QMediaPlayer::LoadedMedia);
631 bufferProgressChanged(0);
642 if (m_seekTarget.has_value())
643 return m_seekTarget->count();
645 return currentPositionMs();
652 const qint64 pos = qRound64(m_mediaEngine->GetCurrentTime() * 1000.0);
654 return m_duration.count() > 0 ? qMin(pos, m_duration.count()) : pos;
662 m_mediaEngine->GetNativeVideoSize(&w, &h);
663 return {
static_cast<
int>(w),
static_cast<
int>(h) };
671 position = qMax(qint64(0), position);
672 if (
const qint64 dur = duration(); dur > 0)
673 position = qMin(position, dur);
679 const bool wasEnded = m_mediaEngine->IsEnded();
681 m_seekTarget = std::chrono::milliseconds(position);
682 m_mediaEngine->SetCurrentTime(position / 1000.0);
684 if (wasEnded && state() != QMediaPlayer::PlayingState)
685 m_mediaEngine->Pause();
689 m_seekTimeoutTimer.start();
691 if (mediaStatus() == QMediaPlayer::EndOfMedia)
692 mediaStatusChanged(QMediaPlayer::LoadedMedia);
694 positionChanged(position);
696 if (isVideoAvailable() && state() == QMediaPlayer::PausedState)
697 m_videoRenderTimer.start();
702 return m_playbackRate;
707 if (qFuzzyCompare(rate, m_playbackRate))
710 m_playbackRate = rate;
713 m_mediaEngine->SetDefaultPlaybackRate(rate);
714 m_mediaEngine->SetPlaybackRate(rate);
717 playbackRateChanged(rate);
725 ComPtr<IMFMediaTimeRange> buffered;
726 if (FAILED(m_mediaEngine->GetBuffered(&buffered)) || !buffered)
729 const DWORD count = buffered->GetLength();
733 const double dur = m_mediaEngine->GetDuration();
734 if (!std::isfinite(dur) || dur <= 0)
738 buffered->GetEnd(count - 1, &end);
739 return static_cast<
float>(qBound(0.0, end / dur, 1.0));
744 QMediaTimeRange ranges;
748 ComPtr<IMFMediaTimeRange> buffered;
749 if (FAILED(m_mediaEngine->GetBuffered(&buffered)) || !buffered)
752 const DWORD count = buffered->GetLength();
754 for (DWORD i = 0; i < count; ++i) {
755 double start = 0, end = 0;
756 buffered->GetStart(i, &start);
757 buffered->GetEnd(i, &end);
758 ranges.addInterval(qRound64(start * 1000), qRound64(end * 1000));
766 return m_duration.count();
782 return PitchCompensationAvailability::AlwaysOn;
792 if (!m_mediaEngine || !isVideoAvailable())
798 const bool isPlaying = (state() == QMediaPlayer::PlayingState);
799 auto stopGuard = qScopeGuard([&] {
800 if (!isPlaying && !m_seekTarget)
801 m_videoRenderTimer.stop();
805 if (m_mediaEngine->OnVideoStreamTick(&pts) != S_OK)
816 const QSize videoSize = nativeVideoSize();
817 if (videoSize.isEmpty())
820 if (videoSize != m_videoSize) {
821 m_videoSize = videoSize;
822 m_transferTexture.Reset();
825 if (!m_transferTexture) {
826 D3D11_TEXTURE2D_DESC desc = {};
827 desc.Width = videoSize.width();
828 desc.Height = videoSize.height();
831 desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
832 desc.SampleDesc.Count = 1;
833 desc.Usage = D3D11_USAGE_DEFAULT;
834 desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
837 HRESULT hr = m_d3dDevice->CreateTexture2D(&desc,
nullptr, &m_transferTexture);
839 qCWarning(lcMediaEngine) <<
"CreateTexture2D:" << QSystemError::windowsComString(hr);
843 updateVideoFrameFormat();
846 MFVideoNormalizedRect srcRect = { 0.0f, 0.0f, 1.0f, 1.0f };
847 RECT destRect = { 0, 0, videoSize.width(), videoSize.height() };
849 HRESULT hr = m_mediaEngine->TransferVideoFrame(m_transferTexture.Get(), &srcRect, &destRect,
853 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
855 if (m_deviceLostRetries >= kMaxDeviceLostRetries) {
856 qCWarning(lcMediaEngine) <<
"Device lost recovery failed after"
857 << m_deviceLostRetries <<
"attempts, giving up";
858 error(QMediaPlayer::ResourceError, u"D3D11 device lost recovery failed"_s);
861 ++m_deviceLostRetries;
863 qCWarning(lcMediaEngine) <<
"Device lost during TransferVideoFrame, recovering"
864 <<
"(attempt" << m_deviceLostRetries <<
")...";
866 const qint64 savedPosition = currentPositionMs();
867 const auto savedState = state();
868 stopPlaybackTimers();
871 releaseMediaEngine();
872 m_transferTexture.Reset();
876 QTimer::singleShot(0, context(), [
this, savedPosition, savedState] {
878 error(QMediaPlayer::ResourceError, u"D3D11 device recreation failed"_s);
882 if (!m_url.isEmpty() || m_stream) {
883 setMedia(m_url, m_stream);
885 if (savedPosition > 0)
886 setPosition(savedPosition);
887 if (savedState == QMediaPlayer::PlayingState)
893 qCWarning(lcMediaEngine) <<
"TransferVideoFrame:" << QSystemError::windowsComString(hr);
898 m_deviceLostRetries = 0;
900 auto buffer =
std::make_unique<QMediaEngineVideoBuffer>(m_transferTexture, m_d3dDevice,
901 videoSize, m_textureImporter);
903 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(buffer), m_videoFormat);
905 auto startTime =
std::chrono::round<
std::chrono::microseconds>(reference_time(pts));
907 frame.setStartTime(startTime.count());
908 if (m_frameDuration > 0us)
909 frame.setEndTime((startTime + m_frameDuration).count());
911 m_videoSink->setVideoFrame(frame);
912 m_videoSink->setNativeSize(videoSize);
920 const QSize size = nativeVideoSize();
924 m_videoFormat = QVideoFrameFormat(size, QVideoFrameFormat::Format_BGRA8888);
925 m_frameDuration = videoFrameDurationUs(m_mediaEngineEx.Get());
933 const QSize size = nativeVideoSize();
935 m_videoSink->setNativeSize(size);
940 auto *newSink = sink ? sink->platformVideoSink() :
nullptr;
941 auto *oldSink = m_videoSink;
942 m_videoSink = newSink;
947 if (newSink && oldSink)
948 newSink->setVideoFrame(oldSink->currentVideoFrame());
953 if (m_audioOutput == output)
956 if (m_audioOutput && m_audioOutput->q)
957 m_audioOutput->q->disconnect(context());
959 m_audioOutput = output;
961 if (output && output->q) {
962 QObject::connect(output->q, &QAudioOutput::volumeChanged, context(), [
this] {
965 QObject::connect(output->q, &QAudioOutput::mutedChanged, context(), [
this] {
968 QObject::connect(output->q, &QAudioOutput::deviceChanged, context(), [
this] {
987 if (!m_audioOutput) {
988 m_mediaEngine->SetMuted(TRUE);
992 m_mediaEngine->SetVolume(
static_cast<
double>(m_audioOutput->volume));
993 m_mediaEngine->SetMuted(m_audioOutput->muted ? TRUE : FALSE);
1000 if (!m_mediaEngine || !m_audioOutput)
1003 const QByteArray id = m_audioOutput->device.id();
1007 ComPtr<IMFMediaEngineAudioEndpointId> endpointId;
1008 if (FAILED(m_mediaEngine.As(&endpointId)))
1011 const QString deviceId = QString::fromUtf8(id);
1012 const HRESULT hr = endpointId->SetAudioEndpointId(
reinterpret_cast<LPCWSTR>(deviceId.utf16()));
1014 qCWarning(lcMediaEngine) <<
"SetAudioEndpointId:" << QSystemError::windowsComString(hr);
1019 if (!m_mediaEngineEx)
1024 const QSize size = nativeVideoSize();
1025 if (!size.isEmpty())
1026 m_metaData.insert(QMediaMetaData::Resolution, size);
1028 if (m_duration > 0ms)
1029 m_metaData.insert(QMediaMetaData::Duration, m_duration.count());
1031 m_metadataFuture.cancel();
1033 const QUrl url = m_url;
1036 if (url.isEmpty()) {
1041 m_metadataFuture = QtConcurrent::run([url]() -> QMediaMetaData {
1042 ComPtr<IMFSourceResolver> resolver;
1043 if (FAILED(MFCreateSourceResolver(&resolver)))
1046 ComPtr<IUnknown> sourceUnk;
1047 MF_OBJECT_TYPE objectType = MF_OBJECT_INVALID;
1048 HRESULT hr = E_FAIL;
1050 if (url.scheme() == u"qrc"_s) {
1051 auto file = std::make_unique<QFile>(u':' + url.path());
1052 if (!file->open(QIODevice::ReadOnly))
1059 ComPtr<MFStream> mfStream =
1060 makeComObject<MFStream>(file.release(),
true,
true );
1062 const auto urlStr = url.toString();
1063 hr = resolver->CreateObjectFromByteStream(
1064 mfStream.Get(),
reinterpret_cast<LPCWSTR>(urlStr.utf16()),
1065 MF_RESOLUTION_MEDIASOURCE
1066 | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE,
1067 nullptr, &objectType, &sourceUnk);
1069 const auto urlStr = url.toString();
1070 hr = resolver->CreateObjectFromURL(
1071 reinterpret_cast<LPCWSTR>(urlStr.utf16()),
1072 MF_RESOLUTION_MEDIASOURCE
1073 | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE,
1074 nullptr, &objectType, &sourceUnk);
1077 if (FAILED(hr) || !sourceUnk)
1080 ComPtr<IMFMediaSource> mediaSource;
1081 if (FAILED(sourceUnk.As(&mediaSource)))
1084 QMediaMetaData result = MFMetaData::fromNative(mediaSource.Get());
1085 mediaSource->Shutdown();
1087 }).then(context(), [
this](QMediaMetaData rich) {
1088 if (!rich.isEmpty()) {
1089 for (
const auto &[key, value] : std::as_const(m_metaData).asKeyValueRange())
1090 rich.insert(key, value);
1099 if (!m_mediaEngineEx)
1104 DWORD streamCount = 0;
1105 if (FAILED(m_mediaEngineEx->GetNumberOfStreams(&streamCount)))
1108 for (DWORD i = 0; i < streamCount; ++i) {
1109 std::optional<TrackType> trackType;
1111 ScopedPropVariant var;
1112 if (FAILED(m_mediaEngineEx->GetStreamAttribute(i, MF_MT_MAJOR_TYPE, var.get())))
1115 if (var->vt == VT_CLSID) {
1116 if (*var->puuid == MFMediaType_Audio)
1117 trackType = TrackType::AudioStream;
1118 else if (*var->puuid == MFMediaType_Video)
1119 trackType = TrackType::VideoStream;
1126 QMediaMetaData trackMeta;
1128 ScopedPropVariant var;
1129 if (SUCCEEDED(m_mediaEngineEx->GetStreamAttribute(i, MF_SD_LANGUAGE, var.get()))) {
1130 if (var->vt == VT_LPWSTR && var->pwszVal)
1131 trackMeta.insert(QMediaMetaData::Language,
1132 languageVariant(QString::fromWCharArray(var->pwszVal)));
1136 m_tracks[*trackType].entries.push_back({ trackMeta, i });
1138 BOOL selected = FALSE;
1139 if (SUCCEEDED(m_mediaEngineEx->GetStreamSelection(i, &selected)) && selected)
1140 m_tracks[*trackType].currentIndex =
1141 static_cast<
int>(m_tracks[*trackType].entries.size()) - 1;
1144 updateSubtitleTracks();
1151 auto &subtitleInfo = m_tracks[TrackType::SubtitleStream];
1157 ComPtr<IMFTimedTextTrackList> trackList;
1158 if (FAILED(m_timedText->GetTextTracks(&trackList)) || !trackList)
1161 const DWORD count = trackList->GetLength();
1162 for (DWORD i = 0; i < count; ++i) {
1163 ComPtr<IMFTimedTextTrack> track;
1164 if (FAILED(trackList->GetTrack(i, &track)) || !track)
1167 QMediaMetaData trackMeta;
1169 QComTaskResource<WCHAR> label;
1170 if (SUCCEEDED(track->GetLabel(label.address())) && label)
1171 trackMeta.insert(QMediaMetaData::Title, QString::fromWCharArray(label.get()));
1173 QComTaskResource<WCHAR> lang;
1174 if (SUCCEEDED(track->GetLanguage(lang.address())) && lang)
1175 trackMeta.insert(QMediaMetaData::Language,
1176 languageVariant(QString::fromWCharArray(lang.get())));
1179 const DWORD trackId = track->GetId();
1181 subtitleInfo.entries.push_back({ trackMeta, trackId });
1183 if (track->IsActive()) {
1184 subtitleInfo.currentIndex =
static_cast<
int>(subtitleInfo.entries.size()) - 1;
1194 const double dur = m_mediaEngine->GetDuration();
1195 const auto newDuration = std::chrono::milliseconds(
1196 (std::isfinite(dur) && dur >= 0) ? qRound64(dur * 1000.0) : 0);
1197 if (newDuration != m_duration) {
1198 m_duration = newDuration;
1199 durationChanged(m_duration.count());
1205 return static_cast<
int>(m_tracks[type].entries.size());
1210 if (trackNumber < 0 || trackNumber >=
static_cast<
int>(m_tracks[type].entries.size()))
1212 return m_tracks[type].entries.at(trackNumber).metaData;
1217 return m_tracks[type].currentIndex;
1222 if (index < -1 || index >=
static_cast<
int>(m_tracks[type].entries.size()))
1225 if (type == TrackType::SubtitleStream) {
1230 const int currentIdx = m_tracks[type].currentIndex;
1231 if (currentIdx >= 0 && currentIdx <
static_cast<
int>(m_tracks[type].entries.size())) {
1232 DWORD trackId = m_tracks[type].entries.at(currentIdx).streamIndex;
1233 m_timedText->SelectTrack(trackId, FALSE);
1237 DWORD trackId = m_tracks[type].entries.at(index).streamIndex;
1238 m_timedText->SelectTrack(trackId, TRUE);
1242 m_videoSink->setSubtitleText({});
1244 m_tracks[type].currentIndex = index;
1245 activeTracksChanged();
1249 if (!m_mediaEngineEx)
1252 const int currentIdx = m_tracks[type].currentIndex;
1253 if (currentIdx >= 0 && currentIdx <
static_cast<
int>(m_tracks[type].entries.size())) {
1254 DWORD streamIdx = m_tracks[type].entries.at(currentIdx).streamIndex;
1255 m_mediaEngineEx->SetStreamSelection(streamIdx, FALSE);
1259 DWORD streamIdx = m_tracks[type].entries.at(index).streamIndex;
1260 m_mediaEngineEx->SetStreamSelection(streamIdx, TRUE);
1263 m_tracks[type].currentIndex = index;
1264 activeTracksChanged();
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")