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