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