Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a835c5b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "aaudio_loopback: use WAV files, reject low gain"

parents 331c180c 4a764a3b
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -3,6 +3,10 @@ cc_test {
    gtest: false,
    srcs: ["src/loopback.cpp"],
    cflags: ["-Wall", "-Werror"],
    shared_libs: ["libaaudio"],
    static_libs: ["libsndfile"],
    shared_libs: [
        "libaaudio",
        "libaudioutils",
        ],
    header_libs: ["libaaudio_example_utils"],
}
+2 −1
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ LOCAL_C_INCLUDES := \
# 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)
+117 −55
Original line number Diff line number Diff line
@@ -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 @@ static int measureLatencyFromEchos(const float *haystack, int haystackSize,
                            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 @@ static int measureLatencyFromEchos(const float *haystack, int haystackSize,
                                      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 @@ public:
        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;
        written = sf_writef_float(sndFile, mData, mFrameCounter);

        sf_close(sndFile);
        return written;
    }
        } else {
            written = (int) fwrite(mData, sizeof(float), mFrameCounter, fid);

    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;
        }
        fclose(fid);
        return written;

        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 @@ public:

    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 @@ class EchoAnalyzer : public LoopbackProcessor {
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 @@ public:
        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,30 +475,23 @@ public:
        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);
        }
    }

@@ -491,13 +536,18 @@ public:
                // 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 @@ public:
                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 @@ public:
        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,7 +631,8 @@ private:
        STATE_WAITING_FOR_SILENCE,
        STATE_SENDING_PULSE,
        STATE_GATHERING_ECHOS,
        STATE_DONE
        STATE_DONE,
        STATE_FAILED
    };

    int             mDownCounter = 500;
@@ -584,11 +643,10 @@ private:
    float           mDesiredEchoGain = 0.95f;
    float           mEchoGain = 1.0f;
    echo_state_t    mState = STATE_INITIAL_SILENCE;
    int32_t       mFrameCounter = 0;

    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 @@ private:
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);
+56 −31
Original line number Diff line number Diff line
@@ -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 @@ struct LoopbackData {

    SineAnalyzer       sineAnalyzer;
    EchoAnalyzer       echoAnalyzer;
    AudioRecording     audioRecorder;
    AudioRecording     audioRecording;
    LoopbackProcessor *loopbackProcessor;
};

@@ -126,7 +126,7 @@ static aaudio_data_callback_result_t MyDataCallbackProc(
            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 @@ static void usage() {
    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 @@ static aaudio_performance_mode_t parsePerformanceMode(char c) {
enum {
    TEST_SINE_MAGNITUDE = 0,
    TEST_ECHO_LATENCY,
    TEST_FILE_LATENCY,
};

static int parseTestMode(char c) {
@@ -217,6 +219,9 @@ static int parseTestMode(char c) {
        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;
@@ -268,6 +273,7 @@ int main(int argc, const char **argv)
    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 @@ int main(int argc, const char **argv)

    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 @@ int main(int argc, const char **argv)
            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 @@ int main(int argc, const char **argv)
    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 @@ int main(int argc, const char **argv)
    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 @@ int main(int argc, const char **argv)

    // 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,6 +455,9 @@ int main(int argc, const char **argv)
        }
    }

    if (loopbackData.loopbackProcessor->getResult() < 0) {
        printf("Test failed!\n");
    } else {
        printf("input error = %d = %s\n",
               loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));

@@ -447,14 +469,17 @@ int main(int argc, const char **argv)

        if (loopbackData.inputError == AAUDIO_OK) {
            if (testMode == TEST_SINE_MAGNITUDE) {
            printAudioGraph(loopbackData.audioRecorder, 200);
                printAudioGraph(loopbackData.audioRecording, 200);
            }
            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:
+0 −1
Original line number Diff line number Diff line
@@ -170,7 +170,6 @@ public:

    aaudio_result_t close() {
        if (mStream != nullptr) {
            printf("call AAudioStream_close(%p)\n", mStream);  fflush(stdout);
            AAudioStream_close(mStream);
            mStream = nullptr;
        }
Loading