diff options
author | Andy Hung <hunga@google.com> | 2014-06-23 16:44:53 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-06-23 14:51:00 +0000 |
commit | eaf07697a9146d27ca863db5c43da64af4f8c94a (patch) | |
tree | 90fc5545cfabb80f800709a2b78840926389378b /services/audioflinger | |
parent | cd8fce8fbe0f515e53a0ea0f50b739cf42ee57a4 (diff) | |
parent | e4fc42359cdd9786e521054a3a0491d6bc3a9e1c (diff) | |
download | frameworks_av-eaf07697a9146d27ca863db5c43da64af4f8c94a.zip frameworks_av-eaf07697a9146d27ca863db5c43da64af4f8c94a.tar.gz frameworks_av-eaf07697a9146d27ca863db5c43da64af4f8c94a.tar.bz2 |
Merge "Add test-mixer AudioMixer test program"
Diffstat (limited to 'services/audioflinger')
-rw-r--r-- | services/audioflinger/tests/Android.mk | 38 | ||||
-rwxr-xr-x | services/audioflinger/tests/mixer_to_wav_tests.sh | 134 | ||||
-rw-r--r-- | services/audioflinger/tests/test-mixer.cpp | 286 |
3 files changed, 458 insertions, 0 deletions
diff --git a/services/audioflinger/tests/Android.mk b/services/audioflinger/tests/Android.mk index f365637..7bba05b 100644 --- a/services/audioflinger/tests/Android.mk +++ b/services/audioflinger/tests/Android.mk @@ -33,3 +33,41 @@ 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/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/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; +} |