summaryrefslogtreecommitdiffstats
path: root/services/audioflinger/tests
diff options
context:
space:
mode:
Diffstat (limited to 'services/audioflinger/tests')
-rw-r--r--services/audioflinger/tests/Android.mk73
-rwxr-xr-xservices/audioflinger/tests/build_and_run_all_unit_tests.sh22
-rwxr-xr-xservices/audioflinger/tests/mixer_to_wav_tests.sh134
-rw-r--r--services/audioflinger/tests/resampler_tests.cpp280
-rwxr-xr-xservices/audioflinger/tests/run_all_unit_tests.sh11
-rw-r--r--services/audioflinger/tests/test-mixer.cpp286
-rw-r--r--services/audioflinger/tests/test_utils.h307
7 files changed, 1113 insertions, 0 deletions
diff --git a/services/audioflinger/tests/Android.mk b/services/audioflinger/tests/Android.mk
new file mode 100644
index 0000000..7bba05b
--- /dev/null
+++ b/services/audioflinger/tests/Android.mk
@@ -0,0 +1,73 @@
+# Build the unit tests for audioflinger
+
+#
+# resampler unit test
+#
+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 \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/services/audioflinger
+
+LOCAL_SRC_FILES := \
+ resampler_tests.cpp
+
+LOCAL_MODULE := resampler_tests
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_EXECUTABLE)
+
+#
+# audio mixer test tool
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ test-mixer.cpp \
+ ../AudioMixer.cpp.arm \
+
+LOCAL_C_INCLUDES := \
+ bionic \
+ bionic/libstdc++/include \
+ external/stlport/stlport \
+ $(call include-path-for, audio-effects) \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/services/audioflinger
+
+LOCAL_STATIC_LIBRARIES := \
+ libsndfile
+
+LOCAL_SHARED_LIBRARIES := \
+ libstlport \
+ libeffects \
+ libnbaio \
+ libcommon_time_client \
+ libaudioresampler \
+ libaudioutils \
+ libdl \
+ libcutils \
+ libutils \
+ liblog
+
+LOCAL_MODULE:= test-mixer
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/audioflinger/tests/build_and_run_all_unit_tests.sh b/services/audioflinger/tests/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..2c453b0
--- /dev/null
+++ b/services/audioflinger/tests/build_and_run_all_unit_tests.sh
@@ -0,0 +1,22 @@
+#!/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
diff --git a/services/audioflinger/tests/mixer_to_wav_tests.sh b/services/audioflinger/tests/mixer_to_wav_tests.sh
new file mode 100755
index 0000000..93bff47
--- /dev/null
+++ b/services/audioflinger/tests/mixer_to_wav_tests.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+#
+# This script uses test-mixer to generate WAV files
+# for evaluation of the AudioMixer component.
+#
+# Sine and chirp signals are used for input because they
+# show up as clear lines, either horizontal or diagonal,
+# on a spectrogram. This means easy verification of multiple
+# track mixing.
+#
+# After execution, look for created subdirectories like
+# mixer_i_i
+# mixer_i_f
+# mixer_f_f
+#
+# Recommend using a program such as audacity to evaluate
+# the output WAV files, e.g.
+#
+# cd testdir
+# audacity *.wav
+#
+# Using Audacity:
+#
+# Under "Waveform" view mode you can zoom into the
+# start of the WAV file to verify proper ramping.
+#
+# Select "Spectrogram" to see verify the lines
+# (sine = horizontal, chirp = diagonal) which should
+# be clear (except for around the start as the volume
+# ramping causes spectral distortion).
+
+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/
+
+# build
+pwd
+mm
+
+# send to device
+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/test-mixer /system/bin
+
+# createwav creates a series of WAV files testing various
+# mixer settings
+# $1 = flags
+# $2 = directory
+function createwav() {
+# create directory if it doesn't exist
+ if [ ! -d $2 ]; then
+ mkdir $2
+ fi
+
+# Test:
+# process__genericResampling
+# track__Resample / track__genericResample
+ adb shell test-mixer $1 -s 48000 \
+ -o /sdcard/tm48000gr.wav \
+ sine:2,4000,7520 chirp:2,9200 sine:1,3000,18000
+ adb pull /sdcard/tm48000gr.wav $2
+
+# Test:
+# process__genericResample
+# track__Resample / track__genericResample
+# track__NoResample / track__16BitsStereo / track__16BitsMono
+# Aux buffer
+ adb shell test-mixer $1 -s 9307 \
+ -a /sdcard/aux9307gra.wav -o /sdcard/tm9307gra.wav \
+ sine:2,1000,3000 sine:1,2000,9307 chirp:2,9307
+ adb pull /sdcard/tm9307gra.wav $2
+ adb pull /sdcard/aux9307gra.wav $2
+
+# Test:
+# process__genericNoResampling
+# track__NoResample / track__16BitsStereo / track__16BitsMono
+ adb shell test-mixer $1 -s 32000 \
+ -o /sdcard/tm32000gnr.wav \
+ sine:2,1000,32000 chirp:2,32000 sine:1,3000,32000
+ adb pull /sdcard/tm32000gnr.wav $2
+
+# Test:
+# process__genericNoResampling
+# track__NoResample / track__16BitsStereo / track__16BitsMono
+# Aux buffer
+ adb shell test-mixer $1 -s 32000 \
+ -a /sdcard/aux32000gnra.wav -o /sdcard/tm32000gnra.wav \
+ sine:2,1000,32000 chirp:2,32000 sine:1,3000,32000
+ adb pull /sdcard/tm32000gnra.wav $2
+ adb pull /sdcard/aux32000gnra.wav $2
+
+# Test:
+# process__NoResampleOneTrack / process__OneTrack16BitsStereoNoResampling
+# Downmixer
+ adb shell test-mixer $1 -s 32000 \
+ -o /sdcard/tm32000nrot.wav \
+ sine:6,1000,32000
+ adb pull /sdcard/tm32000nrot.wav $2
+
+# Test:
+# process__NoResampleOneTrack / OneTrack16BitsStereoNoResampling
+# Aux buffer
+ adb shell test-mixer $1 -s 44100 \
+ -a /sdcard/aux44100nrota.wav -o /sdcard/tm44100nrota.wav \
+ sine:2,2000,44100
+ adb pull /sdcard/tm44100nrota.wav $2
+ adb pull /sdcard/aux44100nrota.wav $2
+}
+
+#
+# Call createwav to generate WAV files in various combinations
+#
+# i_i = integer input track, integer mixer output
+# f_f = float input track, float mixer output
+# i_f = integer input track, float_mixer output
+#
+# If the mixer output is float, then the output WAV file is pcm float.
+#
+# TODO: create a "snr" like "diff" to automatically
+# compare files in these directories together.
+#
+
+createwav "" "tests/mixer_i_i"
+createwav "-f -m" "tests/mixer_f_f"
+createwav "-m" "tests/mixer_i_f"
+
+popd
diff --git a/services/audioflinger/tests/resampler_tests.cpp b/services/audioflinger/tests/resampler_tests.cpp
new file mode 100644
index 0000000..4a67d0b
--- /dev/null
+++ b/services/audioflinger/tests/resampler_tests.cpp
@@ -0,0 +1,280 @@
+/*
+ * 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"
+#include "test_utils.h"
+
+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<int> inputIncr;
+ SignalProvider provider;
+ provider.setChirp<int16_t>(channels,
+ 0., outputFreq/2., outputFreq, outputFreq/2000.);
+ provider.setIncr(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<int> inputIncr;
+ SignalProvider provider;
+ provider.setChirp<int16_t>(channels,
+ 0., inputFreq/2., inputFreq, inputFreq/2000.);
+ provider.setIncr(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]);
+ }
+}
diff --git a/services/audioflinger/tests/run_all_unit_tests.sh b/services/audioflinger/tests/run_all_unit_tests.sh
new file mode 100755
index 0000000..ffae6ae
--- /dev/null
+++ b/services/audioflinger/tests/run_all_unit_tests.sh
@@ -0,0 +1,11 @@
+#!/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
diff --git a/services/audioflinger/tests/test-mixer.cpp b/services/audioflinger/tests/test-mixer.cpp
new file mode 100644
index 0000000..3940702
--- /dev/null
+++ b/services/audioflinger/tests/test-mixer.cpp
@@ -0,0 +1,286 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <math.h>
+#include <vector>
+#include <audio_utils/primitives.h>
+#include <audio_utils/sndfile.h>
+#include <media/AudioBufferProvider.h>
+#include "AudioMixer.h"
+#include "test_utils.h"
+
+/* Testing is typically through creation of an output WAV file from several
+ * source inputs, to be later analyzed by an audio program such as Audacity.
+ *
+ * Sine or chirp functions are typically more useful as input to the mixer
+ * as they show up as straight lines on a spectrogram if successfully mixed.
+ *
+ * A sample shell script is provided: mixer_to_wave_tests.sh
+ */
+
+using namespace android;
+
+static void usage(const char* name) {
+ fprintf(stderr, "Usage: %s [-f] [-m]"
+ " [-s sample-rate] [-o <output-file>] [-a <aux-buffer-file>] [-P csv]"
+ " (<input-file> | <command>)+\n", name);
+ fprintf(stderr, " -f enable floating point input track\n");
+ fprintf(stderr, " -m enable floating point mixer output\n");
+ fprintf(stderr, " -s mixer sample-rate\n");
+ fprintf(stderr, " -o <output-file> WAV file, pcm16 (or float if -m specified)\n");
+ fprintf(stderr, " -a <aux-buffer-file>\n");
+ fprintf(stderr, " -P # frames provided per call to resample() in CSV format\n");
+ fprintf(stderr, " <input-file> is a WAV file\n");
+ fprintf(stderr, " <command> can be 'sine:<channels>,<frequency>,<samplerate>'\n");
+ fprintf(stderr, " 'chirp:<channels>,<samplerate>'\n");
+}
+
+static int writeFile(const char *filename, const void *buffer,
+ uint32_t sampleRate, uint32_t channels, size_t frames, bool isBufferFloat) {
+ if (filename == NULL) {
+ return 0; // ok to pass in NULL filename
+ }
+ // write output to file.
+ SF_INFO info;
+ info.frames = 0;
+ info.samplerate = sampleRate;
+ info.channels = channels;
+ info.format = SF_FORMAT_WAV | (isBufferFloat ? SF_FORMAT_FLOAT : SF_FORMAT_PCM_16);
+ printf("saving file:%s channels:%d samplerate:%d frames:%d\n",
+ filename, info.channels, info.samplerate, frames);
+ SNDFILE *sf = sf_open(filename, SFM_WRITE, &info);
+ if (sf == NULL) {
+ perror(filename);
+ return EXIT_FAILURE;
+ }
+ if (isBufferFloat) {
+ (void) sf_writef_float(sf, (float*)buffer, frames);
+ } else {
+ (void) sf_writef_short(sf, (short*)buffer, frames);
+ }
+ sf_close(sf);
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char* argv[]) {
+ const char* const progname = argv[0];
+ bool useInputFloat = false;
+ bool useMixerFloat = false;
+ bool useRamp = true;
+ uint32_t outputSampleRate = 48000;
+ uint32_t outputChannels = 2; // stereo for now
+ std::vector<int> Pvalues;
+ const char* outputFilename = NULL;
+ const char* auxFilename = NULL;
+ std::vector<int32_t> Names;
+ std::vector<SignalProvider> Providers;
+
+ for (int ch; (ch = getopt(argc, argv, "fms:o:a:P:")) != -1;) {
+ switch (ch) {
+ case 'f':
+ useInputFloat = true;
+ break;
+ case 'm':
+ useMixerFloat = true;
+ break;
+ case 's':
+ outputSampleRate = atoi(optarg);
+ break;
+ case 'o':
+ outputFilename = optarg;
+ break;
+ case 'a':
+ auxFilename = optarg;
+ break;
+ case 'P':
+ if (parseCSV(optarg, Pvalues) < 0) {
+ fprintf(stderr, "incorrect syntax for -P option\n");
+ return EXIT_FAILURE;
+ }
+ break;
+ case '?':
+ default:
+ usage(progname);
+ return EXIT_FAILURE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ usage(progname);
+ return EXIT_FAILURE;
+ }
+ if ((unsigned)argc > AudioMixer::MAX_NUM_TRACKS) {
+ fprintf(stderr, "too many tracks: %d > %u", argc, AudioMixer::MAX_NUM_TRACKS);
+ return EXIT_FAILURE;
+ }
+
+ size_t outputFrames = 0;
+
+ // create providers for each track
+ Providers.resize(argc);
+ for (int i = 0; i < argc; ++i) {
+ static const char chirp[] = "chirp:";
+ static const char sine[] = "sine:";
+ static const double kSeconds = 1;
+
+ if (!strncmp(argv[i], chirp, strlen(chirp))) {
+ std::vector<int> v;
+
+ parseCSV(argv[i] + strlen(chirp), v);
+ if (v.size() == 2) {
+ printf("creating chirp(%d %d)\n", v[0], v[1]);
+ if (useInputFloat) {
+ Providers[i].setChirp<float>(v[0], 0, v[1]/2, v[1], kSeconds);
+ } else {
+ Providers[i].setChirp<int16_t>(v[0], 0, v[1]/2, v[1], kSeconds);
+ }
+ Providers[i].setIncr(Pvalues);
+ } else {
+ fprintf(stderr, "malformed input '%s'\n", argv[i]);
+ }
+ } else if (!strncmp(argv[i], sine, strlen(sine))) {
+ std::vector<int> v;
+
+ parseCSV(argv[i] + strlen(sine), v);
+ if (v.size() == 3) {
+ printf("creating sine(%d %d)\n", v[0], v[1]);
+ if (useInputFloat) {
+ Providers[i].setSine<float>(v[0], v[1], v[2], kSeconds);
+ } else {
+ Providers[i].setSine<int16_t>(v[0], v[1], v[2], kSeconds);
+ }
+ Providers[i].setIncr(Pvalues);
+ } else {
+ fprintf(stderr, "malformed input '%s'\n", argv[i]);
+ }
+ } else {
+ printf("creating filename(%s)\n", argv[i]);
+ if (useInputFloat) {
+ Providers[i].setFile<float>(argv[i]);
+ } else {
+ Providers[i].setFile<short>(argv[i]);
+ }
+ Providers[i].setIncr(Pvalues);
+ }
+ // calculate the number of output frames
+ size_t nframes = (int64_t) Providers[i].getNumFrames() * outputSampleRate
+ / Providers[i].getSampleRate();
+ if (i == 0 || outputFrames > nframes) { // choose minimum for outputFrames
+ outputFrames = nframes;
+ }
+ }
+
+ // create the output buffer.
+ const size_t outputFrameSize = outputChannels
+ * (useMixerFloat ? sizeof(float) : sizeof(int16_t));
+ const size_t outputSize = outputFrames * outputFrameSize;
+ void *outputAddr = NULL;
+ (void) posix_memalign(&outputAddr, 32, outputSize);
+ memset(outputAddr, 0, outputSize);
+
+ // create the aux buffer, if needed.
+ const size_t auxFrameSize = sizeof(int32_t); // Q4.27 always
+ const size_t auxSize = outputFrames * auxFrameSize;
+ void *auxAddr = NULL;
+ if (auxFilename) {
+ (void) posix_memalign(&auxAddr, 32, auxSize);
+ memset(auxAddr, 0, auxSize);
+ }
+
+ // create the mixer.
+ const size_t mixerFrameCount = 320; // typical numbers may range from 240 or 960
+ AudioMixer *mixer = new AudioMixer(mixerFrameCount, outputSampleRate);
+ audio_format_t inputFormat = useInputFloat
+ ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+ audio_format_t mixerFormat = useMixerFloat
+ ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
+ float f = AudioMixer::UNITY_GAIN_FLOAT / Providers.size(); // normalize volume by # tracks
+ static float f0; // zero
+
+ // set up the tracks.
+ for (size_t i = 0; i < Providers.size(); ++i) {
+ //printf("track %d out of %d\n", i, Providers.size());
+ uint32_t channelMask = audio_channel_out_mask_from_count(Providers[i].getNumChannels());
+ int32_t name = mixer->getTrackName(channelMask,
+ inputFormat, AUDIO_SESSION_OUTPUT_MIX);
+ ALOG_ASSERT(name >= 0);
+ Names.push_back(name);
+ mixer->setBufferProvider(name, &Providers[i]);
+ mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
+ (void *) outputAddr);
+ mixer->setParameter(
+ name,
+ AudioMixer::TRACK,
+ AudioMixer::MIXER_FORMAT, (void *)mixerFormat);
+ mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::FORMAT,
+ (void *)(uintptr_t)inputFormat);
+ mixer->setParameter(
+ name,
+ AudioMixer::RESAMPLE,
+ AudioMixer::SAMPLE_RATE,
+ (void *)(uintptr_t)Providers[i].getSampleRate());
+ if (useRamp) {
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f0);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f0);
+ mixer->setParameter(name, AudioMixer::RAMP_VOLUME, AudioMixer::VOLUME0, &f);
+ mixer->setParameter(name, AudioMixer::RAMP_VOLUME, AudioMixer::VOLUME1, &f);
+ } else {
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &f);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &f);
+ }
+ if (auxFilename) {
+ mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
+ (void *) auxAddr);
+ mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::AUXLEVEL, &f0);
+ mixer->setParameter(name, AudioMixer::RAMP_VOLUME, AudioMixer::AUXLEVEL, &f);
+ }
+ mixer->enable(name);
+ }
+
+ // pump the mixer to process data.
+ size_t i;
+ for (i = 0; i < outputFrames - mixerFrameCount; i += mixerFrameCount) {
+ for (size_t j = 0; j < Names.size(); ++j) {
+ mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::MAIN_BUFFER,
+ (char *) outputAddr + i * outputFrameSize);
+ if (auxFilename) {
+ mixer->setParameter(Names[j], AudioMixer::TRACK, AudioMixer::AUX_BUFFER,
+ (char *) auxAddr + i * auxFrameSize);
+ }
+ }
+ mixer->process(AudioBufferProvider::kInvalidPTS);
+ }
+ outputFrames = i; // reset output frames to the data actually produced.
+
+ // write to files
+ writeFile(outputFilename, outputAddr,
+ outputSampleRate, outputChannels, outputFrames, useMixerFloat);
+ if (auxFilename) {
+ // Aux buffer is always in q4_27 format for now.
+ // memcpy_to_i16_from_q4_27(), but with stereo frame count (not sample count)
+ ditherAndClamp((int32_t*)auxAddr, (int32_t*)auxAddr, outputFrames >> 1);
+ writeFile(auxFilename, auxAddr, outputSampleRate, 1, outputFrames, false);
+ }
+
+ delete mixer;
+ free(outputAddr);
+ free(auxAddr);
+ return EXIT_SUCCESS;
+}
diff --git a/services/audioflinger/tests/test_utils.h b/services/audioflinger/tests/test_utils.h
new file mode 100644
index 0000000..f954292
--- /dev/null
+++ b/services/audioflinger/tests/test_utils.h
@@ -0,0 +1,307 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_AUDIO_TEST_UTILS_H
+#define ANDROID_AUDIO_TEST_UTILS_H
+
+#include <audio_utils/sndfile.h>
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+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
+}
+
+// Convert a list of integers in CSV format to a Vector of those values.
+// Returns the number of elements in the list, or -1 on error.
+static inline int parseCSV(const char *string, std::vector<int>& values)
+{
+ // pass 1: count the number of values and do syntax check
+ size_t numValues = 0;
+ bool hadDigit = false;
+ for (const char *p = string; ; ) {
+ switch (*p++) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ hadDigit = true;
+ break;
+ case '\0':
+ if (hadDigit) {
+ // pass 2: allocate and initialize vector of values
+ values.resize(++numValues);
+ values[0] = atoi(p = string);
+ for (size_t i = 1; i < numValues; ) {
+ if (*p++ == ',') {
+ values[i++] = atoi(p);
+ }
+ }
+ return numValues;
+ }
+ // fall through
+ case ',':
+ if (hadDigit) {
+ hadDigit = false;
+ numValues++;
+ break;
+ }
+ // fall through
+ default:
+ return -1;
+ }
+ }
+}
+
+/* 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(void* addr, size_t frames, size_t frameSize,
+ const std::vector<int>& inputIncr)
+ : mAddr(addr),
+ mNumFrames(frames),
+ mFrameSize(frameSize),
+ mNextFrame(0), mUnrel(0), mInputIncr(inputIncr), mNextIdx(0)
+ {
+ }
+
+ TestProvider()
+ : mAddr(NULL), mNumFrames(0), mFrameSize(0),
+ mNextFrame(0), mUnrel(0), mNextIdx(0)
+ {
+ }
+
+ void setIncr(const std::vector<int>& inputIncr) {
+ 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;
+ }
+
+
+protected:
+ 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<int> 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.
+ */
+template<typename T>
+static void createSine(void *vbuffer, size_t frames,
+ size_t channels, double sampleRate, double freq)
+{
+ double tscale = 1. / sampleRate;
+ T* buffer = reinterpret_cast<T*>(vbuffer);
+ 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);
+ }
+ }
+}
+
+/* Creates a buffer filled with a chirp signal (a sine wave sweep).
+ *
+ * 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 void createChirp(void *vbuffer, size_t frames,
+ size_t channels, double sampleRate, double minfreq, double maxfreq)
+{
+ double tscale = 1. / sampleRate;
+ T *buffer = reinterpret_cast<T*>(vbuffer);
+ // note the chirp constant k has a divide-by-two.
+ double k = (maxfreq - minfreq) / (2. * tscale * frames);
+ 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);
+ }
+ }
+}
+
+/* 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().
+ */
+
+class SignalProvider : public TestProvider {
+public:
+ SignalProvider()
+ : mSampleRate(0),
+ mChannels(0)
+ {
+ }
+
+ virtual ~SignalProvider()
+ {
+ free(mAddr);
+ mAddr = NULL;
+ }
+
+ template <typename T>
+ void setChirp(size_t channels, double minfreq, double maxfreq, double sampleRate, double time)
+ {
+ createBufferByFrames<T>(channels, sampleRate, sampleRate*time);
+ createChirp<T>(mAddr, mNumFrames, mChannels, mSampleRate, minfreq, maxfreq);
+ }
+
+ template <typename T>
+ void setSine(size_t channels,
+ double freq, double sampleRate, double time)
+ {
+ createBufferByFrames<T>(channels, sampleRate, sampleRate*time);
+ createSine<T>(mAddr, mNumFrames, mChannels, mSampleRate, freq);
+ }
+
+ template <typename T>
+ void setFile(const char *file_in)
+ {
+ SF_INFO info;
+ info.format = 0;
+ SNDFILE *sf = sf_open(file_in, SFM_READ, &info);
+ if (sf == NULL) {
+ perror(file_in);
+ return;
+ }
+ createBufferByFrames<T>(info.channels, info.samplerate, info.frames);
+ if (is_same<T, float>::value) {
+ (void) sf_readf_float(sf, (float *) mAddr, mNumFrames);
+ } else if (is_same<T, short>::value) {
+ (void) sf_readf_short(sf, (short *) mAddr, mNumFrames);
+ }
+ sf_close(sf);
+ }
+
+ template <typename T>
+ void createBufferByFrames(size_t channels, uint32_t sampleRate, size_t frames)
+ {
+ mNumFrames = frames;
+ mChannels = channels;
+ mFrameSize = mChannels * sizeof(T);
+ free(mAddr);
+ mAddr = malloc(mFrameSize * mNumFrames);
+ mSampleRate = sampleRate;
+ }
+
+ uint32_t getSampleRate() const {
+ return mSampleRate;
+ }
+
+ uint32_t getNumChannels() const {
+ return mChannels;
+ }
+
+protected:
+ uint32_t mSampleRate;
+ uint32_t mChannels;
+};
+
+#endif // ANDROID_AUDIO_TEST_UTILS_H