aboutsummaryrefslogtreecommitdiff
path: root/src/devices/audio_avoiding_pi.jd
blob: 184a1509c5e4c48db4555bb4722d710addb14c91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
page.title=Avoiding Priority Inversion
@jd:body

<div id="qv-wrapper">
  <div id="qv">
    <h2>In this document</h2>
    <ol id="auto-toc">
    </ol>
  </div>
</div>

<p>
This article explains how the Android's audio system attempts to avoid
priority inversion, as of the Android 4.1 (Jellybean) release,
and highlights techniques that you can use too.
</p>

<p>
These techniques may be useful to developers of high-performance
audio apps, OEMs, and SoC providers who are implementing an audio
HAL. Please note that implementing these techniques is not
guaranteed to prevent glitches or other failures, particularly if
used outside of the audio context.
Your results may vary and you should conduct your own
evaluation and testing.
</p>

<h2 id="background">Background</h2>

<p>
The Android audio server "AudioFlinger" and AudioTrack/AudioRecord
client implementation are being re-architected to reduce latency.
This work started in Android 4.1 (Jellybean), continued in 4.2
(Jellybean MR1), and more improvements are likely in "K".
</p>

<p>
The lower latency needed many changes throughout the system. One
important change was to  assign CPU resources to time-critical
threads with a more predictable scheduling policy. Reliable scheduling
allows the audio buffer sizes and counts to be reduced, while still
avoiding artifacts due to underruns.
</p>

<h2 id="priorityInversion">Priority Inversion</h2>

<p>
<a href="http://en.wikipedia.org/wiki/Priority_inversion">Priority inversion</a>
is a classic failure mode of real-time systems,
where a higher-priority task is blocked for an unbounded time waiting
for a lower-priority task to release a resource such as [shared
state protected by] a
<a href="http://en.wikipedia.org/wiki/Mutual_exclusion">mutex</a>.
</p>

<p>
In an audio system, priority inversion typically manifests as a
<a href="http://en.wikipedia.org/wiki/Glitch">glitch</a>
(click, pop, dropout),
<a href="http://en.wikipedia.org/wiki/Max_Headroom_(character)">repeated audio</a>
when circular buffers
are used, or delay in responding to a command.
</p>

<p>
In the Android audio implementation, priority inversion is most
likely to occur in these places, and so we focus attention here:
</p>

<ul>

<li>
between normal mixer thread and fast mixer thread in AudioFlinger
</li>

<li>
between application callback thread for a fast AudioTrack and
fast mixer thread (they both have elevated priority, but slightly
different priorities)
</li>

<li>
within the audio HAL implementation, e.g. for telephony or echo cancellation
</li>

<li>
within the audio driver in kernel
</li>

<li>
between AudioTrack callback thread and other app threads (this is out of our control)
</li>

</ul>

<p>
As of this writing, reduced latency for AudioRecord is planned but
not yet implemented. The likely priority inversion spots will be
similar to those for AudioTrack.
</p>

<h2 id="commonSolutions">Common Solutions</h2>

<p>
The typical solutions listed in the Wikipedia article include:
</p>

<ul>

<li>
disabling interrupts
</li>

<li>
priority inheritance mutexes
</li>

</ul>

<p>
Disabling interrupts is not feasible in Linux user space, and does
not work for SMP.
</p>


<p>
Priority inheritance
<a href="http://en.wikipedia.org/wiki/Futex">futexes</a>
(fast user-space mutexes) are available
in Linux kernel, but are not currently exposed by the Android C
runtime library
<a href="http://en.wikipedia.org/wiki/Bionic_(software)">Bionic</a>.
We chose not to use them in the audio system
because they are relatively heavyweight, and because they rely on
a trusted client.
</p>

<h2 id="androidTechniques">Techniques used by Android</h2>

<p>
We started with "try lock" and lock with timeout. These are
non-blocking and bounded blocking variants of the mutex lock
operation. Try lock and lock with timeout worked fairly well for
us, but were susceptible to a couple of obscure failure modes: the
server was not guaranteed to be able to access the shared state if
the client happened to be busy, and the cumulative timeout could
be too long if there was a long sequence of unrelated locks that
all timed out.
</p>


