resampler_tests.cpp revision 5e58b0abe5b6c8f5bd96a8f78bbeeeb4d3892020
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//#define LOG_NDEBUG 0
18#define LOG_TAG "audioflinger_resampler_tests"
19
20#include <unistd.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <fcntl.h>
24#include <string.h>
25#include <sys/mman.h>
26#include <sys/stat.h>
27#include <errno.h>
28#include <time.h>
29#include <math.h>
30#include <vector>
31#include <utility>
32#include <cutils/log.h>
33#include <gtest/gtest.h>
34#include <media/AudioBufferProvider.h>
35#include "AudioResampler.h"
36#include "test_utils.h"
37
38void resample(int channels, void *output,
39        size_t outputFrames, const std::vector<size_t> &outputIncr,
40        android::AudioBufferProvider *provider, android::AudioResampler *resampler)
41{
42    for (size_t i = 0, j = 0; i < outputFrames; ) {
43        size_t thisFrames = outputIncr[j++];
44        if (j >= outputIncr.size()) {
45            j = 0;
46        }
47        if (thisFrames == 0 || thisFrames > outputFrames - i) {
48            thisFrames = outputFrames - i;
49        }
50        resampler->resample((int32_t*) output + channels*i, thisFrames, provider);
51        i += thisFrames;
52    }
53}
54
55void buffercmp(const void *reference, const void *test,
56        size_t outputFrameSize, size_t outputFrames)
57{
58    for (size_t i = 0; i < outputFrames; ++i) {
59        int check = memcmp((const char*)reference + i * outputFrameSize,
60                (const char*)test + i * outputFrameSize, outputFrameSize);
61        if (check) {
62            ALOGE("Failure at frame %d", i);
63            ASSERT_EQ(check, 0); /* fails */
64        }
65    }
66}
67
68void testBufferIncrement(size_t channels, bool useFloat,
69        unsigned inputFreq, unsigned outputFreq,
70        enum android::AudioResampler::src_quality quality)
71{
72    const audio_format_t format = useFloat ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
73    // create the provider
74    std::vector<int> inputIncr;
75    SignalProvider provider;
76    if (useFloat) {
77        provider.setChirp<float>(channels,
78                0., outputFreq/2., outputFreq, outputFreq/2000.);
79    } else {
80        provider.setChirp<int16_t>(channels,
81                0., outputFreq/2., outputFreq, outputFreq/2000.);
82    }
83    provider.setIncr(inputIncr);
84
85    // calculate the output size
86    size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq;
87    size_t outputFrameSize = channels * (useFloat ? sizeof(float) : sizeof(int32_t));
88    size_t outputSize = outputFrameSize * outputFrames;
89    outputSize &= ~7;
90
91    // create the resampler
92    android::AudioResampler* resampler;
93
94    resampler = android::AudioResampler::create(format, channels, outputFreq, quality);
95    resampler->setSampleRate(inputFreq);
96    resampler->setVolume(android::AudioResampler::UNITY_GAIN_FLOAT,
97            android::AudioResampler::UNITY_GAIN_FLOAT);
98
99    // set up the reference run
100    std::vector<size_t> refIncr;
101    refIncr.push_back(outputFrames);
102    void* reference = malloc(outputSize);
103    resample(channels, reference, outputFrames, refIncr, &provider, resampler);
104
105    provider.reset();
106
107#if 0
108    /* this test will fail - API interface issue: reset() does not clear internal buffers */
109    resampler->reset();
110#else
111    delete resampler;
112    resampler = android::AudioResampler::create(format, channels, outputFreq, quality);
113    resampler->setSampleRate(inputFreq);
114    resampler->setVolume(android::AudioResampler::UNITY_GAIN_FLOAT,
115            android::AudioResampler::UNITY_GAIN_FLOAT);
116#endif
117
118    // set up the test run
119    std::vector<size_t> outIncr;
120    outIncr.push_back(1);
121    outIncr.push_back(2);
122    outIncr.push_back(3);
123    void* test = malloc(outputSize);
124    inputIncr.push_back(1);
125    inputIncr.push_back(3);
126    provider.setIncr(inputIncr);
127    resample(channels, test, outputFrames, outIncr, &provider, resampler);
128
129    // check
130    buffercmp(reference, test, outputFrameSize, outputFrames);
131
132    free(reference);
133    free(test);
134    delete resampler;
135}
136
137template <typename T>
138inline double sqr(T v)
139{
140    double dv = static_cast<double>(v);
141    return dv * dv;
142}
143
144template <typename T>
145double signalEnergy(T *start, T *end, unsigned stride)
146{
147    double accum = 0;
148
149    for (T *p = start; p < end; p += stride) {
150        accum += sqr(*p);
151    }
152    unsigned count = (end - start + stride - 1) / stride;
153    return accum / count;
154}
155
156void testStopbandDownconversion(size_t channels,
157        unsigned inputFreq, unsigned outputFreq,
158        unsigned passband, unsigned stopband,
159        enum android::AudioResampler::src_quality quality)
160{
161    // create the provider
162    std::vector<int> inputIncr;
163    SignalProvider provider;
164    provider.setChirp<int16_t>(channels,
165            0., inputFreq/2., inputFreq, inputFreq/2000.);
166    provider.setIncr(inputIncr);
167
168    // calculate the output size
169    size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq;
170    size_t outputFrameSize = channels * sizeof(int32_t);
171    size_t outputSize = outputFrameSize * outputFrames;
172    outputSize &= ~7;
173
174    // create the resampler
175    android::AudioResampler* resampler;
176
177    resampler = android::AudioResampler::create(AUDIO_FORMAT_PCM_16_BIT,
178            channels, outputFreq, quality);
179    resampler->setSampleRate(inputFreq);
180    resampler->setVolume(android::AudioResampler::UNITY_GAIN_FLOAT,
181            android::AudioResampler::UNITY_GAIN_FLOAT);
182
183    // set up the reference run
184    std::vector<size_t> refIncr;
185    refIncr.push_back(outputFrames);
186    void* reference = malloc(outputSize);
187    resample(channels, reference, outputFrames, refIncr, &provider, resampler);
188
189    int32_t *out = reinterpret_cast<int32_t *>(reference);
190
191    // check signal energy in passband
192    const unsigned passbandFrame = passband * outputFreq / 1000.;
193    const unsigned stopbandFrame = stopband * outputFreq / 1000.;
194
195    // check each channel separately
196    for (size_t i = 0; i < channels; ++i) {
197        double passbandEnergy = signalEnergy(out, out + passbandFrame * channels, channels);
198        double stopbandEnergy = signalEnergy(out + stopbandFrame * channels,
199                out + outputFrames * channels, channels);
200        double dbAtten = -10. * log10(stopbandEnergy / passbandEnergy);
201        ASSERT_GT(dbAtten, 60.);
202
203#if 0
204        // internal verification
205        printf("if:%d  of:%d  pbf:%d  sbf:%d  sbe: %f  pbe: %f  db: %.2f\n",
206                provider.getNumFrames(), outputFrames,
207                passbandFrame, stopbandFrame, stopbandEnergy, passbandEnergy, dbAtten);
208        for (size_t i = 0; i < 10; ++i) {
209            printf("%d\n", out[i+passbandFrame*channels]);
210        }
211        for (size_t i = 0; i < 10; ++i) {
212            printf("%d\n", out[i+stopbandFrame*channels]);
213        }
214#endif
215    }
216
217    free(reference);
218    delete resampler;
219}
220
221/* Buffer increment test
222 *
223 * We compare a reference output, where we consume and process the entire
224 * buffer at a time, and a test output, where we provide small chunks of input
225 * data and process small chunks of output (which may not be equivalent in size).
226 *
227 * Two subtests - fixed phase (3:2 down) and interpolated phase (147:320 up)
228 */
229TEST(audioflinger_resampler, bufferincrement_fixedphase) {
230    // all of these work
231    static const enum android::AudioResampler::src_quality kQualityArray[] = {
232            android::AudioResampler::LOW_QUALITY,
233            android::AudioResampler::MED_QUALITY,
234            android::AudioResampler::HIGH_QUALITY,
235            android::AudioResampler::VERY_HIGH_QUALITY,
236            android::AudioResampler::DYN_LOW_QUALITY,
237            android::AudioResampler::DYN_MED_QUALITY,
238            android::AudioResampler::DYN_HIGH_QUALITY,
239    };
240
241    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
242        testBufferIncrement(2, false, 48000, 32000, kQualityArray[i]);
243    }
244}
245
246TEST(audioflinger_resampler, bufferincrement_interpolatedphase) {
247    // all of these work except low quality
248    static const enum android::AudioResampler::src_quality kQualityArray[] = {
249//           android::AudioResampler::LOW_QUALITY,
250            android::AudioResampler::MED_QUALITY,
251            android::AudioResampler::HIGH_QUALITY,
252            android::AudioResampler::VERY_HIGH_QUALITY,
253            android::AudioResampler::DYN_LOW_QUALITY,
254            android::AudioResampler::DYN_MED_QUALITY,
255            android::AudioResampler::DYN_HIGH_QUALITY,
256    };
257
258    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
259        testBufferIncrement(2, false, 22050, 48000, kQualityArray[i]);
260    }
261}
262
263TEST(audioflinger_resampler, bufferincrement_fixedphase_multi) {
264    // only dynamic quality
265    static const enum android::AudioResampler::src_quality kQualityArray[] = {
266            android::AudioResampler::DYN_LOW_QUALITY,
267            android::AudioResampler::DYN_MED_QUALITY,
268            android::AudioResampler::DYN_HIGH_QUALITY,
269    };
270
271    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
272        testBufferIncrement(4, false, 48000, 32000, kQualityArray[i]);
273    }
274}
275
276TEST(audioflinger_resampler, bufferincrement_interpolatedphase_multi_float) {
277    // only dynamic quality
278    static const enum android::AudioResampler::src_quality kQualityArray[] = {
279            android::AudioResampler::DYN_LOW_QUALITY,
280            android::AudioResampler::DYN_MED_QUALITY,
281            android::AudioResampler::DYN_HIGH_QUALITY,
282    };
283
284    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
285        testBufferIncrement(8, true, 22050, 48000, kQualityArray[i]);
286    }
287}
288
289/* Simple aliasing test
290 *
291 * This checks stopband response of the chirp signal to make sure frequencies
292 * are properly suppressed.  It uses downsampling because the stopband can be
293 * clearly isolated by input frequencies exceeding the output sample rate (nyquist).
294 */
295TEST(audioflinger_resampler, stopbandresponse) {
296    // not all of these may work (old resamplers fail on downsampling)
297    static const enum android::AudioResampler::src_quality kQualityArray[] = {
298            //android::AudioResampler::LOW_QUALITY,
299            //android::AudioResampler::MED_QUALITY,
300            //android::AudioResampler::HIGH_QUALITY,
301            //android::AudioResampler::VERY_HIGH_QUALITY,
302            android::AudioResampler::DYN_LOW_QUALITY,
303            android::AudioResampler::DYN_MED_QUALITY,
304            android::AudioResampler::DYN_HIGH_QUALITY,
305    };
306
307    // in this test we assume a maximum transition band between 12kHz and 20kHz.
308    // there must be at least 60dB relative attenuation between stopband and passband.
309    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
310        testStopbandDownconversion(2, 48000, 32000, 12000, 20000, kQualityArray[i]);
311    }
312
313    // in this test we assume a maximum transition band between 7kHz and 15kHz.
314    // there must be at least 60dB relative attenuation between stopband and passband.
315    // (the weird ratio triggers interpolative resampling)
316    for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) {
317        testStopbandDownconversion(2, 48000, 22101, 7000, 15000, kQualityArray[i]);
318    }
319}
320