blob: 9f06ee7ae7d3936d7adbb9a9d8e6407d701191e3 [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.
*/
// Play an impulse and then record it.
// Measure the round trip latency.
#include <assert.h>
#include <cctype>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <aaudio/AAudio.h>
#define INPUT_PEAK_THRESHOLD 0.1f
#define SILENCE_FRAMES 10000
#define SAMPLE_RATE 48000
#define NUM_SECONDS 7
#define FILENAME "/data/oboe_input.raw"
#define NANOS_PER_MICROSECOND ((int64_t)1000)
#define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
#define MILLIS_PER_SECOND 1000
#define NANOS_PER_SECOND (NANOS_PER_MILLISECOND * MILLIS_PER_SECOND)
class AudioRecorder
{
public:
AudioRecorder() {
}
~AudioRecorder() {
delete[] mData;
}
void allocate(int maxFrames) {
delete[] mData;
mData = new float[maxFrames];
mMaxFrames = maxFrames;
}
void record(int16_t *inputData, int inputChannelCount, int numFrames) {
// stop at end of buffer
if ((mFrameCounter + numFrames) > mMaxFrames) {
numFrames = mMaxFrames - mFrameCounter;
}
for (int i = 0; i < numFrames; i++) {
mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
}
}
void record(float *inputData, int inputChannelCount, int numFrames) {
// stop at end of buffer
if ((mFrameCounter + numFrames) > mMaxFrames) {
numFrames = mMaxFrames - mFrameCounter;
}
for (int i = 0; i < numFrames; i++) {
mData[mFrameCounter++] = inputData[i * inputChannelCount];
}
}
int save(const char *fileName) {
FILE *fid = fopen(fileName, "wb");
if (fid == NULL) {
return errno;
}
int written = fwrite(mData, sizeof(float), mFrameCounter, fid);
fclose(fid);
return written;
}
private:
float *mData = NULL;
int32_t mFrameCounter = 0;
int32_t mMaxFrames = 0;
};
// ====================================================================================
// ========================= Loopback Processor =======================================
// ====================================================================================
class LoopbackProcessor {
public:
// 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;
}
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++;
}
}
}
}
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];
};
const float LoopbackProcessor::mImpulse[5] = {0.5f, 0.9f, 0.0f, -0.9f, -0.5f};
// 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;
aaudio_result_t inputError;
LoopbackProcessor loopbackProcessor;
AudioRecorder audioRecorder;
};
static void convertPcm16ToFloat(const int16_t *source,
float *destination,
int32_t numSamples) {
const 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 aaudio_data_callback_result_t MyDataCallbackProc(
AAudioStream *outputStream,
void *userData,
void *audioData,
int32_t numFrames
) {
(void) outputStream;
LoopbackData *myData = (LoopbackData *) userData;
float *outputData = (float *) audioData;
// Read audio data from the input stream.
int32_t framesRead;
if (numFrames > myData->inputFramesMaximum) {
myData->inputError = AAUDIO_ERROR_OUT_OF_RANGE;
return AAUDIO_CALLBACK_RESULT_STOP;
}
if (myData->inputBuffersToDiscard > 0) {
// Drain the input.
do {
framesRead = AAudioStream_read(myData->inputStream, myData->inputData,
numFrames, 0);
if (framesRead < 0) {
myData->inputError = framesRead;
} else if (framesRead > 0) {
myData->inputBuffersToDiscard--;
}
} while(framesRead > 0);
} else {
framesRead = AAudioStream_read(myData->inputStream, myData->inputData,
numFrames, 0);
if (framesRead < 0) {
myData->inputError = framesRead;
} 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->actualInputChannelCount,
outputData,
myData->actualOutputChannelCount,
framesRead);
}
}
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
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(" n for _NONE\n");
printf(" l for _LATENCY\n");
printf(" p for _POWER_SAVING;\n");
printf("For example: loopback -b2 -pl -Pn\n");
}
static aaudio_performance_mode_t parsePerformanceMode(char c) {
aaudio_performance_mode_t mode = AAUDIO_PERFORMANCE_MODE_NONE;
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 invalue performance mode %c\n", c);
break;
}
return mode;
}
// ====================================================================================
// TODO break up this large main() function into smaller functions
int main(int argc, const char **argv)
{
aaudio_result_t result = AAUDIO_OK;
LoopbackData loopbackData;
AAudioStream *outputStream = nullptr;
const int requestedInputChannelCount = 1;
const int requestedOutputChannelCount = AAUDIO_UNSPECIFIED;
const int requestedSampleRate = SAMPLE_RATE;
int actualSampleRate = 0;
const aaudio_format_t requestedInputFormat = AAUDIO_FORMAT_PCM_I16;
const aaudio_format_t requestedOutputFormat = AAUDIO_FORMAT_PCM_FLOAT;
aaudio_format_t actualInputFormat;
aaudio_format_t actualOutputFormat;
const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
//const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
aaudio_sharing_mode_t actualSharingMode;
AAudioStreamBuilder *builder = nullptr;
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
int32_t framesPerBurst = 0;
float *outputData = NULL;
double deviation;
double latency;
aaudio_performance_mode_t outputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
int32_t burstsPerBuffer = 1; // single buffered
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (arg[0] == '-') {
char option = arg[1];
switch (option) {
case 'b':
burstsPerBuffer = atoi(&arg[2]);
break;
case 'p':
outputPerformanceLevel = parsePerformanceMode(arg[2]);
break;
case 'P':
inputPerformanceLevel = parsePerformanceMode(arg[2]);
break;
default:
usage();
break;
}
} else {
break;
}
}
loopbackData.audioRecorder.allocate(NUM_SECONDS * SAMPLE_RATE);
// 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\n", argv[0]);
// Use an AAudioStreamBuilder to contain requested parameters.
result = AAudio_createStreamBuilder(&builder);
if (result < 0) {
goto finish;
}
// Request common stream properties.
AAudioStreamBuilder_setSampleRate(builder, requestedSampleRate);
AAudioStreamBuilder_setFormat(builder, requestedInputFormat);
AAudioStreamBuilder_setSharingMode(builder, requestedSharingMode);
// Open the input stream.
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT);
AAudioStreamBuilder_setPerformanceMode(builder, inputPerformanceLevel);
AAudioStreamBuilder_setChannelCount(builder, requestedInputChannelCount);
result = AAudioStreamBuilder_openStream(builder, &loopbackData.inputStream);
printf("AAudioStreamBuilder_openStream(input) returned %d = %s\n",
result, AAudio_convertResultToText(result));
if (result < 0) {
goto finish;
}
// Create an output stream using the Builder.
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
AAudioStreamBuilder_setFormat(builder, requestedOutputFormat);
AAudioStreamBuilder_setPerformanceMode(builder, outputPerformanceLevel);
AAudioStreamBuilder_setChannelCount(builder, requestedOutputChannelCount);
AAudioStreamBuilder_setDataCallback(builder, MyDataCallbackProc, &loopbackData);
result = AAudioStreamBuilder_openStream(builder, &outputStream);
printf("AAudioStreamBuilder_openStream(output) returned %d = %s\n",
result, AAudio_convertResultToText(result));
if (result != AAUDIO_OK) {
goto finish;
}
printf("Stream INPUT ---------------------\n");
loopbackData.actualInputChannelCount = AAudioStream_getChannelCount(loopbackData.inputStream);
printf(" channelCount: requested = %d, actual = %d\n", requestedInputChannelCount,
loopbackData.actualInputChannelCount);
printf(" framesPerBurst = %d\n", AAudioStream_getFramesPerBurst(loopbackData.inputStream));
actualInputFormat = AAudioStream_getFormat(loopbackData.inputStream);
printf(" dataFormat: requested = %d, actual = %d\n", requestedInputFormat, actualInputFormat);
assert(actualInputFormat == AAUDIO_FORMAT_PCM_I16);
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.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);
// 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);
// Start output first so input stream runs low.
result = AAudioStream_requestStart(outputStream);
if (result != AAUDIO_OK) {
fprintf(stderr, "ERROR - AAudioStream_requestStart(output) returned %d = %s\n",
result, AAudio_convertResultToText(result));
goto finish;
}
result = AAudioStream_requestStart(loopbackData.inputStream);
if (result != AAUDIO_OK) {
fprintf(stderr, "ERROR - AAudioStream_requestStart(input) returned %d = %s\n",
result, AAudio_convertResultToText(result));
goto finish;
}
printf("------- sleep while the callback runs --------------\n");
fflush(stdout);
sleep(NUM_SECONDS);
printf("input error = %d = %s\n",
loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
printf("AAudioStream_getXRunCount %d\n", AAudioStream_getXRunCount(outputStream));
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));
{
int written = loopbackData.audioRecorder.save(FILENAME);
printf("wrote %d samples to %s\n", written, FILENAME);
}
finish:
AAudioStream_close(outputStream);
AAudioStream_close(loopbackData.inputStream);
delete[] loopbackData.conversionBuffer;
delete[] loopbackData.inputData;
delete[] outputData;
AAudioStreamBuilder_delete(builder);
printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
return (result != AAUDIO_OK) ? EXIT_FAILURE : EXIT_SUCCESS;
}