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