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_OK) {
116 qCWarning(qLcAAudioStream)
117 << "Opening stream failed:" << AAudio_convertResultToText(result);
118 if (isOpen())
119 close();
120 return;
121 }
122
123 qCDebug(qLcAAudioStream) << "Stream opened:" << m_stream << "for device"
124 << AAudioStream_getDeviceId(m_stream);
125
126 // Verify parameters
127 if (builder.deviceId != AAUDIO_UNSPECIFIED
128 && AAudioStream_getDeviceId(m_stream) != builder.deviceId) {
129 qCWarning(qLcAAudioStream) << "Stream device not correct";
130 if (isOpen())
131 close();
132 return;
133 }
134
135 Q_ASSERT(AAudioStream_getSampleRate(m_stream) == builder.format.sampleRate()
136 && AAudioStream_getChannelCount(m_stream) == builder.format.channelCount()
137 && AAudioStream_getFormat(m_stream) == aaudioFormat(builder.format.sampleFormat()));
138
139 StreamParameterSet defaultParams;
140 if ((builder.params.sharingMode == defaultParams.sharingMode
141 || AAudioStream_getSharingMode(m_stream) == builder.params.sharingMode)
142 && (builder.params.direction == defaultParams.direction
143 || AAudioStream_getDirection(m_stream) == builder.params.direction)
144 && (builder.params.outputUsage == defaultParams.outputUsage
145 || AAudioStream_getUsage(m_stream) == builder.params.outputUsage)
146 && (builder.params.outputContentType == defaultParams.outputContentType
147 || AAudioStream_getContentType(m_stream) == builder.params.outputContentType))
148 m_areStreamParametersRespected = true;
149
150 if (builder.params.direction == AAUDIO_DIRECTION_OUTPUT
151 && AAudioStream_getPerformanceMode(m_stream) != AAUDIO_PERFORMANCE_MODE_LOW_LATENCY)
152 qCWarning(qLcAAudioStream) << "Low latency performance mode not set";
153
154 // Set buffer size
155 auto burstSize = AAudioStream_getFramesPerBurst(m_stream);
156 AAudioStream_setBufferSizeInFrames(m_stream, burstSize * bufferSizeInBursts);
157}
158
160{
161 if (isOpen())
162 close();
163}
164
165bool Stream::start()
166{
167 auto result = requestWithExpectedState(AAudioStream_requestStart, AAUDIO_STREAM_STATE_STARTED);
168 return result == AAUDIO_OK;
169}
170
172{
173 // Plays pending data and stops
174 requestWithExpectedState(AAudioStream_requestStop, AAUDIO_STREAM_STATE_STOPPED);
175}
176
178{
179 // Will freeze data flow
180 requestWithExpectedState(AAudioStream_requestPause, AAUDIO_STREAM_STATE_PAUSED);
181}
182
184{
185 // Discards pending data
186 requestWithExpectedState(AAudioStream_requestFlush, AAUDIO_STREAM_STATE_FLUSHED);
187}
188
189bool Stream::isOpen() const
190{
191 return static_cast<bool>(m_stream);
192}
193
195{
196 return m_areStreamParametersRespected;
197}
198
199void Stream::close()
200{
201 Q_ASSERT(isOpen());
202
203 AAudioStream_close(m_stream);
204 m_stream = nullptr;
205}
206
207aaudio_result_t Stream::waitForTargetState(aaudio_stream_state_t targetState)
208{
209 Q_ASSERT(isOpen());
210
211 aaudio_result_t result = AAUDIO_OK;
212 aaudio_stream_state_t currentState = AAudioStream_getState(m_stream);
213 aaudio_stream_state_t inputState = currentState;
214
215 while (result == AAUDIO_OK && currentState != targetState) {
216 qCDebug(qLcAAudioStream) << "Waiting for state change:"
217 << AAudio_convertStreamStateToText(currentState) << " -> "
218 << AAudio_convertStreamStateToText(targetState);
219 result = AAudioStream_waitForStateChange(m_stream, inputState, &currentState,
220 stateChangeTimeout.count());
221 inputState = currentState;
222 }
223
224 return result;
225}
226
227template <typename Functor>
228aaudio_result_t Stream::requestWithExpectedState(Functor &&request, aaudio_stream_state_t expected)
229{
230 auto result = request(m_stream);
231 if (result != AAUDIO_OK) {
232 qCWarning(qLcAAudioStream) << "Request failed:" << AAudio_convertResultToText(result);
233 return result;
234 }
235
236 return waitForTargetState(expected);
237}
238
239} // namespace QtAAudio
240
241QT_END_NAMESPACE
constexpr int bufferSizeInBursts
void setMMapPolicy(int policy)
static aaudio_format_t aaudioFormat(const QAudioFormat::SampleFormat sampleFormat)
constexpr std::chrono::nanoseconds stateChangeTimeout
Stream(const StreamBuilder &builder)
bool areStreamParametersRespected() const
bool isOpen() const