1#!/usr/bin/env python3 2# 3# Copyright 2023 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import ctypes 19import numpy as np 20from scipy import signal 21from mobly import test_runner, base_test 22from mobly.asserts import assert_greater 23import sys 24import os 25 26 27class CResampler: 28 29 def __init__(self, lib, channels, bitdepth): 30 31 self.lib = lib 32 self.channels = channels 33 self.bitdepth = bitdepth 34 35 def resample(self, xs, ratio): 36 37 c_int = ctypes.c_int 38 c_size_t = ctypes.c_size_t 39 c_double = ctypes.c_double 40 c_int16_p = ctypes.POINTER(ctypes.c_int16) 41 c_int32_p = ctypes.POINTER(ctypes.c_int32) 42 43 channels = self.channels 44 bitdepth = self.bitdepth 45 46 xs_min = -(2**(bitdepth - 1)) 47 xs_max = (2**(bitdepth - 1) - 1) 48 xs_int = np.rint(np.clip(np.ldexp(xs, bitdepth-1), xs_min, xs_max)).\ 49 astype([np.int16, np.int32][bitdepth > 16], 'C') 50 51 ys_int = np.empty(int(np.ceil(len(xs) / ratio)), dtype=xs_int.dtype) 52 53 if bitdepth <= 16: 54 lib.resample_i16(c_int(channels), c_int(bitdepth), c_double(ratio), xs_int.ctypes.data_as(c_int16_p), 55 c_size_t(len(xs_int)), ys_int.ctypes.data_as(c_int16_p), c_size_t(len(ys_int))) 56 else: 57 lib.resample_i32(c_int(channels), c_int(bitdepth), c_double(ratio), xs_int.ctypes.data_as(c_int32_p), 58 c_size_t(len(xs_int)), ys_int.ctypes.data_as(c_int32_p), c_size_t(len(ys_int))) 59 60 return np.ldexp(ys_int, 1 - bitdepth) 61 62 63FS = 48e3 64 65 66def snr(x, fs=FS): 67 68 f, p = signal.periodogram(x, fs=fs, scaling='spectrum', window=('kaiser', 38)) 69 70 k = np.argmax(p) 71 s = np.sum(p[k - 19:k + 20]) 72 n = np.sum(p[20:k - 19]) + np.sum(p[k + 20:]) 73 74 return 10 * np.log10(s / n) 75 76 77def mean_snr(resampler, ratio): 78 N = 8192 79 xt = np.arange(2 * N + 128) / FS 80 81 frequencies = [] 82 values = [] 83 84 for f in range(200, 20000, 99): 85 xs = np.sin(2 * np.pi * xt * f) 86 87 frequencies += [f] 88 result = resampler.resample(xs, ratio) 89 values += [snr(result[128:128 + N])] 90 91 k = np.argmin(np.abs(np.array(frequencies) - 18e3)) 92 return np.mean(values[:k]) 93 94 95root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 96lib = ctypes.cdll.LoadLibrary(os.path.join(root, "libasrc_resampler_test.so")) 97 98cresampler_16 = CResampler(lib, 1, 16) 99cresampler_24 = CResampler(lib, 1, 24) 100 101 102class SnrTest(base_test.BaseTestClass): 103 104 def test_16bit_48000_to_44100(self): 105 assert_greater(mean_snr(cresampler_16, 44.1 / 48.0), 94) 106 107 def test_16bit_44100_to_48000(self): 108 assert_greater(mean_snr(cresampler_16, 48.0 / 44.1), 94) 109 110 def test_24bit_48000_to_44100(self): 111 assert_greater(mean_snr(cresampler_24, 44.1 / 48.0), 114) 112 113 def test_24bit_44100_to_48000(self): 114 assert_greater(mean_snr(cresampler_24, 48.0 / 44.1), 114) 115 116 117if __name__ == '__main__': 118 index = sys.argv.index('--') 119 sys.argv = sys.argv[:1] + sys.argv[index + 1:] 120 test_runner.main() 121