1 /*
2  * Copyright (C) 2017 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 // Play sine waves using an AAudio callback.
18 // If a disconnection occurs then reopen the stream on the new device.
19 
20 #include <assert.h>
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <sched.h>
24 #include <stdio.h>
25 #include <math.h>
26 #include <string.h>
27 #include <time.h>
28 #include <aaudio/AAudio.h>
29 
30 #include "AAudioExampleUtils.h"
31 #include "AAudioSimplePlayer.h"
32 #include "AAudioArgsParser.h"
33 
34 #define APP_VERSION  "0.2.1"
35 
36 static constexpr int32_t kDefaultHangTimeMSec = 10;
37 static constexpr int32_t kWorkPeriodSeconds = 6;
38 /**
39  * Open stream, play some sine waves, then close the stream.
40  *
41  * @param argParser
42  * @return AAUDIO_OK or negative error code
43  */
testOpenPlayClose(AAudioArgsParser & argParser,int32_t loopCount,int32_t prefixToneMsec,int32_t hangTimeMSec,int cpuAffinity,double lowWorkLoad,double highWorkLoad,int32_t workPeriodSeconds)44 static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser,
45                                          int32_t loopCount,
46                                          int32_t prefixToneMsec,
47                                          int32_t hangTimeMSec,
48                                          int     cpuAffinity,
49                                          double  lowWorkLoad,
50                                          double  highWorkLoad,
51                                          int32_t workPeriodSeconds)
52 {
53     SineThreadedData_t myData;
54     AAudioSimplePlayer &player = myData.simplePlayer;
55     aaudio_result_t    result = AAUDIO_OK;
56     bool               disconnected = false;
57     bool               bailOut = false;
58     int64_t            startedAtNanos;
59 
60     printf("----------------------- run complete test --------------------------\n");
61     myData.schedulerChecked = false;
62     myData.callbackCount = 0;
63     myData.hangTimeMSec = hangTimeMSec; // test AAudioStream_getXRunCount()
64     myData.cpuAffinity = cpuAffinity;
65 
66     result = player.open(argParser,
67                          SimplePlayerDataCallbackProc,
68                          SimplePlayerErrorCallbackProc,
69                          &myData);
70     if (result != AAUDIO_OK) {
71         fprintf(stderr, "ERROR -  player.open() returned %s\n",
72                 AAudio_convertResultToText(result));
73         goto error;
74     }
75 
76     argParser.compareWithStream(player.getStream());
77 
78     myData.sampleRate = player.getSampleRate();
79     myData.prefixToneFrames = prefixToneMsec * myData.sampleRate / 1000;
80     if (myData.prefixToneFrames > 0) {
81         myData.setupSineBlip();
82     } else {
83         myData.setupSineSweeps();
84     }
85 
86 #if 0
87     //  writes not allowed for callback streams
88     result = player.prime(); // FIXME crashes AudioTrack.cpp
89     if (result != AAUDIO_OK) {
90         fprintf(stderr, "ERROR - player.prime() returned %d\n", result);
91         goto error;
92     }
93 #endif
94 
95     for (int loopIndex = 0; loopIndex < loopCount; loopIndex++) {
96         // Only play data on every other loop so we can hear if there is stale data.
97         double amplitude;
98         int32_t durationSeconds;
99         if ((loopIndex & 1) == 0) {
100             printf("--------------- SINE ------\n");
101             amplitude = 0.2;
102             durationSeconds = argParser.getDurationSeconds();
103         } else {
104             printf("--------------- QUIET -----\n");
105             amplitude = 0.0;
106             durationSeconds = 2; // just wait briefly when quiet
107         }
108         for (int i = 0; i < MAX_CHANNELS; ++i) {
109             myData.sineOscillators[i].setAmplitude(amplitude);
110         }
111 
112         result = player.start();
113         if (result != AAUDIO_OK) {
114             fprintf(stderr, "ERROR - player.start() returned %d\n", result);
115             goto error;
116         }
117 
118         // Play a sine wave in the background.
119         printf("Monitor for %d seconds while audio plays in a callback thread. %d of %d, %d\n",
120                argParser.getDurationSeconds(), (loopIndex + 1), loopCount, workPeriodSeconds);
121         startedAtNanos = getNanoseconds(CLOCK_MONOTONIC);
122         for (int second = 0; second < durationSeconds; second++) {
123             // Sleep a while. Wake up early if there is an error, for example a DISCONNECT.
124             myData.waker.wait(AAUDIO_OK, NANOS_PER_SECOND);
125             int64_t millis =
126                     (getNanoseconds(CLOCK_MONOTONIC) - startedAtNanos) / NANOS_PER_MILLISECOND;
127             result = myData.waker.get();
128             const int32_t framesWritten = (int32_t) AAudioStream_getFramesWritten(player.getStream());
129             const int32_t framesRead = (int32_t) AAudioStream_getFramesRead(player.getStream());
130             const int32_t xruns = AAudioStream_getXRunCount(player.getStream());
131             myData.workload = ((second % (2 * workPeriodSeconds)) < workPeriodSeconds)
132                     ? lowWorkLoad : highWorkLoad;
133             printf(" waker result = %d, at %6d millis"
134                    ", second = %3d, frames written %8d - read %8d = %8d"
135                    ", work = %5.1f, underruns = %d\n",
136                    result, (int) millis,
137                    second,
138                    framesWritten,
139                    framesRead,
140                    framesWritten - framesRead,
141                    myData.workload,
142                    xruns);
143             if (result != AAUDIO_OK) {
144                 disconnected = (result == AAUDIO_ERROR_DISCONNECTED);
145                 bailOut = true;
146                 break;
147             }
148         }
149         printf("AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
150 
151         // Alternate between using stop or pause for each sine/quiet pair.
152         // Repeat this pattern: {sine-stop-quiet-stop-sine-pause-quiet-pause}
153         if ((loopIndex & 2) == 0) {
154             printf("STOP, callback # = %d\n", myData.callbackCount);
155             result = player.stop();
156         } else {
157             printf("PAUSE/FLUSH, callback # = %d\n", myData.callbackCount);
158             result = player.pause();
159             if (result != AAUDIO_OK) {
160                 goto error;
161             }
162             result = player.waitUntilPaused();
163             if (result != AAUDIO_OK) {
164                 goto error;
165             }
166             result = player.flush();
167         }
168         if (result != AAUDIO_OK) {
169             goto error;
170         }
171 
172         if (bailOut) {
173             break;
174         }
175 
176         {
177             aaudio_stream_state_t state = AAudioStream_getState(player.getStream());
178             aaudio_stream_state_t finalState = AAUDIO_STREAM_STATE_UNINITIALIZED;
179             int64_t timeoutNanos = 2000 * NANOS_PER_MILLISECOND;
180             result = AAudioStream_waitForStateChange(player.getStream(), state,
181                                                      &finalState, timeoutNanos);
182             printf("waitForStateChange returns %s, state = %s\n",
183                    AAudio_convertResultToText(result),
184                    AAudio_convertStreamStateToText(finalState));
185             int64_t written = AAudioStream_getFramesWritten(player.getStream());
186             int64_t read = AAudioStream_getFramesRead(player.getStream());
187             printf("   framesWritten = %lld, framesRead = %lld, diff = %d\n",
188                    (long long) written,
189                    (long long) read,
190                    (int) (written - read));
191         }
192 
193     }
194 
195     printf("call close()\n");
196     result = player.close();
197     if (result != AAUDIO_OK) {
198         goto error;
199     }
200 
201     for (int i = 0; i < myData.timestampCount; i++) {
202         Timestamp *timestamp = &myData.timestamps[i];
203         bool retro = (i > 0 &&
204                       ((timestamp->position < (timestamp - 1)->position)
205                        || ((timestamp->nanoseconds < (timestamp - 1)->nanoseconds))));
206         const char *message = retro ? "  <= RETROGRADE!" : "";
207         printf("Timestamp %3d : %8lld, %8lld %s\n", i,
208                (long long) timestamp->position,
209                (long long) timestamp->nanoseconds,
210                message);
211     }
212 
213     if (myData.schedulerChecked) {
214         printf("scheduler = 0x%08x, SCHED_FIFO = 0x%08X\n",
215                myData.scheduler,
216                SCHED_FIFO);
217     }
218 
219     printf("min numFrames = %8d\n", (int) myData.minNumFrames);
220     printf("max numFrames = %8d\n", (int) myData.maxNumFrames);
221 
222     printf("SUCCESS\n");
223 error:
224     player.close();
225     return disconnected ? AAUDIO_ERROR_DISCONNECTED : result;
226 }
227 
usage()228 static void usage() {
229     AAudioArgsParser::usage();
230     printf("      -l{count} loopCount start/stop, every other one is silent\n");
231     printf("      -t{msec}  play a high pitched tone at the beginning\n");
232     printf("      -w{workload}  set base workload, default 0.0\n");
233     printf("      -W{workload}  alternate between this higher workload and base workload\n");
234     printf("      -Z{duration}  number of seconds to spend at each workload, default = %d\n",
235            kWorkPeriodSeconds);
236     printf("      -a{cpu}   set CPU affinity, default none\n");
237     printf("      -h{msec}  force periodic underruns by hanging in callback\n");
238     printf("                If no value specified then %d used.\n",
239             kDefaultHangTimeMSec);
240 }
241 
main(int argc,const char ** argv)242 int main(int argc, const char **argv)
243 {
244     AAudioArgsParser   argParser;
245     aaudio_result_t    result;
246     int32_t            loopCount = 1;
247     int32_t            prefixToneMsec = 0;
248     int32_t            hangTimeMSec = 0;
249     int                cpuAffinity = -1;
250     double             lowWorkLoad = 0.0;
251     double             highWorkLoad = -1.0;
252     int32_t            workPeriodSeconds = kWorkPeriodSeconds;
253 
254     // Make printf print immediately so that debug info is not stuck
255     // in a buffer if we hang or crash.
256     setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
257 
258     printf("%s - Play a sine sweep using an AAudio callback V%s\n",
259         argv[0], APP_VERSION);
260 
261     for (int i = 1; i < argc; i++) {
262         const char *arg = argv[i];
263         if (argParser.parseArg(arg)) {
264             // Handle options that are not handled by the ArgParser
265             if (arg[0] == '-') {
266                 char option = arg[1];
267                 switch (option) {
268                     case 'a':
269                         cpuAffinity = atoi(&arg[2]);
270                         break;
271                     case 'l':
272                         loopCount = atoi(&arg[2]);
273                         break;
274                     case 't':
275                         prefixToneMsec = atoi(&arg[2]);
276                         break;
277                     case 'h':
278                         hangTimeMSec = (arg[2]) // value specified?
279                                 ? atoi(&arg[2])
280                                 : kDefaultHangTimeMSec;
281                         break;
282                     case 'w':
283                         lowWorkLoad = atof(&arg[2]);
284                         break;
285                     case 'W':
286                         highWorkLoad = atof(&arg[2]);
287                         break;
288                     case 'Z':
289                         workPeriodSeconds = atoi(&arg[2]);
290                         break;
291                     default:
292                         usage();
293                         exit(EXIT_FAILURE);
294                         break;
295                 }
296             } else {
297                 usage();
298                 exit(EXIT_FAILURE);
299                 break;
300             }
301         }
302     }
303 
304     if (highWorkLoad > 0) {
305         if (highWorkLoad < lowWorkLoad) {
306             printf("ERROR - -W%f workload lower than -w%f workload", highWorkLoad, lowWorkLoad);
307             return EXIT_FAILURE;
308         }
309     } else {
310         highWorkLoad = lowWorkLoad; // high not specified so use low
311     }
312 
313     // Keep looping until we can complete the test without disconnecting.
314     while((result = testOpenPlayClose(argParser, loopCount,
315             prefixToneMsec, hangTimeMSec,
316             cpuAffinity,
317             lowWorkLoad, highWorkLoad,
318             workPeriodSeconds))
319             == AAUDIO_ERROR_DISCONNECTED);
320 
321     return (result) ? EXIT_FAILURE : EXIT_SUCCESS;
322 }
323