<p>
We also use
<a href="http://en.wikipedia.org/wiki/Linearizability">atomic operations</a>
such as:
</p>

<ul>
<li>increment</li>
<li>bitwise "or"</li>
<li>bitwise "and"</li>
</ul>

<p>
All of these return the previous value, and include the necessary
SMP barriers. The disadvantage is they can require unbounded retries.
In practice, we've found that the retries are not a problem.
</p>

<p>
Note: atomic operations and their interactions with memory barriers
are notoriously badly misunderstood and used incorrectly. We include
these here for completeness, but recommend you also read the article
<a href="https://developer.android.com/training/articles/smp.html">
SMP Primer for Android</a>
for further information.
</p>

<p>
We still have and use most of the above tools, and have recently
added these techniques:
</p>

<ul>

<li>
Use non-blocking single-reader single-writer
<a href="http://en.wikipedia.org/wiki/Circular_buffer">FIFO queues</a>
for data.
</li>

<li>
Try to
<i>copy</i>
state rather than
<i>share</i>
state between high- and
low-priority modules.
</li>

<li>
When state does need to be shared, limit the state to the
maximum-size
<a href="http://en.wikipedia.org/wiki/Word_(computer_architecture)">word</a>
that can be accessed atomically in one bus operation
without retries.
</li>

<li>
For complex multi-word state, use a state queue. A state queue
is basically just a non-blocking single-reader single-writer FIFO
queue used for state rather than data, except the writer collapses
adjacent pushes into a single push.
</li>

<li>
Pay attention to
<a href="http://en.wikipedia.org/wiki/Memory_barrier">memory barriers</a>
for SMP correctness.
</li>

<li>
<a href="http://en.wikipedia.org/wiki/Trust,_but_verify">Trust, but verify</a>.
When sharing
<i>state</i>
between processes, don't
assume that the state is well-formed. For example, check that indices
are within bounds. This verification isn't needed between threads
in the same process, between mutual trusting processes (which
typically have the same UID). It's also unnecessary for shared
<i>data</i>
such as PCM audio where a corruption is inconsequential.
</li>

</ul>

<h2 id="nonBlockingAlgorithms">Non-Blocking Algorithms</h2>

<p>
<a href="http://en.wikipedia.org/wiki/Non-blocking_algorithm">Non-blocking algorithms</a>
have been a subject of much recent study.
But with the exception of single-reader single-writer FIFO queues,
we've found them to be complex and error-prone.
</p>

<p>
In Android 4.2 (Jellybean MR1), you can find our non-blocking,
single-reader/writer classes in these locations:
</p>

<ul>

<li>
frameworks/av/include/media/nbaio/
</li>

<li>
frameworks/av/media/libnbaio/
</li>

<li>
frameworks/av/services/audioflinger/StateQueue*
</li>

</ul>

<p>
These were designed specifically for AudioFlinger and are not
general-purpose. Non-blocking algorithms are notorious for being
difficult to debug. You can look at this code as a model, but be
aware there may be bugs, and the classes are not guaranteed to be
suitable for other purposes.
</p>

<p>
For developers, we may update some of the sample OpenSL ES application
code to use non-blocking, or referencing a non-Android open source
library.
</p>

<h2 id="tools">Tools</h2>

<p>
To the best of our knowledge, there are no automatic tools for
finding priority inversion, especially before it happens. Some
research static code analysis tools are capable of finding priority
inversions if able to access the entire codebase. Of course, if
arbitrary user code is involved (as it is here for the application)
or is a large codebase (as for the Linux kernel and device drivers),
static analysis may be impractical. The most important thing is to
read the code very carefully and get a good grasp on the entire
system and the interactions. Tools such as
<a href="http://developer.android.com/tools/help/systrace.html">systrace</a>
and
<code>ps -t -p</code>
are useful for seeing priority inversion after it occurs, but do
not tell you in advance.
</p>

<h2 id="aFinalWord">A Final Word</h2>

<p>
After all of this discussion, don't be afraid of mutexes. Mutexes
are your friend for ordinary use, when used and implemented correctly
in ordinary non-time-critical use cases. But between high- and
low-priority tasks and in time-sensitive systems mutexes are more
likely to cause trouble.
</p>