Merge "aaudio loopback: improve latency tester" into oc-dr1-dev
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index 45a3beb..57d45cd 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -17,6 +17,7 @@
// Play an impulse and then record it.
// Measure the round trip latency.
+#include <algorithm>
#include <assert.h>
#include <cctype>
#include <math.h>
@@ -25,11 +26,13 @@
#include <unistd.h>
#include <aaudio/AAudio.h>
+#include <aaudio/AAudioTesting.h>
-#define INPUT_PEAK_THRESHOLD 0.1f
-#define SILENCE_FRAMES 10000
+// Tag for machine readable results as property = value pairs
+#define RESULT_TAG "RESULT: "
#define SAMPLE_RATE 48000
-#define NUM_SECONDS 7
+#define NUM_SECONDS 5
+#define NUM_INPUT_CHANNELS 1
#define FILENAME "/data/oboe_input.raw"
#define NANOS_PER_MICROSECOND ((int64_t)1000)
@@ -37,12 +40,172 @@
#define MILLIS_PER_SECOND 1000
#define NANOS_PER_SECOND (NANOS_PER_MILLISECOND * MILLIS_PER_SECOND)
-class AudioRecorder
+#define MAX_ZEROTH_PARTIAL_BINS 40
+
+static const float s_Impulse[] = {
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // silence on each side of the impulse
+ 0.5f, 0.9f, 0.0f, -0.9f, -0.5f, // bipolar
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+
+
+static double calculateCorrelation(const float *a,
+ const float *b,
+ int windowSize)
+{
+ double correlation = 0.0;
+ double sumProducts = 0.0;
+ double sumSquares = 0.0;
+
+ // Correlate a against b.
+ for (int i = 0; i < windowSize; i++) {
+ float s1 = a[i];
+ float s2 = b[i];
+ // Use a normalized cross-correlation.
+ sumProducts += s1 * s2;
+ sumSquares += ((s1 * s1) + (s2 * s2));
+ }
+
+ if (sumSquares >= 0.00000001) {
+ correlation = (float) (2.0 * sumProducts / sumSquares);
+ }
+ return correlation;
+}
+
+static int calculateCorrelations(const float *haystack, int haystackSize,
+ const float *needle, int needleSize,
+ float *results, int resultSize)
+{
+ int ic;
+ int maxCorrelations = haystackSize - needleSize;
+ int numCorrelations = std::min(maxCorrelations, resultSize);
+
+ for (ic = 0; ic < numCorrelations; ic++) {
+ double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
+ results[ic] = correlation;
+ }
+
+ return numCorrelations;
+}
+
+/*==========================================================================================*/
+/**
+ * Scan until we get a correlation of a single scan that goes over the tolerance level,
+ * peaks then drops back down.
+ */
+static double findFirstMatch(const float *haystack, int haystackSize,
+ const float *needle, int needleSize, double threshold )
+{
+ int ic;
+ // How many correlations can we calculate?
+ int numCorrelations = haystackSize - needleSize;
+ double maxCorrelation = 0.0;
+ int peakIndex = -1;
+ double location = -1.0;
+
+ for (ic = 0; ic < numCorrelations; ic++) {
+ double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
+
+ if( (correlation > maxCorrelation) ) {
+ maxCorrelation = correlation;
+ peakIndex = ic;
+ }
+
+ //printf("PaQa_FindFirstMatch: ic = %4d, correlation = %8f, maxSum = %8f\n",
+ // ic, correlation, maxSum );
+ // Are we past what we were looking for?
+ if((maxCorrelation > threshold) && (correlation < 0.5 * maxCorrelation)) {
+ location = peakIndex;
+ break;
+ }
+ }
+
+ return location;
+}
+
+typedef struct LatencyReport_s {
+ double latencyInFrames;
+ double confidence;
+} LatencyReport;
+
+// Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
+// Using first echo instead of the original impulse for a better match.
+int measureLatencyFromEchos(const float *haystack, int haystackSize,
+ const float *needle, int needleSize,
+ LatencyReport *report) {
+ double threshold = 0.1;
+
+ // Find first peak
+ int first = (int) (findFirstMatch(haystack,
+ haystackSize,
+ needle,
+ needleSize,
+ threshold) + 0.5);
+
+ // Use first echo as the needle for the other echos because
+ // it will be more similar.
+ needle = &haystack[first];
+ int again = (int) (findFirstMatch(haystack,
+ haystackSize,
+ needle,
+ needleSize,
+ threshold) + 0.5);
+
+ printf("first = %d, again at %d\n", first, again);
+ first = again;
+
+ // Allocate results array
+ int remaining = haystackSize - first;
+ int generous = 48000 * 2;
+ int numCorrelations = std::min(remaining, generous);
+ float *correlations = new float[numCorrelations];
+ float *harmonicSums = new float[numCorrelations](); // cleared to zero
+
+ // Generate correlation for every position.
+ numCorrelations = calculateCorrelations(&haystack[first], remaining,
+ needle, needleSize,
+ correlations, numCorrelations);
+
+ // Add higher harmonics mapped onto lower harmonics.
+ // This reinforces the "fundamental" echo.
+ const int numEchoes = 10;
+ for (int partial = 1; partial < numEchoes; partial++) {
+ for (int i = 0; i < numCorrelations; i++) {
+ harmonicSums[i / partial] += correlations[i] / partial;
+ }
+ }
+
+ // Find highest peak in correlation array.
+ float maxCorrelation = 0.0;
+ float sumOfPeaks = 0.0;
+ int peakIndex = 0;
+ const int skip = MAX_ZEROTH_PARTIAL_BINS; // skip low bins
+ for (int i = skip; i < numCorrelations; i++) {
+ if (harmonicSums[i] > maxCorrelation) {
+ maxCorrelation = harmonicSums[i];
+ sumOfPeaks += maxCorrelation;
+ peakIndex = i;
+ printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
+ }
+ }
+
+ report->latencyInFrames = peakIndex;
+ if (sumOfPeaks < 0.0001) {
+ report->confidence = 0.0;
+ } else {
+ report->confidence = maxCorrelation / sumOfPeaks;
+ }
+
+ delete[] correlations;
+ delete[] harmonicSums;
+ return 0;
+}
+
+class AudioRecording
{
public:
- AudioRecorder() {
+ AudioRecording() {
}
- ~AudioRecorder() {
+ ~AudioRecording() {
delete[] mData;
}
@@ -52,7 +215,8 @@
mMaxFrames = maxFrames;
}
- void record(int16_t *inputData, int inputChannelCount, int numFrames) {
+ // Write SHORT data from the first channel.
+ int write(int16_t *inputData, int inputChannelCount, int numFrames) {
// stop at end of buffer
if ((mFrameCounter + numFrames) > mMaxFrames) {
numFrames = mMaxFrames - mFrameCounter;
@@ -60,9 +224,11 @@
for (int i = 0; i < numFrames; i++) {
mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
}
+ return numFrames;
}
- void record(float *inputData, int inputChannelCount, int numFrames) {
+ // Write FLOAT data from the first channel.
+ int write(float *inputData, int inputChannelCount, int numFrames) {
// stop at end of buffer
if ((mFrameCounter + numFrames) > mMaxFrames) {
numFrames = mMaxFrames - mFrameCounter;
@@ -70,162 +236,253 @@
for (int i = 0; i < numFrames; i++) {
mData[mFrameCounter++] = inputData[i * inputChannelCount];
}
+ return numFrames;
}
- int save(const char *fileName) {
+ int size() {
+ return mFrameCounter;
+ }
+
+ float *getData() {
+ return mData;
+ }
+
+ int save(const char *fileName, bool writeShorts = true) {
+ int written = 0;
+ const int chunkSize = 64;
FILE *fid = fopen(fileName, "wb");
if (fid == NULL) {
- return errno;
+ return -errno;
}
- int written = fwrite(mData, sizeof(float), mFrameCounter, fid);
+
+ if (writeShorts) {
+ int16_t buffer[chunkSize];
+ int32_t framesLeft = mFrameCounter;
+ int32_t cursor = 0;
+ while (framesLeft) {
+ int32_t framesToWrite = framesLeft < chunkSize ? framesLeft : chunkSize;
+ for (int i = 0; i < framesToWrite; i++) {
+ buffer[i] = (int16_t) (mData[cursor++] * 32767);
+ }
+ written += fwrite(buffer, sizeof(int16_t), framesToWrite, fid);
+ framesLeft -= framesToWrite;
+ }
+ } else {
+ written = fwrite(mData, sizeof(float), mFrameCounter, fid);
+ }
fclose(fid);
return written;
}
private:
- float *mData = NULL;
+ float *mData = nullptr;
int32_t mFrameCounter = 0;
int32_t mMaxFrames = 0;
};
// ====================================================================================
-// ========================= Loopback Processor =======================================
-// ====================================================================================
class LoopbackProcessor {
public:
+ virtual ~LoopbackProcessor() = default;
- // Calculate mean and standard deviation.
- double calculateAverageLatency(double *deviation) {
- if (mLatencyCount <= 0) {
- return -1.0;
- }
- double sum = 0.0;
- for (int i = 0; i < mLatencyCount; i++) {
- sum += mLatencyArray[i];
- }
- double average = sum / mLatencyCount;
- sum = 0.0;
- for (int i = 0; i < mLatencyCount; i++) {
- double error = average - mLatencyArray[i];
- sum += error * error; // squared
- }
- *deviation = sqrt(sum / mLatencyCount);
- return average;
+ virtual void process(float *inputData, int inputChannelCount,
+ float *outputData, int outputChannelCount,
+ int numFrames) = 0;
+
+
+ virtual void report() = 0;
+
+ void setSampleRate(int32_t sampleRate) {
+ mSampleRate = sampleRate;
}
- float getMaxAmplitude() const { return mMaxAmplitude; }
- int getMeasurementCount() const { return mLatencyCount; }
- float getAverageAmplitude() const { return mAmplitudeTotal / mAmplitudeCount; }
-
- // TODO Convert this to a feedback circuit and then use auto-correlation to measure the period.
- void process(float *inputData, int inputChannelCount,
- float *outputData, int outputChannelCount,
- int numFrames) {
- (void) outputChannelCount;
-
- // Measure peak and average amplitude.
- for (int i = 0; i < numFrames; i++) {
- float sample = inputData[i * inputChannelCount];
- if (sample > mMaxAmplitude) {
- mMaxAmplitude = sample;
- }
- if (sample < 0) {
- sample = 0 - sample;
- }
- mAmplitudeTotal += sample;
- mAmplitudeCount++;
- }
-
- // Clear output.
- memset(outputData, 0, numFrames * outputChannelCount * sizeof(float));
-
- // Wait a while between hearing the pulse and starting a new one.
- if (mState == STATE_SILENT) {
- mCounter += numFrames;
- if (mCounter > SILENCE_FRAMES) {
- //printf("LoopbackProcessor send impulse, burst #%d\n", mBurstCounter);
- // copy impulse
- for (float sample : mImpulse) {
- *outputData = sample;
- outputData += outputChannelCount;
- }
- mState = STATE_LISTENING;
- mCounter = 0;
- }
- }
- // Start listening as soon as we send the impulse.
- if (mState == STATE_LISTENING) {
- for (int i = 0; i < numFrames; i++) {
- float sample = inputData[i * inputChannelCount];
- if (sample >= INPUT_PEAK_THRESHOLD) {
- mLatencyArray[mLatencyCount++] = mCounter;
- if (mLatencyCount >= MAX_LATENCY_VALUES) {
- mState = STATE_DONE;
- } else {
- mState = STATE_SILENT;
- }
- mCounter = 0;
- break;
- } else {
- mCounter++;
- }
- }
- }
+ int32_t getSampleRate() {
+ return mSampleRate;
}
- void echo(float *inputData, int inputChannelCount,
- float *outputData, int outputChannelCount,
- int numFrames) {
- int channelsValid = (inputChannelCount < outputChannelCount)
- ? inputChannelCount : outputChannelCount;
- for (int i = 0; i < numFrames; i++) {
- int ic;
- for (ic = 0; ic < channelsValid; ic++) {
- outputData[ic] = inputData[ic];
- }
- for (ic = 0; ic < outputChannelCount; ic++) {
- outputData[ic] = 0;
- }
- inputData += inputChannelCount;
- outputData += outputChannelCount;
- }
- }
private:
- enum {
- STATE_SILENT,
- STATE_LISTENING,
- STATE_DONE
- };
-
- enum {
- MAX_LATENCY_VALUES = 64
- };
-
- int mState = STATE_SILENT;
- int32_t mCounter = 0;
- int32_t mLatencyArray[MAX_LATENCY_VALUES];
- int32_t mLatencyCount = 0;
- float mMaxAmplitude = 0;
- float mAmplitudeTotal = 0;
- int32_t mAmplitudeCount = 0;
- static const float mImpulse[5];
+ int32_t mSampleRate = SAMPLE_RATE;
};
-const float LoopbackProcessor::mImpulse[5] = {0.5f, 0.9f, 0.0f, -0.9f, -0.5f};
+
+// ====================================================================================
+class EchoAnalyzer : public LoopbackProcessor {
+public:
+
+ EchoAnalyzer() : LoopbackProcessor() {
+ audioRecorder.allocate(NUM_SECONDS * SAMPLE_RATE);
+ }
+
+ void setGain(float gain) {
+ mGain = gain;
+ }
+
+ float getGain() {
+ return mGain;
+ }
+
+ void report() override {
+
+ const float *needle = s_Impulse;
+ int needleSize = (int)(sizeof(s_Impulse) / sizeof(float));
+ float *haystack = audioRecorder.getData();
+ int haystackSize = audioRecorder.size();
+ int result = measureLatencyFromEchos(haystack, haystackSize,
+ needle, needleSize,
+ &latencyReport);
+ if (latencyReport.confidence < 0.01) {
+ printf(" ERROR - confidence too low = %f\n", latencyReport.confidence);
+ } else {
+ double latencyMillis = 1000.0 * latencyReport.latencyInFrames / getSampleRate();
+ printf(RESULT_TAG "latency.frames = %8.2f\n", latencyReport.latencyInFrames);
+ printf(RESULT_TAG "latency.msec = %8.2f\n", latencyMillis);
+ printf(RESULT_TAG "latency.confidence = %8.6f\n", latencyReport.confidence);
+ }
+ }
+
+ void process(float *inputData, int inputChannelCount,
+ float *outputData, int outputChannelCount,
+ int numFrames) override {
+ int channelsValid = std::min(inputChannelCount, outputChannelCount);
+
+ audioRecorder.write(inputData, inputChannelCount, numFrames);
+
+ if (mLoopCounter < mLoopStart) {
+ // Output silence at the beginning.
+ for (int i = 0; i < numFrames; i++) {
+ int ic;
+ for (ic = 0; ic < outputChannelCount; ic++) {
+ outputData[ic] = 0;
+ }
+ inputData += inputChannelCount;
+ outputData += outputChannelCount;
+ }
+ } else if (mLoopCounter == mLoopStart) {
+ // Send a bipolar impulse that we can easily detect.
+ for (float sample : s_Impulse) {
+ *outputData = sample;
+ outputData += outputChannelCount;
+ }
+ } else {
+ // Echo input to output.
+ for (int i = 0; i < numFrames; i++) {
+ int ic;
+ for (ic = 0; ic < channelsValid; ic++) {
+ outputData[ic] = inputData[ic] * mGain;
+ }
+ for (; ic < outputChannelCount; ic++) {
+ outputData[ic] = 0;
+ }
+ inputData += inputChannelCount;
+ outputData += outputChannelCount;
+ }
+ }
+
+ mLoopCounter++;
+ }
+
+private:
+ int mLoopCounter = 0;
+ int mLoopStart = 1000;
+ float mGain = 1.0f;
+
+ AudioRecording audioRecorder;
+ LatencyReport latencyReport;
+};
+
+
+// ====================================================================================
+class SineAnalyzer : public LoopbackProcessor {
+public:
+
+ void report() override {
+ double magnitude = calculateMagnitude();
+ printf("sine magnitude = %7.5f\n", magnitude);
+ printf("sine frames = %7d\n", mFrameCounter);
+ printf("sine frequency = %7.1f Hz\n", mFrequency);
+ }
+
+ double calculateMagnitude(double *phasePtr = NULL) {
+ if (mFrameCounter == 0) {
+ return 0.0;
+ }
+ double sinMean = mSinAccumulator / mFrameCounter;
+ double cosMean = mCosAccumulator / mFrameCounter;
+ double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
+ if( phasePtr != NULL )
+ {
+ double phase = atan2( sinMean, cosMean );
+ *phasePtr = phase;
+ }
+ return magnitude;
+ }
+
+ void process(float *inputData, int inputChannelCount,
+ float *outputData, int outputChannelCount,
+ int numFrames) override {
+ double phaseIncrement = 2.0 * M_PI * mFrequency / getSampleRate();
+
+ for (int i = 0; i < numFrames; i++) {
+ // Multiply input by sine/cosine
+ float sample = inputData[i * inputChannelCount];
+ float sinOut = sinf(mPhase);
+ mSinAccumulator += sample * sinOut;
+ mCosAccumulator += sample * cosf(mPhase);
+ // Advance and wrap phase
+ mPhase += phaseIncrement;
+ if (mPhase > (2.0 * M_PI)) {
+ mPhase -= (2.0 * M_PI);
+ }
+
+ // Output sine wave so we can measure it.
+ outputData[i * outputChannelCount] = sinOut;
+ }
+ mFrameCounter += numFrames;
+
+ double magnitude = calculateMagnitude();
+ if (mWaiting) {
+ if (magnitude < 0.001) {
+ // discard silence
+ mFrameCounter = 0;
+ mSinAccumulator = 0.0;
+ mCosAccumulator = 0.0;
+ } else {
+ mWaiting = false;
+ }
+ }
+ };
+
+ void setFrequency(int32_t frequency) {
+ mFrequency = frequency;
+ }
+
+ int32_t getFrequency() {
+ return mFrequency;
+ }
+
+private:
+ double mFrequency = 300.0;
+ double mPhase = 0.0;
+ int32_t mFrameCounter = 0;
+ double mSinAccumulator = 0.0;
+ double mCosAccumulator = 0.0;
+ bool mWaiting = true;
+};
// TODO make this a class that manages its own buffer allocation
struct LoopbackData {
- AAudioStream *inputStream = nullptr;
- int32_t inputFramesMaximum = 0;
- int16_t *inputData = nullptr;
- float *conversionBuffer = nullptr;
- int32_t actualInputChannelCount = 0;
- int32_t actualOutputChannelCount = 0;
- int32_t inputBuffersToDiscard = 10;
+ AAudioStream *inputStream = nullptr;
+ int32_t inputFramesMaximum = 0;
+ int16_t *inputData = nullptr;
+ float *conversionBuffer = nullptr;
+ int32_t actualInputChannelCount = 0;
+ int32_t actualOutputChannelCount = 0;
+ int32_t inputBuffersToDiscard = 10;
- aaudio_result_t inputError;
- LoopbackProcessor loopbackProcessor;
- AudioRecorder audioRecorder;
+ aaudio_result_t inputError;
+ SineAnalyzer sineAnalyzer;
+ EchoAnalyzer echoAnalyzer;
+ LoopbackProcessor *loopbackProcessor;
};
static void convertPcm16ToFloat(const int16_t *source,
@@ -248,6 +505,7 @@
int32_t numFrames
) {
(void) outputStream;
+ aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_CONTINUE;
LoopbackData *myData = (LoopbackData *) userData;
float *outputData = (float *) audioData;
@@ -266,6 +524,7 @@
numFrames, 0);
if (framesRead < 0) {
myData->inputError = framesRead;
+ result = AAUDIO_CALLBACK_RESULT_STOP;
} else if (framesRead > 0) {
myData->inputBuffersToDiscard--;
}
@@ -275,16 +534,13 @@
numFrames, 0);
if (framesRead < 0) {
myData->inputError = framesRead;
+ result = AAUDIO_CALLBACK_RESULT_STOP;
} else if (framesRead > 0) {
- // Process valid input data.
- myData->audioRecorder.record(myData->inputData,
- myData->actualInputChannelCount,
- framesRead);
int32_t numSamples = framesRead * myData->actualInputChannelCount;
convertPcm16ToFloat(myData->inputData, myData->conversionBuffer, numSamples);
- myData->loopbackProcessor.process(myData->conversionBuffer,
+ myData->loopbackProcessor->process(myData->conversionBuffer,
myData->actualInputChannelCount,
outputData,
myData->actualOutputChannelCount,
@@ -292,17 +548,25 @@
}
}
- return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ return result;
}
+
static void usage() {
- printf("loopback: -b{burstsPerBuffer} -p{outputPerfMode} -P{inputPerfMode}\n");
- printf(" -b{burstsPerBuffer} for example 2 for double buffered\n");
- printf(" -p{outputPerfMode} set output AAUDIO_PERFORMANCE_MODE*\n");
- printf(" -P{inputPerfMode} set input AAUDIO_PERFORMANCE_MODE*\n");
+ printf("loopback: -n{numBursts} -p{outPerf} -P{inPerf} -t{test} -g{gain} -f{freq}\n");
+ printf(" -c{inputChannels}\n");
+ printf(" -f{freq} sine frequency\n");
+ printf(" -g{gain} recirculating loopback gain\n");
+ printf(" -m enable MMAP mode\n");
+ printf(" -n{numBursts} buffer size, for example 2 for double buffered\n");
+ printf(" -p{outPerf} set output AAUDIO_PERFORMANCE_MODE*\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(" m for sine magnitude\n");
+ printf(" e for echo latency (default)\n");
printf("For example: loopback -b2 -pl -Pn\n");
}
@@ -320,12 +584,34 @@
mode = AAUDIO_PERFORMANCE_MODE_POWER_SAVING;
break;
default:
- printf("ERROR invalue performance mode %c\n", c);
+ printf("ERROR in value performance mode %c\n", c);
break;
}
return mode;
}
+enum {
+ TEST_SINE_MAGNITUDE = 0,
+ TEST_ECHO_LATENCY,
+};
+
+static int parseTestMode(char c) {
+ int testMode = TEST_ECHO_LATENCY;
+ c = tolower(c);
+ switch (c) {
+ case 'm':
+ testMode = TEST_SINE_MAGNITUDE;
+ break;
+ case 'e':
+ testMode = TEST_ECHO_LATENCY;
+ break;
+ default:
+ printf("ERROR in value test mode %c\n", c);
+ break;
+ }
+ return testMode;
+}
+
// ====================================================================================
// TODO break up this large main() function into smaller functions
int main(int argc, const char **argv)
@@ -334,7 +620,7 @@
LoopbackData loopbackData;
AAudioStream *outputStream = nullptr;
- const int requestedInputChannelCount = 1;
+ int requestedInputChannelCount = NUM_INPUT_CHANNELS;
const int requestedOutputChannelCount = AAUDIO_UNSPECIFIED;
const int requestedSampleRate = SAMPLE_RATE;
int actualSampleRate = 0;
@@ -342,6 +628,9 @@
const aaudio_format_t requestedOutputFormat = AAUDIO_FORMAT_PCM_FLOAT;
aaudio_format_t actualInputFormat;
aaudio_format_t actualOutputFormat;
+ int testMode = TEST_ECHO_LATENCY;
+ double frequency = 1000.0;
+ double gain = 1.0;
const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
//const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
@@ -363,7 +652,19 @@
if (arg[0] == '-') {
char option = arg[1];
switch (option) {
- case 'b':
+ case 'c':
+ requestedInputChannelCount = atoi(&arg[2]);
+ break;
+ case 'f':
+ frequency = atof(&arg[2]);
+ break;
+ case 'g':
+ gain = atof(&arg[2]);
+ break;
+ case 'm':
+ AAudio_setMMapPolicy(AAUDIO_POLICY_AUTO);
+ break;
+ case 'n':
burstsPerBuffer = atoi(&arg[2]);
break;
case 'p':
@@ -372,16 +673,35 @@
case 'P':
inputPerformanceLevel = parsePerformanceMode(arg[2]);
break;
+ case 't':
+ testMode = parseTestMode(arg[2]);
+ break;
default:
usage();
+ exit(0);
break;
}
} else {
+ usage();
+ exit(0);
break;
}
}
- loopbackData.audioRecorder.allocate(NUM_SECONDS * SAMPLE_RATE);
+
+ switch(testMode) {
+ case TEST_SINE_MAGNITUDE:
+ loopbackData.sineAnalyzer.setFrequency(frequency);
+ loopbackData.loopbackProcessor = &loopbackData.sineAnalyzer;
+ break;
+ case TEST_ECHO_LATENCY:
+ loopbackData.echoAnalyzer.setGain(gain);
+ loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
+ break;
+ default:
+ exit(1);
+ break;
+ }
// Make printf print immediately so that debug info is not stuck
// in a buffer if we hang or crash.
@@ -431,47 +751,68 @@
printf(" channelCount: requested = %d, actual = %d\n", requestedInputChannelCount,
loopbackData.actualInputChannelCount);
printf(" framesPerBurst = %d\n", AAudioStream_getFramesPerBurst(loopbackData.inputStream));
+ printf(" bufferSize = %d\n",
+ AAudioStream_getBufferSizeInFrames(loopbackData.inputStream));
+ printf(" bufferCapacity = %d\n",
+ AAudioStream_getBufferCapacityInFrames(loopbackData.inputStream));
+
+ actualSharingMode = AAudioStream_getSharingMode(loopbackData.inputStream);
+ printf(" sharingMode: requested = %d, actual = %d\n",
+ requestedSharingMode, actualSharingMode);
actualInputFormat = AAudioStream_getFormat(loopbackData.inputStream);
- printf(" dataFormat: requested = %d, actual = %d\n", requestedInputFormat, actualInputFormat);
+ printf(" dataFormat: requested = %d, actual = %d\n",
+ requestedInputFormat, actualInputFormat);
assert(actualInputFormat == AAUDIO_FORMAT_PCM_I16);
+ printf(" is MMAP used? = %s\n", AAudioStream_isMMapUsed(loopbackData.inputStream)
+ ? "yes" : "no");
+
+
printf("Stream OUTPUT ---------------------\n");
// Check to see what kind of stream we actually got.
actualSampleRate = AAudioStream_getSampleRate(outputStream);
printf(" sampleRate: requested = %d, actual = %d\n", requestedSampleRate, actualSampleRate);
+ loopbackData.echoAnalyzer.setSampleRate(actualSampleRate);
loopbackData.actualOutputChannelCount = AAudioStream_getChannelCount(outputStream);
printf(" channelCount: requested = %d, actual = %d\n", requestedOutputChannelCount,
loopbackData.actualOutputChannelCount);
actualSharingMode = AAudioStream_getSharingMode(outputStream);
- printf(" sharingMode: requested = %d, actual = %d\n", requestedSharingMode, actualSharingMode);
+ printf(" sharingMode: requested = %d, actual = %d\n",
+ requestedSharingMode, actualSharingMode);
// This is the number of frames that are read in one chunk by a DMA controller
// or a DSP or a mixer.
framesPerBurst = AAudioStream_getFramesPerBurst(outputStream);
printf(" framesPerBurst = %d\n", framesPerBurst);
- printf(" bufferCapacity = %d\n", AAudioStream_getBufferCapacityInFrames(outputStream));
-
- actualOutputFormat = AAudioStream_getFormat(outputStream);
- printf(" dataFormat: requested = %d, actual = %d\n", requestedOutputFormat, actualOutputFormat);
- assert(actualOutputFormat == AAUDIO_FORMAT_PCM_FLOAT);
-
- // Allocate a buffer for the audio data.
- loopbackData.inputFramesMaximum = 32 * framesPerBurst;
-
- loopbackData.inputData = new int16_t[loopbackData.inputFramesMaximum * loopbackData.actualInputChannelCount];
- loopbackData.conversionBuffer = new float[loopbackData.inputFramesMaximum *
- loopbackData.actualInputChannelCount];
-
result = AAudioStream_setBufferSizeInFrames(outputStream, burstsPerBuffer * framesPerBurst);
if (result < 0) { // may be positive buffer size
fprintf(stderr, "ERROR - AAudioStream_setBufferSize() returned %d\n", result);
goto finish;
}
- printf("AAudioStream_setBufferSize() actual = %d\n",result);
+ printf(" bufferSize = %d\n", AAudioStream_getBufferSizeInFrames(outputStream));
+ printf(" bufferCapacity = %d\n", AAudioStream_getBufferCapacityInFrames(outputStream));
+
+ actualOutputFormat = AAudioStream_getFormat(outputStream);
+ printf(" dataFormat: requested = %d, actual = %d\n",
+ requestedOutputFormat, actualOutputFormat);
+ assert(actualOutputFormat == AAUDIO_FORMAT_PCM_FLOAT);
+
+ printf(" is MMAP used? = %s\n", AAudioStream_isMMapUsed(outputStream)
+ ? "yes" : "no");
+
+ // Allocate a buffer for the audio data.
+ loopbackData.inputFramesMaximum = 32 * framesPerBurst;
+ loopbackData.inputBuffersToDiscard = 100;
+
+ loopbackData.inputData = new int16_t[loopbackData.inputFramesMaximum
+ * loopbackData.actualInputChannelCount];
+ loopbackData.conversionBuffer = new float[loopbackData.inputFramesMaximum *
+ loopbackData.actualInputChannelCount];
+
// Start output first so input stream runs low.
result = AAudioStream_requestStart(outputStream);
@@ -500,18 +841,13 @@
printf("framesRead = %d\n", (int) AAudioStream_getFramesRead(outputStream));
printf("framesWritten = %d\n", (int) AAudioStream_getFramesWritten(outputStream));
- latency = loopbackData.loopbackProcessor.calculateAverageLatency(&deviation);
- printf("measured peak = %8.5f\n", loopbackData.loopbackProcessor.getMaxAmplitude());
- printf("threshold = %8.5f\n", INPUT_PEAK_THRESHOLD);
- printf("measured average = %8.5f\n", loopbackData.loopbackProcessor.getAverageAmplitude());
- printf("# latency measurements = %d\n", loopbackData.loopbackProcessor.getMeasurementCount());
- printf("measured latency = %8.2f +/- %4.5f frames\n", latency, deviation);
- printf("measured latency = %8.2f msec <===== !!\n", (1000.0 * latency / actualSampleRate));
+ loopbackData.loopbackProcessor->report();
- {
- int written = loopbackData.audioRecorder.save(FILENAME);
- printf("wrote %d samples to %s\n", written, FILENAME);
- }
+// {
+// int written = loopbackData.audioRecorder.save(FILENAME);
+// printf("wrote %d mono samples to %s on Android device\n", written, FILENAME);
+// }
+
finish:
AAudioStream_close(outputStream);
@@ -521,7 +857,13 @@
delete[] outputData;
AAudioStreamBuilder_delete(builder);
- printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
- return (result != AAUDIO_OK) ? EXIT_FAILURE : EXIT_SUCCESS;
+ printf(RESULT_TAG "error = %d = %s\n", result, AAudio_convertResultToText(result));
+ if ((result != AAUDIO_OK)) {
+ printf("error %d = %s\n", result, AAudio_convertResultToText(result));
+ return EXIT_FAILURE;
+ } else {
+ printf("SUCCESS\n");
+ return EXIT_SUCCESS;
+ }
}
diff --git a/media/libaaudio/examples/utils/dummy.cpp b/media/libaaudio/examples/utils/dummy.cpp
new file mode 100644
index 0000000..8ef7e36
--- /dev/null
+++ b/media/libaaudio/examples/utils/dummy.cpp
@@ -0,0 +1,5 @@
+/**
+ * Dummy file needed to get Android Studio to scan this folder.
+ */
+
+int g_DoNotUseThisVariable = 0;