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

Commit e0a4d2ad authored by Phil Burk's avatar Phil Burk
Browse files

aaudio_loopback: rectify and low pass filter

Help the autocorrelator recognize the peaks easier.
Reduce false detection of suboctaves.

Better timing for devices with high latency.
Improve error reporting.
Set input capacity based on output burst.

Bug: 119032743
Test: see repro case in bug
Test: aaudio_loopback -te -pl
Change-Id: I921c81db882f46ede8988cbdbebda0765e775086
parent 158ce419
Loading
Loading
Loading
Loading
+309 −174
Original line number Diff line number Diff line
@@ -34,12 +34,123 @@

// Tag for machine readable results as property = value pairs
#define LOOPBACK_RESULT_TAG      "RESULT: "
#define LOOPBACK_SAMPLE_RATE     48000

#define MILLIS_PER_SECOND        1000
constexpr int32_t kDefaultSampleRate = 48000;
constexpr int32_t kMillisPerSecond   = 1000;
constexpr int32_t kMinLatencyMillis  = 4;    // arbitrary and very low
constexpr int32_t kMaxLatencyMillis  = 400;  // arbitrary and generous
constexpr double  kMaxEchoGain       = 10.0; // based on experiments, otherwise too noisy
constexpr double  kMinimumConfidence = 0.5;

#define MAX_ZEROTH_PARTIAL_BINS  40
constexpr double MAX_ECHO_GAIN = 10.0; // based on experiments, otherwise autocorrelation too noisy
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);
    for (int i = 0; i < numSpaces; i++) {
        putchar(' ');
    }
    printf("%c\n", c);
}

/*

FIR filter designed with
http://t-filter.appspot.com

sampling frequency: 48000 Hz

* 0 Hz - 8000 Hz
  gain = 1.2
  desired ripple = 5 dB
  actual ripple = 5.595266169703693 dB

* 12000 Hz - 20000 Hz
  gain = 0
  desired attenuation = -40 dB
  actual attenuation = -37.58691566571914 dB

*/

#define FILTER_TAP_NUM 11

static const float sFilterTaps8000[FILTER_TAP_NUM] = {
        -0.05944219353343189f,
        -0.07303434839503208f,
        -0.037690487672689066f,
        0.1870480506596512f,
        0.3910337357836833f,
        0.5333672385425637f,
        0.3910337357836833f,
        0.1870480506596512f,
        -0.037690487672689066f,
        -0.07303434839503208f,
        -0.05944219353343189f
};

class LowPassFilter {
public:

    /*
     * Filter one input sample.
     * @return filtered output
     */
    float filter(float input) {
        float output = 0.0f;
        mX[mCursor] = input;
        // Index backwards over x.
        int xIndex = mCursor + FILTER_TAP_NUM;
        // Write twice so we avoid having to wrap in the middle of the convolution.
        mX[xIndex] = input;
        for (int i = 0; i < FILTER_TAP_NUM; i++) {
            output += sFilterTaps8000[i] * mX[xIndex--];
        }
        if (++mCursor >= FILTER_TAP_NUM) {
            mCursor = 0;
        }
        return output;
    }

    /**
     * @return true if PASSED
     */
    bool test() {
        // Measure the impulse of the filter at different phases so we exercise
        // all the wraparound cases in the FIR.
        for (int offset = 0; offset < (FILTER_TAP_NUM * 2); offset++ ) {
            // printf("LowPassFilter: cursor = %d\n", mCursor);
            // Offset by one each time.
            if (filter(0.0f) != 0.0f) {
                printf("ERROR: filter should return 0.0 before impulse response\n");
                return false;
            }
            for (int i = 0; i < FILTER_TAP_NUM; i++) {
                float output = filter((i == 0) ? 1.0f : 0.0f); // impulse
                if (output != sFilterTaps8000[i]) {
                    printf("ERROR: filter should return impulse response\n");
                    return false;
                }
            }
            for (int i = 0; i < FILTER_TAP_NUM; i++) {
                if (filter(0.0f) != 0.0f) {
                    printf("ERROR: filter should return 0.0 after impulse response\n");
                    return false;
                }
            }
        }
        return true;
    }

private:
    float   mX[FILTER_TAP_NUM * 2]{}; // twice as big as needed to avoid wrapping
    int32_t mCursor = 0;
};

