Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qpulseaudiosource.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
4#include <QtCore/qcoreapplication.h>
5#include <QtCore/qdebug.h>
6#include <QtCore/qmath.h>
7#include <private/qaudiohelpers_p.h>
8
11#include "qpulseaudiodevice_p.h"
12#include "qpulsehelpers_p.h"
13#include <sys/types.h>
14#include <unistd.h>
15#include <mutex> // for lock_guard
16
18
19const int SourcePeriodTimeMs = 50;
20
21static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
22{
23 Q_UNUSED(userdata);
27 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
28}
29
30static void inputStreamStateCallback(pa_stream *stream, void *userdata)
31{
32 Q_UNUSED(userdata);
33 pa_stream_state_t state = pa_stream_get_state(stream);
34#ifdef DEBUG_PULSE
35 qDebug() << "Stream state: " << QPulseAudioInternal::stateToQString(state);
36#endif
37 switch (state) {
38 case PA_STREAM_CREATING:
39 break;
40 case PA_STREAM_READY: {
41#ifdef DEBUG_PULSE
42 QPulseAudioSource *audioInput = static_cast<QPulseAudioSource*>(userdata);
43 const pa_buffer_attr *buffer_attr = pa_stream_get_buffer_attr(stream);
44 qDebug() << "*** maxlength: " << buffer_attr->maxlength;
45 qDebug() << "*** prebuf: " << buffer_attr->prebuf;
46 qDebug() << "*** fragsize: " << buffer_attr->fragsize;
47 qDebug() << "*** minreq: " << buffer_attr->minreq;
48 qDebug() << "*** tlength: " << buffer_attr->tlength;
49
50 pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(audioInput->format());
51 qDebug() << "*** bytes_to_usec: " << pa_bytes_to_usec(buffer_attr->fragsize, &spec);
52#endif
53 }
54 break;
55 case PA_STREAM_TERMINATED:
56 break;
57 case PA_STREAM_FAILED:
58 default:
59 qWarning() << QString::fromLatin1("Stream error: %1").arg(QString::fromUtf8(pa_strerror(pa_context_errno(pa_stream_get_context(stream)))));
61 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
62 break;
63 }
64}
65
66static void inputStreamUnderflowCallback(pa_stream *stream, void *userdata)
67{
68 Q_UNUSED(userdata);
70 qWarning() << "Got a buffer underflow!";
71}
72
73static void inputStreamOverflowCallback(pa_stream *stream, void *userdata)
74{
76 Q_UNUSED(userdata);
77 qWarning() << "Got a buffer overflow!";
78}
79
80static void inputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
81{
83 Q_UNUSED(userdata);
84 Q_UNUSED(success);
85
86 //if (!success)
87 //TODO: Is cork success? i->operation_success = success;
88
90 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
91}
92
95 , m_totalTimeValue(0)
96 , m_audioSource(nullptr)
97 , m_errorState(QAudio::NoError)
98 , m_deviceState(QAudio::StoppedState)
99 , m_volume(qreal(1.0f))
100 , m_pullMode(true)
101 , m_opened(false)
102 , m_bytesAvailable(0)
103 , m_bufferSize(0)
104 , m_periodSize(0)
105 , m_periodTime(SourcePeriodTimeMs)
106 , m_stream(nullptr)
107 , m_device(device)
108{
109 m_timer = new QTimer(this);
110 connect(m_timer, SIGNAL(timeout()), SLOT(userFeed()));
111}
112
114{
115 close();
116 disconnect(m_timer, SIGNAL(timeout()));
118 delete m_timer;
119}
120
121void QPulseAudioSource::setError(QAudio::Error error)
122{
123 if (m_errorState == error)
124 return;
125
128}
129
131{
132 return m_errorState;
133}
134
135void QPulseAudioSource::setState(QAudio::State state)
136{
137 if (m_deviceState == state)
138 return;
139
142}
143
145{
146 return m_deviceState;
147}
148
150{
153}
154
156{
157 return m_format;
158}
159
161{
162 setState(QAudio::StoppedState);
163 setError(QAudio::NoError);
164
165 if (!m_pullMode && m_audioSource) {
166 delete m_audioSource;
167 m_audioSource = nullptr;
168 }
169
170 close();
171
172 if (!open())
173 return;
174
175 m_pullMode = true;
177
178 setState(QAudio::ActiveState);
179}
180
182{
183 setState(QAudio::StoppedState);
184 setError(QAudio::NoError);
185
186 if (!m_pullMode && m_audioSource) {
187 delete m_audioSource;
188 m_audioSource = nullptr;
189 }
190
191 close();
192
193 if (!open())
194 return nullptr;
195
196 m_pullMode = false;
199
200 setState(QAudio::IdleState);
201
202 return m_audioSource;
203}
204
206{
208 return;
209
210 close();
211
212 setError(QAudio::NoError);
213 setState(QAudio::StoppedState);
214}
215
216bool QPulseAudioSource::open()
217{
218 if (m_opened)
219 return true;
220
222
223 if (!pulseEngine->context() || pa_context_get_state(pulseEngine->context()) != PA_CONTEXT_READY) {
224 setError(QAudio::FatalError);
225 setState(QAudio::StoppedState);
226 return false;
227 }
228
230 pa_channel_map channel_map = QPulseAudioInternal::channelMapForAudioFormat(m_format);
231 Q_ASSERT(spec.channels == channel_map.channels);
232
233 if (!pa_sample_spec_valid(&spec)) {
234 setError(QAudio::OpenError);
235 setState(QAudio::StoppedState);
236 return false;
237 }
238
239 m_spec = spec;
240
241#ifdef DEBUG_PULSE
242// QTime now(QTime::currentTime());
243// qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
244#endif
245
246 if (m_streamName.isNull())
247 m_streamName = QString(QLatin1String("QtmPulseStream-%1-%2")).arg(::getpid()).arg(quintptr(this)).toUtf8();
248
249#ifdef DEBUG_PULSE
250 qDebug() << "Format: " << QPulseAudioInternal::sampleFormatToQString(spec.format);
251 qDebug() << "Rate: " << spec.rate;
252 qDebug() << "Channels: " << spec.channels;
253 qDebug() << "Frame size: " << pa_frame_size(&spec);
254#endif
255
256 pulseEngine->lock();
257
258 m_stream = pa_stream_new(pulseEngine->context(), m_streamName.constData(), &spec, &channel_map);
259
260 pa_stream_set_state_callback(m_stream, inputStreamStateCallback, this);
261 pa_stream_set_read_callback(m_stream, inputStreamReadCallback, this);
262
263 pa_stream_set_underflow_callback(m_stream, inputStreamUnderflowCallback, this);
264 pa_stream_set_overflow_callback(m_stream, inputStreamOverflowCallback, this);
265
266 m_periodSize = pa_usec_to_bytes(SourcePeriodTimeMs*1000, &spec);
267
268 int flags = 0;
269 pa_buffer_attr buffer_attr;
270 buffer_attr.maxlength = (uint32_t) -1;
271 buffer_attr.prebuf = (uint32_t) -1;
272 buffer_attr.tlength = (uint32_t) -1;
273 buffer_attr.minreq = (uint32_t) -1;
274 flags |= PA_STREAM_ADJUST_LATENCY;
275
276 if (m_bufferSize > 0)
277 buffer_attr.fragsize = (uint32_t) m_bufferSize;
278 else
279 buffer_attr.fragsize = (uint32_t) m_periodSize;
280
281 flags |= PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING;
282 if (pa_stream_connect_record(m_stream, m_device.data(), &buffer_attr, (pa_stream_flags_t)flags) < 0) {
283 qWarning() << "pa_stream_connect_record() failed!";
284 pa_stream_unref(m_stream);
285 m_stream = nullptr;
286 pulseEngine->unlock();
287 setError(QAudio::OpenError);
288 setState(QAudio::StoppedState);
289 return false;
290 }
291
292// auto *ss = pa_stream_get_sample_spec(m_stream);
293// qDebug() << "connected stream:";
294// qDebug() << " channels" << ss->channels << spec.channels;
295// qDebug() << " format" << ss->format << spec.format;
296// qDebug() << " rate" << ss->rate << spec.rate;
297
298 while (pa_stream_get_state(m_stream) != PA_STREAM_READY)
299 pa_threaded_mainloop_wait(pulseEngine->mainloop());
300
301 const pa_buffer_attr *actualBufferAttr = pa_stream_get_buffer_attr(m_stream);
302 m_periodSize = actualBufferAttr->fragsize;
303 m_periodTime = pa_bytes_to_usec(m_periodSize, &spec) / 1000;
304 if (actualBufferAttr->tlength != (uint32_t)-1)
305 m_bufferSize = actualBufferAttr->tlength;
306
307 pulseEngine->unlock();
308
309 connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSource::onPulseContextFailed);
310
311 m_opened = true;
312 m_timer->start(m_periodTime);
313
314 m_elapsedTimeOffset = 0;
316
317 return true;
318}
319
320void QPulseAudioSource::close()
321{
322 if (!m_opened)
323 return;
324
325 m_timer->stop();
326
328
329 if (m_stream) {
330 std::lock_guard lock(*pulseEngine);
331
332 pa_stream_set_state_callback(m_stream, nullptr, nullptr);
333 pa_stream_set_read_callback(m_stream, nullptr, nullptr);
334 pa_stream_set_underflow_callback(m_stream, nullptr, nullptr);
335 pa_stream_set_overflow_callback(m_stream, nullptr, nullptr);
336
337 pa_stream_disconnect(m_stream);
338 pa_stream_unref(m_stream);
339 m_stream = nullptr;
340 }
341
342 disconnect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioSource::onPulseContextFailed);
343
344 if (!m_pullMode && m_audioSource) {
345 delete m_audioSource;
346 m_audioSource = nullptr;
347 }
348 m_opened = false;
349}
350
351int QPulseAudioSource::checkBytesReady()
352{
354 m_bytesAvailable = 0;
355 } else {
356 m_bytesAvailable = pa_stream_readable_size(m_stream);
357 }
358
359 return m_bytesAvailable;
360}
361
363{
364 return qMax(m_bytesAvailable, 0);
365}
366
368{
369 Q_ASSERT(data != nullptr || len == 0);
370
371 m_bytesAvailable = checkBytesReady();
372
373 setError(QAudio::NoError);
374 if (state() == QAudio::IdleState)
375 setState(QAudio::ActiveState);
376
377 int readBytes = 0;
378
379 if (!m_pullMode && !m_tempBuffer.isEmpty()) {
380 readBytes = qMin(static_cast<int>(len), m_tempBuffer.size());
381 if (readBytes)
382 memcpy(data, m_tempBuffer.constData(), readBytes);
383 m_totalTimeValue += readBytes;
384
385 if (readBytes < m_tempBuffer.size()) {
386 m_tempBuffer.remove(0, readBytes);
387 return readBytes;
388 }
389
390 m_tempBuffer.clear();
391 }
392
393 while (pa_stream_readable_size(m_stream) > 0) {
394 size_t readLength = 0;
395
396#ifdef DEBUG_PULSE
397 qDebug() << "QPulseAudioSource::read -- " << pa_stream_readable_size(m_stream) << " bytes available from pulse audio";
398#endif
399
401 pulseEngine->lock();
402
403 const void *audioBuffer;
404
405 // Second and third parameters (audioBuffer and length) to pa_stream_peek are output parameters,
406 // the audioBuffer pointer is set to point to the actual pulse audio data,
407 // and the length is set to the length of this data.
408 if (pa_stream_peek(m_stream, &audioBuffer, &readLength) < 0) {
409 qWarning() << QString::fromLatin1("pa_stream_peek() failed: %1")
410 .arg(QString::fromUtf8(pa_strerror(pa_context_errno(pa_stream_get_context(m_stream)))));
411 pulseEngine->unlock();
412 return 0;
413 }
414
415 qint64 actualLength = 0;
416 if (m_pullMode) {
417 QByteArray adjusted(readLength, Qt::Uninitialized);
418 applyVolume(audioBuffer, adjusted.data(), readLength);
419 actualLength = m_audioSource->write(adjusted);
420
421 if (actualLength < qint64(readLength)) {
422 pulseEngine->unlock();
423
424 setError(QAudio::UnderrunError);
425 setState(QAudio::IdleState);
426
427 return actualLength;
428 }
429 } else {
430 actualLength = qMin(static_cast<int>(len - readBytes), static_cast<int>(readLength));
431 applyVolume(audioBuffer, data + readBytes, actualLength);
432 }
433
434#ifdef DEBUG_PULSE
435 qDebug() << "QPulseAudioSource::read -- wrote " << actualLength << " to client";
436#endif
437
438 if (actualLength < qint64(readLength)) {
439#ifdef DEBUG_PULSE
440 qDebug() << "QPulseAudioSource::read -- appending " << readLength - actualLength << " bytes of data to temp buffer";
441#endif
442 int diff = readLength - actualLength;
443 int oldSize = m_tempBuffer.size();
444 m_tempBuffer.resize(m_tempBuffer.size() + diff);
445 applyVolume(static_cast<const char *>(audioBuffer) + actualLength, m_tempBuffer.data() + oldSize, diff);
447 }
448
449 m_totalTimeValue += actualLength;
450 readBytes += actualLength;
451
452 pa_stream_drop(m_stream);
453 pulseEngine->unlock();
454
455 if (!m_pullMode && readBytes >= len)
456 break;
457 }
458
459#ifdef DEBUG_PULSE
460 qDebug() << "QPulseAudioSource::read -- returning after reading " << readBytes << " bytes";
461#endif
462
463 return readBytes;
464}
465
466void QPulseAudioSource::applyVolume(const void *src, void *dest, int len)
467{
468 Q_ASSERT((src && dest) || len == 0);
469 if (m_volume < 1.f)
471 else if (len)
472 memcpy(dest, src, len);
473}
474
476{
479
480 {
481 std::lock_guard lock(*pulseEngine);
482 PAOperationUPtr operation(
483 pa_stream_cork(m_stream, 0, inputStreamSuccessCallback, nullptr));
484 pulseEngine->wait(operation.get());
485 }
486
487 m_timer->start(m_periodTime);
488
489 setState(QAudio::ActiveState);
490 setError(QAudio::NoError);
491 }
492}
493
495{
496 if (qFuzzyCompare(m_volume, vol))
497 return;
498
499 m_volume = qBound(qreal(0), vol, qreal(1));
500}
501
503{
504 return m_volume;
505}
506
508{
509 m_bufferSize = value;
510}
511
513{
514 return m_bufferSize;
515}
516
518{
519 if (!m_stream)
520 return 0;
521 pa_usec_t usecs = 0;
522 int result = pa_stream_get_time(m_stream, &usecs);
524// if (result != 0)
525// qWarning() << "no timing info from pulse";
526
527 return usecs;
528}
529
531{
533 setError(QAudio::NoError);
534 setState(QAudio::SuspendedState);
535
536 m_timer->stop();
537
539
540 std::lock_guard lock(*pulseEngine);
541
542 PAOperationUPtr operation(pa_stream_cork(m_stream, 1, inputStreamSuccessCallback, nullptr));
543 pulseEngine->wait(operation.get());
544 }
545}
546
547void QPulseAudioSource::userFeed()
548{
550 return;
551#ifdef DEBUG_PULSE
552// QTime now(QTime::currentTime());
553// qDebug()<< now.second() << "s " << now.msec() << "ms :userFeed() IN";
554#endif
555 deviceReady();
556}
557
558bool QPulseAudioSource::deviceReady()
559{
560 if (m_pullMode) {
561 // reads some audio data and writes it to QIODevice
562 read(nullptr,0);
563 } else {
564 // emits readyRead() so user will call read() on QIODevice to get some audio data
565 if (m_audioSource != nullptr) {
566 PulseInputPrivate *a = qobject_cast<PulseInputPrivate*>(m_audioSource);
567 a->trigger();
568 }
569 }
570 m_bytesAvailable = checkBytesReady();
571
573 return true;
574
575 return true;
576}
577
579{
580 stop();
581 m_bytesAvailable = 0;
582}
583
584void QPulseAudioSource::onPulseContextFailed()
585{
586 close();
587
588 setError(QAudio::FatalError);
589 setState(QAudio::StoppedState);
590}
591
593{
594 m_audioDevice = qobject_cast<QPulseAudioSource*>(audio);
595}
596
598{
599 return m_audioDevice->read(data, len);
600}
601
603{
604 Q_UNUSED(data);
605 Q_UNUSED(len);
606 return 0;
607}
608
610{
611 emit readyRead();
612}
613
615
616#include "moc_qpulseaudiosource_p.cpp"
IOBluetoothDevice * device
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
PulseInputPrivate(QPulseAudioSource *audio)
qint64 readData(char *data, qint64 len) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
The QAudioFormat class stores audio stream parameter information.
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
void clear()
Clears the contents of the byte array and makes it null.
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
QByteArray & remove(qsizetype index, qsizetype len)
Removes len bytes from the array, starting at index position pos, and returns a reference to the arra...
bool isNull() const noexcept
Returns true if this byte array is null; otherwise returns false.
static void processEvents(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Processes some pending events for the calling thread according to the specified flags.
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void errorChanged(QAudio::Error error)
void stateChanged(QAudio::State state)
static QPulseAudioEngine * instance()
pa_threaded_mainloop * mainloop()
void wait(pa_operation *op)
QAudio::Error m_errorState
QAudioFormat format() const override
QAudio::State state() const override
QAudio::State m_deviceState
QPulseAudioSource(const QByteArray &device, QObject *parent)
void setFormat(const QAudioFormat &format) override
QIODevice * start() override
qint64 read(char *data, qint64 len)
qint64 processedUSecs() const override
qsizetype bytesReady() const override
void suspend() override
QAudio::Error error() const override
qsizetype bufferSize() const override
void setBufferSize(qsizetype value) override
void setVolume(qreal volume) override
qreal volume() const override
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
QByteArray toUtf8() const &
Definition qstring.h:563
\inmodule QtCore
Definition qtimer.h:20
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
void stop()
Stops the timer.
Definition qtimer.cpp:226
else opt state
[0]
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
The QAudio namespace contains enums used by the audio classes.
State
\value ActiveState Audio data is being processed, this state is set after start() is called and while...
Definition qaudio.h:25
@ StoppedState
Definition qaudio.h:25
@ SuspendedState
Definition qaudio.h:25
@ IdleState
Definition qaudio.h:25
@ ActiveState
Definition qaudio.h:25
Error
\value NoError No errors have occurred \value OpenError An error occurred opening the audio device \v...
Definition qaudio.h:24
@ UnderrunError
Definition qaudio.h:24
@ FatalError
Definition qaudio.h:24
@ OpenError
Definition qaudio.h:24
@ NoError
Definition qaudio.h:24
static QString sampleFormatToQString(pa_sample_format format)
pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
static QString stateToQString(pa_stream_state_t state)
Combined button and popup list for selecting options.
@ QueuedConnection
constexpr Initialization Uninitialized
DBusConnection const char DBusError * error
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLenum GLsizei length
GLbitfield GLuint64 timeout
[4]
GLenum src
GLbitfield flags
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei GLsizei GLenum format
GLenum GLsizei len
GLuint64EXT * result
[6]
static void inputStreamSuccessCallback(pa_stream *stream, int success, void *userdata)
static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
static void inputStreamStateCallback(pa_stream *stream, void *userdata)
static void inputStreamOverflowCallback(pa_stream *stream, void *userdata)
QT_BEGIN_NAMESPACE const int SourcePeriodTimeMs
static void inputStreamUnderflowCallback(pa_stream *stream, void *userdata)
std::unique_ptr< pa_operation, PAOperationDeleter > PAOperationUPtr
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
@ NoError
Definition main.cpp:34
#define emit
#define Q_UNUSED(x)
size_t quintptr
Definition qtypes.h:72
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
QObject::connect nullptr
myObject disconnect()
[26]
QReadWriteLock lock
[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...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent