aaudio_loopback: use WAV files, reject low gain
Save recording in a WAV file instead of a RAW file.
Add a test mode that reads a WAV file, for debugging auto-correlation.
Cleanup printed reports.
Discard more input buffers at beginning.
Test: this is a test
Change-Id: Ib5ea63882eda325b02716a448897007a945f7312
diff --git a/media/libaaudio/examples/loopback/Android.bp b/media/libaaudio/examples/loopback/Android.bp
index fa8fdc9..5b7d956 100644
--- a/media/libaaudio/examples/loopback/Android.bp
+++ b/media/libaaudio/examples/loopback/Android.bp
@@ -3,6 +3,10 @@
gtest: false,
srcs: ["src/loopback.cpp"],
cflags: ["-Wall", "-Werror"],
- shared_libs: ["libaaudio"],
+ static_libs: ["libsndfile"],
+ shared_libs: [
+ "libaaudio",
+ "libaudioutils",
+ ],
header_libs: ["libaaudio_example_utils"],
}
diff --git a/media/libaaudio/examples/loopback/jni/Android.mk b/media/libaaudio/examples/loopback/jni/Android.mk
index 1fe3def..aebe877 100644
--- a/media/libaaudio/examples/loopback/jni/Android.mk
+++ b/media/libaaudio/examples/loopback/jni/Android.mk
@@ -10,6 +10,7 @@
# NDK recommends using this kind of relative path instead of an absolute path.
LOCAL_SRC_FILES:= ../src/loopback.cpp
LOCAL_CFLAGS := -Wall -Werror
-LOCAL_SHARED_LIBRARIES := libaaudio
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_SHARED_LIBRARIES := libaaudio libaudioutils
LOCAL_MODULE := aaudio_loopback
include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
index 276b45f..b83851a 100644
--- a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
+++ b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
@@ -30,6 +30,8 @@
#include <stdlib.h>
#include <unistd.h>
+#include <audio_utils/sndfile.h>
+
// Tag for machine readable results as property = value pairs
#define LOOPBACK_RESULT_TAG "RESULT: "
#define LOOPBACK_SAMPLE_RATE 48000
@@ -37,6 +39,7 @@
#define MILLIS_PER_SECOND 1000
#define MAX_ZEROTH_PARTIAL_BINS 40
+constexpr double MAX_ECHO_GAIN = 10.0; // based on experiments, otherwise autocorrelation too noisy
static const float s_Impulse[] = {
0.0f, 0.0f, 0.0f, 0.0f, 0.2f, // silence on each side of the impulse
@@ -156,6 +159,8 @@
const float *needle, int needleSize,
LatencyReport *report) {
const double threshold = 0.1;
+ printf("measureLatencyFromEchos: haystackSize = %d, needleSize = %d\n",
+ haystackSize, needleSize);
// Find first peak
int first = (int) (findFirstMatch(haystack,
@@ -173,7 +178,7 @@
needleSize,
threshold) + 0.5);
- printf("first = %d, again at %d\n", first, again);
+ printf("measureLatencyFromEchos: first = %d, again at %d\n", first, again);
first = again;
// Allocate results array
@@ -270,37 +275,60 @@
return mData;
}
+ void setSampleRate(int32_t sampleRate) {
+ mSampleRate = sampleRate;
+ }
+
+ int32_t getSampleRate() {
+ return mSampleRate;
+ }
+
int save(const char *fileName, bool writeShorts = true) {
+ SNDFILE *sndFile = nullptr;
int written = 0;
- const int chunkSize = 64;
- FILE *fid = fopen(fileName, "wb");
- if (fid == NULL) {
+ SF_INFO info = {
+ .frames = mFrameCounter,
+ .samplerate = mSampleRate,
+ .channels = 1,
+ .format = SF_FORMAT_WAV | (writeShorts ? SF_FORMAT_PCM_16 : SF_FORMAT_FLOAT)
+ };
+
+ sndFile = sf_open(fileName, SFM_WRITE, &info);
+ if (sndFile == nullptr) {
+ printf("AudioRecording::save(%s) failed to open file\n", fileName);
return -errno;
}
- 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 = (int) fwrite(mData, sizeof(float), mFrameCounter, fid);
- }
- fclose(fid);
+ written = sf_writef_float(sndFile, mData, mFrameCounter);
+
+ sf_close(sndFile);
return written;
}
+ int load(const char *fileName) {
+ SNDFILE *sndFile = nullptr;
+ SF_INFO info;
+
+ sndFile = sf_open(fileName, SFM_READ, &info);
+ if (sndFile == nullptr) {
+ printf("AudioRecording::load(%s) failed to open file\n", fileName);
+ return -errno;
+ }
+
+ assert(info.channels == 1);
+
+ allocate(info.frames);
+ mFrameCounter = sf_readf_float(sndFile, mData, info.frames);
+
+ sf_close(sndFile);
+ return mFrameCounter;
+ }
+
private:
float *mData = nullptr;
int32_t mFrameCounter = 0;
int32_t mMaxFrames = 0;
+ int32_t mSampleRate = 48000; // common default
};
// ====================================================================================
@@ -320,11 +348,25 @@
virtual void printStatus() {};
+ virtual int getResult() {
+ return -1;
+ }
+
virtual bool isDone() {
return false;
}
- void setSampleRate(int32_t sampleRate) {
+ virtual int save(const char *fileName) {
+ (void) fileName;
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
+ virtual int load(const char *fileName) {
+ (void) fileName;
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
+ virtual void setSampleRate(int32_t sampleRate) {
mSampleRate = sampleRate;
}
@@ -395,7 +437,13 @@
public:
EchoAnalyzer() : LoopbackProcessor() {
- audioRecorder.allocate(2 * LOOPBACK_SAMPLE_RATE);
+ mAudioRecording.allocate(2 * getSampleRate());
+ mAudioRecording.setSampleRate(getSampleRate());
+ }
+
+ void setSampleRate(int32_t sampleRate) override {
+ LoopbackProcessor::setSampleRate(sampleRate);
+ mAudioRecording.setSampleRate(sampleRate);
}
void reset() override {
@@ -406,8 +454,12 @@
mState = STATE_INITIAL_SILENCE;
}
+ virtual int getResult() {
+ return mState == STATE_DONE ? 0 : -1;
+ }
+
virtual bool isDone() {
- return mState == STATE_DONE;
+ return mState == STATE_DONE || mState == STATE_FAILED;
}
void setGain(float gain) {
@@ -423,31 +475,24 @@
printf("EchoAnalyzer ---------------\n");
printf(LOOPBACK_RESULT_TAG "measured.gain = %f\n", mMeasuredLoopGain);
printf(LOOPBACK_RESULT_TAG "echo.gain = %f\n", mEchoGain);
- printf(LOOPBACK_RESULT_TAG "frame.count = %d\n", mFrameCounter);
printf(LOOPBACK_RESULT_TAG "test.state = %d\n", mState);
if (mMeasuredLoopGain >= 0.9999) {
printf(" ERROR - clipping, turn down volume slightly\n");
} else {
const float *needle = s_Impulse;
int needleSize = (int) (sizeof(s_Impulse) / sizeof(float));
- float *haystack = audioRecorder.getData();
- int haystackSize = audioRecorder.size();
- measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &latencyReport);
- if (latencyReport.confidence < 0.01) {
- printf(" ERROR - confidence too low = %f\n", latencyReport.confidence);
+ float *haystack = mAudioRecording.getData();
+ int haystackSize = mAudioRecording.size();
+ measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &mLatencyReport);
+ if (mLatencyReport.confidence < 0.01) {
+ printf(" ERROR - confidence too low = %f\n", mLatencyReport.confidence);
} else {
- double latencyMillis = 1000.0 * latencyReport.latencyInFrames / getSampleRate();
- printf(LOOPBACK_RESULT_TAG "latency.frames = %8.2f\n", latencyReport.latencyInFrames);
+ double latencyMillis = 1000.0 * mLatencyReport.latencyInFrames / getSampleRate();
+ printf(LOOPBACK_RESULT_TAG "latency.frames = %8.2f\n", mLatencyReport.latencyInFrames);
printf(LOOPBACK_RESULT_TAG "latency.msec = %8.2f\n", latencyMillis);
- printf(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f\n", latencyReport.confidence);
+ printf(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f\n", mLatencyReport.confidence);
}
}
-
- {
-#define ECHO_FILENAME "/data/oboe_echo.raw"
- int written = audioRecorder.save(ECHO_FILENAME);
- printf("Echo wrote %d mono samples to %s on Android device\n", written, ECHO_FILENAME);
- }
}
void printStatus() override {
@@ -491,13 +536,18 @@
// If we get several in a row then go to next state.
if (peak > mPulseThreshold) {
if (mDownCounter-- <= 0) {
- nextState = STATE_WAITING_FOR_SILENCE;
//printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
// mLoopCounter, peak);
mDownCounter = 8;
mMeasuredLoopGain = peak; // assumes original pulse amplitude is one
// Calculate gain that will give us a nice decaying echo.
mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
+ if (mEchoGain > MAX_ECHO_GAIN) {
+ printf("ERROR - loop gain too low. Increase the volume.\n");
+ nextState = STATE_FAILED;
+ } else {
+ nextState = STATE_WAITING_FOR_SILENCE;
+ }
}
} else {
mDownCounter = 8;
@@ -524,14 +574,14 @@
break;
case STATE_SENDING_PULSE:
- audioRecorder.write(inputData, inputChannelCount, numFrames);
+ mAudioRecording.write(inputData, inputChannelCount, numFrames);
sendImpulse(outputData, outputChannelCount);
nextState = STATE_GATHERING_ECHOS;
//printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter);
break;
case STATE_GATHERING_ECHOS:
- numWritten = audioRecorder.write(inputData, inputChannelCount, numFrames);
+ numWritten = mAudioRecording.write(inputData, inputChannelCount, numFrames);
peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
if (peak > mMeasuredLoopGain) {
mMeasuredLoopGain = peak; // AGC might be raising gain so adjust it on the fly.
@@ -565,6 +615,14 @@
mLoopCounter++;
}
+ int save(const char *fileName) override {
+ return mAudioRecording.save(fileName);
+ }
+
+ int load(const char *fileName) override {
+ return mAudioRecording.load(fileName);
+ }
+
private:
enum echo_state_t {
@@ -573,22 +631,22 @@
STATE_WAITING_FOR_SILENCE,
STATE_SENDING_PULSE,
STATE_GATHERING_ECHOS,
- STATE_DONE
+ STATE_DONE,
+ STATE_FAILED
};
- int mDownCounter = 500;
- int mLoopCounter = 0;
- float mPulseThreshold = 0.02f;
- float mSilenceThreshold = 0.002f;
- float mMeasuredLoopGain = 0.0f;
- float mDesiredEchoGain = 0.95f;
- float mEchoGain = 1.0f;
- echo_state_t mState = STATE_INITIAL_SILENCE;
- int32_t mFrameCounter = 0;
+ int mDownCounter = 500;
+ int mLoopCounter = 0;
+ float mPulseThreshold = 0.02f;
+ float mSilenceThreshold = 0.002f;
+ float mMeasuredLoopGain = 0.0f;
+ float mDesiredEchoGain = 0.95f;
+ float mEchoGain = 1.0f;
+ echo_state_t mState = STATE_INITIAL_SILENCE;
- AudioRecording audioRecorder;
- LatencyReport latencyReport;
- PeakDetector mPeakDetector;
+ AudioRecording mAudioRecording; // contains only the input after the gain detection burst
+ LatencyReport mLatencyReport;
+ // PeakDetector mPeakDetector;
};
@@ -602,6 +660,10 @@
class SineAnalyzer : public LoopbackProcessor {
public:
+ virtual int getResult() {
+ return mState == STATE_LOCKED ? 0 : -1;
+ }
+
void report() override {
printf("SineAnalyzer ------------------\n");
printf(LOOPBACK_RESULT_TAG "peak.amplitude = %7.5f\n", mPeakAmplitude);
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index ac6024e..d23d907 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -37,10 +37,10 @@
// Tag for machine readable results as property = value pairs
#define RESULT_TAG "RESULT: "
-#define SAMPLE_RATE 48000
#define NUM_SECONDS 5
#define NUM_INPUT_CHANNELS 1
-#define FILENAME "/data/oboe_input.raw"
+#define FILENAME_ALL "/data/loopback_all.wav"
+#define FILENAME_ECHOS "/data/loopback_echos.wav"
#define APP_VERSION "0.1.22"
struct LoopbackData {
@@ -61,7 +61,7 @@
SineAnalyzer sineAnalyzer;
EchoAnalyzer echoAnalyzer;
- AudioRecording audioRecorder;
+ AudioRecording audioRecording;
LoopbackProcessor *loopbackProcessor;
};
@@ -126,7 +126,7 @@
result = AAUDIO_CALLBACK_RESULT_STOP;
} else if (framesRead > 0) {
- myData->audioRecorder.write(myData->inputData,
+ myData->audioRecording.write(myData->inputData,
myData->actualInputChannelCount,
numFrames);
@@ -176,7 +176,8 @@
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\n");
+ printf(" e for echo latency (default)\n");
+ printf(" f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
printf(" -x use EXCLUSIVE mode for output\n");
printf(" -X use EXCLUSIVE mode for input\n");
printf("Example: aaudio_loopback -n2 -pl -Pl -x\n");
@@ -205,6 +206,7 @@
enum {
TEST_SINE_MAGNITUDE = 0,
TEST_ECHO_LATENCY,
+ TEST_FILE_LATENCY,
};
static int parseTestMode(char c) {
@@ -217,6 +219,9 @@
case 'e':
testMode = TEST_ECHO_LATENCY;
break;
+ case 'f':
+ testMode = TEST_FILE_LATENCY;
+ break;
default:
printf("ERROR in value test mode %c\n", c);
break;
@@ -254,13 +259,13 @@
int main(int argc, const char **argv)
{
- AAudioArgsParser argParser;
- AAudioSimplePlayer player;
- AAudioSimpleRecorder recorder;
- LoopbackData loopbackData;
- AAudioStream *outputStream = nullptr;
+ AAudioArgsParser argParser;
+ AAudioSimplePlayer player;
+ AAudioSimpleRecorder recorder;
+ LoopbackData loopbackData;
+ AAudioStream *outputStream = nullptr;
- aaudio_result_t result = AAUDIO_OK;
+ aaudio_result_t result = AAUDIO_OK;
aaudio_sharing_mode_t requestedInputSharingMode = AAUDIO_SHARING_MODE_SHARED;
int requestedInputChannelCount = NUM_INPUT_CHANNELS;
const aaudio_format_t requestedInputFormat = AAUDIO_FORMAT_PCM_I16;
@@ -268,6 +273,7 @@
aaudio_format_t actualInputFormat;
aaudio_format_t actualOutputFormat;
aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
+ int32_t actualSampleRate = 0;
int testMode = TEST_ECHO_LATENCY;
double gain = 1.0;
@@ -324,7 +330,6 @@
int32_t requestedDuration = argParser.getDurationSeconds();
int32_t recordingDuration = std::min(60, requestedDuration);
- loopbackData.audioRecorder.allocate(recordingDuration * SAMPLE_RATE);
switch(testMode) {
case TEST_SINE_MAGNITUDE:
@@ -334,6 +339,16 @@
loopbackData.echoAnalyzer.setGain(gain);
loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
break;
+ case TEST_FILE_LATENCY: {
+ 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\n", read, FILENAME_ECHOS);
+ loopbackData.loopbackProcessor->report();
+ return 0;
+ }
+ break;
default:
exit(1);
break;
@@ -344,7 +359,7 @@
result = player.open(argParser, MyDataCallbackProc, MyErrorCallbackProc, &loopbackData);
if (result != AAUDIO_OK) {
fprintf(stderr, "ERROR - player.open() returned %d\n", result);
- goto finish;
+ exit(1);
}
outputStream = player.getStream();
argParser.compareWithStream(outputStream);
@@ -352,6 +367,10 @@
actualOutputFormat = AAudioStream_getFormat(outputStream);
assert(actualOutputFormat == AAUDIO_FORMAT_PCM_FLOAT);
+ actualSampleRate = AAudioStream_getSampleRate(outputStream);
+ loopbackData.audioRecording.allocate(recordingDuration * actualSampleRate);
+ loopbackData.audioRecording.setSampleRate(actualSampleRate);
+
printf("INPUT stream ----------------------------------------\n");
// Use different parameters for the input.
argParser.setNumberOfBursts(AAUDIO_UNSPECIFIED);
@@ -380,7 +399,7 @@
// Allocate a buffer for the audio data.
loopbackData.inputFramesMaximum = 32 * framesPerBurst;
- loopbackData.inputBuffersToDiscard = 100;
+ loopbackData.inputBuffersToDiscard = 200;
loopbackData.inputData = new int16_t[loopbackData.inputFramesMaximum
* loopbackData.actualInputChannelCount];
@@ -436,25 +455,31 @@
}
}
- printf("input error = %d = %s\n",
- loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
+ if (loopbackData.loopbackProcessor->getResult() < 0) {
+ printf("Test failed!\n");
+ } else {
+ printf("input error = %d = %s\n",
+ loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
- printf("AAudioStream_getXRunCount %d\n", AAudioStream_getXRunCount(outputStream));
- printf("framesRead = %8d\n", (int) AAudioStream_getFramesRead(outputStream));
- printf("framesWritten = %8d\n", (int) AAudioStream_getFramesWritten(outputStream));
- printf("min numFrames = %8d\n", (int) loopbackData.minNumFrames);
- printf("max numFrames = %8d\n", (int) loopbackData.maxNumFrames);
+ printf("AAudioStream_getXRunCount %d\n", AAudioStream_getXRunCount(outputStream));
+ printf("framesRead = %8d\n", (int) AAudioStream_getFramesRead(outputStream));
+ printf("framesWritten = %8d\n", (int) AAudioStream_getFramesWritten(outputStream));
+ printf("min numFrames = %8d\n", (int) loopbackData.minNumFrames);
+ printf("max numFrames = %8d\n", (int) loopbackData.maxNumFrames);
- if (loopbackData.inputError == AAUDIO_OK) {
- if (testMode == TEST_SINE_MAGNITUDE) {
- printAudioGraph(loopbackData.audioRecorder, 200);
+ if (loopbackData.inputError == AAUDIO_OK) {
+ if (testMode == TEST_SINE_MAGNITUDE) {
+ printAudioGraph(loopbackData.audioRecording, 200);
+ }
+ loopbackData.loopbackProcessor->report();
}
- loopbackData.loopbackProcessor->report();
- }
- {
- int written = loopbackData.audioRecorder.save(FILENAME);
- printf("main() wrote %d mono samples to %s on Android device\n", written, FILENAME);
+ int written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
+ printf("main() wrote %d mono samples to %s on Android device\n", written,
+ FILENAME_ECHOS);
+ printf("main() loopbackData.audioRecording.getSampleRate() = %d\n", loopbackData.audioRecording.getSampleRate());
+ written = loopbackData.audioRecording.save(FILENAME_ALL);
+ printf("main() wrote %d mono samples to %s on Android device\n", written, FILENAME_ALL);
}
finish:
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
index 3fafecf..54b77ba 100644
--- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h
+++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -170,7 +170,6 @@
aaudio_result_t close() {
if (mStream != nullptr) {
- printf("call AAudioStream_close(%p)\n", mStream); fflush(stdout);
AAudioStream_close(mStream);
mStream = nullptr;
}
diff --git a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
index 1344273..869fad0 100644
--- a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
+++ b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
@@ -178,7 +178,6 @@
aaudio_result_t close() {
if (mStream != nullptr) {
- printf("call AAudioStream_close(%p)\n", mStream); fflush(stdout);
AAudioStream_close(mStream);
mStream = nullptr;
}