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

Commit 03f696e3 authored by Andy Hung's avatar Andy Hung Committed by Android (Google) Code Review
Browse files

Merge "Improve dynamic audio resampler filter generation"

parents 6340829f 6582f2b1
Loading
Loading
Loading
Loading
+38 −17
Original line number Diff line number Diff line
@@ -161,7 +161,8 @@ void AudioResamplerDyn::Constants::set(
AudioResamplerDyn::AudioResamplerDyn(int bitDepth,
        int inChannelCount, int32_t sampleRate, src_quality quality)
    : AudioResampler(bitDepth, inChannelCount, sampleRate, quality),
    mResampleType(0), mFilterSampleRate(0), mCoefBuffer(NULL)
    mResampleType(0), mFilterSampleRate(0), mFilterQuality(DEFAULT_QUALITY),
    mCoefBuffer(NULL)
{
    mVolumeSimd[0] = mVolumeSimd[1] = 0;
    mConstants.set(128, 8, mSampleRate, mSampleRate); // TODO: set better
@@ -213,19 +214,16 @@ void AudioResamplerDyn::createKaiserFir(Constants &c, double stopBandAtten,
    // test the filter and report results
    double fp = (fcr - tbw/2)/c.mL;
    double fs = (fcr + tbw/2)/c.mL;
    double fmin, fmax;
    testFir(buf, c.mL, c.mHalfNumCoefs, 0., fp, 100, fmin, fmax);
    double d1 = (fmax - fmin)/2.;
    double ap = -20.*log10(1. - d1); // passband ripple
    printf("passband(%lf, %lf): %.8lf %.8lf %.8lf\n", 0., fp, (fmax + fmin)/2., d1, ap);
    testFir(buf, c.mL, c.mHalfNumCoefs, fs, 0.5, 100, fmin, fmax);
    double d2 = fmax;
    double as = -20.*log10(d2); // stopband attenuation
    printf("stopband(%lf, %lf): %.8lf %.8lf %.3lf\n", fs, 0.5, (fmax + fmin)/2., d2, as);
    double passMin, passMax, passRipple;
    double stopMax, stopRipple;
    testFir(buf, c.mL, c.mHalfNumCoefs, fp, fs, /*passSteps*/ 1000, /*stopSteps*/ 100000,
            passMin, passMax, passRipple, stopMax, stopRipple);
    printf("passband(%lf, %lf): %.8lf %.8lf %.8lf\n", 0., fp, passMin, passMax, passRipple);
    printf("stopband(%lf, %lf): %.8lf %.3lf\n", fs, 0.5, stopMax, stopRipple);
#endif
}

// recursive gcd (TODO: verify tail recursion elimination should make this iterate)
// recursive gcd. Using objdump, it appears the tail recursion is converted to a while loop.
static int gcd(int n, int m) {
    if (m == 0) {
        return n;
@@ -233,7 +231,16 @@ static int gcd(int n, int m) {
    return gcd(m, n % m);
}

static bool isClose(int32_t newSampleRate, int32_t prevSampleRate, int32_t filterSampleRate) {
static bool isClose(int32_t newSampleRate, int32_t prevSampleRate,
        int32_t filterSampleRate, int32_t outSampleRate) {

    // different upsampling ratios do not need a filter change.
    if (filterSampleRate != 0
            && filterSampleRate < outSampleRate
            && newSampleRate < outSampleRate)
        return true;

    // check design criteria again if downsampling is detected.
    int pdiff = absdiff(newSampleRate, prevSampleRate);
    int adiff = absdiff(newSampleRate, filterSampleRate);

@@ -255,8 +262,10 @@ void AudioResamplerDyn::setSampleRate(int32_t inSampleRate) {

    // TODO: Add precalculated Equiripple filters

    if (!isClose(inSampleRate, oldSampleRate, mFilterSampleRate)) {
    if (mFilterQuality != getQuality() ||
            !isClose(inSampleRate, oldSampleRate, mFilterSampleRate, mSampleRate)) {
        mFilterSampleRate = inSampleRate;
        mFilterQuality = getQuality();

        // Begin Kaiser Filter computation
        //
@@ -270,12 +279,12 @@ void AudioResamplerDyn::setSampleRate(int32_t inSampleRate) {
        double stopBandAtten;
        double tbwCheat = 1.; // how much we "cheat" into aliasing
        int halfLength;
        if (getQuality() == DYN_HIGH_QUALITY) {
        if (mFilterQuality == DYN_HIGH_QUALITY) {
            // 32b coefficients, 64 length
            useS32 = true;
            stopBandAtten = 98.;
            halfLength = 32;
        } else if (getQuality() == DYN_LOW_QUALITY) {
        } else if (mFilterQuality == DYN_LOW_QUALITY) {
            // 16b coefficients, 16-32 length
            useS32 = false;
            stopBandAtten = 80.;
@@ -289,8 +298,9 @@ void AudioResamplerDyn::setSampleRate(int32_t inSampleRate) {
            } else {
                tbwCheat = 1.03;
            }
        } else { // medium quality
        } else { // DYN_MED_QUALITY
            // 16b coefficients, 32-64 length
            // note: > 64 length filters with 16b coefs can have quantization noise problems
            useS32 = false;
            stopBandAtten = 84.;
            if (mSampleRate >= inSampleRate * 4) {
@@ -315,9 +325,20 @@ void AudioResamplerDyn::setSampleRate(int32_t inSampleRate) {

        int phases = mSampleRate / gcd(mSampleRate, inSampleRate);

        while (phases<63) { // too few phases, allow room for interpolation
        // TODO: Once dynamic sample rate change is an option, the code below
        // should be modified to execute only when dynamic sample rate change is enabled.
        //
        // as above, #phases less than 63 is too few phases for accurate linear interpolation.
        // we increase the phases to compensate, but more phases means more memory per
        // filter and more time to compute the filter.
        //
        // if we know that the filter will be used for dynamic sample rate changes,
        // that would allow us skip this part for fixed sample rate resamplers.
        //
        while (phases<63) {
            phases *= 2; // this code only needed to support dynamic rate changes
        }

        if (phases>=256) {  // too many phases, always interpolate
            phases = 127;
        }
+2 −1
Original line number Diff line number Diff line
@@ -113,7 +113,8 @@ private:
    Constants mConstants;  // current set of coefficient parameters
    int32_t __attribute__ ((aligned (8))) mVolumeSimd[2];
    int32_t mResampleType; // contains the resample type.
    int32_t mFilterSampleRate; // designed sample rate for the filter
    int32_t mFilterSampleRate; // designed filter sample rate.
    src_quality mFilterQuality; // designed filter quality.
    void* mCoefBuffer; // if a filter is created, this is not null
};

+428 −51

File changed.

Preview size limit exceeded, changes collapsed.

+118 −21
Original line number Diff line number Diff line
@@ -57,7 +57,8 @@ static int usage(const char* name) {
int main(int argc, char* argv[]) {

    const char* const progname = argv[0];
    bool profiling = false;
    bool profileResample = false;
    bool profileFilter = false;
    bool writeHeader = false;
    int channels = 1;
    int input_freq = 0;
@@ -65,10 +66,13 @@ int main(int argc, char* argv[]) {
    AudioResampler::src_quality quality = AudioResampler::DEFAULT_QUALITY;

    int ch;
    while ((ch = getopt(argc, argv, "phvsq:i:o:")) != -1) {
    while ((ch = getopt(argc, argv, "pfhvsq:i:o:")) != -1) {
        switch (ch) {
        case 'p':
            profiling = true;
            profileResample = true;
            break;
        case 'f':
            profileFilter = true;
            break;
        case 'h':
            writeHeader = true;
@@ -230,27 +234,108 @@ int main(int argc, char* argv[]) {
    size_t output_size = 2 * 4 * ((int64_t) input_frames * output_freq) / input_freq;
    output_size &= ~7; // always stereo, 32-bits

    if (profileFilter) {
        // Check how fast sample rate changes are that require filter changes.
        // The delta sample rate changes must indicate a downsampling ratio,
        // and must be larger than 10% changes.
        //
        // On fast devices, filters should be generated between 0.1ms - 1ms.
        // (single threaded).
        AudioResampler* resampler = AudioResampler::create(16, channels,
                8000, quality);
        int looplimit = 100;
        timespec start, end;
        clock_gettime(CLOCK_MONOTONIC, &start);
        for (int i = 0; i < looplimit; ++i) {
            resampler->setSampleRate(9000);
            resampler->setSampleRate(12000);
            resampler->setSampleRate(20000);
            resampler->setSampleRate(30000);
        }
        clock_gettime(CLOCK_MONOTONIC, &end);
        int64_t start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
        int64_t end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
        int64_t time = end_ns - start_ns;
        printf("%.2f sample rate changes with filter calculation/sec\n",
                looplimit * 4 / (time / 1e9));

        // Check how fast sample rate changes are without filter changes.
        // This should be very fast, probably 0.1us - 1us per sample rate
        // change.
        resampler->setSampleRate(1000);
        looplimit = 1000;
        clock_gettime(CLOCK_MONOTONIC, &start);
        for (int i = 0; i < looplimit; ++i) {
            resampler->setSampleRate(1000+i);
        }
        clock_gettime(CLOCK_MONOTONIC, &end);
        start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
        end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
        time = end_ns - start_ns;
        printf("%.2f sample rate changes without filter calculation/sec\n",
                looplimit / (time / 1e9));
        resampler->reset();
        delete resampler;
    }

    void* output_vaddr = malloc(output_size);
    AudioResampler* resampler = AudioResampler::create(16, channels,
            output_freq, quality);
    size_t out_frames = output_size/8;

    /* set volume precision to 12 bits, so the volume scale is 1<<12.
     * This means the "integer" part fits in the Q19.12 precision
     * representation of output int32_t.
     *
     * Generally 0 < volumePrecision <= 14 (due to the limits of
     * int16_t values for Volume). volumePrecision cannot be 0 due
     * to rounding and shifts.
     */
    const int volumePrecision = 12; // in bits

    resampler->setSampleRate(input_freq);
    resampler->setVolume(0x1000, 0x1000);
    resampler->setVolume(1 << volumePrecision, 1 << volumePrecision);

    if (profiling) {
        const int looplimit = 100;
    if (profileResample) {
        /*
         * For profiling on mobile devices, upon experimentation
         * it is better to run a few trials with a shorter loop limit,
         * and take the minimum time.
         *
         * Long tests can cause CPU temperature to build up and thermal throttling
         * to reduce CPU frequency.
         *
         * For frequency checks (index=0, or 1, etc.):
         * "cat /sys/devices/system/cpu/cpu${index}/cpufreq/scaling_*_freq"
         *
         * For temperature checks (index=0, or 1, etc.):
         * "cat /sys/class/thermal/thermal_zone${index}/temp"
         *
         * Another way to avoid thermal throttling is to fix the CPU frequency
         * at a lower level which prevents excessive temperatures.
         */
        const int trials = 4;
        const int looplimit = 4;
        timespec start, end;
        int64_t time;

        for (int n = 0; n < trials; ++n) {
            clock_gettime(CLOCK_MONOTONIC, &start);
            for (int i = 0; i < looplimit; ++i) {
                resampler->resample((int*) output_vaddr, out_frames, &provider);
            provider.reset(); //  reset only provider as benchmarking
                provider.reset(); //  during benchmarking reset only the provider
            }
            clock_gettime(CLOCK_MONOTONIC, &end);
            int64_t start_ns = start.tv_sec * 1000000000LL + start.tv_nsec;
            int64_t end_ns = end.tv_sec * 1000000000LL + end.tv_nsec;
        int64_t time = end_ns - start_ns;
        printf("time(ns):%lld  channels:%d  quality:%d\n", time, channels, quality);
        printf("%f Mspl/s\n", out_frames * looplimit / (time / 1e9) / 1e6);
            int64_t diff_ns = end_ns - start_ns;
            if (n == 0 || diff_ns < time) {
                time = diff_ns;   // save the best out of our trials.
            }
        }
        // Mfrms/s is "Millions of output frames per second".
        printf("quality: %d  channels: %d  msec: %lld  Mfrms/s: %.2lf\n",
                quality, channels, time/1000000, out_frames * looplimit / (time / 1e9) / 1e6);
        resampler->reset();
    }

@@ -266,19 +351,31 @@ int main(int argc, char* argv[]) {
    if (gVerbose) {
        printf("reset() complete\n");
    }
    delete resampler;
    resampler = NULL;

    // mono takes left channel only
    // stereo right channel is half amplitude of stereo left channel (due to input creation)
    int32_t* out = (int32_t*) output_vaddr;
    int16_t* convert = (int16_t*) malloc(out_frames * channels * sizeof(int16_t));

    // round to half towards zero and saturate at int16 (non-dithered)
    const int roundVal = (1<<(volumePrecision-1)) - 1; // volumePrecision > 0

    for (size_t i = 0; i < out_frames; i++) {
        for (int j = 0; j < channels; j++) {
            int32_t s = out[i * 2 + j] >> 12;
            if (s > 32767)
                s = 32767;
            else if (s < -32768)
            int32_t s = out[i * 2 + j] + roundVal; // add offset here
            if (s < 0) {
                s = (s + 1) >> volumePrecision; // round to 0
                if (s < -32768) {
                    s = -32768;
                }
            } else {
                s = s >> volumePrecision;
                if (s > 32767) {
                    s = 32767;
                }
            }
            convert[i * channels + j] = int16_t(s);
        }
    }