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

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

Merge "Add test-mixer AudioMixer test program"

parents cd8fce8f e4fc4235
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -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)
+134 −0
Original line number Diff line number Diff line
#!/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
+286 −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.
 */

#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;
}