• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <math.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include <aaudio/AAudio.h>
31 #include <aaudio/AAudioTesting.h>
32 
33 #include "AAudioSimplePlayer.h"
34 #include "AAudioSimpleRecorder.h"
35 #include "AAudioExampleUtils.h"
36 #include "LoopbackAnalyzer.h"
37 
38 // Tag for machine readable results as property = value pairs
39 #define RESULT_TAG              "RESULT: "
40 #define NUM_SECONDS             5
41 #define PERIOD_MILLIS           1000
42 #define NUM_INPUT_CHANNELS      1
43 #define FILENAME_ALL            "/data/loopback_all.wav"
44 #define FILENAME_ECHOS          "/data/loopback_echos.wav"
45 #define APP_VERSION             "0.2.04"
46 
47 constexpr int kNumCallbacksToDrain   = 20;
48 constexpr int kNumCallbacksToDiscard = 20;
49 
50 struct LoopbackData {
51     AAudioStream      *inputStream = nullptr;
52     int32_t            inputFramesMaximum = 0;
53     int16_t           *inputShortData = nullptr;
54     float             *inputFloatData = nullptr;
55     aaudio_format_t    actualInputFormat = AAUDIO_FORMAT_INVALID;
56     int32_t            actualInputChannelCount = 0;
57     int32_t            actualOutputChannelCount = 0;
58     int32_t            numCallbacksToDrain = kNumCallbacksToDrain;
59     int32_t            numCallbacksToDiscard = kNumCallbacksToDiscard;
60     int32_t            minNumFrames = INT32_MAX;
61     int32_t            maxNumFrames = 0;
62     int32_t            insufficientReadCount = 0;
63     int32_t            insufficientReadFrames = 0;
64     int32_t            framesReadTotal = 0;
65     int32_t            framesWrittenTotal = 0;
66     bool               isDone = false;
67 
68     aaudio_result_t    inputError = AAUDIO_OK;
69     aaudio_result_t    outputError = AAUDIO_OK;
70 
71     SineAnalyzer       sineAnalyzer;
72     EchoAnalyzer       echoAnalyzer;
73     AudioRecording     audioRecording;
74     LoopbackProcessor *loopbackProcessor;
75 };
76 
convertPcm16ToFloat(const int16_t * source,float * destination,int32_t numSamples)77 static void convertPcm16ToFloat(const int16_t *source,
78                                 float *destination,
79                                 int32_t numSamples) {
80     constexpr float scaler = 1.0f / 32768.0f;
81     for (int i = 0; i < numSamples; i++) {
82         destination[i] = source[i] * scaler;
83     }
84 }
85 
86 // ====================================================================================
87 // ========================= CALLBACK =================================================
88 // ====================================================================================
89 // Callback function that fills the audio output buffer.
90 
readFormattedData(LoopbackData * myData,int32_t numFrames)91 static int32_t readFormattedData(LoopbackData *myData, int32_t numFrames) {
92     int32_t framesRead = AAUDIO_ERROR_INVALID_FORMAT;
93     if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
94         framesRead = AAudioStream_read(myData->inputStream, myData->inputShortData,
95                                        numFrames,
96                                        0 /* timeoutNanoseconds */);
97     } else if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_FLOAT) {
98         framesRead = AAudioStream_read(myData->inputStream, myData->inputFloatData,
99                                        numFrames,
100                                        0 /* timeoutNanoseconds */);
101     } else {
102         printf("ERROR actualInputFormat = %d\n", myData->actualInputFormat);
103         assert(false);
104     }
105     if (framesRead < 0) {
106         myData->inputError = framesRead;
107         printf("ERROR in read = %d = %s\n", framesRead,
108                AAudio_convertResultToText(framesRead));
109     } else {
110         myData->framesReadTotal += framesRead;
111     }
112     return framesRead;
113 }
114 
MyDataCallbackProc(AAudioStream * outputStream,void * userData,void * audioData,int32_t numFrames)115 static aaudio_data_callback_result_t MyDataCallbackProc(
116         AAudioStream *outputStream,
117         void *userData,
118         void *audioData,
119         int32_t numFrames
120 ) {
121     (void) outputStream;
122     aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_CONTINUE;
123     LoopbackData *myData = (LoopbackData *) userData;
124     float  *outputData = (float  *) audioData;
125 
126     // Read audio data from the input stream.
127     int32_t actualFramesRead;
128 
129     if (numFrames > myData->inputFramesMaximum) {
130         myData->inputError = AAUDIO_ERROR_OUT_OF_RANGE;
131         return AAUDIO_CALLBACK_RESULT_STOP;
132     }
133 
134     if (numFrames > myData->maxNumFrames) {
135         myData->maxNumFrames = numFrames;
136     }
137     if (numFrames < myData->minNumFrames) {
138         myData->minNumFrames = numFrames;
139     }
140 
141     // Silence the output.
142     int32_t numBytes = numFrames * myData->actualOutputChannelCount * sizeof(float);
143     memset(audioData, 0 /* value */, numBytes);
144 
145     if (myData->numCallbacksToDrain > 0) {
146         // Drain the input.
147         int32_t totalFramesRead = 0;
148         do {
149             actualFramesRead = readFormattedData(myData, numFrames);
150             if (actualFramesRead) {
151                 totalFramesRead += actualFramesRead;
152             }
153             // Ignore errors because input stream may not be started yet.
154         } while (actualFramesRead > 0);
155         // Only counts if we actually got some data.
156         if (totalFramesRead > 0) {
157             myData->numCallbacksToDrain--;
158         }
159 
160     } else if (myData->numCallbacksToDiscard > 0) {
161         // Ignore. Allow the input to fill back up to equilibrium with the output.
162         actualFramesRead = readFormattedData(myData, numFrames);
163         if (actualFramesRead < 0) {
164             result = AAUDIO_CALLBACK_RESULT_STOP;
165         }
166         myData->numCallbacksToDiscard--;
167 
168     } else {
169 
170         int32_t numInputBytes = numFrames * myData->actualInputChannelCount * sizeof(float);
171         memset(myData->inputFloatData, 0 /* value */, numInputBytes);
172 
173         // Process data after equilibrium.
174         int64_t inputFramesWritten = AAudioStream_getFramesWritten(myData->inputStream);
175         int64_t inputFramesRead = AAudioStream_getFramesRead(myData->inputStream);
176         int64_t framesAvailable = inputFramesWritten - inputFramesRead;
177         actualFramesRead = readFormattedData(myData, numFrames);
178         if (actualFramesRead < 0) {
179             result = AAUDIO_CALLBACK_RESULT_STOP;
180         } else {
181 
182             if (actualFramesRead < numFrames) {
183                 if(actualFramesRead < (int32_t) framesAvailable) {
184                     printf("insufficient but numFrames = %d"
185                                    ", actualFramesRead = %d"
186                                    ", inputFramesWritten = %d"
187                                    ", inputFramesRead = %d"
188                                    ", available = %d\n",
189                            numFrames,
190                            actualFramesRead,
191                            (int) inputFramesWritten,
192                            (int) inputFramesRead,
193                            (int) framesAvailable);
194                 }
195                 myData->insufficientReadCount++;
196                 myData->insufficientReadFrames += numFrames - actualFramesRead; // deficit
197             }
198 
199             int32_t numSamples = actualFramesRead * myData->actualInputChannelCount;
200 
201             if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
202                 convertPcm16ToFloat(myData->inputShortData, myData->inputFloatData, numSamples);
203             }
204             // Save for later.
205             myData->audioRecording.write(myData->inputFloatData,
206                                          myData->actualInputChannelCount,
207                                          numFrames);
208             // Analyze the data.
209             myData->loopbackProcessor->process(myData->inputFloatData,
210                                                myData->actualInputChannelCount,
211                                                outputData,
212                                                myData->actualOutputChannelCount,
213                                                numFrames);
214             myData->isDone = myData->loopbackProcessor->isDone();
215             if (myData->isDone) {
216                 result = AAUDIO_CALLBACK_RESULT_STOP;
217             }
218         }
219     }
220     myData->framesWrittenTotal += numFrames;
221 
222     return result;
223 }
224 
MyErrorCallbackProc(AAudioStream * stream __unused,void * userData __unused,aaudio_result_t error)225 static void MyErrorCallbackProc(
226         AAudioStream *stream __unused,
227         void *userData __unused,
228         aaudio_result_t error) {
229     printf("Error Callback, error: %d\n",(int)error);
230     LoopbackData *myData = (LoopbackData *) userData;
231     myData->outputError = error;
232 }
233 
usage()234 static void usage() {
235     printf("Usage: aaudio_loopback [OPTION]...\n\n");
236     AAudioArgsParser::usage();
237     printf("      -B{frames}        input capacity in frames\n");
238     printf("      -C{channels}      number of input channels\n");
239     printf("      -F{0,1,2}         input format, 1=I16, 2=FLOAT\n");
240     printf("      -g{gain}          recirculating loopback gain\n");
241     printf("      -P{inPerf}        set input AAUDIO_PERFORMANCE_MODE*\n");
242     printf("          n for _NONE\n");
243     printf("          l for _LATENCY\n");
244     printf("          p for _POWER_SAVING\n");
245     printf("      -t{test}          select test mode\n");
246     printf("          m for sine magnitude\n");
247     printf("          e for echo latency (default)\n");
248     printf("          f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
249     printf("      -X  use EXCLUSIVE mode for input\n");
250     printf("Example:  aaudio_loopback -n2 -pl -Pl -x\n");
251 }
252 
parsePerformanceMode(char c)253 static aaudio_performance_mode_t parsePerformanceMode(char c) {
254     aaudio_performance_mode_t mode = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
255     c = tolower(c);
256     switch (c) {
257         case 'n':
258             mode = AAUDIO_PERFORMANCE_MODE_NONE;
259             break;
260         case 'l':
261             mode = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
262             break;
263         case 'p':
264             mode = AAUDIO_PERFORMANCE_MODE_POWER_SAVING;
265             break;
266         default:
267             printf("ERROR in value performance mode %c\n", c);
268             break;
269     }
270     return mode;
271 }
272 
273 enum {
274     TEST_SINE_MAGNITUDE = 0,
275     TEST_ECHO_LATENCY,
276     TEST_FILE_LATENCY,
277 };
278 
parseTestMode(char c)279 static int parseTestMode(char c) {
280     int testMode = TEST_ECHO_LATENCY;
281     c = tolower(c);
282     switch (c) {
283         case 'm':
284             testMode = TEST_SINE_MAGNITUDE;
285             break;
286         case 'e':
287             testMode = TEST_ECHO_LATENCY;
288             break;
289         case 'f':
290             testMode = TEST_FILE_LATENCY;
291             break;
292         default:
293             printf("ERROR in value test mode %c\n", c);
294             break;
295     }
296     return testMode;
297 }
298 
printAudioGraph(AudioRecording & recording,int numSamples)299 void printAudioGraph(AudioRecording &recording, int numSamples) {
300     int32_t start = recording.size() / 2;
301     int32_t end = start + numSamples;
302     if (end >= recording.size()) {
303         end = recording.size() - 1;
304     }
305     float *data = recording.getData();
306     // Normalize data so we can see it better.
307     float maxSample = 0.01;
308     for (int32_t i = start; i < end; i++) {
309         float samplePos = fabs(data[i]);
310         if (samplePos > maxSample) {
311             maxSample = samplePos;
312         }
313     }
314     float gain = 0.98f / maxSample;
315 
316     for (int32_t i = start; i < end; i++) {
317         float sample = data[i];
318         printf("%6d: %7.4f ", i, sample); // actual value
319         sample *= gain;
320         printAudioScope(sample);
321     }
322 }
323 
324 
325 // ====================================================================================
326 // TODO break up this large main() function into smaller functions
main(int argc,const char ** argv)327 int main(int argc, const char **argv)
328 {
329 
330     AAudioArgsParser      argParser;
331     AAudioSimplePlayer    player;
332     AAudioSimpleRecorder  recorder;
333     LoopbackData          loopbackData;
334     AAudioStream         *inputStream                = nullptr;
335     AAudioStream         *outputStream               = nullptr;
336 
337     aaudio_result_t       result = AAUDIO_OK;
338     aaudio_sharing_mode_t requestedInputSharingMode  = AAUDIO_SHARING_MODE_SHARED;
339     int                   requestedInputChannelCount = NUM_INPUT_CHANNELS;
340     aaudio_format_t       requestedInputFormat       = AAUDIO_FORMAT_UNSPECIFIED;
341     int32_t               requestedInputCapacity     = -1;
342     aaudio_performance_mode_t inputPerformanceLevel  = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
343 
344     int32_t               outputFramesPerBurst = 0;
345 
346     aaudio_format_t       actualOutputFormat         = AAUDIO_FORMAT_INVALID;
347     int32_t               actualSampleRate           = 0;
348     int                   written                    = 0;
349 
350     int                   testMode                   = TEST_ECHO_LATENCY;
351     double                gain                       = 1.0;
352 
353     // Make printf print immediately so that debug info is not stuck
354     // in a buffer if we hang or crash.
355     setvbuf(stdout, NULL, _IONBF, (size_t) 0);
356 
357     printf("%s - Audio loopback using AAudio V" APP_VERSION "\n", argv[0]);
358 
359     for (int i = 1; i < argc; i++) {
360         const char *arg = argv[i];
361         if (argParser.parseArg(arg)) {
362             // Handle options that are not handled by the ArgParser
363             if (arg[0] == '-') {
364                 char option = arg[1];
365                 switch (option) {
366                     case 'B':
367                         requestedInputCapacity = atoi(&arg[2]);
368                         break;
369                     case 'C':
370                         requestedInputChannelCount = atoi(&arg[2]);
371                         break;
372                     case 'F':
373                         requestedInputFormat = atoi(&arg[2]);
374                         break;
375                     case 'g':
376                         gain = atof(&arg[2]);
377                         break;
378                     case 'P':
379                         inputPerformanceLevel = parsePerformanceMode(arg[2]);
380                         break;
381                     case 'X':
382                         requestedInputSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
383                         break;
384                     case 't':
385                         testMode = parseTestMode(arg[2]);
386                         break;
387                     default:
388                         usage();
389                         exit(EXIT_FAILURE);
390                         break;
391                 }
392             } else {
393                 usage();
394                 exit(EXIT_FAILURE);
395                 break;
396             }
397         }
398 
399     }
400 
401     if (inputPerformanceLevel < 0) {
402         printf("illegal inputPerformanceLevel = %d\n", inputPerformanceLevel);
403         exit(EXIT_FAILURE);
404     }
405 
406     int32_t requestedDuration = argParser.getDurationSeconds();
407     int32_t requestedDurationMillis = requestedDuration * MILLIS_PER_SECOND;
408     int32_t timeMillis = 0;
409     int32_t recordingDuration = std::min(60 * 5, requestedDuration);
410 
411     switch(testMode) {
412         case TEST_SINE_MAGNITUDE:
413             loopbackData.loopbackProcessor = &loopbackData.sineAnalyzer;
414             break;
415         case TEST_ECHO_LATENCY:
416             loopbackData.echoAnalyzer.setGain(gain);
417             loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
418             break;
419         case TEST_FILE_LATENCY: {
420             loopbackData.echoAnalyzer.setGain(gain);
421 
422             loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
423             int read = loopbackData.loopbackProcessor->load(FILENAME_ECHOS);
424             printf("main() read %d mono samples from %s on Android device\n", read, FILENAME_ECHOS);
425             loopbackData.loopbackProcessor->report();
426             return 0;
427         }
428             break;
429         default:
430             exit(1);
431             break;
432     }
433 
434     printf("OUTPUT stream ----------------------------------------\n");
435     result = player.open(argParser, MyDataCallbackProc, MyErrorCallbackProc, &loopbackData);
436     if (result != AAUDIO_OK) {
437         fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
438         exit(1);
439     }
440     outputStream = player.getStream();
441 
442     actualOutputFormat = AAudioStream_getFormat(outputStream);
443     if (actualOutputFormat != AAUDIO_FORMAT_PCM_FLOAT) {
444         fprintf(stderr, "ERROR - only AAUDIO_FORMAT_PCM_FLOAT supported\n");
445         exit(1);
446     }
447 
448     actualSampleRate = AAudioStream_getSampleRate(outputStream);
449     loopbackData.audioRecording.allocate(recordingDuration * actualSampleRate);
450     loopbackData.audioRecording.setSampleRate(actualSampleRate);
451     outputFramesPerBurst = AAudioStream_getFramesPerBurst(outputStream);
452 
453     argParser.compareWithStream(outputStream);
454 
455     printf("INPUT  stream ----------------------------------------\n");
456     // Use different parameters for the input.
457     argParser.setNumberOfBursts(AAUDIO_UNSPECIFIED);
458     argParser.setFormat(requestedInputFormat);
459     argParser.setPerformanceMode(inputPerformanceLevel);
460     argParser.setChannelCount(requestedInputChannelCount);
461     argParser.setSharingMode(requestedInputSharingMode);
462 
463     // Make sure the input buffer has plenty of capacity.
464     // Extra capacity on input should not increase latency if we keep it drained.
465     int32_t inputBufferCapacity = requestedInputCapacity;
466     if (inputBufferCapacity < 0) {
467         int32_t outputBufferCapacity = AAudioStream_getBufferCapacityInFrames(outputStream);
468         inputBufferCapacity = 2 * outputBufferCapacity;
469     }
470     argParser.setBufferCapacity(inputBufferCapacity);
471 
472     result = recorder.open(argParser);
473     if (result != AAUDIO_OK) {
474         fprintf(stderr, "ERROR -  recorder.open() returned %d\n", result);
475         goto finish;
476     }
477     inputStream = loopbackData.inputStream = recorder.getStream();
478 
479     {
480         int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
481         result = AAudioStream_setBufferSizeInFrames(inputStream, actualCapacity);
482         if (result < 0) {
483             fprintf(stderr, "ERROR -  AAudioStream_setBufferSizeInFrames() returned %d\n", result);
484             goto finish;
485         } else {}
486     }
487 
488     argParser.compareWithStream(inputStream);
489 
490     // If the input stream is too small then we cannot satisfy the output callback.
491     {
492         int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
493         if (actualCapacity < 2 * outputFramesPerBurst) {
494             fprintf(stderr, "ERROR - input capacity < 2 * outputFramesPerBurst\n");
495             goto finish;
496         }
497     }
498 
499     // ------- Setup loopbackData -----------------------------
500     loopbackData.actualInputFormat = AAudioStream_getFormat(inputStream);
501 
502     loopbackData.actualInputChannelCount = recorder.getChannelCount();
503     loopbackData.actualOutputChannelCount = player.getChannelCount();
504 
505     // Allocate a buffer for the audio data.
506     loopbackData.inputFramesMaximum = 32 * AAudioStream_getFramesPerBurst(inputStream);
507 
508     if (loopbackData.actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
509         loopbackData.inputShortData = new int16_t[loopbackData.inputFramesMaximum
510                                                   * loopbackData.actualInputChannelCount]{};
511     }
512     loopbackData.inputFloatData = new float[loopbackData.inputFramesMaximum *
513                                               loopbackData.actualInputChannelCount]{};
514 
515     loopbackData.loopbackProcessor->reset();
516 
517     // Start OUTPUT first so INPUT does not overflow.
518     result = player.start();
519     if (result != AAUDIO_OK) {
520         printf("ERROR - AAudioStream_requestStart(output) returned %d = %s\n",
521                result, AAudio_convertResultToText(result));
522         goto finish;
523     }
524 
525     result = recorder.start();
526     if (result != AAUDIO_OK) {
527         printf("ERROR - AAudioStream_requestStart(input) returned %d = %s\n",
528                result, AAudio_convertResultToText(result));
529         goto finish;
530     }
531 
532     printf("------- sleep and log while the callback runs --------------\n");
533     while (timeMillis <= requestedDurationMillis) {
534         if (loopbackData.inputError != AAUDIO_OK) {
535             printf("  ERROR on input stream\n");
536             break;
537         } else if (loopbackData.outputError != AAUDIO_OK) {
538                 printf("  ERROR on output stream\n");
539                 break;
540         } else if (loopbackData.isDone) {
541                 printf("  Test says it is DONE!\n");
542                 break;
543         } else {
544             // Log a line of stream data.
545             printf("%7.3f: ", 0.001 * timeMillis); // display in seconds
546             loopbackData.loopbackProcessor->printStatus();
547             printf(" insf %3d,", (int) loopbackData.insufficientReadCount);
548 
549             int64_t inputFramesWritten = AAudioStream_getFramesWritten(inputStream);
550             int64_t inputFramesRead = AAudioStream_getFramesRead(inputStream);
551             int64_t outputFramesWritten = AAudioStream_getFramesWritten(outputStream);
552             int64_t outputFramesRead = AAudioStream_getFramesRead(outputStream);
553             static const int textOffset = strlen("AAUDIO_STREAM_STATE_"); // strip this off
554             printf(" | INPUT: wr %7lld - rd %7lld = %5lld, st %8s, oruns %3d",
555                    (long long) inputFramesWritten,
556                    (long long) inputFramesRead,
557                    (long long) (inputFramesWritten - inputFramesRead),
558                    &AAudio_convertStreamStateToText(
559                            AAudioStream_getState(inputStream))[textOffset],
560                    AAudioStream_getXRunCount(inputStream));
561 
562             printf(" | OUTPUT: wr %7lld - rd %7lld = %5lld, st %8s, uruns %3d\n",
563                    (long long) outputFramesWritten,
564                    (long long) outputFramesRead,
565                     (long long) (outputFramesWritten - outputFramesRead),
566                    &AAudio_convertStreamStateToText(
567                            AAudioStream_getState(outputStream))[textOffset],
568                    AAudioStream_getXRunCount(outputStream)
569             );
570         }
571         int32_t periodMillis = (timeMillis < 2000) ? PERIOD_MILLIS / 4 : PERIOD_MILLIS;
572         usleep(periodMillis * 1000);
573         timeMillis += periodMillis;
574     }
575 
576     result = player.stop();
577     if (result != AAUDIO_OK) {
578         printf("ERROR - player.stop() returned %d = %s\n",
579                result, AAudio_convertResultToText(result));
580         goto finish;
581     }
582 
583     result = recorder.stop();
584     if (result != AAUDIO_OK) {
585         printf("ERROR - recorder.stop() returned %d = %s\n",
586                result, AAudio_convertResultToText(result));
587         goto finish;
588     }
589 
590     printf("input error = %d = %s\n",
591            loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
592 
593     if (loopbackData.inputError == AAUDIO_OK) {
594         if (testMode == TEST_SINE_MAGNITUDE) {
595             printAudioGraph(loopbackData.audioRecording, 200);
596         }
597         // Print again so we don't have to scroll past waveform.
598         printf("OUTPUT Stream ----------------------------------------\n");
599         argParser.compareWithStream(outputStream);
600         printf("INPUT  Stream ----------------------------------------\n");
601         argParser.compareWithStream(inputStream);
602 
603         loopbackData.loopbackProcessor->report();
604     }
605 
606     {
607         int32_t framesRead = AAudioStream_getFramesRead(inputStream);
608         int32_t framesWritten = AAudioStream_getFramesWritten(inputStream);
609         printf("Callback Results ---------------------------------------- INPUT\n");
610         printf("  input overruns   = %d\n", AAudioStream_getXRunCount(inputStream));
611         printf("  framesWritten    = %8d\n", framesWritten);
612         printf("  framesRead       = %8d\n", framesRead);
613         printf("  myFramesRead     = %8d\n", (int) loopbackData.framesReadTotal);
614         printf("  written - read   = %8d\n", (int) (framesWritten - framesRead));
615         printf("  insufficient #   = %8d\n", (int) loopbackData.insufficientReadCount);
616         if (loopbackData.insufficientReadCount > 0) {
617             printf("  insufficient frames = %8d\n", (int) loopbackData.insufficientReadFrames);
618         }
619     }
620     {
621         int32_t framesRead = AAudioStream_getFramesRead(outputStream);
622         int32_t framesWritten = AAudioStream_getFramesWritten(outputStream);
623         printf("Callback Results ---------------------------------------- OUTPUT\n");
624         printf("  output underruns = %d\n", AAudioStream_getXRunCount(outputStream));
625         printf("  myFramesWritten  = %8d\n", (int) loopbackData.framesWrittenTotal);
626         printf("  framesWritten    = %8d\n", framesWritten);
627         printf("  framesRead       = %8d\n", framesRead);
628         printf("  min numFrames    = %8d\n", (int) loopbackData.minNumFrames);
629         printf("  max numFrames    = %8d\n", (int) loopbackData.maxNumFrames);
630     }
631 
632     written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
633     if (written > 0) {
634         printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
635                written, FILENAME_ECHOS);
636     }
637 
638     written = loopbackData.audioRecording.save(FILENAME_ALL);
639     if (written > 0) {
640         printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
641                written, FILENAME_ALL);
642     }
643 
644     if (loopbackData.loopbackProcessor->getResult() < 0) {
645         printf("ERROR: LOOPBACK PROCESSING FAILED. Maybe because the volume was too low.\n");
646         result = loopbackData.loopbackProcessor->getResult();
647     }
648     if (loopbackData.insufficientReadCount > 3) {
649         printf("ERROR: LOOPBACK PROCESSING FAILED. insufficientReadCount too high\n");
650         result = AAUDIO_ERROR_UNAVAILABLE;
651     }
652 
653 finish:
654     player.close();
655     recorder.close();
656     delete[] loopbackData.inputFloatData;
657     delete[] loopbackData.inputShortData;
658 
659     printf(RESULT_TAG "result = %d \n", result); // machine readable
660     printf("result is %s\n", AAudio_convertResultToText(result)); // human readable
661     if (result != AAUDIO_OK) {
662         printf("FAILURE\n");
663         return EXIT_FAILURE;
664     } else {
665         printf("SUCCESS\n");
666         return EXIT_SUCCESS;
667     }
668 }
669 
670