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
qaaudiostream.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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 <chrono>
7#include <dlfcn.h>
8
9QT_BEGIN_NAMESPACE
10
11namespace {
12
13using namespace std::chrono_literals;
14constexpr std::chrono::nanoseconds stateChangeTimeout = 1s;
15constexpr int bufferSizeInBursts = 3; // Triple buffering
16
17static aaudio_format_t aaudioFormat(const QAudioFormat::SampleFormat sampleFormat)
18{
19 switch (sampleFormat) {
20 case QAudioFormat::Int16:
21 case QAudioFormat::UInt8: // NOTE: Requires conversion
22 return AAUDIO_FORMAT_PCM_I16;
23 case QAudioFormat::Int32:
24 return AAUDIO_FORMAT_PCM_I32;
25 case QAudioFormat::Float:
26 return AAUDIO_FORMAT_PCM_FLOAT;
27 case QAudioFormat::Unknown:
28 case QAudioFormat::NSampleFormats:
29 qWarning() << "Sample format not supported by AAudio, needs converting";
30 return AAUDIO_FORMAT_INVALID;
31 }
32}
33
34void setMMapPolicy(int policy)
35{
36 if (QNativeInterface::QAndroidApplication::sdkVersion() < 36)
37 return;
38
39 auto handle = dlopen("libaaudio.so", RTLD_NOW);
40 Q_ASSERT(handle);
41 auto guard = qScopeGuard([handle] {
42 dlclose(handle);
43 });
44
45 auto getPolicy = (int (*)(void))dlsym(handle, "AAudio_getMMapPolicy");
46 Q_ASSERT(getPolicy);
47 auto currentPolicy = getPolicy();
48 if (currentPolicy == policy)
49 return; // No need to set
50
51 auto setPolicy = (aaudio_result_t (*)(int))dlsym(handle, "AAudio_setMMapPolicy");
52 Q_ASSERT(setPolicy);
53 setPolicy(policy);
54};
55
56} // namespace
57
58namespace QtAAudio {
59
60Q_LOGGING_CATEGORY(qLcAAudioStream, "qt.multimedia.android.aaudiostream") // FIXME: Deprecated
61
62StreamBuilder::StreamBuilder(QAudioFormat format)
63 : format{ format }
64{
67}
68
70{
71 AAudioStreamBuilder_delete(m_builder);
72}
73
75{
76 // Set device
77 AAudioStreamBuilder_setDeviceId(m_builder, deviceId);
78
79 // Set buffer capacity
80 AAudioStreamBuilder_setBufferCapacityInFrames(m_builder, bufferCapacity);
81 // TODO: Tuning buffer size at run-time?
82
83 // Set parameters from audio format
84 AAudioStreamBuilder_setSampleRate(m_builder, format.sampleRate());
85 AAudioStreamBuilder_setChannelCount(m_builder, format.channelCount());
86 AAudioStreamBuilder_setFormat(m_builder, aaudioFormat(format.sampleFormat()));
87
88 // Set callbacks
89 AAudioStreamBuilder_setDataCallback(m_builder, callback, userData);
90 AAudioStreamBuilder_setErrorCallback(m_builder, errorCallback, userData);
91
92 // Set other parameters if not default
93 StreamParameterSet defaultParams;
94 if (params.sharingMode != defaultParams.sharingMode)
95 AAudioStreamBuilder_setSharingMode(m_builder, params.sharingMode);
96 // Set performance mode to low latency if mmap policy cannot be set
97 if (params.direction == AAUDIO_DIRECTION_OUTPUT) {
98 AAudioStreamBuilder_setPerformanceMode(m_builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
99 setMMapPolicy(2); // Also set MMap policy to AUTO
100 if (params.outputUsage != defaultParams.outputUsage)
101 AAudioStreamBuilder_setUsage(m_builder, params.outputUsage);
102 if (params.outputContentType != defaultParams.outputContentType)
103 AAudioStreamBuilder_setContentType(m_builder, params.outputContentType);
104 } else {
105 AAudioStreamBuilder_setDirection(m_builder, params.direction);
106 if (params.inputPreset != defaultParams.inputPreset)
107 AAudioStreamBuilder_setInputPreset(m_builder, params.inputPreset);
108 }
109}
110
112{
113 // Request stream open
114 auto result = AAudioStreamBuilder_openStream(builder.m_builder, &m_stream);
115 if (result == AAUDIO_ERROR_INVALID_FORMAT && builder.format.sampleFormat() != QAudioFormat::Float) {
116 // Try opening with a float sample format, which is more likely to be supported
117 // NOTE: We should check sample format after opening in sink/source to adjust
118 // nativeSampleFormat
119 builder.format.setSampleFormat(QAudioFormat::Float);
120 builder.setupBuilder();
121 result = AAudioStreamBuilder_openStream(builder.m_builder, &m_stream);
122 }
123
124 if (result != AAUDIO_OK) {
125 qCWarning(qLcAAudioStream)
126 << "Opening stream failed:" << AAudio_convertResultToText(result);
127 if (isOpen())
128 close();
129 return;
130 }
131
132 qCDebug(qLcAAudioStream) << "Stream opened:" << m_stream << "for device"
133 << AAudioStream_getDeviceId(m_stream);
134
135 // Verify parameters
136 if (builder.deviceId != AAUDIO_UNSPECIFIED
137 && AAudioStream_getDeviceId(m_stream) != builder.deviceId) {
138 qCWarning(qLcAAudioStream) << "Stream device not correct";
139 if (isOpen())
140 close();
141 return;
142 }
143
144 Q_ASSERT(AAudioStream_getSampleRate(m_stream) == builder.format.sampleRate()
145 && AAudioStream_getChannelCount(m_stream) == builder.format.channelCount()
146 && AAudioStream_getFormat(m_stream) == aaudioFormat(builder.format.sampleFormat()));
147
148 StreamParameterSet defaultParams;
149 if ((builder.params.sharingMode == defaultParams.sharingMode
150 || AAudioStream_getSharingMode(m_stream) == builder.params.sharingMode)
151 && (builder.params.direction == defaultParams.direction
152 || AAudioStream_getDirection(m_stream) == builder.params.direction)
153 && (builder.params.outputUsage == defaultParams.outputUsage
154 || AAudioStream_getUsage(m_stream) == builder.params.outputUsage)
155 && (builder.params.outputContentType == defaultParams.outputContentType
156 || AAudioStream_getContentType(m_stream) == builder.params.outputContentType))
157 m_areStreamParametersRespected = true;
158
159 if (builder.params.direction == AAUDIO_DIRECTION_OUTPUT
160 && AAudioStream_getPerformanceMode(m_stream) != AAUDIO_PERFORMANCE_MODE_LOW_LATENCY)
161 qCWarning(qLcAAudioStream) << "Low latency performance mode not set";
162
163 // Set buffer size
164 auto burstSize = AAudioStream_getFramesPerBurst(m_stream);
165 AAudioStream_setBufferSizeInFrames(m_stream, burstSize * bufferSizeInBursts);
166}
167
169{
170 if (isOpen())
171 close();
172}
173
174bool Stream::start()
175{
176 auto result = requestWithExpectedState(AAudioStream_requestStart, AAUDIO_STREAM_STATE_STARTED);
177 return result == AAUDIO_OK;
178}
179
181{
182 // Plays pending data and stops
183 requestWithExpectedState(AAudioStream_requestStop, AAUDIO_STREAM_STATE_STOPPED);
184}
185
187{
188 // Will freeze data flow
189 requestWithExpectedState(AAudioStream_requestPause, AAUDIO_STREAM_STATE_PAUSED);
190}
191
193{
194 // Discards pending data
195 requestWithExpectedState(AAudioStream_requestFlush, AAUDIO_STREAM_STATE_FLUSHED);
196}
197
198bool Stream::isOpen() const
199{
200 return static_cast<bool>(m_stream);
201}
202
204{
205 return m_areStreamParametersRespected;
206}
207
208void Stream::close()
209{
210 Q_ASSERT(isOpen());
211
212 AAudioStream_close(m_stream);
213 m_stream = nullptr;
214}
215
216aaudio_result_t Stream::waitForTargetState(aaudio_stream_state_t targetState)
217{
218 Q_ASSERT(isOpen());
219
220 aaudio_result_t result = AAUDIO_OK;
221 aaudio_stream_state_t currentState = AAudioStream_getState(m_stream);
222 aaudio_stream_state_t inputState = currentState;
223
224 while (result == AAUDIO_OK && currentState != targetState) {
225 qCDebug(qLcAAudioStream) << "Waiting for state change:"
226 << AAudio_convertStreamStateToText(currentState) << " -> "
227 << AAudio_convertStreamStateToText(targetState);
228 result = AAudioStream_waitForStateChange(m_stream, inputState, &currentState,
229 stateChangeTimeout.count());
230 inputState = currentState;
231 }
232
233 return result;
234}
235
236template <typename Functor>
237aaudio_result_t Stream::requestWithExpectedState(Functor &&request, aaudio_stream_state_t expected)
238{
239 auto result = request(m_stream);
240 if (result != AAUDIO_OK) {
241 qCWarning(qLcAAudioStream) << "Request failed:" << AAudio_convertResultToText(result);
242 return result;
243 }
244
245 return waitForTargetState(expected);
246}
247
248} // namespace QtAAudio
249
250QT_END_NAMESPACE
constexpr int bufferSizeInBursts
void setMMapPolicy(int policy)
static aaudio_format_t aaudioFormat(const QAudioFormat::SampleFormat sampleFormat)
constexpr std::chrono::nanoseconds stateChangeTimeout
bool areStreamParametersRespected() const
Stream(StreamBuilder &builder)
bool isOpen() const