Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qdarwinaudiosink.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
6#include "qcoreaudioutils_p.h"
8#include <qmediadevices.h>
9
10#include <QtCore/QDataStream>
11#include <QtCore/QTimer>
12#include <QtCore/QDebug>
13
14#include <AudioUnit/AudioUnit.h>
15#include <AudioToolbox/AudioToolbox.h>
16#if defined(Q_OS_MACOS)
17# include <AudioUnit/AudioComponent.h>
18#endif
19
20#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
21# include <QtMultimedia/private/qaudiohelpers_p.h>
22#endif
23
25
26QDarwinAudioSinkBuffer::QDarwinAudioSinkBuffer(int bufferSize, int maxPeriodSize, const QAudioFormat &audioFormat)
27 : m_deviceError(false)
28 , m_maxPeriodSize(maxPeriodSize)
29 , m_device(0)
30{
31 m_buffer = new CoreAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize)));
32 m_bytesPerFrame = audioFormat.bytesPerFrame();
33 m_periodTime = m_maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.sampleRate();
34
35 m_fillTimer = new QTimer(this);
36 connect(m_fillTimer, SIGNAL(timeout()), SLOT(fillBuffer()));
37}
38
40{
41 delete m_buffer;
42}
43
45{
46 bool wecan = true;
47 qint64 framesRead = 0;
48
49 while (wecan && framesRead < maxFrames) {
50 CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame);
51
52 if (region.second > 0) {
53 // Ensure that we only read whole frames.
54 region.second -= region.second % m_bytesPerFrame;
55
56 if (region.second > 0) {
57 memcpy(data + (framesRead * m_bytesPerFrame), region.first, region.second);
58 framesRead += region.second / m_bytesPerFrame;
59 } else
60 wecan = false; // If there is only a partial frame left we should exit.
61 }
62 else
63 wecan = false;
64
65 m_buffer->releaseReadRegion(region);
66 }
67
68 if (framesRead == 0 && m_deviceError)
69 framesRead = -1;
70
71 return framesRead;
72}
73
75{
76 bool wecan = true;
78
79 maxSize -= maxSize % m_bytesPerFrame;
80 while (wecan && bytesWritten < maxSize) {
82
83 if (region.second > 0) {
84 memcpy(region.first, data + bytesWritten, region.second);
85 bytesWritten += region.second;
86 }
87 else
88 wecan = false;
89
90 m_buffer->releaseWriteRegion(region);
91 }
92
93 if (bytesWritten > 0)
95
96 return bytesWritten;
97}
98
100{
101 return m_buffer->free();
102}
103
105{
106 m_buffer->reset();
107 m_device = nullptr;
108 m_deviceError = false;
109}
110
112{
113 if (m_device != device) {
114 m_device = device;
115 if (m_device != 0)
116 fillBuffer();
117 }
118}
119
121{
122 if (m_device != 0)
123 m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime);
124}
125
127{
128 m_fillTimer->stop();
129}
130
131void QDarwinAudioSinkBuffer::fillBuffer()
132{
133 const int free = m_buffer->free();
134 const int writeSize = free - (free % m_maxPeriodSize);
135
136 if (writeSize > 0) {
137 bool wecan = true;
138 int filled = 0;
139
140 while (!m_deviceError && wecan && filled < writeSize) {
141 CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(writeSize - filled);
142
143 if (region.second > 0) {
144 region.second = m_device->read(region.first, region.second);
145 if (region.second > 0)
146 filled += region.second;
147 else if (region.second == 0)
148 wecan = false;
149 else if (region.second < 0) {
150 m_fillTimer->stop();
151 region.second = 0;
152 m_deviceError = true;
153 }
154 }
155 else
156 wecan = false;
157
158 m_buffer->releaseWriteRegion(region);
159 }
160
161 if (filled > 0)
162 emit readyRead();
163 }
164}
165
168 , m_audioBuffer(audioBuffer)
169{
171}
172
174{
175 Q_UNUSED(data);
176 Q_UNUSED(len);
177
178 return 0;
179}
180
182{
183 return m_audioBuffer->writeBytes(data, len);
184}
185
187 : QPlatformAudioSink(parent), m_audioDeviceInfo(device)
188{
189 QAudioDevice di = device;
190 if (di.isNull())
192#if defined(Q_OS_MACOS)
193 const QCoreAudioDeviceInfo *info = static_cast<const QCoreAudioDeviceInfo *>(di.handle());
194 Q_ASSERT(info);
195 m_audioDeviceId = info->deviceID();
196#endif
197 m_device = di.id();
198
199 m_clockFrequency = CoreAudioUtils::frequency() / 1000;
200 m_audioThreadState.storeRelaxed(Stopped);
201}
202
204{
205 close();
206}
207
209{
210 QIODevice* op = device;
211
212 if (!m_audioDeviceInfo.isFormatSupported(m_audioFormat) || !open()) {
213 m_stateCode = QAudio::StoppedState;
214 m_errorCode = QAudio::OpenError;
215 return;
216 }
217
218 reset();
219 m_audioBuffer->reset();
220 m_audioBuffer->setPrefetchDevice(op);
221
222 if (op == 0) {
223 op = m_audioIO;
224 m_stateCode = QAudio::IdleState;
225 }
226 else
227 m_stateCode = QAudio::ActiveState;
228
229 // Start
230 m_pullMode = true;
231 m_errorCode = QAudio::NoError;
232 m_totalFrames = 0;
233
234 if (m_stateCode == QAudio::ActiveState)
235 audioThreadStart();
236
237 emit stateChanged(m_stateCode);
238}
239
241{
242 if (!m_audioDeviceInfo.isFormatSupported(m_audioFormat) || !open()) {
243 m_stateCode = QAudio::StoppedState;
244 m_errorCode = QAudio::OpenError;
245 return m_audioIO;
246 }
247
248 reset();
249 m_audioBuffer->reset();
250 m_audioBuffer->setPrefetchDevice(0);
251
252 m_stateCode = QAudio::IdleState;
253
254 // Start
255 m_pullMode = false;
256 m_errorCode = QAudio::NoError;
257 m_totalFrames = 0;
258
259 emit stateChanged(m_stateCode);
260
261 return m_audioIO;
262}
263
265{
266 if (m_stateCode == QAudio::StoppedState)
267 return;
268
269 audioThreadDrain();
270
271 m_stateCode = QAudio::StoppedState;
272 m_errorCode = QAudio::NoError;
273 emit stateChanged(m_stateCode);
274}
275
277{
278 if (m_stateCode == QAudio::StoppedState)
279 return;
280
281 audioThreadStop();
282
283 m_stateCode = QAudio::StoppedState;
284 m_errorCode = QAudio::NoError;
285 emit stateChanged(m_stateCode);
286}
287
289{
290 if (m_stateCode != QAudio::ActiveState && m_stateCode != QAudio::IdleState)
291 return;
292
293 audioThreadStop();
294
295 m_suspendedInStateCode = m_stateCode;
296 m_stateCode = QAudio::SuspendedState;
297 m_errorCode = QAudio::NoError;
298 emit stateChanged(m_stateCode);
299}
300
302{
303 if (m_stateCode != QAudio::SuspendedState)
304 return;
305
306 audioThreadStart();
307
308 m_stateCode = m_suspendedInStateCode;
309 m_errorCode = QAudio::NoError;
310 emit stateChanged(m_stateCode);
311}
312
314{
315 return m_audioBuffer->available();
316}
317
319{
320 if (m_stateCode == QAudio::StoppedState)
321 m_internalBufferSize = value;
322}
323
325{
326 return m_internalBufferSize;
327}
328
330{
331 return m_totalFrames * 1000000 / m_audioFormat.sampleRate();
332}
333
335{
336 return m_errorCode;
337}
338
340{
341 return m_stateCode;
342}
343
345{
346 if (m_stateCode == QAudio::StoppedState)
347 m_audioFormat = format;
348}
349
351{
352 return m_audioFormat;
353}
354
356{
357 m_cachedVolume = qBound(qreal(0.0), volume, qreal(1.0));
358 if (!m_isOpen)
359 return;
360
361#if defined(Q_OS_MACOS)
362 //on OS X the volume can be set directly on the AudioUnit
363 if (AudioUnitSetParameter(m_audioUnit,
364 kHALOutputParam_Volume,
365 kAudioUnitScope_Global,
366 0 /* bus */,
367 m_cachedVolume,
368 0) == noErr)
369 m_volume = m_cachedVolume;
370#endif
371}
372
374{
375 return m_cachedVolume;
376}
377
378void QDarwinAudioSink::deviceStopped()
379{
380 emit stateChanged(m_stateCode);
381}
382
383void QDarwinAudioSink::inputReady()
384{
385 if (m_stateCode != QAudio::IdleState)
386 return;
387
388 audioThreadStart();
389
390 m_stateCode = QAudio::ActiveState;
391 m_errorCode = QAudio::NoError;
392
393 emit stateChanged(m_stateCode);
394}
395
396OSStatus QDarwinAudioSink::renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
397{
398 Q_UNUSED(ioActionFlags);
399 Q_UNUSED(inTimeStamp);
400 Q_UNUSED(inBusNumber);
401 Q_UNUSED(inNumberFrames);
402
403 QDarwinAudioSink* d = static_cast<QDarwinAudioSink*>(inRefCon);
404
405 const int threadState = d->m_audioThreadState.fetchAndAddAcquire(0);
406 if (threadState == Stopped) {
407 ioData->mBuffers[0].mDataByteSize = 0;
408 d->audioDeviceStop();
409 }
410 else {
411 const UInt32 bytesPerFrame = d->m_streamFormat.mBytesPerFrame;
412 qint64 framesRead;
413
414 framesRead = d->m_audioBuffer->readFrames((char*)ioData->mBuffers[0].mData,
415 ioData->mBuffers[0].mDataByteSize / bytesPerFrame);
416
417 if (framesRead > 0) {
418 ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame;
419 d->m_totalFrames += framesRead;
420
421#if defined(Q_OS_MACOS)
422 // If playback is already stopped.
423 if (threadState != Running) {
424 qreal oldVolume = d->m_cachedVolume;
425 // Decrease volume smoothly.
426 d->setVolume(d->m_volume / 2);
427 d->m_cachedVolume = oldVolume;
428 }
429#elif defined(Q_OS_IOS) || defined(Q_OS_TVOS)
430 // on iOS we have to adjust the sound volume ourselves
431 if (!qFuzzyCompare(d->m_cachedVolume, qreal(1.0f))) {
433 d->m_audioFormat,
434 ioData->mBuffers[0].mData, /* input */
435 ioData->mBuffers[0].mData, /* output */
436 ioData->mBuffers[0].mDataByteSize);
437 }
438#endif
439
440 }
441 else {
442 ioData->mBuffers[0].mDataByteSize = 0;
443 if (framesRead == 0) {
444 if (threadState == Draining)
445 d->audioDeviceStop();
446 else
447 d->audioDeviceIdle();
448 }
449 else
450 d->audioDeviceError();
451 }
452 }
453
454 return noErr;
455}
456
457bool QDarwinAudioSink::open()
458{
459#if defined(Q_OS_IOS)
460 // Set default category to Ambient (implies MixWithOthers). This makes sure audio stops playing
461 // if the screen is locked or if the Silent switch is toggled.
464#endif
465
466 if (m_errorCode != QAudio::NoError)
467 return false;
468
469 if (m_isOpen) {
470 setVolume(m_cachedVolume);
471 return true;
472 }
473
474 AudioComponentDescription componentDescription;
475 componentDescription.componentType = kAudioUnitType_Output;
476#if defined(Q_OS_MACOS)
477 componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
478#else
479 componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
480#endif
481 componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
482 componentDescription.componentFlags = 0;
483 componentDescription.componentFlagsMask = 0;
484
485 AudioComponent component = AudioComponentFindNext(0, &componentDescription);
486 if (component == 0) {
487 qWarning() << "QAudioOutput: Failed to find Output component";
488 return false;
489 }
490
491 if (AudioComponentInstanceNew(component, &m_audioUnit) != noErr) {
492 qWarning() << "QAudioOutput: Unable to Open Output Component";
493 return false;
494 }
495
496 // register callback
497 AURenderCallbackStruct callback;
498 callback.inputProc = renderCallback;
499 callback.inputProcRefCon = this;
500
501 if (AudioUnitSetProperty(m_audioUnit,
502 kAudioUnitProperty_SetRenderCallback,
503 kAudioUnitScope_Global,
504 0,
505 &callback,
506 sizeof(callback)) != noErr) {
507 qWarning() << "QAudioOutput: Failed to set AudioUnit callback";
508 return false;
509 }
510
511#if defined(Q_OS_MACOS)
512 //Set Audio Device
513 if (AudioUnitSetProperty(m_audioUnit,
514 kAudioOutputUnitProperty_CurrentDevice,
515 kAudioUnitScope_Global,
516 0,
517 &m_audioDeviceId,
518 sizeof(m_audioDeviceId)) != noErr) {
519 qWarning() << "QAudioOutput: Unable to use configured device";
520 return false;
521 }
522#endif
523 UInt32 size;
524
525
526 // Set stream format
527 m_streamFormat = CoreAudioUtils::toAudioStreamBasicDescription(m_audioFormat);
528 size = sizeof(m_streamFormat);
529
530 if (AudioUnitSetProperty(m_audioUnit,
531 kAudioUnitProperty_StreamFormat,
532 kAudioUnitScope_Input,
533 0,
534 &m_streamFormat,
535 size) != noErr) {
536 qWarning() << "QAudioOutput: Unable to Set Stream information";
537 return false;
538 }
539
540 // Allocate buffer
541 UInt32 numberOfFrames = 0;
542#if defined(Q_OS_MACOS)
543 size = sizeof(UInt32);
544 if (AudioUnitGetProperty(m_audioUnit,
545 kAudioDevicePropertyBufferFrameSize,
546 kAudioUnitScope_Global,
547 0,
548 &numberOfFrames,
549 &size) != noErr) {
550 qWarning() << "QAudioSource: Failed to get audio period size";
551 return false;
552 }
553#else //iOS
555 bufferSize *= m_streamFormat.mSampleRate;
556 numberOfFrames = bufferSize;
557#endif
558
559 m_periodSizeBytes = numberOfFrames * m_streamFormat.mBytesPerFrame;
560 if (m_internalBufferSize < m_periodSizeBytes * 2)
561 m_internalBufferSize = m_periodSizeBytes * 2;
562 else
563 m_internalBufferSize -= m_internalBufferSize % m_streamFormat.mBytesPerFrame;
564
565 m_audioBuffer = new QDarwinAudioSinkBuffer(m_internalBufferSize, m_periodSizeBytes, m_audioFormat);
566 connect(m_audioBuffer, SIGNAL(readyRead()), SLOT(inputReady())); //Pull
567
568 m_audioIO = new QDarwinAudioSinkDevice(m_audioBuffer, this);
569
570 //Init
571 if (AudioUnitInitialize(m_audioUnit)) {
572 qWarning() << "QAudioOutput: Failed to initialize AudioUnit";
573 return false;
574 }
575
576 m_isOpen = true;
577
578 setVolume(m_cachedVolume);
579
580 return true;
581}
582
583void QDarwinAudioSink::close()
584{
585 if (m_audioUnit != 0) {
586 audioDeviceStop();
587 AudioUnitUninitialize(m_audioUnit);
588 AudioComponentInstanceDispose(m_audioUnit);
589 }
590
591 delete m_audioBuffer;
592}
593
594void QDarwinAudioSink::audioThreadStart()
595{
596 startTimers();
597 audioDeviceStart();
598}
599
600void QDarwinAudioSink::audioThreadStop()
601{
602 stopTimers();
603
604 // It's common practice to call AudioOutputUnitStop
605 // from the thread where the audio output was started,
606 // so we don't have to rely on the stops inside renderCallback.
607 audioDeviceStop();
608}
609
610void QDarwinAudioSink::audioThreadDrain()
611{
612 stopTimers();
613
614 QMutexLocker lock(&m_mutex);
615
616 if (m_audioThreadState.testAndSetAcquire(Running, Draining)) {
617 constexpr int MaxDrainWaitingTime = 500;
618
619 m_threadFinished.wait(&m_mutex, MaxDrainWaitingTime);
620
621 if (m_audioThreadState.fetchAndStoreRelaxed(Stopped) != Stopped) {
622 qWarning() << "Couldn't wait for draining; force stop";
623
624 AudioOutputUnitStop(m_audioUnit);
625 }
626 }
627}
628
629void QDarwinAudioSink::audioDeviceStart()
630{
631 QMutexLocker lock(&m_mutex);
632
633 const auto state = m_audioThreadState.loadAcquire();
634 if (state == Stopped) {
635 m_audioThreadState.storeRelaxed(Running);
636 AudioOutputUnitStart(m_audioUnit);
637 } else {
638 qWarning() << "Unexpected state on audio device start:" << state;
639 }
640}
641
642void QDarwinAudioSink::audioDeviceStop()
643{
644 {
645 QMutexLocker lock(&m_mutex);
646
647 AudioOutputUnitStop(m_audioUnit);
648 m_audioThreadState.storeRelaxed(Stopped);
649 }
650
651 m_threadFinished.wakeOne();
652}
653
654void QDarwinAudioSink::audioDeviceIdle()
655{
656 if (m_stateCode != QAudio::ActiveState)
657 return;
658
659 m_errorCode = QAudio::UnderrunError;
660 m_stateCode = QAudio::IdleState;
661
662 audioDeviceStop();
663
664 QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
665}
666
667void QDarwinAudioSink::audioDeviceError()
668{
669 if (m_stateCode != QAudio::ActiveState)
670 return;
671
672 m_errorCode = QAudio::IOError;
673 m_stateCode = QAudio::StoppedState;
674
675 audioDeviceStop();
676
677 QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
678}
679
680void QDarwinAudioSink::startTimers()
681{
682 m_audioBuffer->startFillTimer();
683}
684
685void QDarwinAudioSink::stopTimers()
686{
687 m_audioBuffer->stopFillTimer();
688}
689
691
692#include "moc_qdarwinaudiosink_p.cpp"
IOBluetoothDevice * device
void releaseWriteRegion(Region const &region)
Region acquireWriteRegion(int size)
Region acquireReadRegion(int size)
void releaseReadRegion(Region const &region)
QPair< char *, int > Region
bool setCategory(AudioSessionCategorys category, AudioSessionCategoryOptions options=None)
static CoreAudioSessionManager & instance()
static AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const &audioFormat)
static double frequency()
The QAudioDevice class provides an information about audio devices and their functionality.
bool isFormatSupported(const QAudioFormat &format) const
Returns true if the supplied settings are supported by the audio device described by this QAudioDevic...
QByteArray id
\qmlproperty string QtMultimedia::audioDevice::id
const QAudioDevicePrivate * handle() const
bool isNull() const
Returns whether this QAudioDevice object holds a valid device definition.
The QAudioFormat class stores audio stream parameter information.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
constexpr int bytesPerFrame() const
Returns the number of bytes required to represent one frame (a sample in each channel) in this format...
void storeRelaxed(T newValue) noexcept
T loadAcquire() const noexcept
bool testAndSetAcquire(T expectedValue, T newValue) noexcept
T fetchAndStoreRelaxed(T newValue) noexcept
qint64 readFrames(char *data, qint64 maxFrames)
qint64 writeBytes(const char *data, qint64 maxSize)
void setPrefetchDevice(QIODevice *device)
QDarwinAudioSinkBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const &audioFormat)
QDarwinAudioSinkDevice(QDarwinAudioSinkBuffer *audioBuffer, QObject *parent)
qint64 writeData(const char *data, qint64 len)
Writes up to maxSize bytes from data to the device.
qint64 readData(char *data, qint64 len)
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
void setFormat(const QAudioFormat &format)
qint64 processedUSecs() const
QAudioFormat format() const
QDarwinAudioSink(const QAudioDevice &device, QObject *parent)
void setBufferSize(qsizetype value)
QAudio::State state() const
QAudio::Error error() const
void setVolume(qreal volume)
qsizetype bytesFree() const
qsizetype bufferSize() const
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\inmodule QtCore
Definition qmutex.h:317
\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 stateChanged(QAudio::State state)
\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
bool wait(QMutex *, QDeadlineTimer=QDeadlineTimer(QDeadlineTimer::Forever))
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
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
@ OpenError
Definition qaudio.h:24
@ NoError
Definition qaudio.h:24
@ IOError
Definition qaudio.h:24
Combined button and popup list for selecting options.
@ QueuedConnection
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
#define qWarning
Definition qlogging.h:162
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLbitfield GLuint64 timeout
[4]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei GLsizei GLenum format
GLenum GLsizei len
static qreal component(const QPointF &point, unsigned int i)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
QFileInfo info(fileName)
[8]
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