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
47constexpr int kNumCallbacksToDrain   = 20;
48constexpr int kNumCallbacksToDiscard = 20;
49
50struct 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
77static 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
91static 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
115static 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
225static 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
234static 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
253static 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
273enum {
274    TEST_SINE_MAGNITUDE = 0,
275    TEST_ECHO_LATENCY,
276    TEST_FILE_LATENCY,
277};
278
279static 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
299void 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
327int 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
653finish:
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