• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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