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

Commit 546734b3 authored by Andy Hung's avatar Andy Hung
Browse files

Add tests directory for AudioFlinger

Native tests for audio resampler through gtest framework.

Change-Id: Ia340ca51f9b9b78d67e03e3541e4462b98578c95
parent 49869253
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
# Build the unit tests for audioflinger

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SHARED_LIBRARIES := \
	liblog \
	libutils \
	libcutils \
	libstlport \
	libaudioutils \
	libaudioresampler

LOCAL_STATIC_LIBRARIES := \
	libgtest \
	libgtest_main

LOCAL_C_INCLUDES := \
	bionic \
	bionic/libstdc++/include \
	external/gtest/include \
	external/stlport/stlport \
	frameworks/av/services/audioflinger

LOCAL_SRC_FILES := \
	resampler_tests.cpp

LOCAL_MODULE := resampler_tests
LOCAL_MODULE_TAGS := tests

include $(BUILD_EXECUTABLE)
+22 −0
Original line number Diff line number Diff line
#!/bin/bash

if [ -z "$ANDROID_BUILD_TOP" ]; then
    echo "Android build environment not set"
    exit -1
fi

# ensure we have mm
. $ANDROID_BUILD_TOP/build/envsetup.sh

pushd $ANDROID_BUILD_TOP/frameworks/av/services/audioflinger/
pwd
mm

echo "waiting for device"
adb root && adb wait-for-device remount
adb push $OUT/system/lib/libaudioresampler.so /system/lib
adb push $OUT/system/bin/resampler_tests /system/bin

sh $ANDROID_BUILD_TOP/frameworks/av/services/audioflinger/tests/run_all_unit_tests.sh

popd
+471 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "audioflinger_resampler_tests"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include <math.h>
#include <vector>
#include <utility>
#include <cutils/log.h>
#include <gtest/gtest.h>
#include <media/AudioBufferProvider.h>
#include "AudioResampler.h"

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

template<typename T, typename U>
struct is_same
{
    static const bool value = false;
};

template<typename T>
struct is_same<T, T>  // partial specialization
{
    static const bool value = true;
};

template<typename T>
static inline T convertValue(double val)
{
    if (is_same<T, int16_t>::value) {
        return floor(val * 32767.0 + 0.5);
    } else if (is_same<T, int32_t>::value) {
        return floor(val * (1UL<<31) + 0.5);
    }
    return val; // assume float or double
}

/* Creates a type-independent audio buffer provider from
 * a buffer base address, size, framesize, and input increment array.
 *
 * No allocation or deallocation of the provided buffer is done.
 */
class TestProvider : public android::AudioBufferProvider {
public:
    TestProvider(const void* addr, size_t frames, size_t frameSize,
            const std::vector<size_t>& inputIncr)
    : mAddr(addr),
      mNumFrames(frames),
      mFrameSize(frameSize),
      mNextFrame(0), mUnrel(0), mInputIncr(inputIncr), mNextIdx(0)
    {
    }

    virtual android::status_t getNextBuffer(Buffer* buffer, int64_t pts __unused = kInvalidPTS )
    {
        size_t requestedFrames = buffer->frameCount;
        if (requestedFrames > mNumFrames - mNextFrame) {
            buffer->frameCount = mNumFrames - mNextFrame;
        }
        if (!mInputIncr.empty()) {
            size_t provided = mInputIncr[mNextIdx++];
            ALOGV("getNextBuffer() mValue[%d]=%u not %u",
                    mNextIdx-1, provided, buffer->frameCount);
            if (provided < buffer->frameCount) {
                buffer->frameCount = provided;
            }
            if (mNextIdx >= mInputIncr.size()) {
                mNextIdx = 0;
            }
        }
        ALOGV("getNextBuffer() requested %u frames out of %u frames available"
                " and returned %u frames\n",
                requestedFrames, mNumFrames - mNextFrame, buffer->frameCount);
        mUnrel = buffer->frameCount;
        if (buffer->frameCount > 0) {
            buffer->raw = (char *)mAddr + mFrameSize * mNextFrame;
            return android::NO_ERROR;
        } else {
            buffer->raw = NULL;
            return android::NOT_ENOUGH_DATA;
        }
    }

