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
setCloseEnabled(bool enabled)113 void setCloseEnabled(bool enabled) {
114 mCloseEnabled = enabled;
115 }
116
restartStream()117 aaudio_result_t restartStream() {
118 int retriesLeft = mMaxRetries;
119 aaudio_result_t result;
120 do {
121 closeAudioStream();
122 if (mOpenDelayMillis) usleep(mOpenDelayMillis * 1000);
123 openAudioStream(mDirection, mRequestedSharingMode);
124 // It is possible for the stream to be disconnected, or stolen between the time
125 // it is opened and when it is started. If that happens then try again.
126 // If it was stolen then it should succeed the second time because there will already be
127 // a SHARED stream, which will not get stolen.
128 result = AAudioStream_requestStart(mStream);
129 printf("%s: AAudioStream_requestStart() returns %s\n",
130 mName.c_str(),
131 AAudio_convertResultToText(result));
132 } while (retriesLeft-- > 0 && result != AAUDIO_OK);
133 return result;
134 }
135
onAudioReady(void *,int32_t numFrames)136 aaudio_data_callback_result_t onAudioReady(
137 void * /*audioData */,
138 int32_t numFrames) {
139 callbackCount++;
140 framesCalled += numFrames;
141 return AAUDIO_CALLBACK_RESULT_CONTINUE;
142 }
143
openAudioStream(aaudio_direction_t direction,aaudio_sharing_mode_t requestedSharingMode)144 aaudio_result_t openAudioStream(aaudio_direction_t direction,
145 aaudio_sharing_mode_t requestedSharingMode) {
146 std::lock_guard<std::mutex> lock(mLock);
147
148 AAudioStreamBuilder *builder = nullptr;
149 mDirection = direction;
150 mRequestedSharingMode = requestedSharingMode;
151
152 // Use an AAudioStreamBuilder to contain requested parameters.
153 aaudio_result_t result = AAudio_createStreamBuilder(&builder);
154 if (result != AAUDIO_OK) {
155 printf("AAudio_createStreamBuilder returned %s",
156 AAudio_convertResultToText(result));
157 return result;
158 }
159
160 // Request stream properties.
161 AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
162 AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
163 AAudioStreamBuilder_setSharingMode(builder, mRequestedSharingMode);
164 AAudioStreamBuilder_setDirection(builder, direction);
165 AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, this);
166 AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, this);
167
168 // Create an AAudioStream using the Builder.
169 result = AAudioStreamBuilder_openStream(builder, &mStream);
170 AAudioStreamBuilder_delete(builder);
171 builder = nullptr;
172 if (result != AAUDIO_OK) {
173 printf("AAudioStreamBuilder_openStream returned %s",
174 AAudio_convertResultToText(result));
175 }
176
177 // See what kind of stream we actually opened.
178 int32_t deviceId = AAudioStream_getDeviceId(mStream);
179 sharingMode = AAudioStream_getSharingMode(mStream);
180 performanceMode = AAudioStream_getPerformanceMode(mStream);
181 isMMap = AAudioStream_isMMapUsed(mStream);
182 printf("%s: opened: deviceId = %3d, sharingMode = %s, perf = %s, %s --------\n",
183 mName.c_str(),
184 deviceId,
185 s_sharingModeToText(sharingMode),
186 s_performanceModeToText(performanceMode),
187 (isMMap ? "MMAP" : "Legacy")
188 );
189
190 return result;
191 }
192
closeAudioStream()193 aaudio_result_t closeAudioStream() {
194 std::lock_guard<std::mutex> lock(mLock);
195 aaudio_result_t result = AAUDIO_OK;
196 if (mStream != nullptr) {
197 if (mCloseEnabled) {
198 result = AAudioStream_close(mStream);
199 printf("AAudioStream_close() returned %s\n",
200 AAudio_convertResultToText(result));
201 } else {
202 printf("AAudioStream_close() DISABLED!\n");
203 }
204 mStream = nullptr;
205 }
206 return result;
207 }
208
209 /**
210 * @return 0 is OK, -1 for error
211 */
checkEnginePositions()212 int checkEnginePositions() {
213 std::lock_guard<std::mutex> lock(mLock);
214 if (mStream == nullptr) return 0;
215
216 const int64_t framesRead = AAudioStream_getFramesRead(mStream);
217 const int64_t framesWritten = AAudioStream_getFramesWritten(mStream);
218 const int32_t delta = (int32_t)(framesWritten - framesRead);
219 printf("%s: playing framesRead = %7d, framesWritten = %7d"
220 ", delta = %4d, framesCalled = %6d, callbackCount = %4d\n",
221 mName.c_str(),
222 (int32_t) framesRead,
223 (int32_t) framesWritten,
224 delta,
225 framesCalled.load(),
226 callbackCount.load()
227 );
228 if (delta > AAudioStream_getBufferCapacityInFrames(mStream)) {
229 printf("ERROR - delta > capacity\n");
230 return -1;
231 }
232 return 0;
233 }
234
start()235 aaudio_result_t start() {
236 std::lock_guard<std::mutex> lock(mLock);
237 reset();
238 if (mStream == nullptr) return 0;
239 return AAudioStream_requestStart(mStream);
240 }
241
pause()242 aaudio_result_t pause() {
243 std::lock_guard<std::mutex> lock(mLock);
244 if (mStream == nullptr) return 0;
245 return AAudioStream_requestPause(mStream);
246 }
247
stop()248 aaudio_result_t stop() {
249 std::lock_guard<std::mutex> lock(mLock);
250 if (mStream == nullptr) return 0;
251 return AAudioStream_requestStop(mStream);
252 }
253
hasAdvanced()254 bool hasAdvanced() {
255 std::lock_guard<std::mutex> lock(mLock);
256 if (mStream == nullptr) return 0;
257 if (mDirection == AAUDIO_DIRECTION_OUTPUT) {
258 return AAudioStream_getFramesRead(mStream) > 0;
259 } else {
260 return AAudioStream_getFramesWritten(mStream) > 0;
261 }
262 }
263
verify()264 aaudio_result_t verify() {
265 int errorCount = 0;
266 if (hasAdvanced()) {
267 printf("%s: stream is running => PASS\n", mName.c_str());
268 } else {
269 errorCount++;
270 printf("%s: stream should be running => FAIL!!\n", mName.c_str());
271 }
272
273 if (isMMap) {
274 printf("%s: data path is MMAP => PASS\n", mName.c_str());
275 } else {
276 errorCount++;
277 printf("%s: data path is Legacy! => FAIL\n", mName.c_str());
278 }
279
280 // Check for PASS/FAIL
281 if (sharingMode == AAUDIO_SHARING_MODE_SHARED) {
282 printf("%s: mode is SHARED => PASS\n", mName.c_str());
283 } else {
284 errorCount++;
285 printf("%s: modes is EXCLUSIVE => FAIL!!\n", mName.c_str());
286 }
287 return errorCount ? AAUDIO_ERROR_INVALID_FORMAT : AAUDIO_OK;
288 }
289
290 private:
reset()291 void reset() {
292 framesCalled.store(0);
293 callbackCount.store(0);
294 }
295
296 AAudioStream *mStream = nullptr;
297 aaudio_direction_t mDirection = AAUDIO_DIRECTION_OUTPUT;
298 aaudio_sharing_mode_t mRequestedSharingMode = AAUDIO_UNSPECIFIED;
299 std::mutex mLock;
300 std::string mName;
301 int mMaxRetries = 1;
302 int mOpenDelayMillis = 0;
303 bool mCloseEnabled = true;
304 };
305
306 // Callback function that fills the audio output buffer.
s_myDataCallbackProc(AAudioStream *,void * userData,void * audioData,int32_t numFrames)307 static aaudio_data_callback_result_t s_myDataCallbackProc(
308 AAudioStream * /* stream */,
309 void *userData,
310 void *audioData,
311 int32_t numFrames
312 ) {
313 AudioEngine *engine = (AudioEngine *)userData;
314 return engine->onAudioReady(audioData, numFrames);
315 }
316
s_myRestartStreamProc(void * userData)317 static void s_myRestartStreamProc(void *userData) {
318 LOGI("%s() called", __func__);
319 printf("%s() - restart in separate thread\n", __func__);
320 AudioEngine *engine = (AudioEngine *) userData;
321 engine->restartStream();
322 }
323
s_myErrorCallbackProc(AAudioStream *,void * userData,aaudio_result_t error)324 static void s_myErrorCallbackProc(
325 AAudioStream * /* stream */,
326 void *userData,
327 aaudio_result_t error) {
328 LOGI("%s() called", __func__);
329 printf("%s() - error = %s\n", __func__, AAudio_convertResultToText(error));
330 // Handle error on a separate thread.
331 std::thread t(s_myRestartStreamProc, userData);
332 t.detach();
333 }
334
s_usage()335 static void s_usage() {
336 printf("test_steal_exclusive [-i] [-r{maxRetries}] [-d{delay}] [-p{pausedTime}]-s -c{flag}\n");
337 printf(" -i direction INPUT, otherwise OUTPUT\n");
338 printf(" -d Delay open by milliseconds, default = 0\n");
339 printf(" -p Pause first stream then sleep for msec before opening second streams, default = 0\n");
340 printf(" -r max Retries in the error callback, default = 1\n");
341 printf(" -s try to open in SHARED mode\n");
342 printf(" -c enable or disabling Closing of the stream with 0/1, default = 1\n");
343 }
344
main(int argc,char ** argv)345 int main(int argc, char ** argv) {
346 AudioEngine victim("victim");
347 AudioEngine thief("thief");
348 aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT;
349 aaudio_result_t result = AAUDIO_OK;
350 int errorCount = 0;
351 int maxRetries = 1;
352 int openDelayMillis = 0;
353 bool closeEnabled = true;
354 aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
355 int pausedMillis = 0;
356
357 // Make printf print immediately so that debug info is not stuck
358 // in a buffer if we hang or crash.
359 setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
360
361 printf("Test interaction between streams V1.1\n");
362 printf("\n");
363
364 for (int i = 1; i < argc; i++) {
365 const char *arg = argv[i];
366 if (arg[0] == '-') {
367 char option = arg[1];
368 switch (option) {
369 case 'c':
370 closeEnabled = atoi(&arg[2]) != 0;
371 break;
372 case 'd':
373 openDelayMillis = atoi(&arg[2]);
374 break;
375 case 'i':
376 direction = AAUDIO_DIRECTION_INPUT;
377 break;
378 case 'p':
379 pausedMillis = atoi(&arg[2]);
380 break;
381 case 'r':
382 maxRetries = atoi(&arg[2]);
383 break;
384 case 's':
385 requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
386 break;
387 default:
388 s_usage();
389 exit(EXIT_FAILURE);
390 break;
391 }
392 } else {
393 s_usage();
394 exit(EXIT_FAILURE);
395 break;
396 }
397 }
398
399 victim.setOpenDelayMillis(openDelayMillis);
400 thief.setOpenDelayMillis(openDelayMillis);
401 victim.setMaxRetries(maxRetries);
402 thief.setMaxRetries(maxRetries);
403 victim.setCloseEnabled(closeEnabled);
404 thief.setCloseEnabled(closeEnabled);
405
406 result = victim.openAudioStream(direction, requestedSharingMode);
407 if (result != AAUDIO_OK) {
408 printf("s_OpenAudioStream victim returned %s\n",
409 AAudio_convertResultToText(result));
410 errorCount++;
411 }
412
413 if (victim.sharingMode == requestedSharingMode) {
414 printf("Victim modes is %s => OK\n", s_sharingModeToText(requestedSharingMode));
415 } else {
416 printf("Victim modes should be %s => test not valid!\n",
417 s_sharingModeToText(requestedSharingMode));
418 goto onerror;
419 }
420
421 if (victim.isMMap) {
422 printf("Victim data path is MMAP => OK\n");
423 } else {
424 printf("Victim data path is Legacy! => test not valid\n");
425 goto onerror;
426 }
427
428 // Start stream.
429 result = victim.start();
430 printf("AAudioStream_requestStart(VICTIM) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
431 if (result != AAUDIO_OK) {
432 errorCount++;
433 }
434
435 if (result == AAUDIO_OK) {
436 const int watchLoops = SOLO_DURATION_MSEC / SLEEP_DURATION_MSEC;
437 for (int i = watchLoops; i > 0; i--) {
438 errorCount += victim.checkEnginePositions() ? 1 : 0;
439 usleep(SLEEP_DURATION_MSEC * 1000);
440 }
441 }
442
443 if (pausedMillis > 0) {
444 printf("Pausing the VICTIM for %d millis before starting THIEF -----\n", pausedMillis);
445 victim.pause();
446 usleep(pausedMillis * 1000);
447 }
448
449 printf("Trying to start the THIEF stream, which may steal the VICTIM MMAP resource -----\n");
450 result = thief.openAudioStream(direction, requestedSharingMode);
451 if (result != AAUDIO_OK) {
452 printf("s_OpenAudioStream victim returned %s\n",
453 AAudio_convertResultToText(result));
454 errorCount++;
455 }
456
457 // Start stream.
458 result = thief.start();
459 printf("AAudioStream_requestStart(THIEF) returned %d >>>>>>>>>>>>>>>>>>>>>>\n", result);
460 if (result != AAUDIO_OK) {
461 errorCount++;
462 }
463
464 if (pausedMillis > 0) {
465 result = victim.start();
466 printf("Restarting VICTIM, AAudioStream_requestStart(VICTIM) returned %d "
467 ">>>>>>>>>>>>>>>>>>>>>>\n", result);
468 if (result == AAUDIO_ERROR_DISCONNECTED) {
469 // The stream is disconnected due to thief steal the resource
470 printf("VICTIM was disconnected while hanging as the THIEF "
471 "stole the resource >>>>>>>>>>>>>>>>>>>>>>\n");
472 result = victim.restartStream();
473 printf("Restarting VICTIM, AAudioStream_requestStart(VICTIM) returned %d "
474 ">>>>>>>>>>>>>>>>>>>>>>\n", result);
475 if (result != AAUDIO_OK) {
476 errorCount++;
477 }
478 } else {
479 errorCount++;
480 }
481 }
482
483 // Give stream time to advance.
484 usleep(SLEEP_DURATION_MSEC * 1000);
485
486 if (victim.verify()) {
487 errorCount++;
488 goto onerror;
489 }
490 if (thief.verify()) {
491 errorCount++;
492 goto onerror;
493 }
494
495 LOGI("Both streams running. Ask user to plug in headset. ====");
496 printf("\n====\nPlease PLUG IN A HEADSET now! - OPTIONAL\n====\n\n");
497
498 if (result == AAUDIO_OK) {
499 const int watchLoops = DUET_DURATION_MSEC / SLEEP_DURATION_MSEC;
500 for (int i = watchLoops; i > 0; i--) {
501 errorCount += victim.checkEnginePositions() ? 1 : 0;
502 errorCount += thief.checkEnginePositions() ? 1 : 0;
503 usleep(SLEEP_DURATION_MSEC * 1000);
504 }
505 }
506
507 errorCount += victim.verify() ? 1 : 0;
508 errorCount += thief.verify() ? 1 : 0;
509
510 result = victim.stop();
511 printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
512 if (result != AAUDIO_OK) {
513 printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
514 errorCount++;
515 }
516 result = thief.stop();
517 printf("AAudioStream_requestStop() returned %d <<<<<<<<<<<<<<<<<<<<<\n", result);
518 if (result != AAUDIO_OK) {
519 printf("stop result = %d = %s\n", result, AAudio_convertResultToText(result));
520 errorCount++;
521 }
522
523 onerror:
524 victim.closeAudioStream();
525 thief.closeAudioStream();
526
527 printf("aaudio result = %d = %s\n", result, AAudio_convertResultToText(result));
528 printf("test %s\n", errorCount ? "FAILED" : "PASSED");
529
530 return errorCount ? EXIT_FAILURE : EXIT_SUCCESS;
531 }
532