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 && QtPrivate::fuzzyCompare(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 UINT32 nominalRange = 0;
225
226 if (SUCCEEDED(m_videoMediaType->GetUINT32(MF_MT_VIDEO_NOMINAL_RANGE, &nominalRange)))
227 m_colorRange = QWindowsMultimediaUtils::colorRangeFromNominalRange(nominalRange);
228
229 UINT32 yuvMatrix = 0;
230
231 if (SUCCEEDED(m_videoMediaType->GetUINT32(MF_MT_YUV_MATRIX, &yuvMatrix))) {
232 m_colorSpace = QWindowsMultimediaUtils::colorSpaceFromMatrix(yuvMatrix);
233 }
234 }
235 }
236 }
237 }
238 }
239 }
240
241 return hr;
242}
243
244HRESULT QWindowsMediaDeviceReader::initAudioType(IMFMediaType *mediaType, UINT32 channels, UINT32 samplesPerSec, bool flt)
245{
246 if (!mediaType)
247 return E_INVALIDARG;
248
249 HRESULT hr = mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
250 if (SUCCEEDED(hr)) {
251 hr = mediaType->SetGUID(MF_MT_SUBTYPE, flt ? MFAudioFormat_Float : MFAudioFormat_PCM);
252 if (SUCCEEDED(hr)) {
253 hr = mediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels);
254 if (SUCCEEDED(hr)) {
255 hr = mediaType->SetUINT32(MF_MT_AUDIO_CHANNEL_MASK,
256 (channels == 1) ? SPEAKER_FRONT_CENTER : (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT ));
257 if (SUCCEEDED(hr)) {
258 hr = mediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, samplesPerSec);
259 if (SUCCEEDED(hr)) {
260 UINT32 bitsPerSample = flt ? 32 : 16;
261 UINT32 bytesPerFrame = channels * bitsPerSample/8;
262 hr = mediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample);
263 if (SUCCEEDED(hr)) {
264 hr = mediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, bytesPerFrame);
265 if (SUCCEEDED(hr)) {
266 hr = mediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerFrame * samplesPerSec);
267 }
268 }
269 }
270 }
271 }
272 }
273 }
274
275 return hr;
276}
277
278// Prepares the source audio stream.
279HRESULT QWindowsMediaDeviceReader::prepareAudioStream()
280{
281 if (!m_sourceReader)
282 return E_FAIL;
283
284 if (!m_audioSource)
285 return S_OK; // It may be video-only
286
287 HRESULT hr = m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM),
288 &m_audioMediaType);
289 if (SUCCEEDED(hr)) {
290 hr = initAudioType(m_audioMediaType, 2, 48000, true);
291 if (SUCCEEDED(hr)) {
292 hr = m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM),
293 nullptr, m_audioMediaType);
294 if (SUCCEEDED(hr)) {
295 hr = m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE);
296 }
297 }
298 }
299 return hr;
300}
301
302// Retrieves the indexes for selected video/audio streams.
303HRESULT QWindowsMediaDeviceReader::initSourceIndexes()
304{
305 if (!m_sourceReader)
306 return E_FAIL;
307
308 m_sourceVideoStreamIndex = MF_SOURCE_READER_INVALID_STREAM_INDEX;
309 m_sourceAudioStreamIndex = MF_SOURCE_READER_INVALID_STREAM_INDEX;
310
311 DWORD index = 0;
312 BOOL selected = FALSE;
313
314 while (m_sourceReader->GetStreamSelection(index, &selected) == S_OK) {
315 if (selected) {
316 IMFMediaType *mediaType = nullptr;
317 if (SUCCEEDED(m_sourceReader->GetCurrentMediaType(index, &mediaType))) {
318 GUID majorType = GUID_NULL;
319 if (SUCCEEDED(mediaType->GetGUID(MF_MT_MAJOR_TYPE, &majorType))) {
320 if (majorType == MFMediaType_Video)
321 m_sourceVideoStreamIndex = index;
322 else if (majorType == MFMediaType_Audio)
323 m_sourceAudioStreamIndex = index;
324 }
325 mediaType->Release();
326 }
327 }
328 ++index;
329 }
330 if ((m_videoSource && m_sourceVideoStreamIndex == MF_SOURCE_READER_INVALID_STREAM_INDEX) ||
331 (m_audioSource && m_sourceAudioStreamIndex == MF_SOURCE_READER_INVALID_STREAM_INDEX))
332 return E_FAIL;
333 return S_OK;
334}
335
336bool QWindowsMediaDeviceReader::setAudioOutput(const QString &audioOutputId)
337{
338 QMutexLocker locker(&m_mutex);
339
340 stopMonitoring();
341
342 m_audioOutputId = audioOutputId;
343
344 if (!m_active || m_audioOutputId.isEmpty())
345 return true;
346
347 HRESULT hr = startMonitoring();
348
349 return SUCCEEDED(hr);
350}
351
352HRESULT QWindowsMediaDeviceReader::startMonitoring()
353{
354 if (m_audioOutputId.isEmpty())
355 return E_FAIL;
356
357 IMFAttributes *sinkAttributes = nullptr;
358
359 HRESULT hr = MFCreateAttributes(&sinkAttributes, 1);
360 if (SUCCEEDED(hr)) {
361
362 hr = sinkAttributes->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID,
363 reinterpret_cast<LPCWSTR>(m_audioOutputId.utf16()));
364 if (SUCCEEDED(hr)) {
365
366 IMFMediaSink *mediaSink = nullptr;
367 hr = MFCreateAudioRenderer(sinkAttributes, &mediaSink);
368 if (SUCCEEDED(hr)) {
369
370 IMFStreamSink *streamSink = nullptr;
371 hr = mediaSink->GetStreamSinkByIndex(0, &streamSink);
372 if (SUCCEEDED(hr)) {
373
374 IMFMediaTypeHandler *typeHandler = nullptr;
375 hr = streamSink->GetMediaTypeHandler(&typeHandler);
376 if (SUCCEEDED(hr)) {
377
378 hr = typeHandler->IsMediaTypeSupported(m_audioMediaType, nullptr);
379 if (SUCCEEDED(hr)) {
380
381 hr = typeHandler->SetCurrentMediaType(m_audioMediaType);
382 if (SUCCEEDED(hr)) {
383
384 IMFAttributes *writerAttributes = nullptr;
385
386 HRESULT hr = MFCreateAttributes(&writerAttributes, 1);
387 if (SUCCEEDED(hr)) {
388
389 hr = writerAttributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, TRUE);
390 if (SUCCEEDED(hr)) {
391
392 IMFSinkWriter *sinkWriter = nullptr;
393 hr = MFCreateSinkWriterFromMediaSink(mediaSink, writerAttributes, &sinkWriter);
394 if (SUCCEEDED(hr)) {
395
396 hr = sinkWriter->SetInputMediaType(0, m_audioMediaType, nullptr);
397 if (SUCCEEDED(hr)) {
398
399 IMFSimpleAudioVolume *audioVolume = nullptr;
400
401 if (SUCCEEDED(MFGetService(
402 mediaSink, MR_POLICY_VOLUME_SERVICE,
403 IID_PPV_ARGS(&audioVolume)))) {
404 audioVolume->SetMasterVolume(float(m_outputVolume));
405 audioVolume->SetMute(m_outputMuted);
406 audioVolume->Release();
407 }
408
409 hr = sinkWriter->BeginWriting();
410 if (SUCCEEDED(hr)) {
411 m_monitorSink = mediaSink;
412 m_monitorSink->AddRef();
413 m_monitorWriter = sinkWriter;
414 m_monitorWriter->AddRef();
415 }
416 }
417 sinkWriter->Release();
418 }
419 }
420 writerAttributes->Release();
421 }
422 }
423 }
424 typeHandler->Release();
425 }
426 streamSink->Release();
427 }
428 mediaSink->Release();
429 }
430 }
431 sinkAttributes->Release();
432 }
433
434 return hr;
435}
436
437void QWindowsMediaDeviceReader::stopMonitoring()
438{
439 if (m_monitorWriter) {
440 m_monitorWriter->Release();
441 m_monitorWriter = nullptr;
442 }
443 if (m_monitorSink) {
444 m_monitorSink->Shutdown();
445 m_monitorSink->Release();
446 m_monitorSink = nullptr;
447 }
448}
449
450// Activates the requested camera/microphone for streaming.
451// One of the IDs may be empty for video-only/audio-only.
452bool QWindowsMediaDeviceReader::activate(const QString &cameraId,
453 const QCameraFormat &cameraFormat,
454 const QString &microphoneId)
455{
456 QMutexLocker locker(&m_mutex);
457
458 if (cameraId.isEmpty() && microphoneId.isEmpty())
459 return false;
460
461 stopMonitoring();
462 releaseResources();
463
464 m_active = false;
465 m_streaming = false;
466
467 if (!cameraId.isEmpty()) {
468 if (!SUCCEEDED(createSource(cameraId, true, &m_videoSource))) {
469 releaseResources();
470 return false;
471 }
472 }
473
474 if (!microphoneId.isEmpty()) {
475 if (!SUCCEEDED(createSource(microphoneId, false, &m_audioSource))) {
476 releaseResources();
477 return false;
478 }
479 }
480
481 if (!SUCCEEDED(createAggregateReader(m_videoSource, m_audioSource, &m_aggregateSource, &m_sourceReader))) {
482 releaseResources();
483 return false;
484 }
485
486 DWORD mediaTypeIndex = findMediaTypeIndex(cameraFormat);
487
488 if (!SUCCEEDED(prepareVideoStream(mediaTypeIndex))) {
489 releaseResources();
490 return false;
491 }
492
493 if (!SUCCEEDED(prepareAudioStream())) {
494 releaseResources();
495 return false;
496 }
497
498 if (!SUCCEEDED(initSourceIndexes())) {
499 releaseResources();
500 return false;
501 }
502
503 updateSinkInputMediaTypes();
504 startMonitoring();
505
506 // Request the first frame or audio sample.
507 if (!SUCCEEDED(m_sourceReader->ReadSample(MF_SOURCE_READER_ANY_STREAM, 0, nullptr, nullptr, nullptr, nullptr))) {
508 releaseResources();
509 return false;
510 }
511
512 m_active = true;
513 return true;
514}
515
517{
518 stopMonitoring();
519 stopStreaming();
520 m_active = false;
521 m_streaming = false;
522}
523
524void QWindowsMediaDeviceReader::stopStreaming()
525{
526 QMutexLocker locker(&m_mutex);
527 releaseResources();
528}
529
530// Releases allocated streaming stuff.
531void QWindowsMediaDeviceReader::releaseResources()
532{
533 if (m_videoMediaType) {
534 m_videoMediaType->Release();
535 m_videoMediaType = nullptr;
536 }
537 if (m_audioMediaType) {
538 m_audioMediaType->Release();
539 m_audioMediaType = nullptr;
540 }
541 if (m_sourceReader) {
542 m_sourceReader->Release();
543 m_sourceReader = nullptr;
544 }
545 if (m_aggregateSource) {
546 m_aggregateSource->Release();
547 m_aggregateSource = nullptr;
548 }
549 if (m_videoSource) {
550 m_videoSource->Release();
551 m_videoSource = nullptr;
552 }
553 if (m_audioSource) {
554 m_audioSource->Release();
555 m_audioSource = nullptr;
556 }
557}
558
559HRESULT QWindowsMediaDeviceReader::createVideoMediaType(const GUID &format, UINT32 bitRate, UINT32 width,
560 UINT32 height, qreal frameRate, IMFMediaType **mediaType)
561{
562 if (!mediaType)
563 return E_INVALIDARG;
564
565 *mediaType = nullptr;
566 IMFMediaType *targetMediaType = nullptr;
567
568 if (SUCCEEDED(MFCreateMediaType(&targetMediaType))) {
569
570 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video))) {
571
572 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_SUBTYPE, format))) {
573
574 if (SUCCEEDED(targetMediaType->SetUINT32(MF_MT_AVG_BITRATE, bitRate))) {
575
576 if (SUCCEEDED(MFSetAttributeSize(targetMediaType, MF_MT_FRAME_SIZE, width, height))) {
577
578 if (SUCCEEDED(MFSetAttributeRatio(targetMediaType, MF_MT_FRAME_RATE,
579 UINT32(frameRate * 1000), 1000))) {
580 UINT32 t1, t2;
581 if (SUCCEEDED(MFGetAttributeRatio(m_videoMediaType, MF_MT_PIXEL_ASPECT_RATIO, &t1, &t2))) {
582
583 if (SUCCEEDED(MFSetAttributeRatio(targetMediaType, MF_MT_PIXEL_ASPECT_RATIO, t1, t2))) {
584
585 if (SUCCEEDED(m_videoMediaType->GetUINT32(MF_MT_INTERLACE_MODE, &t1))) {
586
587 if (SUCCEEDED(targetMediaType->SetUINT32(MF_MT_INTERLACE_MODE, t1))) {
588
589 *mediaType = targetMediaType;
590 return S_OK;
591 }
592 }
593 }
594 }
595 }
596 }
597 }
598 }
599 }
600 targetMediaType->Release();
601 }
602 return E_FAIL;
603}
604
605HRESULT QWindowsMediaDeviceReader::createAudioMediaType(const GUID &format, UINT32 bitRate, IMFMediaType **mediaType)
606{
607 if (!mediaType)
608 return E_INVALIDARG;
609
610 *mediaType = nullptr;
611 IMFMediaType *targetMediaType = nullptr;
612
613 if (SUCCEEDED(MFCreateMediaType(&targetMediaType))) {
614
615 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio))) {
616
617 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_SUBTYPE, format))) {
618
619 if (bitRate == 0 || SUCCEEDED(targetMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bitRate / 8))) {
620
621 *mediaType = targetMediaType;
622 return S_OK;
623 }
624 }
625 }
626 targetMediaType->Release();
627 }
628 return E_FAIL;
629}
630
631HRESULT QWindowsMediaDeviceReader::updateSinkInputMediaTypes()
632{
633 HRESULT hr = S_OK;
634 if (m_sinkWriter) {
635 if (m_videoSource && m_videoMediaType && m_sinkVideoStreamIndex != MF_SINK_WRITER_INVALID_STREAM_INDEX) {
636 hr = m_sinkWriter->SetInputMediaType(m_sinkVideoStreamIndex, m_videoMediaType, nullptr);
637 }
638 if (SUCCEEDED(hr)) {
639 if (m_audioSource && m_audioMediaType && m_sinkAudioStreamIndex != MF_SINK_WRITER_INVALID_STREAM_INDEX) {
640 hr = m_sinkWriter->SetInputMediaType(m_sinkAudioStreamIndex, m_audioMediaType, nullptr);
641 }
642 }
643 }
644 return hr;
645}
646
647QMediaRecorder::Error QWindowsMediaDeviceReader::startRecording(
648 const QString &fileName, const GUID &container, const GUID &videoFormat, UINT32 videoBitRate,
649 UINT32 width, UINT32 height, qreal frameRate, const GUID &audioFormat, UINT32 audioBitRate)
650{
651 QMutexLocker locker(&m_mutex);
652
653 if (!m_active || m_recording || (videoFormat == GUID_NULL && audioFormat == GUID_NULL))
654 return QMediaRecorder::ResourceError;
655
656 ComPtr<IMFAttributes> writerAttributes;
657
658 HRESULT hr = MFCreateAttributes(writerAttributes.GetAddressOf(), 2);
659 if (FAILED(hr))
660 return QMediaRecorder::ResourceError;
661
662 // Set callback so OnFinalize() is called after video is saved.
663 hr = writerAttributes->SetUnknown(MF_SINK_WRITER_ASYNC_CALLBACK,
664 static_cast<IMFSinkWriterCallback*>(this));
665 if (FAILED(hr))
666 return QMediaRecorder::ResourceError;
667
668 hr = writerAttributes->SetGUID(MF_TRANSCODE_CONTAINERTYPE, container);
669 if (FAILED(hr))
670 return QMediaRecorder::ResourceError;
671
672 ComPtr<IMFSinkWriter> sinkWriter;
673 hr = MFCreateSinkWriterFromURL(reinterpret_cast<LPCWSTR>(fileName.utf16()),
674 nullptr, writerAttributes.Get(), sinkWriter.GetAddressOf());
675 if (FAILED(hr))
676 return QMediaRecorder::LocationNotWritable;
677
678 m_sinkVideoStreamIndex = MF_SINK_WRITER_INVALID_STREAM_INDEX;
679 m_sinkAudioStreamIndex = MF_SINK_WRITER_INVALID_STREAM_INDEX;
680
681 if (m_videoSource && videoFormat != GUID_NULL) {
682 IMFMediaType *targetMediaType = nullptr;
683
684 hr = createVideoMediaType(videoFormat, videoBitRate, width, height, frameRate, &targetMediaType);
685 if (SUCCEEDED(hr)) {
686
687 hr = sinkWriter->AddStream(targetMediaType, &m_sinkVideoStreamIndex);
688 if (SUCCEEDED(hr)) {
689
690 hr = sinkWriter->SetInputMediaType(m_sinkVideoStreamIndex, m_videoMediaType, nullptr);
691 }
692 targetMediaType->Release();
693 }
694 }
695
696 if (SUCCEEDED(hr)) {
697 if (m_audioSource && audioFormat != GUID_NULL) {
698 IMFMediaType *targetMediaType = nullptr;
699
700 hr = createAudioMediaType(audioFormat, audioBitRate, &targetMediaType);
701 if (SUCCEEDED(hr)) {
702
703 hr = sinkWriter->AddStream(targetMediaType, &m_sinkAudioStreamIndex);
704 if (SUCCEEDED(hr)) {
705
706 hr = sinkWriter->SetInputMediaType(m_sinkAudioStreamIndex, m_audioMediaType, nullptr);
707 }
708 targetMediaType->Release();
709 }
710 }
711 }
712
713 if (FAILED(hr))
714 return QMediaRecorder::FormatError;
715
716 hr = sinkWriter->BeginWriting();
717 if (FAILED(hr))
718 return QMediaRecorder::ResourceError;
719
720 m_sinkWriter = sinkWriter.Detach();
721 m_lastDuration = -1;
722 m_currentDuration = 0;
723 updateDuration();
724 m_durationTimer.start();
725 m_recording = true;
726 m_firstFrame = true;
727 m_paused = false;
728 m_pauseChanging = false;
729
730 return QMediaRecorder::NoError;
731}
732
734{
735 QMutexLocker locker(&m_mutex);
736
737 if (m_sinkWriter && m_recording) {
738
739 HRESULT hr = m_sinkWriter->Finalize();
740
741 if (SUCCEEDED(hr)) {
742 m_hasFinalized.wait(&m_mutex);
743 } else {
744 m_sinkWriter->Release();
745 m_sinkWriter = nullptr;
746
747 QMetaObject::invokeMethod(this, "recordingError",
748 Qt::QueuedConnection, Q_ARG(int, hr));
749 }
750 }
751
752 m_recording = false;
753 m_paused = false;
754 m_pauseChanging = false;
755
756 m_durationTimer.stop();
757 m_lastDuration = -1;
758 m_currentDuration = -1;
759}
760
762{
763 if (!m_recording || m_paused)
764 return false;
765 m_pauseTime = m_lastTimestamp;
766 m_paused = true;
767 m_pauseChanging = true;
768 return true;
769}
770
772{
773 if (!m_recording || !m_paused)
774 return false;
775 m_paused = false;
776 m_pauseChanging = true;
777 return true;
778}
779
780//from IUnknown
782{
783 if (!ppvObject)
784 return E_POINTER;
785 if (riid == IID_IMFSourceReaderCallback) {
786 *ppvObject = static_cast<IMFSourceReaderCallback*>(this);
787 } else if (riid == IID_IMFSinkWriterCallback) {
788 *ppvObject = static_cast<IMFSinkWriterCallback*>(this);
789 } else if (riid == IID_IUnknown) {
790 *ppvObject = static_cast<IUnknown*>(static_cast<IMFSourceReaderCallback*>(this));
791 } else {
792 *ppvObject = nullptr;
793 return E_NOINTERFACE;
794 }
795 AddRef();
796 return S_OK;
797}
798
799STDMETHODIMP_(ULONG) QWindowsMediaDeviceReader::AddRef(void)
800{
801 return InterlockedIncrement(&m_cRef);
802}
803
804STDMETHODIMP_(ULONG) QWindowsMediaDeviceReader::Release(void)
805{
806 LONG cRef = InterlockedDecrement(&m_cRef);
807 if (cRef == 0) {
808 this->deleteLater();
809 }
810 return cRef;
811}
812
814{
815 return m_frameWidth;
816}
817
819{
820 return m_frameHeight;
821}
822
824{
825 return m_frameRate;
826}
827
829{
830 m_inputMuted = muted;
831}
832
834{
835 m_inputVolume = qBound(0.0, volume, 1.0);
836}
837
839{
840 QMutexLocker locker(&m_mutex);
841
842 m_outputMuted = muted;
843
844 if (m_active && m_monitorSink) {
845 IMFSimpleAudioVolume *audioVolume = nullptr;
846 if (SUCCEEDED(MFGetService(m_monitorSink, MR_POLICY_VOLUME_SERVICE,
847 IID_PPV_ARGS(&audioVolume)))) {
848 audioVolume->SetMute(m_outputMuted);
849 audioVolume->Release();
850 }
851 }
852}
853
855{
856 QMutexLocker locker(&m_mutex);
857
858 m_outputVolume = qBound(0.0, volume, 1.0);
859
860 if (m_active && m_monitorSink) {
861 IMFSimpleAudioVolume *audioVolume = nullptr;
862 if (SUCCEEDED(MFGetService(m_monitorSink, MR_POLICY_VOLUME_SERVICE,
863 IID_PPV_ARGS(&audioVolume)))) {
864 audioVolume->SetMasterVolume(float(m_outputVolume));
865 audioVolume->Release();
866 }
867 }
868}
869
870void QWindowsMediaDeviceReader::updateDuration()
871{
872 if (m_currentDuration >= 0 && m_lastDuration != m_currentDuration) {
873 m_lastDuration = m_currentDuration;
874 emit durationChanged(m_currentDuration);
875 }
876}
877
878//from IMFSourceReaderCallback
879STDMETHODIMP QWindowsMediaDeviceReader::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
880 DWORD dwStreamFlags, LONGLONG llTimestamp,
881 IMFSample *pSample)
882{
883 QMutexLocker locker(&m_mutex);
884
885 if (FAILED(hrStatus)) {
886 emit streamingError(int(hrStatus));
887 return hrStatus;
888 }
889
890 m_lastTimestamp = llTimestamp;
891
892 if ((dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) {
893 m_streaming = false;
894 emit streamingStopped();
895 } else {
896
897 if (!m_streaming) {
898 m_streaming = true;
899 emit streamingStarted();
900 }
901 if (pSample) {
902
903 if (m_monitorWriter && dwStreamIndex == m_sourceAudioStreamIndex)
904 m_monitorWriter->WriteSample(0, pSample);
905
906 if (m_recording) {
907
908 if (m_firstFrame) {
909 m_timeOffset = llTimestamp;
910 m_firstFrame = false;
911 emit recordingStarted();
912 }
913
914 if (m_pauseChanging) {
915 // Recording time should not pass while paused.
916 if (m_paused)
917 m_pauseTime = llTimestamp;
918 else
919 m_timeOffset += llTimestamp - m_pauseTime;
920 m_pauseChanging = false;
921 }
922
923 // Send the video frame or audio sample to be encoded.
924 if (m_sinkWriter && !m_paused) {
925
926 pSample->SetSampleTime(llTimestamp - m_timeOffset);
927
928 if (dwStreamIndex == m_sourceVideoStreamIndex) {
929
930 m_sinkWriter->WriteSample(m_sinkVideoStreamIndex, pSample);
931
932 } else if (dwStreamIndex == m_sourceAudioStreamIndex) {
933
934 float volume = m_inputMuted ? 0.0f : float(m_inputVolume);
935
936 // Change the volume of the audio sample, if needed.
937 if (volume != 1.0f) {
938 IMFMediaBuffer *mediaBuffer = nullptr;
939 if (SUCCEEDED(pSample->ConvertToContiguousBuffer(&mediaBuffer))) {
940
941 DWORD bufLen = 0;
942 BYTE *buffer = nullptr;
943
944 if (SUCCEEDED(mediaBuffer->Lock(&buffer, nullptr, &bufLen))) {
945
946 float *floatBuffer = reinterpret_cast<float*>(buffer);
947
948 for (DWORD i = 0; i < bufLen/4; ++i)
949 floatBuffer[i] *= volume;
950
951 mediaBuffer->Unlock();
952 }
953 mediaBuffer->Release();
954 }
955 }
956
957 m_sinkWriter->WriteSample(m_sinkAudioStreamIndex, pSample);
958 }
959 m_currentDuration = (llTimestamp - m_timeOffset) / 10000;
960 }
961 }
962
963 // Generate a new QVideoFrame from IMFSample.
964 if (dwStreamIndex == m_sourceVideoStreamIndex) {
965 IMFMediaBuffer *mediaBuffer = nullptr;
966 if (SUCCEEDED(pSample->ConvertToContiguousBuffer(&mediaBuffer))) {
967
968 DWORD bufLen = 0;
969 BYTE *buffer = nullptr;
970
971 if (SUCCEEDED(mediaBuffer->Lock(&buffer, nullptr, &bufLen))) {
972 auto bytes = QByteArray(reinterpret_cast<char*>(buffer), bufLen);
973 QVideoFrameFormat format(QSize(m_frameWidth, m_frameHeight), m_pixelFormat);
974 format.setColorRange(m_colorRange);
975 format.setColorSpace(m_colorSpace);
976
977 QVideoFrame frame = QVideoFramePrivate::createFrame(
978 std::make_unique<QMemoryVideoBuffer>(std::move(bytes), m_stride),
979 std::move(format));
980
981 // WMF uses 100-nanosecond units, Qt uses microseconds
982 frame.setStartTime(llTimestamp * 0.1);
983
984 LONGLONG duration = -1;
985 if (SUCCEEDED(pSample->GetSampleDuration(&duration)))
986 frame.setEndTime((llTimestamp + duration) * 0.1);
987
988 emit videoFrameChanged(frame);
989
990 mediaBuffer->Unlock();
991 }
992 mediaBuffer->Release();
993 }
994 }
995 }
996 // request the next video frame or sound sample
997 if (m_sourceReader)
998 m_sourceReader->ReadSample(MF_SOURCE_READER_ANY_STREAM,
999 0, nullptr, nullptr, nullptr, nullptr);
1000 }
1001
1002 return S_OK;
1003}
1004
1006{
1007 return S_OK;
1008}
1009
1011{
1012 return S_OK;
1013}
1014
1015//from IMFSinkWriterCallback
1017{
1018 QMutexLocker locker(&m_mutex);
1019 if (m_sinkWriter) {
1020 m_sinkWriter->Release();
1021 m_sinkWriter = nullptr;
1022 }
1023 emit recordingStopped();
1024 m_hasFinalized.notify_one();
1025 return S_OK;
1026}
1027
1032
1033QT_END_NAMESPACE
1034
1035#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
Combined button and popup list for selecting options.
STDMETHODIMP_(ULONG) QWindowsMediaDeviceReader