    virtual void releaseBuffer(Buffer* buffer)
    {
        if (buffer->frameCount > mUnrel) {
            ALOGE("releaseBuffer() released %u frames but only %u available "
                    "to release\n", buffer->frameCount, mUnrel);
            mNextFrame += mUnrel;
            mUnrel = 0;
        } else {

            ALOGV("releaseBuffer() released %u frames out of %u frames available "
                    "to release\n", buffer->frameCount, mUnrel);
            mNextFrame += buffer->frameCount;
            mUnrel -= buffer->frameCount;
        }
        buffer->frameCount = 0;
        buffer->raw = NULL;
    }

    void reset()
    {
        mNextFrame = 0;
    }

    size_t getNumFrames()
    {
        return mNumFrames;
    }

    void setIncr(const std::vector<size_t> inputIncr)
    {
        mNextIdx = 0;
        mInputIncr = inputIncr;
    }

protected:
    const void* mAddr;   // base address
    size_t mNumFrames;   // total frames
    int mFrameSize;      // frame size (# channels * bytes per sample)
    size_t mNextFrame;   // index of next frame to provide
    size_t mUnrel;       // number of frames not yet released
    std::vector<size_t> mInputIncr; // number of frames provided per call
    size_t mNextIdx;     // index of next entry in mInputIncr to use
};

/* Creates a buffer filled with a sine wave.
 *
 * Returns a pair consisting of the sine signal buffer and the number of frames.
 * The caller must delete[] the buffer when no longer needed (no shared_ptr<>).
 */
template<typename T>
static std::pair<T*, size_t> createSine(size_t channels,
        double freq, double samplingRate, double time)
{
    double tscale = 1. / samplingRate;
    size_t frames = static_cast<size_t>(samplingRate * time);
    T* buffer = new T[frames * channels];
    for (size_t i = 0; i < frames; ++i) {
        double t = i * tscale;
        double y = sin(2. * M_PI * freq * t);
        T yt = convertValue<T>(y);

        for (size_t j = 0; j < channels; ++j) {
            buffer[i*channels + j] = yt / (j + 1);
        }
    }
    return std::make_pair(buffer, frames);
}

/* Creates a buffer filled with a chirp signal (a sine wave sweep).
 *
 * Returns a pair consisting of the chirp signal buffer and the number of frames.
 * The caller must delete[] the buffer when no longer needed (no shared_ptr<>).
 *
 * When creating the Chirp, note that the frequency is the true sinusoidal
 * frequency not the sampling rate.
 *
 * http://en.wikipedia.org/wiki/Chirp
 */
template<typename T>
static std::pair<T*, size_t> createChirp(size_t channels,
        double minfreq, double maxfreq, double samplingRate, double time)
{
    double tscale = 1. / samplingRate;
    size_t frames = static_cast<size_t>(samplingRate * time);
    T *buffer = new T[frames * channels];
    // note the chirp constant k has a divide-by-two.
    double k = (maxfreq - minfreq) / (2. * time);
    for (size_t i = 0; i < frames; ++i) {
        double t = i * tscale;
        double y = sin(2. * M_PI * (k * t + minfreq) * t);
        T yt = convertValue<T>(y);

        for (size_t j = 0; j < channels; ++j) {
            buffer[i*channels + j] = yt / (j + 1);
        }
    }
    return std::make_pair(buffer, frames);
}

/* This derived class creates a buffer provider of datatype T,
 * consisting of an input signal, e.g. from createChirp().
 * The number of frames can be obtained from the base class
 * TestProvider::getNumFrames().
 */
template <typename T>
class SignalProvider : public TestProvider {
public:
    SignalProvider(const std::pair<T*, size_t>& bufferInfo, size_t channels,
            const std::vector<size_t>& values)
    : TestProvider(bufferInfo.first, bufferInfo.second, channels * sizeof(T), values),
      mManagedPtr(bufferInfo.first)
    {
    }

    virtual ~SignalProvider()
    {
        delete[] mManagedPtr;
    }

protected:
    T* mManagedPtr;
};

void resample(void *output, size_t outputFrames, const std::vector<size_t> &outputIncr,
        android::AudioBufferProvider *provider, android::AudioResampler *resampler)
{
    for (size_t i = 0, j = 0; i < outputFrames; ) {
        size_t thisFrames = outputIncr[j++];
        if (j >= outputIncr.size()) {
            j = 0;
        }
        if (thisFrames == 0 || thisFrames > outputFrames - i) {
            thisFrames = outputFrames - i;
        }
        resampler->resample((int32_t*) output + 2*i, thisFrames, provider);
        i += thisFrames;
    }
}

