xref: /aosp_15_r20/external/tinyalsa_new/tests/src/pcm_loopback_test.cc (revision 02e95f1a335b55495d41ca67eaf42361f13704fa)
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