blob: 4affaed66bfa8c6b8275fd7cedeb5a3a58c7eb83 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Audio loopback tests to measure the round trip latency and glitches.
#include <algorithm>
#include <assert.h>
#include <cctype>
#include <errno.h>
#include <iomanip>
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <aaudio/AAudio.h>
#include <aaudio/AAudioTesting.h>
#include "AAudioSimplePlayer.h"
#include "AAudioSimpleRecorder.h"
#include "AAudioExampleUtils.h"
// Get logging macros from OboeTester
#include "android_debug.h"
// Get signal analyzers from OboeTester
#include "analyzer/GlitchAnalyzer.h"
#include "analyzer/LatencyAnalyzer.h"
#include "../../utils/AAudioExampleUtils.h"
// V0.4.00 = rectify and low-pass filter the echos, auto-correlate entire echo
// V0.4.01 = add -h hang option
// fix -n option to set output buffer for -tm
// plot first glitch
// V0.4.02 = allow -n0 for minimal buffer size
// V0.5.00 = use latency analyzer copied from OboeTester, uses random noise for latency
// V0.5.01 = use latency analyzer directly from OboeTester in external/oboe
#define APP_VERSION "0.5.01"
// Tag for machine readable results as property = value pairs
#define RESULT_TAG "RESULT: "
#define FILENAME_ALL "/data/loopback_all.wav"
#define FILENAME_ECHOS "/data/loopback_echos.wav"
#define FILENAME_PROCESSED "/data/loopback_processed.wav"
constexpr int kLogPeriodMillis = 1000;
constexpr int kNumInputChannels = 1;
constexpr int kNumCallbacksToDrain = 20;
constexpr int kNumCallbacksToNotRead = 0; // let input fill back up
constexpr int kNumCallbacksToDiscard = 20;
constexpr int kDefaultHangTimeMillis = 50;
constexpr int kMaxGlitchEventsToSave = 32;
static void printAudioScope(float sample) {
const int maxStars = 80; // arbitrary, fits on one line
char c = '*';
if (sample < -1.0) {
sample = -1.0;
c = '$';
} else if (sample > 1.0) {
sample = 1.0;
c = '$';
}
int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
printf("%*c%c\n", numSpaces, ' ', c);
}
struct LoopbackData {
AAudioStream *inputStream = nullptr;
AAudioStream *outputStream = nullptr;
int32_t inputFramesMaximum = 0;
int16_t *inputShortData = nullptr;
float *inputFloatData = nullptr;
aaudio_format_t actualInputFormat = AAUDIO_FORMAT_INVALID;
int32_t actualInputChannelCount = 0;
int32_t actualOutputChannelCount = 0;
int32_t numCallbacksToDrain = kNumCallbacksToDrain;
int32_t numCallbacksToNotRead = kNumCallbacksToNotRead;
int32_t numCallbacksToDiscard = kNumCallbacksToDiscard;
int32_t minNumFrames = INT32_MAX;
int32_t maxNumFrames = 0;
int32_t insufficientReadCount = 0;
int32_t insufficientReadFrames = 0;
int32_t framesReadTotal = 0;
int32_t framesWrittenTotal = 0;
int32_t hangPeriodMillis = 5 * 1000; // time between hangs
int32_t hangCountdownFrames = 5 * 48000; // frames til next hang
int32_t hangTimeMillis = 0; // 0 for no hang
bool isDone = false;
aaudio_result_t inputError = AAUDIO_OK;
aaudio_result_t outputError = AAUDIO_OK;
GlitchAnalyzer sineAnalyzer;
WhiteNoiseLatencyAnalyzer echoAnalyzer;
AudioRecording audioRecording;
LoopbackProcessor *loopbackProcessor;
int32_t glitchFrames[kMaxGlitchEventsToSave];
int32_t numGlitchEvents = 0;
void hangIfRequested(int32_t numFrames) {
if (hangTimeMillis > 0) {
hangCountdownFrames -= numFrames;
if (hangCountdownFrames <= 0) {
const int64_t startNanos = getNanoseconds();
usleep(hangTimeMillis * 1000);
const int64_t endNanos = getNanoseconds();
const int32_t elapsedMicros = (int32_t)
((endNanos - startNanos) / 1000);
printf("callback hanging for %d millis, actual = %d micros\n",
hangTimeMillis, elapsedMicros);
hangCountdownFrames = (int64_t) hangPeriodMillis
* AAudioStream_getSampleRate(outputStream)
/ 1000;
}
}
}
};
static void convertPcm16ToFloat(const int16_t *source,
float *destination,
int32_t numSamples) {
constexpr float scaler = 1.0f / 32768.0f;
for (int i = 0; i < numSamples; i++) {
destination[i] = source[i] * scaler;
}
}
// ====================================================================================
// ========================= CALLBACK =================================================
// ====================================================================================
// Callback function that fills the audio output buffer.
static int32_t readFormattedData(LoopbackData *myData, int32_t numFrames) {
int32_t framesRead = AAUDIO_ERROR_INVALID_FORMAT;
if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
framesRead = AAudioStream_read(myData->inputStream, myData->inputShortData,
numFrames,
0 /* timeoutNanoseconds */);
} else if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_FLOAT) {
framesRead = AAudioStream_read(myData->inputStream, myData->inputFloatData,
numFrames,
0 /* timeoutNanoseconds */);
} else {
printf("ERROR actualInputFormat = %d\n", myData->actualInputFormat);
assert(false);
}
if (framesRead < 0) {
// Expect INVALID_STATE if STATE_STARTING
if (myData->framesReadTotal > 0) {
myData->inputError = framesRead;
printf("ERROR in read = %d = %s\n", framesRead,
AAudio_convertResultToText(framesRead));
} else {
framesRead = 0;
}
} else {
myData->framesReadTotal += framesRead;
}
return framesRead;
}
static aaudio_data_callback_result_t MyDataCallbackProc(
AAudioStream *outputStream,
void *userData,
void *audioData,
int32_t numFrames
) {
(void) outputStream;
aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_CONTINUE;
LoopbackData *myData = (LoopbackData *) userData;
float *outputData = (float *) audioData;
// Read audio data from the input stream.
int32_t actualFramesRead;
if (numFrames > myData->inputFramesMaximum) {
myData->inputError = AAUDIO_ERROR_OUT_OF_RANGE;
return AAUDIO_CALLBACK_RESULT_STOP;
}
if (numFrames > myData->maxNumFrames) {
myData->maxNumFrames = numFrames;
}
if (numFrames < myData->minNumFrames) {
myData->minNumFrames = numFrames;
}
// Silence the output.
int32_t numBytes = numFrames * myData->actualOutputChannelCount * sizeof(float);
memset(audioData, 0 /* value */, numBytes);
if (myData->numCallbacksToDrain > 0) {
// Drain the input.
int32_t totalFramesRead = 0;
do {
actualFramesRead = readFormattedData(myData, numFrames);
if (actualFramesRead > 0) {
totalFramesRead += actualFramesRead;
} else if (actualFramesRead < 0) {
result = AAUDIO_CALLBACK_RESULT_STOP;
}
// Ignore errors because input stream may not be started yet.
} while (actualFramesRead > 0);
// Only counts if we actually got some data.
if (totalFramesRead > 0) {
myData->numCallbacksToDrain--;
}
} else if (myData->numCallbacksToNotRead > 0) {
// Let the input fill up a bit so we are not so close to the write pointer.
myData->numCallbacksToNotRead--;
} else if (myData->numCallbacksToDiscard > 0) {
// Ignore. Allow the input to fill back up to equilibrium with the output.
actualFramesRead = readFormattedData(myData, numFrames);
if (actualFramesRead < 0) {
result = AAUDIO_CALLBACK_RESULT_STOP;
}
myData->numCallbacksToDiscard--;
} else {
myData->hangIfRequested(numFrames);
int32_t numInputBytes = numFrames * myData->actualInputChannelCount * sizeof(float);
memset(myData->inputFloatData, 0 /* value */, numInputBytes);
// Process data after equilibrium.
int64_t inputFramesWritten = AAudioStream_getFramesWritten(myData->inputStream);
int64_t inputFramesRead = AAudioStream_getFramesRead(myData->inputStream);
int64_t framesAvailable = inputFramesWritten - inputFramesRead;
actualFramesRead = readFormattedData(myData, numFrames); // READ
if (actualFramesRead < 0) {
result = AAUDIO_CALLBACK_RESULT_STOP;
} else {
if (actualFramesRead < numFrames) {
if(actualFramesRead < (int32_t) framesAvailable) {
printf("insufficient for no reason, numFrames = %d"
", actualFramesRead = %d"
", inputFramesWritten = %d"
", inputFramesRead = %d"
", available = %d\n",
numFrames,
actualFramesRead,
(int) inputFramesWritten,
(int) inputFramesRead,
(int) framesAvailable);
}
myData->insufficientReadCount++;
myData->insufficientReadFrames += numFrames - actualFramesRead; // deficit
// printf("Error insufficientReadCount = %d\n",(int)myData->insufficientReadCount);
}
int32_t numSamples = actualFramesRead * myData->actualInputChannelCount;
if (myData->actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
convertPcm16ToFloat(myData->inputShortData, myData->inputFloatData, numSamples);
}
// Analyze the data.
myData->loopbackProcessor->process(myData->inputFloatData,
myData->actualInputChannelCount,
numFrames,
outputData,
myData->actualOutputChannelCount,
numFrames);
//
// if (procResult == LoopbackProcessor::PROCESS_RESULT_GLITCH) {
// if (myData->numGlitchEvents < kMaxGlitchEventsToSave) {
// myData->glitchFrames[myData->numGlitchEvents++] = myData->audioRecording.size();
// }
// }
// Save for later.
myData->audioRecording.write(myData->inputFloatData,
myData->actualInputChannelCount,
actualFramesRead);
myData->isDone = myData->loopbackProcessor->isDone();
if (myData->isDone) {
result = AAUDIO_CALLBACK_RESULT_STOP;
}
}
}
myData->framesWrittenTotal += numFrames;
return result;
}
static void MyErrorCallbackProc(
AAudioStream * /* stream */,
void * userData,
aaudio_result_t error) {
printf("Error Callback, error: %d\n",(int)error);
LoopbackData *myData = (LoopbackData *) userData;
myData->outputError = error;
}
static void usage() {
printf("Usage: aaudio_loopback [OPTION]...\n\n");
AAudioArgsParser::usage();
printf(" -B{frames} input capacity in frames\n");
printf(" -C{channels} number of input channels\n");
printf(" -D{deviceId} input device ID\n");
printf(" -F{0,1,2} input format, 1=I16, 2=FLOAT\n");
printf(" -g{gain} recirculating loopback gain\n");
printf(" -h{hangMillis} occasionally hang in the callback\n");
printf(" -P{inPerf} set input AAUDIO_PERFORMANCE_MODE*\n");
printf(" n for _NONE\n");
printf(" l for _LATENCY\n");
printf(" p for _POWER_SAVING\n");
printf(" -t{test} select test mode\n");
printf(" g for Glitch detection\n");
printf(" l for round trip Latency (default)\n");
printf(" f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
printf(" -X use EXCLUSIVE mode for input\n");
printf("Example: aaudio_loopback -n2 -pl -Pl -x\n");
}
static aaudio_performance_mode_t parsePerformanceMode(char c) {
aaudio_performance_mode_t mode = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
c = tolower(c);
switch (c) {
case 'n':
mode = AAUDIO_PERFORMANCE_MODE_NONE;
break;
case 'l':
mode = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
break;
case 'p':
mode = AAUDIO_PERFORMANCE_MODE_POWER_SAVING;
break;
default:
printf("ERROR in value performance mode %c\n", c);
break;
}
return mode;
}
enum {
TEST_GLITCHES = 0,
TEST_LATENCY,
TEST_FILE_LATENCY,
};
static int parseTestMode(char c) {
int testMode = TEST_LATENCY;
c = tolower(c);
switch (c) {
case 'm': // deprecated
case 'g':
testMode = TEST_GLITCHES;
break;
case 'e': // deprecated
case 'l':
testMode = TEST_LATENCY;
break;
case 'f':
testMode = TEST_FILE_LATENCY;
break;
default:
printf("ERROR in value test mode %c\n", c);
break;
}
return testMode;
}
void printAudioGraphRegion(AudioRecording &recording, int32_t start, int32_t end) {
if (end >= recording.size()) {
end = recording.size() - 1;
}
float *data = recording.getData();
// Normalize data so we can see it better.
float maxSample = 0.01;
for (int32_t i = start; i < end; i++) {
float samplePos = fabs(data[i]);
if (samplePos > maxSample) {
maxSample = samplePos;
}
}
float gain = 0.98f / maxSample;
for (int32_t i = start; i < end; i++) {
float sample = data[i];
printf("%6d: %7.4f ", i, sample); // actual value
sample *= gain;
printAudioScope(sample);
}
}
// ====================================================================================
// TODO break up this large main() function into smaller functions
int main(int argc, const char **argv)
{
AAudioArgsParser argParser;
AAudioSimplePlayer player;
AAudioSimpleRecorder recorder;
LoopbackData loopbackData;
AAudioStream *inputStream = nullptr;
AAudioStream *outputStream = nullptr;
aaudio_result_t result = AAUDIO_OK;
int32_t requestedInputDeviceId = AAUDIO_UNSPECIFIED;
aaudio_sharing_mode_t requestedInputSharingMode = AAUDIO_SHARING_MODE_SHARED;
int requestedInputChannelCount = kNumInputChannels;
aaudio_format_t requestedInputFormat = AAUDIO_FORMAT_UNSPECIFIED;
int32_t requestedInputCapacity = AAUDIO_UNSPECIFIED;
aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
int32_t outputFramesPerBurst = 0;
aaudio_format_t actualOutputFormat = AAUDIO_FORMAT_INVALID;
int32_t actualSampleRate = 0;
int written = 0;
int testMode = TEST_LATENCY;
double gain = 1.0;
int hangTimeMillis = 0;
std::string report;
// Make printf print immediately so that debug info is not stuck
// in a buffer if we hang or crash.
setvbuf(stdout, NULL, _IONBF, (size_t) 0);
printf("%s - Audio loopback using AAudio V" APP_VERSION "\n", argv[0]);
// Use LOW_LATENCY as the default to match input default.
argParser.setPerformanceMode(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (argParser.parseArg(arg)) {
// Handle options that are not handled by the ArgParser
if (arg[0] == '-') {
char option = arg[1];
switch (option) {
case 'B':
requestedInputCapacity = atoi(&arg[2]);
break;
case 'C':
requestedInputChannelCount = atoi(&arg[2]);
break;
case 'D':
requestedInputDeviceId = atoi(&arg[2]);
break;
case 'F':
requestedInputFormat = atoi(&arg[2]);
break;
case 'g':
gain = atof(&arg[2]);
break;
case 'h':
// Was there a number after the "-h"?
if (arg[2]) {
hangTimeMillis = atoi(&arg[2]);
} else {
// If no number then use the default.
hangTimeMillis = kDefaultHangTimeMillis;
}
break;
case 'P':
inputPerformanceLevel = parsePerformanceMode(arg[2]);
break;
case 'X':
requestedInputSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
break;
case 't':
testMode = parseTestMode(arg[2]);
break;
default:
usage();
exit(EXIT_FAILURE);
break;
}
} else {
usage();
exit(EXIT_FAILURE);
break;
}
}
}
if (inputPerformanceLevel < 0) {
printf("illegal inputPerformanceLevel = %d\n", inputPerformanceLevel);
exit(EXIT_FAILURE);
}
int32_t requestedDuration = argParser.getDurationSeconds();
int32_t requestedDurationMillis = requestedDuration * kMillisPerSecond;
int32_t timeMillis = 0;
int32_t recordingDuration = std::min(60 * 5, requestedDuration);
int32_t requestedOutputBursts = argParser.getNumberOfBursts();
switch(testMode) {
case TEST_GLITCHES:
loopbackData.loopbackProcessor = &loopbackData.sineAnalyzer;
break;
case TEST_LATENCY:
// TODO loopbackData.echoAnalyzer.setGain(gain);
loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
break;
case TEST_FILE_LATENCY: {
// TODO loopbackData.echoAnalyzer.setGain(gain);
loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
int read = loopbackData.loopbackProcessor->load(FILENAME_ECHOS);
printf("main() read %d mono samples from %s on Android device, rate = %d\n",
read, FILENAME_ECHOS,
loopbackData.loopbackProcessor->getSampleRate());
std::cout << loopbackData.loopbackProcessor->analyze();
goto report_result;
}
break;
default:
exit(1);
break;
}
printf("OUTPUT stream ----------------------------------------\n");
result = player.open(argParser, MyDataCallbackProc, MyErrorCallbackProc, &loopbackData);
if (result != AAUDIO_OK) {
fprintf(stderr, "ERROR - player.open() returned %d\n", result);
exit(1);
}
outputStream = loopbackData.outputStream = player.getStream();
actualOutputFormat = AAudioStream_getFormat(outputStream);
if (actualOutputFormat != AAUDIO_FORMAT_PCM_FLOAT) {
fprintf(stderr, "ERROR - only AAUDIO_FORMAT_PCM_FLOAT supported\n");
exit(1);
}
actualSampleRate = AAudioStream_getSampleRate(outputStream);
loopbackData.audioRecording.allocate(recordingDuration * actualSampleRate);
loopbackData.audioRecording.setSampleRate(actualSampleRate);
outputFramesPerBurst = AAudioStream_getFramesPerBurst(outputStream);
argParser.compareWithStream(outputStream);
printf("INPUT stream ----------------------------------------\n");
// Use different parameters for the input.
argParser.setDeviceId(requestedInputDeviceId);
argParser.setNumberOfBursts(AAudioParameters::kDefaultNumberOfBursts);
argParser.setFormat(requestedInputFormat);
argParser.setPerformanceMode(inputPerformanceLevel);
argParser.setChannelCount(requestedInputChannelCount);
argParser.setSharingMode(requestedInputSharingMode);
if (requestedInputCapacity != AAUDIO_UNSPECIFIED) {
printf("Warning! If you set input capacity then maybe no FAST track on Legacy path!\n");
}
argParser.setBufferCapacity(requestedInputCapacity);
result = recorder.open(argParser);
if (result != AAUDIO_OK) {
fprintf(stderr, "ERROR - recorder.open() returned %d\n", result);
goto finish;
}
inputStream = loopbackData.inputStream = recorder.getStream();
{
int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
(void) AAudioStream_setBufferSizeInFrames(inputStream, actualCapacity);
if (testMode == TEST_GLITCHES
&& requestedOutputBursts == AAUDIO_UNSPECIFIED) {
result = AAudioStream_setBufferSizeInFrames(outputStream, actualCapacity);
if (result < 0) {
fprintf(stderr, "ERROR - AAudioStream_setBufferSizeInFrames(output) returned %d\n",
result);
goto finish;
} else {
printf("Output buffer size set to match input capacity = %d frames!\n", result);
}
}
// If the input stream is too small then we cannot satisfy the output callback.
if (actualCapacity < 2 * outputFramesPerBurst) {
fprintf(stderr, "ERROR - input capacity < 2 * outputFramesPerBurst\n");
goto finish;
}
}
argParser.compareWithStream(inputStream);
// ------- Setup loopbackData -----------------------------
loopbackData.actualInputFormat = AAudioStream_getFormat(inputStream);
loopbackData.actualInputChannelCount = recorder.getChannelCount();
loopbackData.actualOutputChannelCount = player.getChannelCount();
// Allocate a buffer for the audio data.
loopbackData.inputFramesMaximum = 32 * AAudioStream_getFramesPerBurst(inputStream);
if (loopbackData.actualInputFormat == AAUDIO_FORMAT_PCM_I16) {
loopbackData.inputShortData = new int16_t[loopbackData.inputFramesMaximum
* loopbackData.actualInputChannelCount]{};
}
loopbackData.inputFloatData = new float[loopbackData.inputFramesMaximum *
loopbackData.actualInputChannelCount]{};
loopbackData.hangTimeMillis = hangTimeMillis;
loopbackData.loopbackProcessor->prepareToTest();
// Start OUTPUT first so INPUT does not overflow.
result = player.start();
if (result != AAUDIO_OK) {
goto finish;
}
result = recorder.start();
if (result != AAUDIO_OK) {
goto finish;
}
printf("------- sleep and log while the callback runs --------------\n");
while (timeMillis <= requestedDurationMillis) {
if (loopbackData.inputError != AAUDIO_OK) {
printf(" ERROR on input stream\n");
break;
} else if (loopbackData.outputError != AAUDIO_OK) {
printf(" ERROR on output stream\n");
break;
} else if (loopbackData.isDone) {
printf(" Test says it is DONE!\n");
break;
} else {
// Log a line of stream data.
printf("%7.3f: ", 0.001 * timeMillis); // display in seconds
loopbackData.loopbackProcessor->printStatus();
printf(" insf %3d,", (int) loopbackData.insufficientReadCount);
int64_t inputFramesWritten = AAudioStream_getFramesWritten(inputStream);
int64_t inputFramesRead = AAudioStream_getFramesRead(inputStream);
int64_t outputFramesWritten = AAudioStream_getFramesWritten(outputStream);
int64_t outputFramesRead = AAudioStream_getFramesRead(outputStream);
static const int textOffset = strlen("AAUDIO_STREAM_STATE_"); // strip this off
printf(" | INPUT: wr %7lld - rd %7lld = %5lld, st %8s, oruns %3d",
(long long) inputFramesWritten,
(long long) inputFramesRead,
(long long) (inputFramesWritten - inputFramesRead),
&AAudio_convertStreamStateToText(
AAudioStream_getState(inputStream))[textOffset],
AAudioStream_getXRunCount(inputStream));
printf(" | OUTPUT: wr %7lld - rd %7lld = %5lld, st %8s, uruns %3d\n",
(long long) outputFramesWritten,
(long long) outputFramesRead,
(long long) (outputFramesWritten - outputFramesRead),
&AAudio_convertStreamStateToText(
AAudioStream_getState(outputStream))[textOffset],
AAudioStream_getXRunCount(outputStream)
);
}
int32_t periodMillis = (timeMillis < 2000) ? kLogPeriodMillis / 4 : kLogPeriodMillis;
usleep(periodMillis * 1000);
timeMillis += periodMillis;
}
result = player.stop();
if (result != AAUDIO_OK) {
printf("ERROR - player.stop() returned %d = %s\n",
result, AAudio_convertResultToText(result));
goto finish;
}
result = recorder.stop();
if (result != AAUDIO_OK) {
printf("ERROR - recorder.stop() returned %d = %s\n",
result, AAudio_convertResultToText(result));
goto finish;
}
printf("input error = %d = %s\n",
loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
/*
// TODO Restore this code some day if we want to save files.
written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
if (written > 0) {
printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
written, FILENAME_ECHOS);
}
written = loopbackData.audioRecording.save(FILENAME_ALL);
if (written > 0) {
printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
written, FILENAME_ALL);
}
*/
if (loopbackData.inputError == AAUDIO_OK) {
if (testMode == TEST_GLITCHES) {
if (loopbackData.numGlitchEvents > 0) {
// Graph around the first glitch if there is one.
const int32_t start = loopbackData.glitchFrames[0] - 8;
const int32_t end = start + outputFramesPerBurst + 8 + 8;
printAudioGraphRegion(loopbackData.audioRecording, start, end);
} else {
// Or graph the middle of the signal.
const int32_t start = loopbackData.audioRecording.size() / 2;
const int32_t end = start + 200;
printAudioGraphRegion(loopbackData.audioRecording, start, end);
}
}
std::cout << "Please wait several seconds for analysis to complete.\n";
std::cout << loopbackData.loopbackProcessor->analyze();
}
{
int32_t framesRead = AAudioStream_getFramesRead(inputStream);
int32_t framesWritten = AAudioStream_getFramesWritten(inputStream);
const int64_t framesAvailable = framesWritten - framesRead;
printf("Callback Results ---------------------------------------- INPUT\n");
printf(" input overruns = %8d\n", AAudioStream_getXRunCount(inputStream));
printf(" framesWritten = %8d\n", framesWritten);
printf(" framesRead = %8d\n", framesRead);
printf(" myFramesRead = %8d\n", (int) loopbackData.framesReadTotal);
printf(" written - read = %8d\n", (int) framesAvailable);
printf(" insufficient # = %8d\n", (int) loopbackData.insufficientReadCount);
if (loopbackData.insufficientReadCount > 0) {
printf(" insuffic. frames = %8d\n", (int) loopbackData.insufficientReadFrames);
}
int32_t actualInputCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
if (framesAvailable > 2 * actualInputCapacity) {
printf(" WARNING: written - read > 2*capacity !\n");
}
}
{
int32_t framesRead = AAudioStream_getFramesRead(outputStream);
int32_t framesWritten = AAudioStream_getFramesWritten(outputStream);
printf("Callback Results ---------------------------------------- OUTPUT\n");
printf(" output underruns = %8d\n", AAudioStream_getXRunCount(outputStream));
printf(" myFramesWritten = %8d\n", (int) loopbackData.framesWrittenTotal);
printf(" framesWritten = %8d\n", framesWritten);
printf(" framesRead = %8d\n", framesRead);
printf(" min numFrames = %8d\n", (int) loopbackData.minNumFrames);
printf(" max numFrames = %8d\n", (int) loopbackData.maxNumFrames);
}
if (loopbackData.insufficientReadCount > 3) {
printf("ERROR: LOOPBACK PROCESSING FAILED. insufficientReadCount too high\n");
result = AAUDIO_ERROR_UNAVAILABLE;
}
finish:
player.close();
recorder.close();
delete[] loopbackData.inputFloatData;
delete[] loopbackData.inputShortData;
report_result:
for (int i = 0; i < loopbackData.numGlitchEvents; i++) {
printf(" glitch at frame %d\n", loopbackData.glitchFrames[i]);
}
written = loopbackData.loopbackProcessor->save(FILENAME_PROCESSED);
if (written > 0) {
printf("main() wrote %8d processed samples to \"%s\" on Android device\n",
written, FILENAME_PROCESSED);
}
if (loopbackData.loopbackProcessor->getResult() < 0) {
result = loopbackData.loopbackProcessor->getResult();
}
printf(RESULT_TAG "result = %d \n", result); // machine readable
printf("result is %s\n", AAudio_convertResultToText(result)); // human readable
if (result != AAUDIO_OK) {
printf("TEST FAILED\n");
return EXIT_FAILURE;
} else {
printf("TEST PASSED\n");
return EXIT_SUCCESS;
}
}