1 /* pcm_loopback_test.c
2 **
3 ** Copyright 2020, The Android Open Source Project
4 **
5 ** Redistribution and use in source and binary forms, with or without
6 ** modification, are permitted provided that the following conditions are met:
7 ** * Redistributions of source code must retain the above copyright
8 ** notice, this list of conditions and the following disclaimer.
9 ** * Redistributions in binary form must reproduce the above copyright
10 ** notice, this list of conditions and the following disclaimer in the
11 ** documentation and/or other materials provided with the distribution.
12 ** * Neither the name of The Android Open Source Project nor the names of
13 ** its contributors may be used to endorse or promote products derived
14 ** from this software without specific prior written permission.
15 **
16 ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
17 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
20 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26 ** DAMAGE.
27 */
28 #include "pcm_test_device.h"
29
30 #include <chrono>
31 #include <cmath>
32 #include <cstring>
33 #include <iostream>
34 #include <thread>
35
36 #include <gtest/gtest.h>
37
38 #include "tinyalsa/pcm.h"
39
40 namespace tinyalsa {
41 namespace testing {
42
43 template<int32_t CH, int32_t SR, pcm_format F>
44 class SilenceGenerator {
45 public:
GetFormat()46 pcm_format GetFormat() {
47 return F;
48 }
49
GetChannels()50 int32_t GetChannels() {
51 return CH;
52 };
53
GetSamplingRate()54 int32_t GetSamplingRate() {
55 return SR;
56 };
57
Read(void * buffer,int32_t size)58 virtual int32_t Read(void *buffer, int32_t size) {
59 std::memset(buffer, 0, size);
60 return size;
61 }
62 };
63
64 template<pcm_format F>
65 struct PcmFormat {
66 using Type = void;
67 static constexpr pcm_format kFormat = F;
68 static constexpr int32_t kMax = 0;
69 static constexpr int32_t kMin = 0;
70 };
71
72 template<>
73 struct PcmFormat<PCM_FORMAT_S16_LE> {
74 using Type = int16_t;
75 static constexpr pcm_format kFormat = PCM_FORMAT_S16_LE;
76 static constexpr Type kMax = std::numeric_limits<Type>::max();
77 static constexpr Type kMin = std::numeric_limits<Type>::min();
78 };
79
80 template<>
81 struct PcmFormat<PCM_FORMAT_FLOAT_LE> {
82 using Type = float;
83 static constexpr pcm_format kFormat = PCM_FORMAT_FLOAT_LE;
84 static constexpr Type kMax = 1.0;
85 static constexpr Type kMin = -1.0;
86 };
87
88 // CH: channels
89 // SR: sampling rate
90 // FQ: sine wave frequency
91 // L: max level
92 template<int32_t CH, int32_t SR, int32_t FQ, int32_t L, pcm_format F>
93 class SineToneGenerator : public SilenceGenerator<CH, SR, F> {
94 private:
95 using Type = typename PcmFormat<F>::Type;
96 static constexpr double kPi = M_PI;
97 static constexpr double kStep = FQ * CH * kPi / SR;
98
99 double channels[CH];
100 double gain;
101
GetSample(double radian)102 Type GetSample(double radian) {
103 double sine = std::sin(radian) * gain;
104 if (sine >= 1.0) {
105 return PcmFormat<F>::kMax;
106 } else if (sine <= -1.0) {
107 return PcmFormat<F>::kMin;
108 }
109 return static_cast<Type>(sine * PcmFormat<F>::kMax);
110 }
111
112 public:
SineToneGenerator()113 SineToneGenerator() {
114 constexpr double phase = (CH == 1) ? 0 : kPi / 2 / (CH - 1);
115
116 channels[0] = 0.0;
117 for (int32_t i = 1; i < CH; ++i) {
118 channels[i] = channels[i - 1] + phase;
119 }
120
121 gain = std::pow(M_E, std::log(10) * static_cast<double>(L) / 20.0);
122 }
123
124 ~SineToneGenerator() = default;
125
Read(void * buffer,int32_t size)126 int32_t Read(void *buffer, int32_t size) override {
127 Type *pcm_buffer = reinterpret_cast<Type *>(buffer);
128
129 size = (size / (CH * sizeof(Type))) * (CH * sizeof(Type));
130 int32_t samples = size / sizeof(Type);
131 int32_t s = 0;
132
133 while (s < samples) {
134 for (int32_t i = 0; i < CH; ++i) {
135 pcm_buffer[s++] = GetSample(channels[i]);
136 channels[i] += kStep;
137 }
138 }
139 return size;
140 }
141 };
142
143 template<typename T>
Energy(T * buffer,size_t samples)144 static double Energy(T *buffer, size_t samples) {
145 double sum = 0.0;
146 for (size_t i = 0; i < samples; i++) {
147 sum += static_cast<double>(buffer[i]) * static_cast<double>(buffer[i]);
148 }
149 return sum;
150 }
151
152 template<typename F>
153 class PcmLoopbackTest : public ::testing::Test {
154 protected:
155 PcmLoopbackTest() = default;
156 virtual ~PcmLoopbackTest() = default;
157
SetUp()158 void SetUp() override {
159 static constexpr pcm_config kInConfig = {
160 .channels = kDefaultChannels,
161 .rate = kDefaultSamplingRate,
162 .period_size = kDefaultPeriodSize,
163 .period_count = kDefaultPeriodCount,
164 .format = kPcmForamt,
165 .start_threshold = 0,
166 .stop_threshold = 0,
167 .silence_threshold = 0,
168 .silence_size = 0,
169 };
170 pcm_in = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kInConfig);
171 ASSERT_TRUE(pcm_is_ready(pcm_in));
172
173 static constexpr pcm_config kOutConfig = {
174 .channels = kDefaultChannels,
175 .rate = kDefaultSamplingRate,
176 .period_size = kDefaultPeriodSize,
177 .period_count = kDefaultPeriodCount,
178 .format = kPcmForamt,
179 .start_threshold = kDefaultPeriodSize,
180 .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
181 .silence_threshold = 0,
182 .silence_size = 0,
183 };
184 pcm_out = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kOutConfig);
185 ASSERT_TRUE(pcm_is_ready(pcm_out));
186 ASSERT_EQ(pcm_link(pcm_in, pcm_out), 0);
187 }
188
TearDown()189 void TearDown() override {
190 ASSERT_EQ(pcm_unlink(pcm_in), 0);
191 pcm_close(pcm_in);
192 pcm_close(pcm_out);
193 std::this_thread::sleep_for(std::chrono::milliseconds(100));
194 }
195
196 static constexpr unsigned int kDefaultPeriodTimeInMs =
197 kDefaultPeriodSize * 1000 / kDefaultSamplingRate;
198 static constexpr pcm_format kPcmForamt = F::kFormat;
199 pcm *pcm_in;
200 pcm *pcm_out;
201 };
202
203 using S16bitlePcmFormat = PcmFormat<PCM_FORMAT_S16_LE>;
204 using FloatPcmFormat = PcmFormat<PCM_FORMAT_FLOAT_LE>;
205
206 using Formats = ::testing::Types<S16bitlePcmFormat, FloatPcmFormat>;
207
208 TYPED_TEST_SUITE(PcmLoopbackTest, Formats);
209
TYPED_TEST(PcmLoopbackTest,Loopback)210 TYPED_TEST(PcmLoopbackTest, Loopback) {
211 static constexpr unsigned int kDefaultPeriodTimeInMs = this->kDefaultPeriodTimeInMs;
212 static constexpr pcm_format kPcmForamt = this->kPcmForamt;
213 pcm *pcm_in = this->pcm_in;
214 pcm *pcm_out = this->pcm_out;
215
216 bool stopping = false;
217 ASSERT_EQ(pcm_get_subdevice(pcm_in), pcm_get_subdevice(pcm_out));
218
219 std::thread capture([pcm_in, &stopping] {
220 size_t buffer_size = pcm_frames_to_bytes(pcm_in, kDefaultPeriodSize);
221 unsigned int frames = pcm_bytes_to_frames(pcm_in, buffer_size);
222 auto buffer = std::make_unique<unsigned char[]>(buffer_size);
223 int32_t counter = 0;
224 while (!stopping) {
225 int res = pcm_readi(pcm_in, buffer.get(), frames);
226 if (res == -1) {
227 std::cout << pcm_get_error(pcm_in) << std::endl;
228 std::this_thread::sleep_for(std::chrono::milliseconds(kDefaultPeriodTimeInMs));
229 counter++;
230 continue;
231 }
232
233 // Test the energy of the buffer after the sine tone samples fill in the buffer.
234 // Therefore, check the buffer 5 times later.
235 if (counter >= 5) {
236 double e = Energy(buffer.get(), frames * kDefaultChannels);
237 EXPECT_GT(e, 0.0) << counter;
238 }
239 counter++;
240 }
241 std::cout << "read count = " << counter << std::endl;
242 });
243
244 std::thread playback([pcm_out, &stopping] {
245 SineToneGenerator<kDefaultChannels, kDefaultSamplingRate, 1000, 0, kPcmForamt> generator;
246 size_t buffer_size = pcm_frames_to_bytes(pcm_out, kDefaultPeriodSize);
247 unsigned int frames = pcm_bytes_to_frames(pcm_out, buffer_size);
248 std::cout << buffer_size << std::endl;
249 auto buffer = std::make_unique<unsigned char[]>(buffer_size);
250 int32_t counter = 0;
251 while (!stopping) {
252 generator.Read(buffer.get(), buffer_size);
253 EXPECT_EQ(pcm_writei(pcm_out, buffer.get(), frames), frames) << counter;
254 counter++;
255 }
256 std::cout << "write count = " << counter << std::endl;
257 });
258
259 std::this_thread::sleep_for(std::chrono::milliseconds(500));
260 stopping = true;
261 capture.join();
262 playback.join();
263 }
264
265 } // namespace testing
266 } // namespace tinyalsa
267