// A narrow impulse seems to have better immunity against over estimating the
// latency due to detecting subharmonics by the auto-correlator.
@@ -78,6 +189,12 @@ private:
    int64_t mSeed = 99887766;
};


typedef struct LatencyReport_s {
    double latencyInFrames;
    double confidence;
} LatencyReport;

static double calculateCorrelation(const float *a,
                                   const float *b,
                                   int windowSize)
@@ -101,127 +218,75 @@ static double calculateCorrelation(const float *a,
    return correlation;
}

static int calculateCorrelations(const float *haystack, int haystackSize,
                                 const float *needle, int needleSize,
                                 float *results, int resultSize)
{
    int maxCorrelations = haystackSize - needleSize;
    int numCorrelations = std::min(maxCorrelations, resultSize);

    for (int 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;
    const double backThresholdScaler = 0.5;

    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 < backThresholdScaler * maxCorrelation)) {
            location = peakIndex;
            break;
        }
    }

    return location;
static int measureLatencyFromEchos(const float *data,
                                   int32_t numFloats,
                                   int32_t sampleRate,
                                   LatencyReport *report) {
    // Allocate results array
    const int minReasonableLatencyFrames = sampleRate * kMinLatencyMillis / kMillisPerSecond;
    const int maxReasonableLatencyFrames = sampleRate * kMaxLatencyMillis / kMillisPerSecond;
    int32_t maxCorrelationSize = maxReasonableLatencyFrames * 3;
    int numCorrelations = std::min(numFloats, maxCorrelationSize);
    float *correlations = new float[numCorrelations]{};
    float *harmonicSums = new float[numCorrelations]{};

    // Perform sliding auto-correlation.
    // Skip first frames to avoid huge peak at zero offset.
    for (int i = minReasonableLatencyFrames; i < numCorrelations; i++) {
        int32_t remaining = numFloats - i;
        float correlation = (float) calculateCorrelation(&data[i], data, remaining);
        correlations[i] = correlation;
        // printf("correlation[%d] = %f\n", ic, correlation);
    }

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.
static int measureLatencyFromEchos(const float *haystack, int haystackSize,
                            const float *needle, int needleSize,
                            LatencyReport *report) {
    const double threshold = 0.1;
    // printf("%s: haystackSize = %d, needleSize = %d\n", __func__, haystackSize, needleSize);

    // 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);
    first = again;

    // Allocate results array
    int remaining = haystackSize - first;
    const int maxReasonableLatencyFrames = 48000 * 2; // arbitrary but generous value
    int numCorrelations = std::min(remaining, maxReasonableLatencyFrames);
    float *correlations = new float[numCorrelations];
    float *harmonicSums = new float[numCorrelations](); // set 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.
    // Add higher harmonics mapped onto lower harmonics. This reinforces the "fundamental" echo.
    const int numEchoes = 8;
    for (int partial = 1; partial < numEchoes; partial++) {
        for (int i = 0; i < numCorrelations; i++) {
        for (int i = minReasonableLatencyFrames; 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++) {
    for (int i = 0; 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) {
/*
    {
        int32_t topPeak = peakIndex * 7 / 2;
        for (int i = 0; i < topPeak; i++) {
            float sample = harmonicSums[i];
            printf("%4d: %7.5f ", i, sample);
            printAudioScope(sample);
        }
    }
*/

    // Calculate confidence.
    if (maxCorrelation < 0.001) {
        report->confidence = 0.0;
    } else {
        report->confidence = maxCorrelation / sumOfPeaks;
        // Compare peak to average value around peak.
        int32_t numSamples = std::min(numCorrelations, peakIndex * 2);
        if (numSamples <= 0) {
            report->confidence = 0.0;
        } else {
            double sum = 0.0;
            for (int i = 0; i < numSamples; i++) {
                sum += harmonicSums[i];
            }
            const double average = sum / numSamples;
            const double ratio = average / maxCorrelation; // will be < 1.0
            report->confidence = 1.0 - sqrt(ratio);
        }
    }

    delete[] correlations;
@@ -317,7 +382,9 @@ public:
        }

        assert(info.channels == 1);
        assert(info.format == SF_FORMAT_FLOAT);

        setSampleRate(info.samplerate);
        allocate(info.frames);
        mFrameCounter = sf_readf_float(sndFile, mData, info.frames);

@@ -325,11 +392,49 @@ public:
        return mFrameCounter;
    }

    /**
     * Square the samples so they are all positive and so the peaks are emphasized.
     */
    void square() {
        for (int i = 0; i < mFrameCounter; i++) {
            const float sample = mData[i];
            mData[i] = sample * sample;
        }
    }

    /**
     * Low pass filter the recording using a simple FIR filter.
     * Note that the lowpass filter cutoff tracks the sample rate.
     * That is OK because the impulse width is a fixed number of samples.
     */
    void lowPassFilter() {
        for (int i = 0; i < mFrameCounter; i++) {
            mData[i] = mLowPassFilter.filter(mData[i]);
        }
    }

    /**
     * Remove DC offset using a one-pole one-zero IIR filter.
     */
    void dcBlocker() {
        const float R = 0.996; // narrow notch at zero Hz
        float x1 = 0.0;
        float y1 = 0.0;
        for (int i = 0; i < mFrameCounter; i++) {
            const float x = mData[i];
            const float y = x - x1 + (R * y1);
            mData[i] = y;
            y1 = y;
            x1 = x;
        }
    }

private:
    float        *mData = nullptr;
    int32_t       mFrameCounter = 0;
    int32_t       mMaxFrames = 0;
    int32_t mSampleRate = 48000; // common default
    int32_t       mSampleRate = kDefaultSampleRate; // common default
    LowPassFilter mLowPassFilter;
};

// ====================================================================================
@@ -349,8 +454,12 @@ public:

    virtual void printStatus() {};

    virtual int getResult() {
        return -1;
    int32_t getResult() {
        return mResult;
    }

    void setResult(int32_t result) {
        mResult = result;
    }

    virtual bool isDone() {
@@ -379,7 +488,7 @@ public:
    static float measurePeakAmplitude(float *inputData, int inputChannelCount, int numFrames) {
        float peak = 0.0f;
        for (int i = 0; i < numFrames; i++) {
            float pos = fabs(*inputData);
            const float pos = fabs(*inputData);
            if (pos > peak) {
                peak = pos;
            }
@@ -390,7 +499,8 @@ public:


private:
    int32_t mSampleRate = LOOPBACK_SAMPLE_RATE;
    int32_t mSampleRate = kDefaultSampleRate;
    int32_t mResult = 0;
};

class PeakDetector {
@@ -409,24 +519,6 @@ private:
    float  mPrevious = 0.0f;
};


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);
    for (int i = 0; i < numSpaces; i++) {
        putchar(' ');
    }
    printf("%c\n", c);
}

// ====================================================================================
/**
 * Measure latency given a loopback stream data.
@@ -447,17 +539,13 @@ public:
    }

    void reset() override {
        mDownCounter = 200;
        mDownCounter = getSampleRate() / 2;
        mLoopCounter = 0;
        mMeasuredLoopGain = 0.0f;
        mEchoGain = 1.0f;
        mState = STATE_INITIAL_SILENCE;
    }

    virtual int getResult() {
        return mState == STATE_DONE ? 0 : -1;
    }

    virtual bool isDone() {
        return mState == STATE_DONE || mState == STATE_FAILED;
    }
@@ -470,34 +558,57 @@ public:
        return mEchoGain;
    }

    void report() override {
    bool testLowPassFilter() {
        LowPassFilter filter;
        return filter.test();
    }

    void report() override {
        printf("EchoAnalyzer ---------------\n");
        if (getResult() != 0) {
            printf(LOOPBACK_RESULT_TAG "result          = %d\n", getResult());
            return;
        }

        // printf("LowPassFilter test %s\n", testLowPassFilter() ? "PASSED" : "FAILED");

        printf(LOOPBACK_RESULT_TAG "measured.gain          = %8f\n", mMeasuredLoopGain);
        printf(LOOPBACK_RESULT_TAG "echo.gain              = %8f\n", mEchoGain);
        printf(LOOPBACK_RESULT_TAG "test.state             = %8d\n", mState);
        printf(LOOPBACK_RESULT_TAG "test.state.name        = %8s\n", convertStateToText(mState));

        if (mState == STATE_WAITING_FOR_SILENCE) {
            printf("WARNING - Stuck waiting for silence. Input may be too noisy!\n");
        }
        if (mMeasuredLoopGain >= 0.9999) {
            setResult(ERROR_NOISY);
        } else if (mMeasuredLoopGain >= 0.9999) {
            printf("   ERROR - clipping, turn down volume slightly\n");
            setResult(ERROR_CLIPPING);
        } else if (mState != STATE_DONE && mState != STATE_GATHERING_ECHOS) {
            printf("WARNING - Bad state. Check volume on device.\n");
            setResult(ERROR_INVALID_STATE);
        } else {
            const float *needle = s_Impulse;
            int needleSize = (int) (sizeof(s_Impulse) / sizeof(float));
            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 * mLatencyReport.latencyInFrames / getSampleRate();
            // Cleanup the signal to improve the auto-correlation.
            mAudioRecording.dcBlocker();
            mAudioRecording.square();
            mAudioRecording.lowPassFilter();

            printf("Please wait several seconds for auto-correlation to complete.\n");
            measureLatencyFromEchos(mAudioRecording.getData(),
                                    mAudioRecording.size(),
                                    getSampleRate(),
                                    &mLatencyReport);

            double latencyMillis = kMillisPerSecond * (double) 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",
                   mLatencyReport.confidence);
            if (mLatencyReport.confidence < kMinimumConfidence) {
                printf("   ERROR - confidence too low!\n");
                setResult(ERROR_CONFIDENCE);
            }
        }
    }
@@ -523,6 +634,11 @@ public:
        sendImpulses(outputData, outputChannelCount, kImpulseSizeInFrames);
    }

    // @return number of frames for a typical block of processing
    int32_t getBlockFrames() {
        return getSampleRate() / 8;
    }

    void process(float *inputData, int inputChannelCount,
                 float *outputData, int outputChannelCount,
                 int numFrames) override {
@@ -540,10 +656,11 @@ public:
                for (int i = 0; i < numSamples; i++) {
                    outputData[i] = 0;
                }
                if (mDownCounter-- <= 0) {
                mDownCounter -= numFrames;
                if (mDownCounter <= 0) {
                    nextState = STATE_MEASURING_GAIN;
                    //printf("%5d: switch to STATE_MEASURING_GAIN\n", mLoopCounter);
                    mDownCounter = 8;
                    mDownCounter = getBlockFrames() * 2;
                }
                break;

@@ -552,15 +669,16 @@ public:
                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
                // If we get several in a row then go to next state.
                if (peak > mPulseThreshold) {
                    if (mDownCounter-- <= 0) {
                    mDownCounter -= numFrames;
                    if (mDownCounter <= 0) {
                        //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
                        //       mLoopCounter, peak);
                        mDownCounter = 8;
                        mDownCounter = getBlockFrames();
                        mMeasuredLoopGain = peak;  // assumes original pulse amplitude is one
                        mSilenceThreshold = peak * 0.1; // scale silence to measured pulse
                        // Calculate gain that will give us a nice decaying echo.
                        mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
                        if (mEchoGain > MAX_ECHO_GAIN) {
                        if (mEchoGain > kMaxEchoGain) {
                            printf("ERROR - loop gain too low. Increase the volume.\n");
                            nextState = STATE_FAILED;
                        } else {
@@ -568,7 +686,7 @@ public:
                        }
                    }
                } else if (numFrames > kImpulseSizeInFrames){ // ignore short callbacks
                    mDownCounter = 8;
                    mDownCounter = getBlockFrames();
                }
                break;

@@ -581,13 +699,14 @@ public:
                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
                // If we get several in a row then go to next state.
                if (peak < mSilenceThreshold) {
                    if (mDownCounter-- <= 0) {
                    mDownCounter -= numFrames;
                    if (mDownCounter <= 0) {
                        nextState = STATE_SENDING_PULSE;
                        //printf("%5d: switch to STATE_SENDING_PULSE\n", mLoopCounter);
                        mDownCounter = 8;
                        mDownCounter = getBlockFrames();
                    }
                } else {
                    mDownCounter = 8;
                    mDownCounter = getBlockFrames();
                }
                break;

@@ -620,11 +739,11 @@ public:
                }
                if (numWritten  < numFrames) {
                    nextState = STATE_DONE;
                    //printf("%5d: switch to STATE_DONE\n", mLoopCounter);
                }
                break;

            case STATE_DONE:
            case STATE_FAILED:
            default:
                break;
        }
@@ -638,11 +757,22 @@ public:
    }

    int load(const char *fileName) override {
        return mAudioRecording.load(fileName);
        int result = mAudioRecording.load(fileName);
        setSampleRate(mAudioRecording.getSampleRate());
        mState = STATE_DONE;
        return result;
    }

private:

    enum error_code {
        ERROR_OK = 0,
        ERROR_NOISY = -99,
        ERROR_CLIPPING,
        ERROR_CONFIDENCE,
        ERROR_INVALID_STATE
    };

    enum echo_state {
        STATE_INITIAL_SILENCE,
        STATE_MEASURING_GAIN,
@@ -708,10 +838,6 @@ 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     = %8f\n", mPeakAmplitude);
@@ -724,10 +850,9 @@ public:
        float signalToNoiseDB = 10.0 * log(signalToNoise);
        printf(LOOPBACK_RESULT_TAG "signal.to.noise.db = %8.2f\n", signalToNoiseDB);
        if (signalToNoiseDB < MIN_SNRATIO_DB) {
            printf("WARNING - signal to noise ratio is too low! < %d dB\n", MIN_SNRATIO_DB);
            printf("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.\n", MIN_SNRATIO_DB);
            setResult(ERROR_NOISY);
        }
        printf(LOOPBACK_RESULT_TAG "phase.offset       = %8.5f\n", mPhaseOffset);
        printf(LOOPBACK_RESULT_TAG "ref.phase          = %8.5f\n", mPhase);
        printf(LOOPBACK_RESULT_TAG "frames.accumulated = %8d\n", mFramesAccumulated);
        printf(LOOPBACK_RESULT_TAG "sine.period        = %8d\n", mSinePeriod);
        printf(LOOPBACK_RESULT_TAG "test.state         = %8d\n", mState);
@@ -736,10 +861,15 @@ public:
        bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
        if (!gotLock) {
            printf("ERROR - failed to lock on reference sine tone\n");
            setResult(ERROR_NO_LOCK);
        } else {
            // Only print if meaningful.
            printf(LOOPBACK_RESULT_TAG "glitch.count       = %8d\n", mGlitchCount);
            printf(LOOPBACK_RESULT_TAG "max.glitch         = %8f\n", mMaxGlitchDelta);
            if (mGlitchCount > 0) {
                printf("ERROR - number of glitches > 0\n");
                setResult(ERROR_GLITCHES);
            }
        }
    }

@@ -913,6 +1043,13 @@ public:

private:

    enum error_code {
        OK,
        ERROR_NO_LOCK = -80,
        ERROR_GLITCHES,
        ERROR_NOISY
    };

    enum sine_state_t {
        STATE_IDLE,
        STATE_MEASURE_NOISE,
@@ -963,8 +1100,6 @@ private:
    sine_state_t  mState = STATE_IDLE;
};


#undef LOOPBACK_SAMPLE_RATE
#undef LOOPBACK_RESULT_TAG

#endif /* AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H */
+46 −29

File changed.

Preview size limit exceeded, changes collapsed.

+4 −2
Original line number Diff line number Diff line
@@ -130,10 +130,12 @@ public:
    }

    int32_t getBufferCapacity() const {
        printf("%s() returns %d\n", __func__, mBufferCapacity);
        return mBufferCapacity;
    }

    void setBufferCapacity(int32_t frames) {
        printf("%s(%d)\n", __func__, frames);
        mBufferCapacity = frames;
    }

@@ -197,7 +199,7 @@ public:
     * @param builder
     */
    void applyParameters(AAudioStreamBuilder *builder) const {
        AAudioStreamBuilder_setBufferCapacityInFrames(builder, mBufferCapacity);
        AAudioStreamBuilder_setBufferCapacityInFrames(builder, getBufferCapacity());
        AAudioStreamBuilder_setChannelCount(builder, mChannelCount);
        AAudioStreamBuilder_setDeviceId(builder, mDeviceId);
        AAudioStreamBuilder_setFormat(builder, mFormat);
@@ -281,7 +283,7 @@ public:
                    if (strlen(arg) > 2) {
                        policy = atoi(&arg[2]);
                    }
                    if (!AAudio_setMMapPolicy(policy)) {
                    if (AAudio_setMMapPolicy(policy) != AAUDIO_OK) {
                        printf("ERROR: invalid MMAP policy mode %i\n", policy);
                    }
                } break;