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
qwindowsmediadevicereader.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
5
6#include "private/qwindowsmultimediautils_p.h"
7#include <qvideosink.h>
8#include <qmediadevices.h>
9#include <qaudiodevice.h>
10#include <private/qmemoryvideobuffer_p.h>
11#include <private/qwindowsmfdefs_p.h>
12#include <private/qcomptr_p.h>
13#include <QtCore/qdebug.h>
14
15#include <mmdeviceapi.h>
16
18
19enum { MEDIA_TYPE_INDEX_DEFAULT = 0xffffffff };
20
22 : QObject(parent)
23{
24 m_durationTimer.setInterval(100);
25 connect(&m_durationTimer, &QTimer::timeout, this, &QWindowsMediaDeviceReader::updateDuration);
26}
27
33
34// Creates a video or audio media source specified by deviceId (symbolic link)
35HRESULT QWindowsMediaDeviceReader::createSource(const QString &deviceId, bool video, IMFMediaSource **source)
36{
37 if (!source)
38 return E_INVALIDARG;
39
40 *source = nullptr;
41 IMFAttributes *sourceAttributes = nullptr;
42
43 HRESULT hr = MFCreateAttributes(&sourceAttributes, 2);
44 if (SUCCEEDED(hr)) {
45
46 hr = sourceAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
49 if (SUCCEEDED(hr)) {
50
51 hr = sourceAttributes->SetString(video ? MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK
52 : MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID,
53 reinterpret_cast<LPCWSTR>(deviceId.utf16()));
54 if (SUCCEEDED(hr)) {
55
56 hr = MFCreateDeviceSource(sourceAttributes, source);
57 }
58 }
59 sourceAttributes->Release();
60 }
61
62 return hr;
63}
64
65// Creates a source/reader aggregating two other sources (video/audio).
66// If one of the sources is null the result will be video-only or audio-only.
67HRESULT QWindowsMediaDeviceReader::createAggregateReader(IMFMediaSource *firstSource,
68 IMFMediaSource *secondSource,
69 IMFMediaSource **aggregateSource,
70 IMFSourceReader **sourceReader)
71{
72 if ((!firstSource && !secondSource) || !aggregateSource || !sourceReader)
73 return E_INVALIDARG;
74
75 *aggregateSource = nullptr;
76 *sourceReader = nullptr;
77
78 IMFCollection *sourceCollection = nullptr;
79
80 HRESULT hr = MFCreateCollection(&sourceCollection);
81 if (SUCCEEDED(hr)) {
82
83 if (firstSource)
84 sourceCollection->AddElement(firstSource);
85
86 if (secondSource)
87 sourceCollection->AddElement(secondSource);
88
89 hr = MFCreateAggregateSource(sourceCollection, aggregateSource);
90 if (SUCCEEDED(hr)) {
91
92 IMFAttributes *readerAttributes = nullptr;
93
94 hr = MFCreateAttributes(&readerAttributes, 1);
95 if (SUCCEEDED(hr)) {
96
97 // Set callback so OnReadSample() is called for each new video frame or audio sample.
98 hr = readerAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK,
99 static_cast<IMFSourceReaderCallback*>(this));
100 if (SUCCEEDED(hr)) {
101
102 hr = MFCreateSourceReaderFromMediaSource(*aggregateSource, readerAttributes, sourceReader);
103 }
104 readerAttributes->Release();
105 }
106 }
107 sourceCollection->Release();
108 }
109 return hr;
110}
111
112// Selects the requested resolution/frame rate (if specified),
113// or chooses a high quality configuration otherwise.
114DWORD QWindowsMediaDeviceReader::findMediaTypeIndex(const QCameraFormat &reqFormat)
115{
116 DWORD mediaIndex = MEDIA_TYPE_INDEX_DEFAULT;
117
118 if (m_sourceReader && m_videoSource) {
119
120 DWORD index = 0;
121 IMFMediaType *mediaType = nullptr;
122
123 UINT32 currArea = 0;
124 float currFrameRate = 0.0f;
125
126 while (SUCCEEDED(m_sourceReader->GetNativeMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
127 index, &mediaType))) {
128
129 GUID subtype = GUID_NULL;
130 if (SUCCEEDED(mediaType->GetGUID(MF_MT_SUBTYPE, &subtype))) {
131
133 if (pixelFormat != QVideoFrameFormat::Format_Invalid) {
134
135 UINT32 width, height;
136 if (SUCCEEDED(MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, &width, &height))) {
137
138 UINT32 num, den;
139 if (SUCCEEDED(MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, &num, &den))) {
140
141 UINT32 area = width * height;
142 float frameRate = float(num) / den;
143
144 if (!reqFormat.isNull()
145 && UINT32(reqFormat.resolution().width()) == width
146 && UINT32(reqFormat.resolution().height()) == height
147 && qFuzzyCompare(reqFormat.maxFrameRate(), frameRate)
148 && reqFormat.pixelFormat() == pixelFormat) {
149 mediaType->Release();
150 return index;
151 }
152
153 if ((currFrameRate < 29.9 && currFrameRate < frameRate) ||
154 (currFrameRate == frameRate && currArea < area)) {
155 currArea = area;
156 currFrameRate = frameRate;
157 mediaIndex = index;
158 }
159 }
160 }
161 }
162 }
163 mediaType->Release();
164 ++index;
165 }
166 }
167
168 return mediaIndex;
169}
170
171
172// Prepares the source video stream and gets some metadata.
173HRESULT QWindowsMediaDeviceReader::prepareVideoStream(DWORD mediaTypeIndex)
174{
175 if (!m_sourceReader)
176 return E_FAIL;
177
178 if (!m_videoSource)
179 return S_OK; // It may be audio-only
180
181 HRESULT hr;
182
183 if (mediaTypeIndex == MEDIA_TYPE_INDEX_DEFAULT) {
184 hr = m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
185 &m_videoMediaType);
186 } else {
187 hr = m_sourceReader->GetNativeMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
188 mediaTypeIndex, &m_videoMediaType);
189 if (SUCCEEDED(hr))
190 hr = m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
191 nullptr, m_videoMediaType);
192 }
193
194 if (SUCCEEDED(hr)) {
195
196 GUID subtype = GUID_NULL;
197 hr = m_videoMediaType->GetGUID(MF_MT_SUBTYPE, &subtype);
198 if (SUCCEEDED(hr)) {
199
201
202 if (m_pixelFormat == QVideoFrameFormat::Format_Invalid) {
203 hr = E_FAIL;
204 } else {
205
206 // get the frame dimensions
207 hr = MFGetAttributeSize(m_videoMediaType, MF_MT_FRAME_SIZE, &m_frameWidth, &m_frameHeight);
208 if (SUCCEEDED(hr)) {
209
210 // and the stride, which we need to convert the frame later
211 hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, m_frameWidth, &m_stride);
212 if (SUCCEEDED(hr)) {
213 m_stride = qAbs(m_stride);
214 UINT32 frameRateNum, frameRateDen;
215 hr = MFGetAttributeRatio(m_videoMediaType, MF_MT_FRAME_RATE, &frameRateNum, &frameRateDen);
216 if (SUCCEEDED(hr)) {
217
218 m_frameRate = qreal(frameRateNum) / frameRateDen;
219
220 hr = m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM), TRUE);
221 }
222 }
223 }
224 }
225 }
226 }
227
228 return hr;
229}
230
231HRESULT QWindowsMediaDeviceReader::initAudioType(IMFMediaType *mediaType, UINT32 channels, UINT32 samplesPerSec, bool flt)
232{
233 if (!mediaType)
234 return E_INVALIDARG;
235
236 HRESULT hr = mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
237 if (SUCCEEDED(hr)) {
238 hr = mediaType->SetGUID(MF_MT_SUBTYPE, flt ? MFAudioFormat_Float : MFAudioFormat_PCM);
239 if (SUCCEEDED(hr)) {
240 hr = mediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels);
241 if (SUCCEEDED(hr)) {
242 hr = mediaType->SetUINT32(MF_MT_AUDIO_CHANNEL_MASK,
243 (channels == 1) ? SPEAKER_FRONT_CENTER : (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT ));
244 if (SUCCEEDED(hr)) {
245 hr = mediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, samplesPerSec);
246 if (SUCCEEDED(hr)) {
247 UINT32 bitsPerSample = flt ? 32 : 16;
248 UINT32 bytesPerFrame = channels * bitsPerSample/8;
249 hr = mediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample);
250 if (SUCCEEDED(hr)) {
251 hr = mediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, bytesPerFrame);
252 if (SUCCEEDED(hr)) {
253 hr = mediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerFrame * samplesPerSec);
254 }
255 }
256 }
257 }
258 }
259 }
260 }
261
262 return hr;
263}
264
265// Prepares the source audio stream.
266HRESULT QWindowsMediaDeviceReader::prepareAudioStream()
267{
268 if (!m_sourceReader)
269 return E_FAIL;
270
271 if (!m_audioSource)
272 return S_OK; // It may be video-only
273
274 HRESULT hr = m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM),
275 &m_audioMediaType);
276 if (SUCCEEDED(hr)) {
277 hr = initAudioType(m_audioMediaType, 2, 48000, true);
278 if (SUCCEEDED(hr)) {
279 hr = m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM),
280 nullptr, m_audioMediaType);
281 if (SUCCEEDED(hr)) {
282 hr = m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE);
283 }
284 }
285 }
286 return hr;
287}
288
289// Retrieves the indexes for selected video/audio streams.
290HRESULT QWindowsMediaDeviceReader::initSourceIndexes()
291{
292 if (!m_sourceReader)
293 return E_FAIL;
294
295 m_sourceVideoStreamIndex = MF_SOURCE_READER_INVALID_STREAM_INDEX;
296 m_sourceAudioStreamIndex = MF_SOURCE_READER_INVALID_STREAM_INDEX;
297
298 DWORD index = 0;
299 BOOL selected = FALSE;
300
301 while (m_sourceReader->GetStreamSelection(index, &selected) == S_OK) {
302 if (selected) {
303 IMFMediaType *mediaType = nullptr;
304 if (SUCCEEDED(m_sourceReader->GetCurrentMediaType(index, &mediaType))) {
305 GUID majorType = GUID_NULL;
306 if (SUCCEEDED(mediaType->GetGUID(MF_MT_MAJOR_TYPE, &majorType))) {
307 if (majorType == MFMediaType_Video)
308 m_sourceVideoStreamIndex = index;
309 else if (majorType == MFMediaType_Audio)
310 m_sourceAudioStreamIndex = index;
311 }
312 mediaType->Release();
313 }
314 }
315 ++index;
316 }
317 if ((m_videoSource && m_sourceVideoStreamIndex == MF_SOURCE_READER_INVALID_STREAM_INDEX) ||
318 (m_audioSource && m_sourceAudioStreamIndex == MF_SOURCE_READER_INVALID_STREAM_INDEX))
319 return E_FAIL;
320 return S_OK;
321}
322
324{
325 QMutexLocker locker(&m_mutex);
326
327 stopMonitoring();
328
329 m_audioOutputId = audioOutputId;
330
331 if (!m_active || m_audioOutputId.isEmpty())
332 return true;
333
334 HRESULT hr = startMonitoring();
335
336 return SUCCEEDED(hr);
337}
338
339HRESULT QWindowsMediaDeviceReader::startMonitoring()
340{
341 if (m_audioOutputId.isEmpty())
342 return E_FAIL;
343
344 IMFAttributes *sinkAttributes = nullptr;
345
346 HRESULT hr = MFCreateAttributes(&sinkAttributes, 1);
347 if (SUCCEEDED(hr)) {
348
349 hr = sinkAttributes->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID,
350 reinterpret_cast<LPCWSTR>(m_audioOutputId.utf16()));
351 if (SUCCEEDED(hr)) {
352
353 IMFMediaSink *mediaSink = nullptr;
354 hr = MFCreateAudioRenderer(sinkAttributes, &mediaSink);
355 if (SUCCEEDED(hr)) {
356
357 IMFStreamSink *streamSink = nullptr;
358 hr = mediaSink->GetStreamSinkByIndex(0, &streamSink);
359 if (SUCCEEDED(hr)) {
360
361 IMFMediaTypeHandler *typeHandler = nullptr;
362 hr = streamSink->GetMediaTypeHandler(&typeHandler);
363 if (SUCCEEDED(hr)) {
364
365 hr = typeHandler->IsMediaTypeSupported(m_audioMediaType, nullptr);
366 if (SUCCEEDED(hr)) {
367
368 hr = typeHandler->SetCurrentMediaType(m_audioMediaType);
369 if (SUCCEEDED(hr)) {
370
371 IMFAttributes *writerAttributes = nullptr;
372
373 HRESULT hr = MFCreateAttributes(&writerAttributes, 1);
374 if (SUCCEEDED(hr)) {
375
376 hr = writerAttributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, TRUE);
377 if (SUCCEEDED(hr)) {
378
379 IMFSinkWriter *sinkWriter = nullptr;
380 hr = MFCreateSinkWriterFromMediaSink(mediaSink, writerAttributes, &sinkWriter);
381 if (SUCCEEDED(hr)) {
382
383 hr = sinkWriter->SetInputMediaType(0, m_audioMediaType, nullptr);
384 if (SUCCEEDED(hr)) {
385
386 IMFSimpleAudioVolume *audioVolume = nullptr;
387
388 if (SUCCEEDED(MFGetService(mediaSink, QMM_MR_POLICY_VOLUME_SERVICE, IID_PPV_ARGS(&audioVolume)))) {
389 audioVolume->SetMasterVolume(float(m_outputVolume));
390 audioVolume->SetMute(m_outputMuted);
391 audioVolume->Release();
392 }
393
394 hr = sinkWriter->BeginWriting();
395 if (SUCCEEDED(hr)) {
396 m_monitorSink = mediaSink;
397 m_monitorSink->AddRef();
398 m_monitorWriter = sinkWriter;
399 m_monitorWriter->AddRef();
400 }
401 }
402 sinkWriter->Release();
403 }
404 }
405 writerAttributes->Release();
406 }
407 }
408 }
409 typeHandler->Release();
410 }
411 streamSink->Release();
412 }
413 mediaSink->Release();
414 }
415 }
416 sinkAttributes->Release();
417 }
418
419 return hr;
420}
421
422void QWindowsMediaDeviceReader::stopMonitoring()
423{
424 if (m_monitorWriter) {
425 m_monitorWriter->Release();
426 m_monitorWriter = nullptr;
427 }
428 if (m_monitorSink) {
429 m_monitorSink->Shutdown();
430 m_monitorSink->Release();
431 m_monitorSink = nullptr;
432 }
433}
434
435// Activates the requested camera/microphone for streaming.
436// One of the IDs may be empty for video-only/audio-only.
438 const QCameraFormat &cameraFormat,
439 const QString &microphoneId)
440{
441 QMutexLocker locker(&m_mutex);
442
443 if (cameraId.isEmpty() && microphoneId.isEmpty())
444 return false;
445
446 stopMonitoring();
447 releaseResources();
448
449 m_active = false;
450 m_streaming = false;
451
452 if (!cameraId.isEmpty()) {
453 if (!SUCCEEDED(createSource(cameraId, true, &m_videoSource))) {
454 releaseResources();
455 return false;
456 }
457 }
458
459 if (!microphoneId.isEmpty()) {
460 if (!SUCCEEDED(createSource(microphoneId, false, &m_audioSource))) {
461 releaseResources();
462 return false;
463 }
464 }
465
466 if (!SUCCEEDED(createAggregateReader(m_videoSource, m_audioSource, &m_aggregateSource, &m_sourceReader))) {
467 releaseResources();
468 return false;
469 }
470
471 DWORD mediaTypeIndex = findMediaTypeIndex(cameraFormat);
472
473 if (!SUCCEEDED(prepareVideoStream(mediaTypeIndex))) {
474 releaseResources();
475 return false;
476 }
477
478 if (!SUCCEEDED(prepareAudioStream())) {
479 releaseResources();
480 return false;
481 }
482
483 if (!SUCCEEDED(initSourceIndexes())) {
484 releaseResources();
485 return false;
486 }
487
488 updateSinkInputMediaTypes();
489 startMonitoring();
490
491 // Request the first frame or audio sample.
492 if (!SUCCEEDED(m_sourceReader->ReadSample(MF_SOURCE_READER_ANY_STREAM, 0, nullptr, nullptr, nullptr, nullptr))) {
493 releaseResources();
494 return false;
495 }
496
497 m_active = true;
498 return true;
499}
500
502{
503 stopMonitoring();
504 stopStreaming();
505 m_active = false;
506 m_streaming = false;
507}
508
509void QWindowsMediaDeviceReader::stopStreaming()
510{
511 QMutexLocker locker(&m_mutex);
512 releaseResources();
513}
514
515// Releases allocated streaming stuff.
516void QWindowsMediaDeviceReader::releaseResources()
517{
518 if (m_videoMediaType) {
519 m_videoMediaType->Release();
520 m_videoMediaType = nullptr;
521 }
522 if (m_audioMediaType) {
523 m_audioMediaType->Release();
524 m_audioMediaType = nullptr;
525 }
526 if (m_sourceReader) {
527 m_sourceReader->Release();
528 m_sourceReader = nullptr;
529 }
530 if (m_aggregateSource) {
531 m_aggregateSource->Release();
532 m_aggregateSource = nullptr;
533 }
534 if (m_videoSource) {
535 m_videoSource->Release();
536 m_videoSource = nullptr;
537 }
538 if (m_audioSource) {
539 m_audioSource->Release();
540 m_audioSource = nullptr;
541 }
542}
543
544HRESULT QWindowsMediaDeviceReader::createVideoMediaType(const GUID &format, UINT32 bitRate, UINT32 width,
545 UINT32 height, qreal frameRate, IMFMediaType **mediaType)
546{
547 if (!mediaType)
548 return E_INVALIDARG;
549
550 *mediaType = nullptr;
551 IMFMediaType *targetMediaType = nullptr;
552
553 if (SUCCEEDED(MFCreateMediaType(&targetMediaType))) {
554
555 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video))) {
556
557 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_SUBTYPE, format))) {
558
559 if (SUCCEEDED(targetMediaType->SetUINT32(MF_MT_AVG_BITRATE, bitRate))) {
560
561 if (SUCCEEDED(MFSetAttributeSize(targetMediaType, MF_MT_FRAME_SIZE, width, height))) {
562
563 if (SUCCEEDED(MFSetAttributeRatio(targetMediaType, MF_MT_FRAME_RATE,
564 UINT32(frameRate * 1000), 1000))) {
565 UINT32 t1, t2;
566 if (SUCCEEDED(MFGetAttributeRatio(m_videoMediaType, MF_MT_PIXEL_ASPECT_RATIO, &t1, &t2))) {
567
568 if (SUCCEEDED(MFSetAttributeRatio(targetMediaType, MF_MT_PIXEL_ASPECT_RATIO, t1, t2))) {
569
570 if (SUCCEEDED(m_videoMediaType->GetUINT32(MF_MT_INTERLACE_MODE, &t1))) {
571
572 if (SUCCEEDED(targetMediaType->SetUINT32(MF_MT_INTERLACE_MODE, t1))) {
573
574 *mediaType = targetMediaType;
575 return S_OK;
576 }
577 }
578 }
579 }
580 }
581 }
582 }
583 }
584 }
585 targetMediaType->Release();
586 }
587 return E_FAIL;
588}
589
590HRESULT QWindowsMediaDeviceReader::createAudioMediaType(const GUID &format, UINT32 bitRate, IMFMediaType **mediaType)
591{
592 if (!mediaType)
593 return E_INVALIDARG;
594
595 *mediaType = nullptr;
596 IMFMediaType *targetMediaType = nullptr;
597
598 if (SUCCEEDED(MFCreateMediaType(&targetMediaType))) {
599
600 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio))) {
601
602 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_SUBTYPE, format))) {
603
604 if (bitRate == 0 || SUCCEEDED(targetMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bitRate / 8))) {
605
606 *mediaType = targetMediaType;
607 return S_OK;
608 }
609 }
610 }
611 targetMediaType->Release();
612 }
613 return E_FAIL;
614}
615
616HRESULT QWindowsMediaDeviceReader::updateSinkInputMediaTypes()
617{
618 HRESULT hr = S_OK;
619 if (m_sinkWriter) {
620 if (m_videoSource && m_videoMediaType && m_sinkVideoStreamIndex != MF_SINK_WRITER_INVALID_STREAM_INDEX) {
621 hr = m_sinkWriter->SetInputMediaType(m_sinkVideoStreamIndex, m_videoMediaType, nullptr);
622 }
623 if (SUCCEEDED(hr)) {
624 if (m_audioSource && m_audioMediaType && m_sinkAudioStreamIndex != MF_SINK_WRITER_INVALID_STREAM_INDEX) {
625 hr = m_sinkWriter->SetInputMediaType(m_sinkAudioStreamIndex, m_audioMediaType, nullptr);
626 }
627 }
628 }
629 return hr;
630}
631
633 const QString &fileName, const GUID &container, const GUID &videoFormat, UINT32 videoBitRate,
634 UINT32 width, UINT32 height, qreal frameRate, const GUID &audioFormat, UINT32 audioBitRate)
635{
636 QMutexLocker locker(&m_mutex);
637
638 if (!m_active || m_recording || (videoFormat == GUID_NULL && audioFormat == GUID_NULL))
640
641 ComPtr<IMFAttributes> writerAttributes;
642
643 HRESULT hr = MFCreateAttributes(writerAttributes.GetAddressOf(), 2);
644 if (FAILED(hr))
646
647 // Set callback so OnFinalize() is called after video is saved.
648 hr = writerAttributes->SetUnknown(MF_SINK_WRITER_ASYNC_CALLBACK,
649 static_cast<IMFSinkWriterCallback*>(this));
650 if (FAILED(hr))
652
653 hr = writerAttributes->SetGUID(QMM_MF_TRANSCODE_CONTAINERTYPE, container);
654 if (FAILED(hr))
656
657 ComPtr<IMFSinkWriter> sinkWriter;
658 hr = MFCreateSinkWriterFromURL(reinterpret_cast<LPCWSTR>(fileName.utf16()),
659 nullptr, writerAttributes.Get(), sinkWriter.GetAddressOf());
660 if (FAILED(hr))
662
663 m_sinkVideoStreamIndex = MF_SINK_WRITER_INVALID_STREAM_INDEX;
664 m_sinkAudioStreamIndex = MF_SINK_WRITER_INVALID_STREAM_INDEX;
665
666 if (m_videoSource && videoFormat != GUID_NULL) {
667 IMFMediaType *targetMediaType = nullptr;
668
669 hr = createVideoMediaType(videoFormat, videoBitRate, width, height, frameRate, &targetMediaType);
670 if (SUCCEEDED(hr)) {
671
672 hr = sinkWriter->AddStream(targetMediaType, &m_sinkVideoStreamIndex);
673 if (SUCCEEDED(hr)) {
674
675 hr = sinkWriter->SetInputMediaType(m_sinkVideoStreamIndex, m_videoMediaType, nullptr);
676 }
677 targetMediaType->Release();
678 }
679 }
680
681 if (SUCCEEDED(hr)) {
682 if (m_audioSource && audioFormat != GUID_NULL) {
683 IMFMediaType *targetMediaType = nullptr;
684
685 hr = createAudioMediaType(audioFormat, audioBitRate, &targetMediaType);
686 if (SUCCEEDED(hr)) {
687
688 hr = sinkWriter->AddStream(targetMediaType, &m_sinkAudioStreamIndex);
689 if (SUCCEEDED(hr)) {
690
691 hr = sinkWriter->SetInputMediaType(m_sinkAudioStreamIndex, m_audioMediaType, nullptr);
692 }
693 targetMediaType->Release();
694 }
695 }
696 }
697
698 if (FAILED(hr))
700
701 hr = sinkWriter->BeginWriting();
702 if (FAILED(hr))
704
705 m_sinkWriter = sinkWriter.Detach();
706 m_lastDuration = -1;
707 m_currentDuration = 0;
708 updateDuration();
709 m_durationTimer.start();
710 m_recording = true;
711 m_firstFrame = true;
712 m_paused = false;
713 m_pauseChanging = false;
714
716}
717
719{
720 QMutexLocker locker(&m_mutex);
721
722 if (m_sinkWriter && m_recording) {
723
724 HRESULT hr = m_sinkWriter->Finalize();
725
726 if (SUCCEEDED(hr)) {
727 m_hasFinalized.wait(&m_mutex);
728 } else {
729 m_sinkWriter->Release();
730 m_sinkWriter = nullptr;
731
732 QMetaObject::invokeMethod(this, "recordingError",
733 Qt::QueuedConnection, Q_ARG(int, hr));
734 }
735 }
736
737 m_recording = false;
738 m_paused = false;
739 m_pauseChanging = false;
740
741 m_durationTimer.stop();
742 m_lastDuration = -1;
743 m_currentDuration = -1;
744}
745
747{
748 if (!m_recording || m_paused)
749 return false;
750 m_pauseTime = m_lastTimestamp;
751 m_paused = true;
752 m_pauseChanging = true;
753 return true;
754}
755
757{
758 if (!m_recording || !m_paused)
759 return false;
760 m_paused = false;
761 m_pauseChanging = true;
762 return true;
763}
764
765//from IUnknown
766STDMETHODIMP QWindowsMediaDeviceReader::QueryInterface(REFIID riid, LPVOID *ppvObject)
767{
768 if (!ppvObject)
769 return E_POINTER;
770 if (riid == IID_IMFSourceReaderCallback) {
771 *ppvObject = static_cast<IMFSourceReaderCallback*>(this);
772 } else if (riid == IID_IMFSinkWriterCallback) {
773 *ppvObject = static_cast<IMFSinkWriterCallback*>(this);
774 } else if (riid == IID_IUnknown) {
775 *ppvObject = static_cast<IUnknown*>(static_cast<IMFSourceReaderCallback*>(this));
776 } else {
777 *ppvObject = nullptr;
778 return E_NOINTERFACE;
779 }
780 AddRef();
781 return S_OK;
782}
783
784STDMETHODIMP_(ULONG) QWindowsMediaDeviceReader::AddRef(void)
785{
786 return InterlockedIncrement(&m_cRef);
787}
788
789STDMETHODIMP_(ULONG) QWindowsMediaDeviceReader::Release(void)
790{
791 LONG cRef = InterlockedDecrement(&m_cRef);
792 if (cRef == 0) {
793 this->deleteLater();
794 }
795 return cRef;
796}
797
799{
800 return m_frameWidth;
801}
802
804{
805 return m_frameHeight;
806}
807
809{
810 return m_frameRate;
811}
812
814{
815 m_inputMuted = muted;
816}
817
819{
820 m_inputVolume = qBound(0.0, volume, 1.0);
821}
822
824{
825 QMutexLocker locker(&m_mutex);
826
827 m_outputMuted = muted;
828
829 if (m_active && m_monitorSink) {
830 IMFSimpleAudioVolume *audioVolume = nullptr;
831 if (SUCCEEDED(MFGetService(m_monitorSink, QMM_MR_POLICY_VOLUME_SERVICE,
832 IID_PPV_ARGS(&audioVolume)))) {
833 audioVolume->SetMute(m_outputMuted);
834 audioVolume->Release();
835 }
836 }
837}
838
840{
841 QMutexLocker locker(&m_mutex);
842
843 m_outputVolume = qBound(0.0, volume, 1.0);
844
845 if (m_active && m_monitorSink) {
846 IMFSimpleAudioVolume *audioVolume = nullptr;
847 if (SUCCEEDED(MFGetService(m_monitorSink, QMM_MR_POLICY_VOLUME_SERVICE,
848 IID_PPV_ARGS(&audioVolume)))) {
849 audioVolume->SetMasterVolume(float(m_outputVolume));
850 audioVolume->Release();
851 }
852 }
853}
854
855void QWindowsMediaDeviceReader::updateDuration()
856{
857 if (m_currentDuration >= 0 && m_lastDuration != m_currentDuration) {
858 m_lastDuration = m_currentDuration;
859 emit durationChanged(m_currentDuration);
860 }
861}
862
863//from IMFSourceReaderCallback
864STDMETHODIMP QWindowsMediaDeviceReader::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
865 DWORD dwStreamFlags, LONGLONG llTimestamp,
866 IMFSample *pSample)
867{
868 QMutexLocker locker(&m_mutex);
869
870 if (FAILED(hrStatus)) {
871 emit streamingError(int(hrStatus));
872 return hrStatus;
873 }
874
875 m_lastTimestamp = llTimestamp;
876
877 if ((dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) {
878 m_streaming = false;
880 } else {
881
882 if (!m_streaming) {
883 m_streaming = true;
885 }
886 if (pSample) {
887
888 if (m_monitorWriter && dwStreamIndex == m_sourceAudioStreamIndex)
889 m_monitorWriter->WriteSample(0, pSample);
890
891 if (m_recording) {
892
893 if (m_firstFrame) {
894 m_timeOffset = llTimestamp;
895 m_firstFrame = false;
897 }
898
899 if (m_pauseChanging) {
900 // Recording time should not pass while paused.
901 if (m_paused)
902 m_pauseTime = llTimestamp;
903 else
904 m_timeOffset += llTimestamp - m_pauseTime;
905 m_pauseChanging = false;
906 }
907
908 // Send the video frame or audio sample to be encoded.
909 if (m_sinkWriter && !m_paused) {
910
911 pSample->SetSampleTime(llTimestamp - m_timeOffset);
912
913 if (dwStreamIndex == m_sourceVideoStreamIndex) {
914
915 m_sinkWriter->WriteSample(m_sinkVideoStreamIndex, pSample);
916
917 } else if (dwStreamIndex == m_sourceAudioStreamIndex) {
918
919 float volume = m_inputMuted ? 0.0f : float(m_inputVolume);
920
921 // Change the volume of the audio sample, if needed.
922 if (volume != 1.0f) {
923 IMFMediaBuffer *mediaBuffer = nullptr;
924 if (SUCCEEDED(pSample->ConvertToContiguousBuffer(&mediaBuffer))) {
925
926 DWORD bufLen = 0;
927 BYTE *buffer = nullptr;
928
929 if (SUCCEEDED(mediaBuffer->Lock(&buffer, nullptr, &bufLen))) {
930
931 float *floatBuffer = reinterpret_cast<float*>(buffer);
932
933 for (DWORD i = 0; i < bufLen/4; ++i)
934 floatBuffer[i] *= volume;
935
936 mediaBuffer->Unlock();
937 }
938 mediaBuffer->Release();
939 }
940 }
941
942 m_sinkWriter->WriteSample(m_sinkAudioStreamIndex, pSample);
943 }
944 m_currentDuration = (llTimestamp - m_timeOffset) / 10000;
945 }
946 }
947
948 // Generate a new QVideoFrame from IMFSample.
949 if (dwStreamIndex == m_sourceVideoStreamIndex) {
950 IMFMediaBuffer *mediaBuffer = nullptr;
951 if (SUCCEEDED(pSample->ConvertToContiguousBuffer(&mediaBuffer))) {
952
953 DWORD bufLen = 0;
954 BYTE *buffer = nullptr;
955
956 if (SUCCEEDED(mediaBuffer->Lock(&buffer, nullptr, &bufLen))) {
957 auto bytes = QByteArray(reinterpret_cast<char*>(buffer), bufLen);
958
959 QVideoFrame frame(new QMemoryVideoBuffer(bytes, m_stride),
960 QVideoFrameFormat(QSize(m_frameWidth, m_frameHeight), m_pixelFormat));
961
962 // WMF uses 100-nanosecond units, Qt uses microseconds
963 frame.setStartTime(llTimestamp * 0.1);
964
965 LONGLONG duration = -1;
966 if (SUCCEEDED(pSample->GetSampleDuration(&duration)))
967 frame.setEndTime((llTimestamp + duration) * 0.1);
968
970
971 mediaBuffer->Unlock();
972 }
973 mediaBuffer->Release();
974 }
975 }
976 }
977 // request the next video frame or sound sample
978 if (m_sourceReader)
979 m_sourceReader->ReadSample(MF_SOURCE_READER_ANY_STREAM,
980 0, nullptr, nullptr, nullptr, nullptr);
981 }
982
983 return S_OK;
984}
985
987{
988 return S_OK;
989}
990
991STDMETHODIMP QWindowsMediaDeviceReader::OnEvent(DWORD, IMFMediaEvent*)
992{
993 return S_OK;
994}
995
996//from IMFSinkWriterCallback
998{
999 QMutexLocker locker(&m_mutex);
1000 if (m_sinkWriter) {
1001 m_sinkWriter->Release();
1002 m_sinkWriter = nullptr;
1003 }
1005 m_hasFinalized.notify_one();
1006 return S_OK;
1007}
1008
1009STDMETHODIMP QWindowsMediaDeviceReader::OnMarker(DWORD, LPVOID)
1010{
1011 return S_OK;
1012}
1013
1015
1016#include "moc_qwindowsmediadevicereader_p.cpp"
The QCameraFormat class describes a video format supported by a camera device. \inmodule QtMultimedia...
Error
\qmlproperty enumeration QtMultimedia::MediaRecorder::error
The QMemoryVideoBuffer class provides a system memory allocated video data buffer.
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qobject.h:103
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
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
const ushort * utf16() const
Returns the QString as a '\0\'-terminated array of unsigned shorts.
Definition qstring.cpp:6995
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
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
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
bool wait(QMutex *, QDeadlineTimer=QDeadlineTimer(QDeadlineTimer::Forever))
void durationChanged(qint64 duration)
bool activate(const QString &cameraId, const QCameraFormat &cameraFormat, const QString &microphoneId)
void videoFrameChanged(const QVideoFrame &frame)
bool setAudioOutput(const QString &audioOutputId)
QWindowsMediaDeviceReader(QObject *parent=nullptr)
QMediaRecorder::Error startRecording(const QString &fileName, const GUID &container, const GUID &videoFormat, UINT32 videoBitRate, UINT32 width, UINT32 height, qreal frameRate, const GUID &audioFormat, UINT32 audioBitRate)
STDMETHODIMP OnFlush(DWORD dwStreamIndex) override
STDMETHODIMP OnFinalize(HRESULT hrStatus) override
STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample) override
void streamingError(int errorCode)
STDMETHODIMP OnMarker(DWORD dwStreamIndex, LPVOID pvContext) override
STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) override
STDMETHODIMP OnEvent(DWORD dwStreamIndex, IMFMediaEvent *pEvent) override
Combined button and popup list for selecting options.
Q_MULTIMEDIA_EXPORT QVideoFrameFormat::PixelFormat pixelFormatFromMediaSubtype(const GUID &subtype)
@ QueuedConnection
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
static int area(const QSize &s)
Definition qicon.cpp:153
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
GLint GLsizei GLsizei height
GLuint index
[2]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLenum GLuint buffer
GLint GLsizei width
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
GLuint num
#define t2
#define t1
#define emit
double qreal
Definition qtypes.h:187
IUIViewSettingsInterop __RPC__in REFIID riid
long HRESULT
STDMETHODIMP_(ULONG) QWindowsMediaDeviceReader
const GUID QMM_MF_TRANSCODE_CONTAINERTYPE
const GUID QMM_MR_POLICY_VOLUME_SERVICE
const GUID QMM_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
const GUID QMM_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID
HRESULT WINAPI MFCreateDeviceSource(IMFAttributes *pAttributes, IMFMediaSource **ppSource)
QFrame frame
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...