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