1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /**
18 * This test starts an exclusive stream.
19 * Then a few seconds later it starts a second exclusive stream.
20 * The first stream should get stolen and they should both end up
21 * as SHARED streams.
22 * The test will print PASS or FAIL.
23 *
24 * If you plug in a headset during the test then you can get them to both
25 * open at almost the same time. This can result in a race condition.
26 * Both streams may try to automatically reopen their streams in EXCLUSIVE mode.
27 * The first stream will have its EXCLUSIVE stream stolen by the second stream.
28 * It will usually get disconnected between its Open and Start calls.
29 * This can also occur in normal use. But is unlikely because the window is very narrow.
30 * In this case, where two streams are responding to the same disconnect event,
31 * it will usually happen.
32 *
33 * Because the stream has not started, this condition will not trigger an onError callback.
34 * But the stream will get an error returned from AAudioStream_requestStart().
35 * The test uses this result to trigger a retry in the onError callback.
36 * That is the best practice for any app restarting a stream.
37 *
38 * You should see that both streams are advancing after the disconnect.
39 *
40 * The headset can connect using a 3.5 mm jack, or USB-C or Bluetooth.
41 *
42 * This test can be used with INPUT by using the -i command line option.
43 * Before running the test you will need to enter "adb root" so that
44 * you can have permission to record.
45 * Also the headset needs to have a microphone.
46 * Then the test should behave essentially the same.
47 */
48
49 #include <atomic>
50 #include <mutex>
51 #include <stdio.h>
52 #include <thread>
53 #include <unistd.h>
54
55 #include <android/log.h>
56
57 #include <aaudio/AAudio.h>
58 #include <aaudio/AAudioTesting.h>
59
60 #define DEFAULT_TIMEOUT_NANOS ((int64_t)1000000000)
61 #define SOLO_DURATION_MSEC 2000
62 #define DUET_DURATION_MSEC 8000
63 #define SLEEP_DURATION_MSEC 500
64
65 #define MODULE_NAME "stealAudio"
66 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
67
s_sharingModeToText(aaudio_sharing_mode_t mode)68 static const char * s_sharingModeToText(aaudio_sharing_mode_t mode) {
69 return (mode == AAUDIO_SHARING_MODE_EXCLUSIVE) ? "EXCLUSIVE"
70 : ((mode == AAUDIO_SHARING_MODE_SHARED) ? "SHARED"
71 : AAudio_convertResultToText(mode));
72 }
73
s_performanceModeToText(aaudio_performance_mode_t mode)74 static const char * s_performanceModeToText(aaudio_performance_mode_t mode) {
75 return (mode == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) ? "LOWLAT"
76 : ((mode == AAUDIO_PERFORMANCE_MODE_NONE) ? "NONE"
77 : AAudio_convertResultToText(mode));
78 }
79
80 static aaudio_data_callback_result_t s_myDataCallbackProc(
81 AAudioStream * /* stream */,
82 void *userData,
83 void *audioData,
84 int32_t numFrames);
85
86 static void s_myErrorCallbackProc(
87 AAudioStream *stream,
88 void *userData,
89 aaudio_result_t error);
90
91 class AudioEngine {
92 public:
93
AudioEngine(const char * name)94 AudioEngine(const char *name) {
95 mName = name;
96 }
97
98 // These counters are read and written by the callback and the main thread.
99 std::atomic<int32_t> framesCalled{};
100 std::atomic<int32_t> callbackCount{};
101 std::atomic<aaudio_sharing_mode_t> sharingMode{};
102 std::atomic<aaudio_performance_mode_t> performanceMode{};
103 std::atomic<bool> isMMap{false};
104
setMaxRetries(int maxRetries)105 void setMaxRetries(int maxRetries) {
106 mMaxRetries = maxRetries;
107 }
108
setOpenDelayMillis(int openDelayMillis)109 void setOpenDelayMillis(int openDelayMillis) {
110 mOpenDelayMillis = openDelayMillis;
111 }
112
restartStream()113 void restartStream() {
114 int retriesLeft = mMaxRetries;
115 aaudio_result_t result;
116 do {
117 closeAudioStream();
118 if (mOpenDelayMillis) usleep(mOpenDelayMillis * 1000);
119 openAudioStream(mDirection, mRequestedSharingMode);
120 // It is possible for the stream to be disconnected, or stolen between the time
121 // it is opened and when it is started. If that happens then try again.
122 // If it was stolen then it should succeed the second time because there will already be
123 // a SHARED stream, which will not get stolen.
124 result = AAudioStream_requestStart(mStream);
125 printf("%s: AAudioStream_requestStart() returns %s\n",
126 mName.c_str(),
127 AAudio_convertResultToText(result));
128 } while (retriesLeft-- > 0 && result != AAUDIO_OK);
129 }
130
onAudioReady(void *,int32_t numFrames)131 aaudio_data_callback_result_t onAudioReady(
132 void * /*audioData */,
133 int32_t numFrames) {
134 callbackCount++;
135 framesCalled += numFrames;
136 return AAUDIO_CALLBACK_RESULT_CONTINUE;
137 }
138
openAudioStream(aaudio_direction_t direction,aaudio_sharing_mode_t requestedSharingMode)139 aaudio_result_t openAudioStream(aaudio_direction_t direction,
140 aaudio_sharing_mode_t requestedSharingMode) {
141 std::lock_guard<std::mutex> lock(mLock);
142
143 AAudioStreamBuilder *builder = nullptr;
144 mDirection = direction;
145 mRequestedSharingMode = requestedSharingMode;
146
147 // Use an AAudioStreamBuilder to contain requested parameters.
148 aaudio_result_t result = AAudio_createStreamBuilder(&builder);
149 if (result != AAUDIO_OK) {
150 printf("AAudio_createStreamBuilder returned %s",
151 AAudio_convertResultToText(result));
152 return result;
153 }
154
155 // Request stream properties.
156 AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
157 AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
158 AAudioStreamBuilder_setSharingMode(builder, mRequestedSharingMode);
159 AAudioStreamBuilder_setDirection(builder, direction);
160 AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, this);
161 AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, this);
162
163 // Create an AAudioStream using the Builder.
164 result = AAudioStreamBuilder_openStream(builder, &mStream);
165 AAudioStreamBuilder_delete(builder);
166 builder = nullptr;
167 if (result != AAUDIO_OK) {
168 printf("AAudioStreamBuilder_openStream returned %s",
169 AAudio_convertResultToText(result));
170 }
171
172 // See what kind of stream we actually opened.
173 int32_t deviceId = AAudioStream_getDeviceId(mStream);
174 sharingMode = AAudioStream_getSharingMode(mStream);
175 performanceMode = AAudioStream_getPerformanceMode(mStream);
176 isMMap = AAudioStream_isMMapUsed(mStream);
177 printf("%s: opened: deviceId = %3d, sharingMode = %s, perf = %s, %s --------\n",
178 mName.c_str(),
179 deviceId,
180 s_sharingModeToText(sharingMode),
181 s_performanceModeToText(performanceMode),
182 (isMMap ? "MMAP" : "Legacy")
183 );
184
185 return result;
186 }
187
closeAudioStream()188 aaudio_result_t closeAudioStream() {
189 std::lock_guard<std::mutex> lock(mLock);
190 aaudio_result_t result = AAUDIO_OK;
191 if (mStream != nullptr) {
192 result = AAudioStream_close(mStream);
193 if (result != AAUDIO_OK) {
194 printf("AAudioStream_close returned %s\n",
195 AAudio_convertResultToText(result));
196 }
197 mStream = nullptr;
198 }
199 return result;
200 }
201
202 /**
203 * @return 0 is OK, -1 for error
204 */
checkEnginePositions()205 int checkEnginePositions() {
206 std::lock_guard<std::mutex> lock(mLock);
207 if (mStream == nullptr) return 0;
208
209 const int64_t framesRead = AAudioStream_getFramesRead(mStream);
210 const int64_t framesWritten = AAudioStream_getFramesWritten(mStream);
211 const int32_t delta = (int32_t)(framesWritten - framesRead);
212 printf("%s: playing framesRead = %7d, framesWritten = %7d"
213 ", delta = %4d, framesCalled = %6d, callbackCount = %4d\n",
214 mName.c_str(),
215 (int32_t) framesRead,
216 (int32_t) framesWritten,
217 delta,
218 framesCalled.load(),
219 callbackCount.load()
220 );
221 if (delta > AAudioStream_getBufferCapacityInFrames(mStream)) {
222 printf("ERROR - delta > capacity\n");
223 return -1;
224 }
225 return 0;
226 }
227
start()228 aaudio_result_t start() {
229 std::lock_guard<std::mutex> lock(mLock);
230 reset();
231 if (mStream == nullptr) return 0;
232 return AAudioStream_requestStart(mStream);
233 }
234
stop()235 aaudio_result_t stop() {
236 std::lock_guard<std::mutex> lock(mLock);
237 if (mStream == nullptr) return 0;
238 return AAudioStream_requestStop(mStream);
239 }
240
hasAdvanced()241 bool hasAdvanced() {
242 std::lock_guard<std::mutex> lock(mLock);
243 if (mStream == nullptr) return 0;
244 if (mDirection == AAUDIO_DIRECTION_OUTPUT) {
245 return AAudioStream_getFramesRead(mStream) > 0;
246 } else {
247 return AAudioStream_getFramesWritten(mStream) > 0;
248 }
249 }
250
verify()251 aaudio_result_t verify() {
252 int errorCount = 0;
253 if (hasAdvanced()) {
254 printf("%s: stream is running => PASS\n", mName.c_str());
255 } else {
256 errorCount++;
257 printf("%s: stream should be running => FAIL!!\n", mName.c_str());
258 }
259
260 if (isMMap) {
261 printf("%s: data path is MMAP => PASS\n", mName.c_str());
262 } else {
263 errorCount++;
264 printf("%s: data path is Legacy! => FAIL\n", mName.c_str());
265 }
266
267 // Check for PASS/FAIL
268 if (sharingMode == AAUDIO_SHARING_MODE_SHARED) {
269 printf("%s: mode is SHARED => PASS\n", mName.c_str());
270 } else {
271 errorCount++;
272 printf("%s: modes is EXCLUSIVE => FAIL!!\n", mName.c_str());
273 }
274 return errorCount ? AAUDIO_ERROR_INVALID_FORMAT : AAUDIO_OK;
275 }
276
277 private:
reset()278 void reset() {
279 framesCalled.store(0);
280 callbackCount.store(0);
281 }
282
283 AAudioStream *mStream = nullptr;
284 aaudio_direction_t mDirection = AAUDIO_DIRECTION_OUTPUT;
285 aaudio_sharing_mode_t mRequestedSharingMode = AAUDIO_UNSPECIFIED;
286 std::mutex mLock;
287 std::string mName;
288 int mMaxRetries = 1;
289 int mOpenDelayMillis = 0;
290 };
291
292 // Callback function that fills the audio output buffer.
s_myDataCallbackProc(AAudioStream *,void * userData,void * audioData,int32_t numFrames)293 static aaudio_data_callback_result_t s_myDataCallbackProc(
294 AAudioStream * /* stream */,
295 void *userData,
296 void *audioData,
297 int32_t numFrames
298 ) {
299 AudioEngine *engine = (AudioEngine *)userData;
300 return engine->onAudioReady(audioData, numFrames);
301 }
302
s_myRestartStreamProc(void * userData)303 static void s_myRestartStreamProc(void *userData) {
304 LOGI("%s() called", __func__);
305 printf("%s() - restart in separate thread\n", __func__);
306 AudioEngine *engine = (AudioEngine *) userData;
307 engine->restartStream();
308 }
309
s_myErrorCallbackProc(AAudioStream *,void * userData,aaudio_result_t error)310 static void s_myErrorCallbackProc(
311 AAudioStream * /* stream */,
312 void *userData,
313 aaudio_result_t error) {
314 LOGI("%s() called", __func__);
315 printf("%s() - error = %s\n", __func__, AAudio_convertResultToText(error));
316 // Handle error on a separate thread.
317 std::thread t(s_myRestartStreamProc, userData);
318 t.detach();
319 }
320
s_usage()321 static void s_usage() {
322 printf("test_steal_exclusive [-i] [-r{maxRetries}] [-d{delay}] -s\n");
323 printf(" -i direction INPUT, otherwise OUTPUT\n");
324 printf(" -d delay open by milliseconds, default = 0\n");
325 printf(" -r max retries in the error callback, default = 1\n");
326 printf(" -s try to open in SHARED mode\n");
327 }
328
main(int argc,char ** argv)329 int main(int argc, char ** argv) {
330 AudioEngine victim("victim");
331 AudioEngine thief("thief");
332 aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT;
333 aaudio_result_t result = AAUDIO_OK;
334 int errorCount = 0;
335 int maxRetries = 1;
336 int openDelayMillis = 0;
337 aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
338
339 // Make printf print immediately so that debug info is not stuck
340 // in a buffer if we hang or crash.
341 setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
342
343 printf("Test interaction between streams V1.1\n");
344 printf("\n");
345
346 for (int i = 1; i < argc; i++) {
347 const char *arg = argv[i];
348 if (arg[0] == '-') {
349 char option = arg[1];
350 switch (option) {
351 case 'd':
352 openDelayMillis = atoi(&arg[2]);
353 break;
354 case 'i':
355 direction = AAUDIO_DIRECTION_INPUT;
356 break;
357 case 'r':
358 maxRetries = atoi(&arg[2]);
359 break;
360 case 's':
361 requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
362 break;
363 default:
364 s_usage();
365 exit(EXIT_FAILURE);
366 break;
367 }
368 } else {
369 s_usage();
370 exit(EXIT_FAILURE);
371 break;
372 }
373 }
374
375 victim.setOpenDelayMillis(openDelayMillis);
376 thief.setOpenDelayMillis(openDelayMillis);
377 victim.setMaxRetries(maxRetries);
378 thief.setMaxRetries(maxRetries);
379
380 result = victim.openAudioStream(direction, requestedSharingMode);
381 if (result != AAUDIO_OK) {
382 printf("s_OpenAudioStream victim returned %s\n",
383 AAudio_convertResultToText(result));
384 errorCount++;
385 }
386
387 if (victim.sharingMode == requestedSharingMode) {
388 printf("Victim modes is %s => OK\n", s_sharingModeToText(requestedSharingMode));
389 } else {
390 printf("Victim modes should be %s => test not valid!\n",
391 s_sharingModeToText(requestedSharingMode));
392 goto onerror;
393 }
394
395 if (victim.isMMap) {
396 printf("Victim data path is MMAP => OK\n");
397 } else {
398 printf("Victim data path is Legacy! => test not valid\n");
399 goto onerror;
400 }
401
402 // Start stream.
403 result = victim.start();
404 printf("AAudioStream_requestStart(VICTIM) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
405 if (result != AAUDIO_OK) {
406 errorCount++;
407 }
408
409 if (result == AAUDIO_OK) {
410 const int watchLoops = SOLO_DURATION_MSEC / SLEEP_DURATION_MSEC;
411 for (int i = watchLoops; i > 0; i--) {
412 errorCount += victim.checkEnginePositions() ? 1 : 0;
413 usleep(SLEEP_DURATION_MSEC * 1000);
414 }
415 }
416
417 printf("Trying to start the THIEF stream, which may steal the VICTIM MMAP resource -----\n");
418 result = thief.openAudioStream(direction, requestedSharingMode);
419 if (result != AAUDIO_OK) {
420 printf("s_OpenAudioStream victim returned %s\n",
421 AAudio_convertResultToText(result));
422 errorCount++;
423 }
424
425 // Start stream.
426 result = thief.start();
427 printf("AAudioStream_requestStart(THIEF) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
428 if (result != AAUDIO_OK) {
429 errorCount++;
430 }
431
432 // Give stream time to advance.
433 usleep(SLEEP_DURATION_MSEC * 1000);
434
435 if (victim.verify()) {
436 errorCount++;
437 goto onerror;
438 }
439 if (thief.verify()) {
440 errorCount++;
441 goto onerror;
442 }
443
444 LOGI("Both streams running. Ask user to plug in headset. ====");
445 printf("\n====\nPlease PLUG IN A HEADSET now!\n====\n\n");
446
447 if (result == AAUDIO_OK) {
448 const int watchLoops = DUET_DURATION_MSEC / SLEEP_DURATION_MSEC;
449 for (int i = watchLoops; i > 0; i--) {
450 errorCount += victim.checkEnginePositions() ? 1 : 0;
451 errorCount += thief.checkEnginePositions() ? 1 : 0;
452 usleep(SLEEP_DURATION_MSEC * 1000);
453 }
454 }
455
456 errorCount += victim.verify() ? 1 : 0;
457 errorCount += thief.verify() ? 1 : 0;
458
459 result = victim.stop();
460 printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
461 if (result != AAUDIO_OK) {
462 printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
463 errorCount++;
464 }
465 result = thief.stop();
466 printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
467 if (result != AAUDIO_OK) {
468 printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
469 errorCount++;
470 }
471
472 onerror:
473 victim.closeAudioStream();
474 thief.closeAudioStream();
475
476 printf("aaudio result = %d = %s\n", result, AAudio_convertResultToText(result));
477 printf("test %s\n", errorCount ? "FAILED" : "PASSED");
478
479 return errorCount ? EXIT_FAILURE : EXIT_SUCCESS;
480 }
481