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