void buffercmp(const void *reference, const void *test,
        size_t outputFrameSize, size_t outputFrames)
{
    for (size_t i = 0; i < outputFrames; ++i) {
        int check = memcmp((const char*)reference + i * outputFrameSize,
                (const char*)test + i * outputFrameSize, outputFrameSize);
        if (check) {
            ALOGE("Failure at frame %d", i);
            ASSERT_EQ(check, 0); /* fails */
        }
    }
}

void testBufferIncrement(size_t channels, unsigned inputFreq, unsigned outputFreq,
        enum android::AudioResampler::src_quality quality)
{
    // create the provider
    std::vector<size_t> inputIncr;
    SignalProvider<int16_t> provider(createChirp<int16_t>(channels,
            0., outputFreq/2., outputFreq, outputFreq/2000.),
            channels, inputIncr);

    // calculate the output size
    size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq;
    size_t outputFrameSize = 2 * sizeof(int32_t);
    size_t outputSize = outputFrameSize * outputFrames;
    outputSize &= ~7;

    // create the resampler
    const int volumePrecision = 12; /* typical unity gain */
    android::AudioResampler* resampler;

    resampler = android::AudioResampler::create(16, channels, outputFreq, quality);
    resampler->setSampleRate(inputFreq);
    resampler->setVolume(1 << volumePrecision, 1 << volumePrecision);

    // set up the reference run
    std::vector<size_t> refIncr;
    refIncr.push_back(outputFrames);
    void* reference = malloc(outputSize);
    resample(reference, outputFrames, refIncr, &provider, resampler);

    provider.reset();

#if 0
    /* this test will fail - API interface issue: reset() does not clear internal buffers */
    resampler->reset();
#else
    delete resampler;
    resampler = android::AudioResampler::create(16, channels, outputFreq, quality);
    resampler->setSampleRate(inputFreq);
    resampler->setVolume(1 << volumePrecision, 1 << volumePrecision);
#endif

    // set up the test run
    std::vector<size_t> outIncr;
    outIncr.push_back(1);
    outIncr.push_back(2);
    outIncr.push_back(3);
    void* test = malloc(outputSize);
    resample(test, outputFrames, outIncr, &provider, resampler);

    // check
    buffercmp(reference, test, outputFrameSize, outputFrames);

    free(reference);
    free(test);
    delete resampler;
}

template <typename T>
inline double sqr(T v)
{
    double dv = static_cast<double>(v);
    return dv * dv;
}

template <typename T>
double signalEnergy(T *start, T *end, unsigned stride)
{
    double accum = 0;

    for (T *p = start; p < end; p += stride) {
        accum += sqr(*p);
    }
    unsigned count = (end - start + stride - 1) / stride;
    return accum / count;
}

void testStopbandDownconversion(size_t channels,
        unsigned inputFreq, unsigned outputFreq,
        unsigned passband, unsigned stopband,
        enum android::AudioResampler::src_quality quality)
{
    // create the provider
    std::vector<size_t> inputIncr;
    SignalProvider<int16_t> provider(createChirp<int16_t>(channels,
            0., inputFreq/2., inputFreq, inputFreq/2000.),
            channels, inputIncr);

    // calculate the output size
    size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq;
    size_t outputFrameSize = 2 * sizeof(int32_t);
    size_t outputSize = outputFrameSize * outputFrames;
    outputSize &= ~7;

    // create the resampler
    const int volumePrecision = 12; /* typical unity gain */
    android::AudioResampler* resampler;

    resampler = android::AudioResampler::create(16, channels, outputFreq, quality);
    resampler->setSampleRate(inputFreq);
    resampler->setVolume(1 << volumePrecision, 1 << volumePrecision);

    // set up the reference run
    std::vector<size_t> refIncr;
    refIncr.push_back(outputFrames);
    void* reference = malloc(outputSize);
    resample(reference, outputFrames, refIncr, &provider, resampler);

    int32_t *out = reinterpret_cast<int32_t *>(reference);

    // check signal energy in passband
    const unsigned passbandFrame = passband * outputFreq / 1000.;
    const unsigned stopbandFrame = stopband * outputFreq / 1000.;

