6#include "private/qwindowsmultimediautils_p.h"
10#include <private/qmemoryvideobuffer_p.h>
11#include <private/qwindowsmfdefs_p.h>
12#include <private/qcomptr_p.h>
13#include <QtCore/qdebug.h>
15#include <mmdeviceapi.h>
35HRESULT QWindowsMediaDeviceReader::createSource(
const QString &deviceId,
bool video, IMFMediaSource **
source)
41 IMFAttributes *sourceAttributes =
nullptr;
43 HRESULT hr = MFCreateAttributes(&sourceAttributes, 2);
46 hr = sourceAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
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()));
59 sourceAttributes->Release();
67HRESULT QWindowsMediaDeviceReader::createAggregateReader(IMFMediaSource *firstSource,
68 IMFMediaSource *secondSource,
69 IMFMediaSource **aggregateSource,
70 IMFSourceReader **sourceReader)
72 if ((!firstSource && !secondSource) || !aggregateSource || !sourceReader)
75 *aggregateSource =
nullptr;
76 *sourceReader =
nullptr;
78 IMFCollection *sourceCollection =
nullptr;
80 HRESULT hr = MFCreateCollection(&sourceCollection);
84 sourceCollection->AddElement(firstSource);
87 sourceCollection->AddElement(secondSource);
89 hr = MFCreateAggregateSource(sourceCollection, aggregateSource);
92 IMFAttributes *readerAttributes =
nullptr;
94 hr = MFCreateAttributes(&readerAttributes, 1);
98 hr = readerAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK,
99 static_cast<IMFSourceReaderCallback*
>(
this));
102 hr = MFCreateSourceReaderFromMediaSource(*aggregateSource, readerAttributes, sourceReader);
104 readerAttributes->Release();
107 sourceCollection->Release();
114DWORD QWindowsMediaDeviceReader::findMediaTypeIndex(
const QCameraFormat &reqFormat)
118 if (m_sourceReader && m_videoSource) {
121 IMFMediaType *mediaType =
nullptr;
124 float currFrameRate = 0.0f;
126 while (SUCCEEDED(m_sourceReader->GetNativeMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
127 index, &mediaType))) {
129 GUID subtype = GUID_NULL;
130 if (SUCCEEDED(mediaType->GetGUID(MF_MT_SUBTYPE, &subtype))) {
136 if (SUCCEEDED(MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, &
width, &
height))) {
139 if (SUCCEEDED(MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, &
num, &den))) {
149 mediaType->Release();
153 if ((currFrameRate < 29.9 && currFrameRate <
frameRate) ||
163 mediaType->Release();
173HRESULT QWindowsMediaDeviceReader::prepareVideoStream(DWORD mediaTypeIndex)
184 hr = m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
187 hr = m_sourceReader->GetNativeMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
188 mediaTypeIndex, &m_videoMediaType);
190 hr = m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
191 nullptr, m_videoMediaType);
196 GUID subtype = GUID_NULL;
197 hr = m_videoMediaType->GetGUID(MF_MT_SUBTYPE, &subtype);
207 hr = MFGetAttributeSize(m_videoMediaType, MF_MT_FRAME_SIZE, &m_frameWidth, &m_frameHeight);
211 hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, m_frameWidth, &m_stride);
213 m_stride =
qAbs(m_stride);
214 UINT32 frameRateNum, frameRateDen;
215 hr = MFGetAttributeRatio(m_videoMediaType, MF_MT_FRAME_RATE, &frameRateNum, &frameRateDen);
218 m_frameRate =
qreal(frameRateNum) / frameRateDen;
220 hr = m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_VIDEO_STREAM), TRUE);
231HRESULT QWindowsMediaDeviceReader::initAudioType(IMFMediaType *mediaType, UINT32 channels, UINT32 samplesPerSec,
bool flt)
236 HRESULT hr = mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
238 hr = mediaType->SetGUID(MF_MT_SUBTYPE, flt ? MFAudioFormat_Float : MFAudioFormat_PCM);
240 hr = mediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels);
242 hr = mediaType->SetUINT32(MF_MT_AUDIO_CHANNEL_MASK,
243 (channels == 1) ? SPEAKER_FRONT_CENTER : (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT ));
245 hr = mediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, samplesPerSec);
247 UINT32 bitsPerSample = flt ? 32 : 16;
248 UINT32 bytesPerFrame = channels * bitsPerSample/8;
249 hr = mediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample);
251 hr = mediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, bytesPerFrame);
253 hr = mediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerFrame * samplesPerSec);
266HRESULT QWindowsMediaDeviceReader::prepareAudioStream()
274 HRESULT hr = m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM),
277 hr = initAudioType(m_audioMediaType, 2, 48000,
true);
279 hr = m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM),
280 nullptr, m_audioMediaType);
282 hr = m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE);
290HRESULT QWindowsMediaDeviceReader::initSourceIndexes()
295 m_sourceVideoStreamIndex = MF_SOURCE_READER_INVALID_STREAM_INDEX;
296 m_sourceAudioStreamIndex = MF_SOURCE_READER_INVALID_STREAM_INDEX;
299 BOOL selected = FALSE;
301 while (m_sourceReader->GetStreamSelection(
index, &selected) == S_OK) {
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;
312 mediaType->Release();
317 if ((m_videoSource && m_sourceVideoStreamIndex == MF_SOURCE_READER_INVALID_STREAM_INDEX) ||
318 (m_audioSource && m_sourceAudioStreamIndex == MF_SOURCE_READER_INVALID_STREAM_INDEX))
329 m_audioOutputId = audioOutputId;
331 if (!m_active || m_audioOutputId.
isEmpty())
334 HRESULT hr = startMonitoring();
336 return SUCCEEDED(hr);
339HRESULT QWindowsMediaDeviceReader::startMonitoring()
344 IMFAttributes *sinkAttributes =
nullptr;
346 HRESULT hr = MFCreateAttributes(&sinkAttributes, 1);
349 hr = sinkAttributes->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID,
350 reinterpret_cast<LPCWSTR
>(m_audioOutputId.
utf16()));
353 IMFMediaSink *mediaSink =
nullptr;
354 hr = MFCreateAudioRenderer(sinkAttributes, &mediaSink);
357 IMFStreamSink *streamSink =
nullptr;
358 hr = mediaSink->GetStreamSinkByIndex(0, &streamSink);
361 IMFMediaTypeHandler *typeHandler =
nullptr;
362 hr = streamSink->GetMediaTypeHandler(&typeHandler);
365 hr = typeHandler->IsMediaTypeSupported(m_audioMediaType,
nullptr);
368 hr = typeHandler->SetCurrentMediaType(m_audioMediaType);
371 IMFAttributes *writerAttributes =
nullptr;
373 HRESULT hr = MFCreateAttributes(&writerAttributes, 1);
376 hr = writerAttributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, TRUE);
379 IMFSinkWriter *sinkWriter =
nullptr;
380 hr = MFCreateSinkWriterFromMediaSink(mediaSink, writerAttributes, &sinkWriter);
383 hr = sinkWriter->SetInputMediaType(0, m_audioMediaType,
nullptr);
386 IMFSimpleAudioVolume *audioVolume =
nullptr;
389 audioVolume->SetMasterVolume(
float(m_outputVolume));
390 audioVolume->SetMute(m_outputMuted);
391 audioVolume->Release();
394 hr = sinkWriter->BeginWriting();
396 m_monitorSink = mediaSink;
397 m_monitorSink->AddRef();
398 m_monitorWriter = sinkWriter;
399 m_monitorWriter->AddRef();
402 sinkWriter->Release();
405 writerAttributes->Release();
409 typeHandler->Release();
411 streamSink->Release();
413 mediaSink->Release();
416 sinkAttributes->Release();
422void QWindowsMediaDeviceReader::stopMonitoring()
424 if (m_monitorWriter) {
425 m_monitorWriter->Release();
426 m_monitorWriter =
nullptr;
429 m_monitorSink->Shutdown();
430 m_monitorSink->Release();
431 m_monitorSink =
nullptr;
453 if (!SUCCEEDED(createSource(cameraId,
true, &m_videoSource))) {
460 if (!SUCCEEDED(createSource(microphoneId,
false, &m_audioSource))) {
466 if (!SUCCEEDED(createAggregateReader(m_videoSource, m_audioSource, &m_aggregateSource, &m_sourceReader))) {
471 DWORD mediaTypeIndex = findMediaTypeIndex(cameraFormat);
473 if (!SUCCEEDED(prepareVideoStream(mediaTypeIndex))) {
478 if (!SUCCEEDED(prepareAudioStream())) {
483 if (!SUCCEEDED(initSourceIndexes())) {
488 updateSinkInputMediaTypes();
492 if (!SUCCEEDED(m_sourceReader->ReadSample(MF_SOURCE_READER_ANY_STREAM, 0,
nullptr,
nullptr,
nullptr,
nullptr))) {
509void QWindowsMediaDeviceReader::stopStreaming()
516void QWindowsMediaDeviceReader::releaseResources()
518 if (m_videoMediaType) {
519 m_videoMediaType->Release();
520 m_videoMediaType =
nullptr;
522 if (m_audioMediaType) {
523 m_audioMediaType->Release();
524 m_audioMediaType =
nullptr;
526 if (m_sourceReader) {
527 m_sourceReader->Release();
528 m_sourceReader =
nullptr;
530 if (m_aggregateSource) {
531 m_aggregateSource->Release();
532 m_aggregateSource =
nullptr;
535 m_videoSource->Release();
536 m_videoSource =
nullptr;
539 m_audioSource->Release();
540 m_audioSource =
nullptr;
544HRESULT QWindowsMediaDeviceReader::createVideoMediaType(
const GUID &
format, UINT32 bitRate, UINT32
width,
550 *mediaType =
nullptr;
551 IMFMediaType *targetMediaType =
nullptr;
553 if (SUCCEEDED(MFCreateMediaType(&targetMediaType))) {
555 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video))) {
557 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_SUBTYPE,
format))) {
559 if (SUCCEEDED(targetMediaType->SetUINT32(MF_MT_AVG_BITRATE, bitRate))) {
561 if (SUCCEEDED(MFSetAttributeSize(targetMediaType, MF_MT_FRAME_SIZE,
width,
height))) {
563 if (SUCCEEDED(MFSetAttributeRatio(targetMediaType, MF_MT_FRAME_RATE,
566 if (SUCCEEDED(MFGetAttributeRatio(m_videoMediaType, MF_MT_PIXEL_ASPECT_RATIO, &
t1, &
t2))) {
568 if (SUCCEEDED(MFSetAttributeRatio(targetMediaType, MF_MT_PIXEL_ASPECT_RATIO,
t1,
t2))) {
570 if (SUCCEEDED(m_videoMediaType->GetUINT32(MF_MT_INTERLACE_MODE, &
t1))) {
572 if (SUCCEEDED(targetMediaType->SetUINT32(MF_MT_INTERLACE_MODE,
t1))) {
574 *mediaType = targetMediaType;
585 targetMediaType->Release();
590HRESULT QWindowsMediaDeviceReader::createAudioMediaType(
const GUID &
format, UINT32 bitRate, IMFMediaType **mediaType)
595 *mediaType =
nullptr;
596 IMFMediaType *targetMediaType =
nullptr;
598 if (SUCCEEDED(MFCreateMediaType(&targetMediaType))) {
600 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio))) {
602 if (SUCCEEDED(targetMediaType->SetGUID(MF_MT_SUBTYPE,
format))) {
604 if (bitRate == 0 || SUCCEEDED(targetMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bitRate / 8))) {
606 *mediaType = targetMediaType;
611 targetMediaType->Release();
616HRESULT QWindowsMediaDeviceReader::updateSinkInputMediaTypes()
620 if (m_videoSource && m_videoMediaType && m_sinkVideoStreamIndex != MF_SINK_WRITER_INVALID_STREAM_INDEX) {
621 hr = m_sinkWriter->SetInputMediaType(m_sinkVideoStreamIndex, m_videoMediaType,
nullptr);
624 if (m_audioSource && m_audioMediaType && m_sinkAudioStreamIndex != MF_SINK_WRITER_INVALID_STREAM_INDEX) {
625 hr = m_sinkWriter->SetInputMediaType(m_sinkAudioStreamIndex, m_audioMediaType,
nullptr);
633 const QString &
fileName,
const GUID &container,
const GUID &videoFormat, UINT32 videoBitRate,
638 if (!m_active || m_recording || (videoFormat == GUID_NULL && audioFormat == GUID_NULL))
641 ComPtr<IMFAttributes> writerAttributes;
643 HRESULT hr = MFCreateAttributes(writerAttributes.GetAddressOf(), 2);
648 hr = writerAttributes->SetUnknown(MF_SINK_WRITER_ASYNC_CALLBACK,
649 static_cast<IMFSinkWriterCallback*
>(
this));
657 ComPtr<IMFSinkWriter> sinkWriter;
658 hr = MFCreateSinkWriterFromURL(
reinterpret_cast<LPCWSTR
>(
fileName.utf16()),
659 nullptr, writerAttributes.Get(), sinkWriter.GetAddressOf());
663 m_sinkVideoStreamIndex = MF_SINK_WRITER_INVALID_STREAM_INDEX;
664 m_sinkAudioStreamIndex = MF_SINK_WRITER_INVALID_STREAM_INDEX;
666 if (m_videoSource && videoFormat != GUID_NULL) {
667 IMFMediaType *targetMediaType =
nullptr;
669 hr = createVideoMediaType(videoFormat, videoBitRate,
width,
height,
frameRate, &targetMediaType);
672 hr = sinkWriter->AddStream(targetMediaType, &m_sinkVideoStreamIndex);
675 hr = sinkWriter->SetInputMediaType(m_sinkVideoStreamIndex, m_videoMediaType,
nullptr);
677 targetMediaType->Release();
682 if (m_audioSource && audioFormat != GUID_NULL) {
683 IMFMediaType *targetMediaType =
nullptr;
685 hr = createAudioMediaType(audioFormat, audioBitRate, &targetMediaType);
688 hr = sinkWriter->AddStream(targetMediaType, &m_sinkAudioStreamIndex);
691 hr = sinkWriter->SetInputMediaType(m_sinkAudioStreamIndex, m_audioMediaType,
nullptr);
693 targetMediaType->Release();
701 hr = sinkWriter->BeginWriting();
705 m_sinkWriter = sinkWriter.Detach();
707 m_currentDuration = 0;
709 m_durationTimer.
start();
713 m_pauseChanging =
false;
722 if (m_sinkWriter && m_recording) {
724 HRESULT hr = m_sinkWriter->Finalize();
727 m_hasFinalized.
wait(&m_mutex);
729 m_sinkWriter->Release();
730 m_sinkWriter =
nullptr;
739 m_pauseChanging =
false;
741 m_durationTimer.
stop();
743 m_currentDuration = -1;
748 if (!m_recording || m_paused)
750 m_pauseTime = m_lastTimestamp;
752 m_pauseChanging =
true;
758 if (!m_recording || !m_paused)
761 m_pauseChanging =
true;
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));
777 *ppvObject =
nullptr;
778 return E_NOINTERFACE;
786 return InterlockedIncrement(&m_cRef);
791 LONG cRef = InterlockedDecrement(&m_cRef);
805 return m_frameHeight;
815 m_inputMuted = muted;
820 m_inputVolume =
qBound(0.0, volume, 1.0);
827 m_outputMuted = muted;
829 if (m_active && m_monitorSink) {
830 IMFSimpleAudioVolume *audioVolume =
nullptr;
832 IID_PPV_ARGS(&audioVolume)))) {
833 audioVolume->SetMute(m_outputMuted);
834 audioVolume->Release();
843 m_outputVolume =
qBound(0.0, volume, 1.0);
845 if (m_active && m_monitorSink) {
846 IMFSimpleAudioVolume *audioVolume =
nullptr;
848 IID_PPV_ARGS(&audioVolume)))) {
849 audioVolume->SetMasterVolume(
float(m_outputVolume));
850 audioVolume->Release();
855void QWindowsMediaDeviceReader::updateDuration()
857 if (m_currentDuration >= 0 && m_lastDuration != m_currentDuration) {
858 m_lastDuration = m_currentDuration;
865 DWORD dwStreamFlags, LONGLONG llTimestamp,
870 if (FAILED(hrStatus)) {
875 m_lastTimestamp = llTimestamp;
877 if ((dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) {
888 if (m_monitorWriter && dwStreamIndex == m_sourceAudioStreamIndex)
889 m_monitorWriter->WriteSample(0, pSample);
894 m_timeOffset = llTimestamp;
895 m_firstFrame =
false;
899 if (m_pauseChanging) {
902 m_pauseTime = llTimestamp;
904 m_timeOffset += llTimestamp - m_pauseTime;
905 m_pauseChanging =
false;
909 if (m_sinkWriter && !m_paused) {
911 pSample->SetSampleTime(llTimestamp - m_timeOffset);
913 if (dwStreamIndex == m_sourceVideoStreamIndex) {
915 m_sinkWriter->WriteSample(m_sinkVideoStreamIndex, pSample);
917 }
else if (dwStreamIndex == m_sourceAudioStreamIndex) {
919 float volume = m_inputMuted ? 0.0f : float(m_inputVolume);
922 if (volume != 1.0f) {
923 IMFMediaBuffer *mediaBuffer =
nullptr;
924 if (SUCCEEDED(pSample->ConvertToContiguousBuffer(&mediaBuffer))) {
929 if (SUCCEEDED(mediaBuffer->Lock(&
buffer,
nullptr, &bufLen))) {
931 float *floatBuffer =
reinterpret_cast<float*
>(
buffer);
933 for (DWORD
i = 0;
i < bufLen/4; ++
i)
934 floatBuffer[
i] *= volume;
936 mediaBuffer->Unlock();
938 mediaBuffer->Release();
942 m_sinkWriter->WriteSample(m_sinkAudioStreamIndex, pSample);
944 m_currentDuration = (llTimestamp - m_timeOffset) / 10000;
949 if (dwStreamIndex == m_sourceVideoStreamIndex) {
950 IMFMediaBuffer *mediaBuffer =
nullptr;
951 if (SUCCEEDED(pSample->ConvertToContiguousBuffer(&mediaBuffer))) {
956 if (SUCCEEDED(mediaBuffer->Lock(&
buffer,
nullptr, &bufLen))) {
963 frame.setStartTime(llTimestamp * 0.1);
965 LONGLONG duration = -1;
966 if (SUCCEEDED(pSample->GetSampleDuration(&duration)))
967 frame.setEndTime((llTimestamp + duration) * 0.1);
971 mediaBuffer->Unlock();
973 mediaBuffer->Release();
979 m_sourceReader->ReadSample(MF_SOURCE_READER_ANY_STREAM,
980 0,
nullptr,
nullptr,
nullptr,
nullptr);
1001 m_sinkWriter->Release();
1002 m_sinkWriter =
nullptr;
1016#include "moc_qwindowsmediadevicereader_p.cpp"
The QMemoryVideoBuffer class provides a system memory allocated video data buffer.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
void deleteLater()
\threadsafe
constexpr int height() const noexcept
Returns the height.
constexpr int width() const noexcept
Returns the width.
\macro QT_RESTRICTED_CAST_FROM_ASCII
const ushort * utf16() const
Returns the QString as a '\0\'-terminated array of unsigned shorts.
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
void setInterval(int msec)
void stop()
Stops the timer.
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
The QVideoFrame class represents a frame of video data.
bool wait(QMutex *, QDeadlineTimer=QDeadlineTimer(QDeadlineTimer::Forever))
Combined button and popup list for selecting options.
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
static int area(const QSize &s)
constexpr const T & qBound(const T &min, const T &val, const T &max)
constexpr T qAbs(const T &t)
#define Q_ARG(Type, data)
GLint GLsizei GLsizei height
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
IUIViewSettingsInterop __RPC__in REFIID riid
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)
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent