1 /*
2  * Copyright (C) 2016 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 // Audio loopback tests to measure the round trip latency and glitches.
18 
19 #include <algorithm>
20 #include <assert.h>
21 #include <cctype>
22 #include <errno.h>
23 #include <iomanip>
24 #include <iostream>
25 #include <math.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 #include <aaudio/AAudio.h>
33 #include <aaudio/AAudioTesting.h>
34 
35 #include "AAudioSimplePlayer.h"
36 #include "AAudioSimpleRecorder.h"
37 #include "AAudioExampleUtils.h"
38 
39 #include "analyzer/GlitchAnalyzer.h"
40 #include "analyzer/LatencyAnalyzer.h"
41 #include "../../utils/AAudioExampleUtils.h"
42 
43 // V0.4.00 = rectify and low-pass filter the echos, auto-correlate entire echo
44 // V0.4.01 = add -h hang option
45 //           fix -n option to set output buffer for -tm
46 //           plot first glitch
47 // V0.4.02 = allow -n0 for minimal buffer size
48 // V0.5.00 = use latency analyzer from OboeTester, uses random noise for latency
49 #define APP_VERSION             "0.5.00"
50 
51 // Tag for machine readable results as property = value pairs
52 #define RESULT_TAG              "RESULT: "
53 #define FILENAME_ALL            "/data/loopback_all.wav"
54 #define FILENAME_ECHOS          "/data/loopback_echos.wav"
55 #define FILENAME_PROCESSED      "/data/loopback_processed.wav"
56 
57 constexpr int kLogPeriodMillis       = 1000;
58 constexpr int kNumInputChannels      = 1;
59 constexpr int kNumCallbacksToDrain   = 20;
60 constexpr int kNumCallbacksToNotRead = 0; // let input fill back up
61 constexpr int kNumCallbacksToDiscard = 20;
62 constexpr int kDefaultHangTimeMillis = 50;
63 constexpr int kMaxGlitchEventsToSave = 32;
64 
printAudioScope(float sample)65 static void printAudioScope(float sample) {
66     const int maxStars = 80; // arbitrary, fits on one line
67     char c = '*';
68     if (sample < -1.0) {
69         sample = -1.0;
70         c = '$';
71     } else if (sample > 1.0) {
72         sample = 1.0;
73         c = '$';
74     }
75     int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
76     printf("%*c%c\n", numSpaces, ' ', c);
77 }
78 
79 struct LoopbackData {
80     AAudioStream      *inputStream = nullptr;
81     AAudioStream      *outputStream = nullptr;
82     int32_t            inputFramesMaximum = 0;
83     int16_t           *inputShortData = nullptr;
84     float             *inputFloatData = nullptr;
85     aaudio_format_t    actualInputFormat = AAUDIO_FORMAT_INVALID;
86     int32_t            actualInputChannelCount = 0;
87     int32_t            actualOutputChannelCount = 0;
88     int32_t            numCallbacksToDrain = kNumCallbacksToDrain;
89     int32_t            numCallbacksToNotRead = kNumCallbacksToNotRead;
90     int32_t            numCallbacksToDiscard = kNumCallbacksToDiscard;
91     int32_t            minNumFrames = INT32_MAX;
92     int32_t            maxNumFrames = 0;
93     int32_t            insufficientReadCount = 0;
94     int32_t            insufficientReadFrames = 0;
95     int32_t            framesReadTotal = 0;
96     int32_t            framesWrittenTotal = 0;
97     int32_t            hangPeriodMillis = 5 * 1000; // time between hangs
98     int32_t            hangCountdownFrames = 5 * 48000; // frames til next hang
99     int32_t            hangTimeMillis = 0; // 0 for no hang
100     bool               isDone = false;
101 
102     aaudio_result_t    inputError = AAUDIO_OK;
103     aaudio_result_t    outputError = AAUDIO_OK;
104 
105     GlitchAnalyzer     sineAnalyzer;
106     PulseLatencyAnalyzer echoAnalyzer;
107     AudioRecording     audioRecording;
108     LoopbackProcessor *loopbackProcessor;
109 
110     int32_t            glitchFrames[kMaxGlitchEventsToSave];
111     int32_t            numGlitchEvents = 0;
112 
hangIfRequestedLoopbackData113     void hangIfRequested(int32_t numFrames) {
114         if (hangTimeMillis > 0) {
115             hangCountdownFrames -= numFrames;
116             if (hangCountdownFrames <= 0) {
117                 const int64_t startNanos = getNanoseconds();
118                 usleep(hangTimeMillis * 1000);
119                 const int64_t endNanos = getNanoseconds();
120                 const int32_t elapsedMicros = (int32_t)
121                         ((endNanos - startNanos) / 1000);
122                 printf("callback hanging for %d millis, actual = %d micros\n",
123                        hangTimeMillis, elapsedMicros);
124                 hangCountdownFrames = (int64_t) hangPeriodMillis
125                         * AAudioStream_getSampleRate(outputStream)
126                         / 1000;
127             }
128         }
129 
130 
131     }
132 };
133 
convertPcm16ToFloat(const int16_t * source,float * destination,int32_t numSamples)134 static void convertPcm16ToFloat(const int16_t *source,
135                                 float *destination,
136                                 int32_t numSamples) {
137     constexpr float scaler = 1.0f / 32768.0f;
138     for (int i = 0; i < numSamples; i++) {
139         destination[i] = source[i] * scaler;
140     }
141 }
142 
143 // ====================================================================================
144 // ========================= CALLBACK =================================================
145 // ====================================================================================
146 // Callback function that fills the audio output buffer.
147 
readFormattedData(LoopbackData * myData,int32_t numFrames)148 static int32_t readFormattedData(LoopbackData *myData, int32_t numFrames) {
149     int32_t framesRead = AAUDIO_ERROR_INVALID_FORMAT;
150     if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
151         framesRead = AAudioStream_read(myData->inputStream, myData->inputShortData,
152                                        numFrames,
153                                        0 /* timeoutNanoseconds */);
154     } else if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_FLOAT) {
155         framesRead = AAudioStream_read(myData->inputStream, myData->inputFloatData,
156                                        numFrames,
157                                        0 /* timeoutNanoseconds */);
158     } else {
159         printf("ERROR actualInputFormat = %d\n", myData->actualInputFormat);
160         assert(false);
161     }
162     if (framesRead < 0) {
163         // Expect INVALID_STATE if STATE_STARTING
164         if (myData->framesReadTotal > 0) {
165             myData->inputError = framesRead;
166             printf("ERROR in read = %d = %s\n", framesRead,
167                    AAudio_convertResultToText(framesRead));
168         } else {
169             framesRead = 0;
170         }
171     } else {
172         myData->framesReadTotal += framesRead;
173     }
174     return framesRead;
175 }
176 
MyDataCallbackProc(AAudioStream * outputStream,void * userData,void * audioData,int32_t numFrames)177 static aaudio_data_callback_result_t MyDataCallbackProc(
178         AAudioStream *outputStream,
179         void *userData,
180         void *audioData,
181         int32_t numFrames
182 ) {
183     (void) outputStream;
184     aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_CONTINUE;
185     LoopbackData *myData = (LoopbackData *) userData;
186     float  *outputData = (float  *) audioData;
187 
188     // Read audio data from the input stream.
189     int32_t actualFramesRead;
190 
191     if (numFrames > myData->inputFramesMaximum) {
192         myData->inputError = AAUDIO_ERROR_OUT_OF_RANGE;
193         return AAUDIO_CALLBACK_RESULT_STOP;
194     }
195 
196     if (numFrames > myData->maxNumFrames) {
197         myData->maxNumFrames = numFrames;
198     }
199     if (numFrames < myData->minNumFrames) {
200         myData->minNumFrames = numFrames;
201     }
202 
203     // Silence the output.
204     int32_t numBytes = numFrames * myData->actualOutputChannelCount * sizeof(float);
205     memset(audioData, 0 /* value */, numBytes);
206 
207     if (myData->numCallbacksToDrain > 0) {
208         // Drain the input.
209         int32_t totalFramesRead = 0;
210         do {
211             actualFramesRead = readFormattedData(myData, numFrames);
212             if (actualFramesRead > 0) {
213                 totalFramesRead += actualFramesRead;
214             } else if (actualFramesRead < 0) {
215                 result = AAUDIO_CALLBACK_RESULT_STOP;
216             }
217             // Ignore errors because input stream may not be started yet.
218         } while (actualFramesRead > 0);
219         // Only counts if we actually got some data.
220         if (totalFramesRead > 0) {
221             myData->numCallbacksToDrain--;
222         }
223 
224     } else if (myData->numCallbacksToNotRead > 0) {
225         // Let the input fill up a bit so we are not so close to the write pointer.
226         myData->numCallbacksToNotRead--;
227     } else if (myData->numCallbacksToDiscard > 0) {
228         // Ignore. Allow the input to fill back up to equilibrium with the output.
229         actualFramesRead = readFormattedData(myData, numFrames);
230         if (actualFramesRead < 0) {
231             result = AAUDIO_CALLBACK_RESULT_STOP;
232         }
233         myData->numCallbacksToDiscard--;
234 
235     } else {
236         myData->hangIfRequested(numFrames);
237 
238         int32_t numInputBytes = numFrames * myData->actualInputChannelCount * sizeof(float);
239         memset(myData->inputFloatData, 0 /* value */, numInputBytes);
240 
241         // Process data after equilibrium.
242         int64_t inputFramesWritten = AAudioStream_getFramesWritten(myData->inputStream);
243         int64_t inputFramesRead = AAudioStream_getFramesRead(myData->inputStream);
244         int64_t framesAvailable = inputFramesWritten - inputFramesRead;
245 
246         actualFramesRead = readFormattedData(myData, numFrames); // READ
247         if (actualFramesRead < 0) {
248             result = AAUDIO_CALLBACK_RESULT_STOP;
249         } else {
250 
251             if (actualFramesRead < numFrames) {
252                 if(actualFramesRead < (int32_t) framesAvailable) {
253                     printf("insufficient for no reason, numFrames = %d"
254                                    ", actualFramesRead = %d"
255                                    ", inputFramesWritten = %d"
256                                    ", inputFramesRead = %d"
257                                    ", available = %d\n",
258                            numFrames,
259                            actualFramesRead,
260                            (int) inputFramesWritten,
261                            (int) inputFramesRead,
262                            (int) framesAvailable);
263                 }
264                 myData->insufficientReadCount++;
265                 myData->insufficientReadFrames += numFrames - actualFramesRead; // deficit
266                 // printf("Error insufficientReadCount = %d\n",(int)myData->insufficientReadCount);
267             }
268 
269             int32_t numSamples = actualFramesRead * myData->actualInputChannelCount;
270 
271             if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
272                 convertPcm16ToFloat(myData->inputShortData, myData->inputFloatData, numSamples);
273             }
274 
275             // Analyze the data.
276             myData->loopbackProcessor->process(myData->inputFloatData,
277                                                myData->actualInputChannelCount,
278                                                numFrames,
279                                                outputData,
280                                                myData->actualOutputChannelCount,
281                                                numFrames);
282 //
283 //            if (procResult == LoopbackProcessor::PROCESS_RESULT_GLITCH) {
284 //                if (myData->numGlitchEvents < kMaxGlitchEventsToSave) {
285 //                    myData->glitchFrames[myData->numGlitchEvents++] = myData->audioRecording.size();
286 //                }
287 //            }
288 
289             // Save for later.
290             myData->audioRecording.write(myData->inputFloatData,
291                                          myData->actualInputChannelCount,
292                                          actualFramesRead);
293 
294             myData->isDone = myData->loopbackProcessor->isDone();
295             if (myData->isDone) {
296                 result = AAUDIO_CALLBACK_RESULT_STOP;
297             }
298         }
299     }
300     myData->framesWrittenTotal += numFrames;
301 
302     return result;
303 }
304 
MyErrorCallbackProc(AAudioStream *,void * userData,aaudio_result_t error)305 static void MyErrorCallbackProc(
306         AAudioStream * /* stream */,
307         void * userData,
308         aaudio_result_t error) {
309     printf("Error Callback, error: %d\n",(int)error);
310     LoopbackData *myData = (LoopbackData *) userData;
311     myData->outputError = error;
312 }
313 
usage()314 static void usage() {
315     printf("Usage: aaudio_loopback [OPTION]...\n\n");
316     AAudioArgsParser::usage();
317     printf("      -B{frames}        input capacity in frames\n");
318     printf("      -C{channels}      number of input channels\n");
319     printf("      -D{deviceId}      input device ID\n");
320     printf("      -F{0,1,2}         input format, 1=I16, 2=FLOAT\n");
321     printf("      -g{gain}          recirculating loopback gain\n");
322     printf("      -h{hangMillis}    occasionally hang in the callback\n");
323     printf("      -P{inPerf}        set input AAUDIO_PERFORMANCE_MODE*\n");
324     printf("          n for _NONE\n");
325     printf("          l for _LATENCY\n");
326     printf("          p for _POWER_SAVING\n");
327     printf("      -t{test}          select test mode\n");
328     printf("          g for Glitch detection\n");
329     printf("          l for round trip Latency (default)\n");
330     printf("          f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
331     printf("      -X  use EXCLUSIVE mode for input\n");
332     printf("Example:  aaudio_loopback -n2 -pl -Pl -x\n");
333 }
334 
parsePerformanceMode(char c)335 static aaudio_performance_mode_t parsePerformanceMode(char c) {
336     aaudio_performance_mode_t mode = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
337     c = tolower(c);
338     switch (c) {
339         case 'n':
340             mode = AAUDIO_PERFORMANCE_MODE_NONE;
341             break;
342         case 'l':
343             mode = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
344             break;
345         case 'p':
346             mode = AAUDIO_PERFORMANCE_MODE_POWER_SAVING;
347             break;
348         default:
349             printf("ERROR in value performance mode %c\n", c);
350             break;
351     }
352     return mode;
353 }
354 
355 enum {
356     TEST_GLITCHES = 0,
357     TEST_LATENCY,
358     TEST_FILE_LATENCY,
359 };
360 
parseTestMode(char c)361 static int parseTestMode(char c) {
362     int testMode = TEST_LATENCY;
363     c = tolower(c);
364     switch (c) {
365         case 'm': // deprecated
366         case 'g':
367             testMode = TEST_GLITCHES;
368             break;
369         case 'e': // deprecated
370         case 'l':
371             testMode = TEST_LATENCY;
372             break;
373         case 'f':
374             testMode = TEST_FILE_LATENCY;
375             break;
376         default:
377             printf("ERROR in value test mode %c\n", c);
378             break;
379     }
380     return testMode;
381 }
382 
printAudioGraphRegion(AudioRecording & recording,int32_t start,int32_t end)383 void printAudioGraphRegion(AudioRecording &recording, int32_t start, int32_t end) {
384     if (end >= recording.size()) {
385         end = recording.size() - 1;
386     }
387     float *data = recording.getData();
388     // Normalize data so we can see it better.
389     float maxSample = 0.01;
390     for (int32_t i = start; i < end; i++) {
391         float samplePos = fabs(data[i]);
392         if (samplePos > maxSample) {
393             maxSample = samplePos;
394         }
395     }
396     float gain = 0.98f / maxSample;
397 
398     for (int32_t i = start; i < end; i++) {
399         float sample = data[i];
400         printf("%6d: %7.4f ", i, sample); // actual value
401         sample *= gain;
402         printAudioScope(sample);
403     }
404 }
405 
406 
407 // ====================================================================================
408 // TODO break up this large main() function into smaller functions
main(int argc,const char ** argv)409 int main(int argc, const char **argv)
410 {
411 
412     AAudioArgsParser      argParser;
413     AAudioSimplePlayer    player;
414     AAudioSimpleRecorder  recorder;
415     LoopbackData          loopbackData;
416     AAudioStream         *inputStream                = nullptr;
417     AAudioStream         *outputStream               = nullptr;
418 
419     aaudio_result_t       result = AAUDIO_OK;
420     int32_t               requestedInputDeviceId     = AAUDIO_UNSPECIFIED;
421     aaudio_sharing_mode_t requestedInputSharingMode  = AAUDIO_SHARING_MODE_SHARED;
422     int                   requestedInputChannelCount = kNumInputChannels;
423     aaudio_format_t       requestedInputFormat       = AAUDIO_FORMAT_UNSPECIFIED;
424     int32_t               requestedInputCapacity     = AAUDIO_UNSPECIFIED;
425     aaudio_performance_mode_t inputPerformanceLevel  = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
426 
427     int32_t               outputFramesPerBurst       = 0;
428 
429     aaudio_format_t       actualOutputFormat         = AAUDIO_FORMAT_INVALID;
430     int32_t               actualSampleRate           = 0;
431     int                   written                    = 0;
432 
433     int                   testMode                   = TEST_LATENCY;
434     double                gain                       = 1.0;
435     int                   hangTimeMillis             = 0;
436     std::string           report;
437 
438     // Make printf print immediately so that debug info is not stuck
439     // in a buffer if we hang or crash.
440     setvbuf(stdout, NULL, _IONBF, (size_t) 0);
441 
442     printf("%s - Audio loopback using AAudio V" APP_VERSION "\n", argv[0]);
443 
444     // Use LOW_LATENCY as the default to match input default.
445     argParser.setPerformanceMode(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
446 
447     for (int i = 1; i < argc; i++) {
448         const char *arg = argv[i];
449         if (argParser.parseArg(arg)) {
450             // Handle options that are not handled by the ArgParser
451             if (arg[0] == '-') {
452                 char option = arg[1];
453                 switch (option) {
454                     case 'B':
455                         requestedInputCapacity = atoi(&arg[2]);
456                         break;
457                     case 'C':
458                         requestedInputChannelCount = atoi(&arg[2]);
459                         break;
460                     case 'D':
461                         requestedInputDeviceId = atoi(&arg[2]);
462                         break;
463                     case 'F':
464                         requestedInputFormat = atoi(&arg[2]);
465                         break;
466                     case 'g':
467                         gain = atof(&arg[2]);
468                         break;
469                     case 'h':
470                         // Was there a number after the "-h"?
471                         if (arg[2]) {
472                             hangTimeMillis = atoi(&arg[2]);
473                         } else {
474                             // If no number then use the default.
475                             hangTimeMillis = kDefaultHangTimeMillis;
476                         }
477                         break;
478                     case 'P':
479                         inputPerformanceLevel = parsePerformanceMode(arg[2]);
480                         break;
481                     case 'X':
482                         requestedInputSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
483                         break;
484                     case 't':
485                         testMode = parseTestMode(arg[2]);
486                         break;
487                     default:
488                         usage();
489                         exit(EXIT_FAILURE);
490                         break;
491                 }
492             } else {
493                 usage();
494                 exit(EXIT_FAILURE);
495                 break;
496             }
497         }
498 
499     }
500 
501     if (inputPerformanceLevel < 0) {
502         printf("illegal inputPerformanceLevel = %d\n", inputPerformanceLevel);
503         exit(EXIT_FAILURE);
504     }
505 
506     int32_t requestedDuration = argParser.getDurationSeconds();
507     int32_t requestedDurationMillis = requestedDuration * kMillisPerSecond;
508     int32_t timeMillis = 0;
509     int32_t recordingDuration = std::min(60 * 5, requestedDuration);
510 
511     int32_t requestedOutputBursts = argParser.getNumberOfBursts();
512 
513     switch(testMode) {
514         case TEST_GLITCHES:
515             loopbackData.loopbackProcessor = &loopbackData.sineAnalyzer;
516             break;
517         case TEST_LATENCY:
518             // TODO loopbackData.echoAnalyzer.setGain(gain);
519             loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
520             break;
521         case TEST_FILE_LATENCY: {
522             // TODO loopbackData.echoAnalyzer.setGain(gain);
523             loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
524             int read = loopbackData.loopbackProcessor->load(FILENAME_ECHOS);
525             printf("main() read %d mono samples from %s on Android device, rate = %d\n",
526                    read, FILENAME_ECHOS,
527                    loopbackData.loopbackProcessor->getSampleRate());
528             std::cout << loopbackData.loopbackProcessor->analyze();
529             goto report_result;
530         }
531             break;
532         default:
533             exit(1);
534             break;
535     }
536 
537     printf("OUTPUT stream ----------------------------------------\n");
538     result = player.open(argParser, MyDataCallbackProc, MyErrorCallbackProc, &loopbackData);
539     if (result != AAUDIO_OK) {
540         fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
541         exit(1);
542     }
543     outputStream = loopbackData.outputStream = player.getStream();
544 
545     actualOutputFormat = AAudioStream_getFormat(outputStream);
546     if (actualOutputFormat != AAUDIO_FORMAT_PCM_FLOAT) {
547         fprintf(stderr, "ERROR - only AAUDIO_FORMAT_PCM_FLOAT supported\n");
548         exit(1);
549     }
550 
551     actualSampleRate = AAudioStream_getSampleRate(outputStream);
552     loopbackData.audioRecording.allocate(recordingDuration * actualSampleRate);
553     loopbackData.audioRecording.setSampleRate(actualSampleRate);
554     outputFramesPerBurst = AAudioStream_getFramesPerBurst(outputStream);
555 
556     argParser.compareWithStream(outputStream);
557 
558     printf("INPUT  stream ----------------------------------------\n");
559     // Use different parameters for the input.
560     argParser.setDeviceId(requestedInputDeviceId);
561     argParser.setNumberOfBursts(AAudioParameters::kDefaultNumberOfBursts);
562     argParser.setFormat(requestedInputFormat);
563     argParser.setPerformanceMode(inputPerformanceLevel);
564     argParser.setChannelCount(requestedInputChannelCount);
565     argParser.setSharingMode(requestedInputSharingMode);
566     if (requestedInputCapacity != AAUDIO_UNSPECIFIED) {
567         printf("Warning! If you set input capacity then maybe no FAST track on Legacy path!\n");
568     }
569     argParser.setBufferCapacity(requestedInputCapacity);
570 
571     result = recorder.open(argParser);
572     if (result != AAUDIO_OK) {
573         fprintf(stderr, "ERROR -  recorder.open() returned %d\n", result);
574         goto finish;
575     }
576     inputStream = loopbackData.inputStream = recorder.getStream();
577 
578     {
579         int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
580         (void) AAudioStream_setBufferSizeInFrames(inputStream, actualCapacity);
581 
582         if (testMode == TEST_GLITCHES
583                 && requestedOutputBursts == AAUDIO_UNSPECIFIED) {
584             result = AAudioStream_setBufferSizeInFrames(outputStream, actualCapacity);
585             if (result < 0) {
586                 fprintf(stderr, "ERROR -  AAudioStream_setBufferSizeInFrames(output) returned %d\n",
587                         result);
588                 goto finish;
589             } else {
590                 printf("Output buffer size set to match input capacity = %d frames!\n", result);
591             }
592         }
593 
594         // If the input stream is too small then we cannot satisfy the output callback.
595         if (actualCapacity < 2 * outputFramesPerBurst) {
596             fprintf(stderr, "ERROR - input capacity < 2 * outputFramesPerBurst\n");
597             goto finish;
598         }
599     }
600 
601     argParser.compareWithStream(inputStream);
602 
603     // ------- Setup loopbackData -----------------------------
604     loopbackData.actualInputFormat = AAudioStream_getFormat(inputStream);
605 
606     loopbackData.actualInputChannelCount = recorder.getChannelCount();
607     loopbackData.actualOutputChannelCount = player.getChannelCount();
608 
609     // Allocate a buffer for the audio data.
610     loopbackData.inputFramesMaximum = 32 * AAudioStream_getFramesPerBurst(inputStream);
611 
612     if (loopbackData.actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
613         loopbackData.inputShortData = new int16_t[loopbackData.inputFramesMaximum
614                                                   * loopbackData.actualInputChannelCount]{};
615     }
616     loopbackData.inputFloatData = new float[loopbackData.inputFramesMaximum *
617                                               loopbackData.actualInputChannelCount]{};
618 
619     loopbackData.hangTimeMillis = hangTimeMillis;
620 
621     loopbackData.loopbackProcessor->prepareToTest();
622 
623     // Start OUTPUT first so INPUT does not overflow.
624     result = player.start();
625     if (result != AAUDIO_OK) {
626         goto finish;
627     }
628 
629     result = recorder.start();
630     if (result != AAUDIO_OK) {
631         goto finish;
632     }
633 
634     printf("------- sleep and log while the callback runs --------------\n");
635     while (timeMillis <= requestedDurationMillis) {
636         if (loopbackData.inputError != AAUDIO_OK) {
637             printf("  ERROR on input stream\n");
638             break;
639         } else if (loopbackData.outputError != AAUDIO_OK) {
640                 printf("  ERROR on output stream\n");
641                 break;
642         } else if (loopbackData.isDone) {
643                 printf("  Test says it is DONE!\n");
644                 break;
645         } else {
646             // Log a line of stream data.
647             printf("%7.3f: ", 0.001 * timeMillis); // display in seconds
648             loopbackData.loopbackProcessor->printStatus();
649             printf(" insf %3d,", (int) loopbackData.insufficientReadCount);
650 
651             int64_t inputFramesWritten = AAudioStream_getFramesWritten(inputStream);
652             int64_t inputFramesRead = AAudioStream_getFramesRead(inputStream);
653             int64_t outputFramesWritten = AAudioStream_getFramesWritten(outputStream);
654             int64_t outputFramesRead = AAudioStream_getFramesRead(outputStream);
655             static const int textOffset = strlen("AAUDIO_STREAM_STATE_"); // strip this off
656             printf(" | INPUT: wr %7lld - rd %7lld = %5lld, st %8s, oruns %3d",
657                    (long long) inputFramesWritten,
658                    (long long) inputFramesRead,
659                    (long long) (inputFramesWritten - inputFramesRead),
660                    &AAudio_convertStreamStateToText(
661                            AAudioStream_getState(inputStream))[textOffset],
662                    AAudioStream_getXRunCount(inputStream));
663 
664             printf(" | OUTPUT: wr %7lld - rd %7lld = %5lld, st %8s, uruns %3d\n",
665                    (long long) outputFramesWritten,
666                    (long long) outputFramesRead,
667                     (long long) (outputFramesWritten - outputFramesRead),
668                    &AAudio_convertStreamStateToText(
669                            AAudioStream_getState(outputStream))[textOffset],
670                    AAudioStream_getXRunCount(outputStream)
671             );
672         }
673         int32_t periodMillis = (timeMillis < 2000) ? kLogPeriodMillis / 4 : kLogPeriodMillis;
674         usleep(periodMillis * 1000);
675         timeMillis += periodMillis;
676     }
677 
678     result = player.stop();
679     if (result != AAUDIO_OK) {
680         printf("ERROR - player.stop() returned %d = %s\n",
681                result, AAudio_convertResultToText(result));
682         goto finish;
683     }
684 
685     result = recorder.stop();
686     if (result != AAUDIO_OK) {
687         printf("ERROR - recorder.stop() returned %d = %s\n",
688                result, AAudio_convertResultToText(result));
689         goto finish;
690     }
691 
692     printf("input error = %d = %s\n",
693            loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
694 /*
695     // TODO Restore this code some day if we want to save files.
696     written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
697     if (written > 0) {
698         printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
699                written, FILENAME_ECHOS);
700     }
701 
702     written = loopbackData.audioRecording.save(FILENAME_ALL);
703     if (written > 0) {
704         printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
705                written, FILENAME_ALL);
706     }
707 */
708     if (loopbackData.inputError == AAUDIO_OK) {
709         if (testMode == TEST_GLITCHES) {
710             if (loopbackData.numGlitchEvents > 0) {
711                 // Graph around the first glitch if there is one.
712                 const int32_t start = loopbackData.glitchFrames[0] - 8;
713                 const int32_t end = start + outputFramesPerBurst + 8 + 8;
714                 printAudioGraphRegion(loopbackData.audioRecording, start, end);
715             } else {
716                 // Or graph the middle of the signal.
717                 const int32_t start = loopbackData.audioRecording.size() / 2;
718                 const int32_t end = start + 200;
719                 printAudioGraphRegion(loopbackData.audioRecording, start, end);
720             }
721         }
722 
723         std::cout << "Please wait several seconds for analysis to complete.\n";
724         std::cout << loopbackData.loopbackProcessor->analyze();
725     }
726 
727     {
728         int32_t framesRead = AAudioStream_getFramesRead(inputStream);
729         int32_t framesWritten = AAudioStream_getFramesWritten(inputStream);
730         const int64_t framesAvailable = framesWritten - framesRead;
731         printf("Callback Results ---------------------------------------- INPUT\n");
732         printf("  input overruns   = %8d\n", AAudioStream_getXRunCount(inputStream));
733         printf("  framesWritten    = %8d\n", framesWritten);
734         printf("  framesRead       = %8d\n", framesRead);
735         printf("  myFramesRead     = %8d\n", (int) loopbackData.framesReadTotal);
736         printf("  written - read   = %8d\n", (int) framesAvailable);
737         printf("  insufficient #   = %8d\n", (int) loopbackData.insufficientReadCount);
738         if (loopbackData.insufficientReadCount > 0) {
739             printf("  insuffic. frames = %8d\n", (int) loopbackData.insufficientReadFrames);
740         }
741         int32_t actualInputCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
742         if (framesAvailable > 2 * actualInputCapacity) {
743             printf("  WARNING: written - read > 2*capacity !\n");
744         }
745     }
746 
747     {
748         int32_t framesRead = AAudioStream_getFramesRead(outputStream);
749         int32_t framesWritten = AAudioStream_getFramesWritten(outputStream);
750         printf("Callback Results ---------------------------------------- OUTPUT\n");
751         printf("  output underruns = %8d\n", AAudioStream_getXRunCount(outputStream));
752         printf("  myFramesWritten  = %8d\n", (int) loopbackData.framesWrittenTotal);
753         printf("  framesWritten    = %8d\n", framesWritten);
754         printf("  framesRead       = %8d\n", framesRead);
755         printf("  min numFrames    = %8d\n", (int) loopbackData.minNumFrames);
756         printf("  max numFrames    = %8d\n", (int) loopbackData.maxNumFrames);
757     }
758 
759     if (loopbackData.insufficientReadCount > 3) {
760         printf("ERROR: LOOPBACK PROCESSING FAILED. insufficientReadCount too high\n");
761         result = AAUDIO_ERROR_UNAVAILABLE;
762     }
763 
764 finish:
765     player.close();
766     recorder.close();
767     delete[] loopbackData.inputFloatData;
768     delete[] loopbackData.inputShortData;
769 
770 report_result:
771 
772     for (int i = 0; i < loopbackData.numGlitchEvents; i++) {
773         printf("  glitch at frame %d\n", loopbackData.glitchFrames[i]);
774     }
775 
776     written = loopbackData.loopbackProcessor->save(FILENAME_PROCESSED);
777     if (written > 0) {
778         printf("main() wrote %8d processed samples to \"%s\" on Android device\n",
779                written, FILENAME_PROCESSED);
780     }
781 
782     if (loopbackData.loopbackProcessor->getResult() < 0) {
783         result = loopbackData.loopbackProcessor->getResult();
784     }
785     printf(RESULT_TAG "result = %d \n", result); // machine readable
786     printf("result is %s\n", AAudio_convertResultToText(result)); // human readable
787     if (result != AAUDIO_OK) {
788         printf("TEST FAILED\n");
789         return EXIT_FAILURE;
790     } else {
791         printf("TEST PASSED\n");
792         return EXIT_SUCCESS;
793     }
794 }
795