    // check each channel separately
    for (size_t i = 0; i < channels; ++i) {
        double passbandEnergy = signalEnergy(out, out + passbandFrame * channels, channels);
        double stopbandEnergy = signalEnergy(out + stopbandFrame * channels,
                out + outputFrames * channels, channels);
        double dbAtten = -10. * log10(stopbandEnergy / passbandEnergy);
        ASSERT_GT(dbAtten, 60.);

#if 0
        // internal verification
        printf("if:%d  of:%d  pbf:%d  sbf:%d  sbe: %f  pbe: %f  db: %.2f\n",
                provider.getNumFrames(), outputFrames,
                passbandFrame, stopbandFrame, stopbandEnergy, passbandEnergy, dbAtten);
        for (size_t i = 0; i < 10; ++i) {
            printf("%d\n", out[i+passbandFrame*channels]);
        }
        for (size_t i = 0; i < 10; ++i) {
            printf("%d\n", out[i+stopbandFrame*channels]);
        }
#endif
    }

    free(reference);
    delete resampler;
}

/* Buffer increment test
 *
 * We compare a reference output, where we consume and process the entire
 * buffer at a time, and a test output, where we provide small chunks of input
 * data and process small chunks of output (which may not be equivalent in size).
 *
 * Two subtests - fixed phase (3:2 down) and interpolated phase (147:320 up)
 */
TEST(audioflinger_resampler, bufferincrement_fixedphase) {
    // all of these work
    static const enum android::AudioResampler::src_quality kQualityArray[] = {
            android::AudioResampler::LOW_QUALITY,
            android::AudioResampler::MED_QUALITY,
            android::AudioResampler::HIGH_QUALITY,
            android::AudioResampler::VERY_HIGH_QUALITY,
            android::AudioResampler::DYN_LOW_QUALITY,
            android::AudioResampler::DYN_MED_QUALITY,
            android::AudioResampler::DYN_HIGH_QUALITY,
    };

    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
        testBufferIncrement(2, 48000, 32000, kQualityArray[i]);
    }
}

TEST(audioflinger_resampler, bufferincrement_interpolatedphase) {
    // all of these work except low quality
    static const enum android::AudioResampler::src_quality kQualityArray[] = {
//           android::AudioResampler::LOW_QUALITY,
            android::AudioResampler::MED_QUALITY,
            android::AudioResampler::HIGH_QUALITY,
            android::AudioResampler::VERY_HIGH_QUALITY,
            android::AudioResampler::DYN_LOW_QUALITY,
            android::AudioResampler::DYN_MED_QUALITY,
            android::AudioResampler::DYN_HIGH_QUALITY,
    };

    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
        testBufferIncrement(2, 22050, 48000, kQualityArray[i]);
    }
}

/* Simple aliasing test
 *
 * This checks stopband response of the chirp signal to make sure frequencies
 * are properly suppressed.  It uses downsampling because the stopband can be
 * clearly isolated by input frequencies exceeding the output sample rate (nyquist).
 */
TEST(audioflinger_resampler, stopbandresponse) {
    // not all of these may work (old resamplers fail on downsampling)
    static const enum android::AudioResampler::src_quality kQualityArray[] = {
            //android::AudioResampler::LOW_QUALITY,
            //android::AudioResampler::MED_QUALITY,
            //android::AudioResampler::HIGH_QUALITY,
            //android::AudioResampler::VERY_HIGH_QUALITY,
            android::AudioResampler::DYN_LOW_QUALITY,
            android::AudioResampler::DYN_MED_QUALITY,
            android::AudioResampler::DYN_HIGH_QUALITY,
    };

    // in this test we assume a maximum transition band between 12kHz and 20kHz.
    // there must be at least 60dB relative attenuation between stopband and passband.
    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
        testStopbandDownconversion(2, 48000, 32000, 12000, 20000, kQualityArray[i]);
    }

    // in this test we assume a maximum transition band between 7kHz and 15kHz.
    // there must be at least 60dB relative attenuation between stopband and passband.
    // (the weird ratio triggers interpolative resampling)
    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
        testStopbandDownconversion(2, 48000, 22101, 7000, 15000, kQualityArray[i]);
    }
}
+11 −0
Original line number Diff line number Diff line
#!/bin/bash

if [ -z "$ANDROID_BUILD_TOP" ]; then
    echo "Android build environment not set"
    exit -1
fi

echo "waiting for device"
adb root && adb wait-for-device remount

adb shell /system/bin/resampler_tests