1 /*
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 // MSVC++ requires this to be set before any other includes to get M_PI.
12 #define _USE_MATH_DEFINES
13
14 #include "modules/audio_processing/beamformer/nonlinear_beamformer.h"
15
16 #include <math.h>
17
18 #include "api/array_view.h"
19 #include "modules/audio_processing/audio_buffer.h"
20 #include "modules/audio_processing/test/audio_buffer_tools.h"
21 #include "modules/audio_processing/test/bitexactness_tools.h"
22 #include "test/gtest.h"
23
24 namespace webrtc {
25 namespace {
26
27 const int kChunkSizeMs = 10;
28 const int kSampleRateHz = 16000;
29
AzimuthToSphericalPoint(float azimuth_radians)30 SphericalPointf AzimuthToSphericalPoint(float azimuth_radians) {
31 return SphericalPointf(azimuth_radians, 0.f, 1.f);
32 }
33
Verify(NonlinearBeamformer * bf,float target_azimuth_radians)34 void Verify(NonlinearBeamformer* bf, float target_azimuth_radians) {
35 EXPECT_TRUE(bf->IsInBeam(AzimuthToSphericalPoint(target_azimuth_radians)));
36 EXPECT_TRUE(bf->IsInBeam(AzimuthToSphericalPoint(
37 target_azimuth_radians - NonlinearBeamformer::kHalfBeamWidthRadians +
38 0.001f)));
39 EXPECT_TRUE(bf->IsInBeam(AzimuthToSphericalPoint(
40 target_azimuth_radians + NonlinearBeamformer::kHalfBeamWidthRadians -
41 0.001f)));
42 EXPECT_FALSE(bf->IsInBeam(AzimuthToSphericalPoint(
43 target_azimuth_radians - NonlinearBeamformer::kHalfBeamWidthRadians -
44 0.001f)));
45 EXPECT_FALSE(bf->IsInBeam(AzimuthToSphericalPoint(
46 target_azimuth_radians + NonlinearBeamformer::kHalfBeamWidthRadians +
47 0.001f)));
48 }
49
AimAndVerify(NonlinearBeamformer * bf,float target_azimuth_radians)50 void AimAndVerify(NonlinearBeamformer* bf, float target_azimuth_radians) {
51 bf->AimAt(AzimuthToSphericalPoint(target_azimuth_radians));
52 Verify(bf, target_azimuth_radians);
53 }
54
55 // Bitexactness test code.
56 const size_t kNumFramesToProcess = 1000;
57
ProcessOneFrame(int sample_rate_hz,AudioBuffer * capture_audio_buffer,NonlinearBeamformer * beamformer)58 void ProcessOneFrame(int sample_rate_hz,
59 AudioBuffer* capture_audio_buffer,
60 NonlinearBeamformer* beamformer) {
61 if (sample_rate_hz > AudioProcessing::kSampleRate16kHz) {
62 capture_audio_buffer->SplitIntoFrequencyBands();
63 }
64
65 beamformer->AnalyzeChunk(*capture_audio_buffer->split_data_f());
66 capture_audio_buffer->set_num_channels(1);
67 beamformer->PostFilter(capture_audio_buffer->split_data_f());
68
69 if (sample_rate_hz > AudioProcessing::kSampleRate16kHz) {
70 capture_audio_buffer->MergeFrequencyBands();
71 }
72 }
73
BeamformerSampleRate(int sample_rate_hz)74 int BeamformerSampleRate(int sample_rate_hz) {
75 return (sample_rate_hz > AudioProcessing::kSampleRate16kHz
76 ? AudioProcessing::kSampleRate16kHz
77 : sample_rate_hz);
78 }
79
RunBitExactnessTest(int sample_rate_hz,const std::vector<Point> & array_geometry,const SphericalPointf & target_direction,rtc::ArrayView<const float> output_reference)80 void RunBitExactnessTest(int sample_rate_hz,
81 const std::vector<Point>& array_geometry,
82 const SphericalPointf& target_direction,
83 rtc::ArrayView<const float> output_reference) {
84 NonlinearBeamformer beamformer(array_geometry, 1u, target_direction);
85 beamformer.Initialize(AudioProcessing::kChunkSizeMs,
86 BeamformerSampleRate(sample_rate_hz));
87
88 const StreamConfig capture_config(sample_rate_hz, array_geometry.size(),
89 false);
90 AudioBuffer capture_buffer(
91 capture_config.num_frames(), capture_config.num_channels(),
92 capture_config.num_frames(), capture_config.num_channels(),
93 capture_config.num_frames());
94 test::InputAudioFile capture_file(
95 test::GetApmCaptureTestVectorFileName(sample_rate_hz));
96 std::vector<float> capture_input(capture_config.num_frames() *
97 capture_config.num_channels());
98 for (size_t frame_no = 0u; frame_no < kNumFramesToProcess; ++frame_no) {
99 ReadFloatSamplesFromStereoFile(capture_config.num_frames(),
100 capture_config.num_channels(), &capture_file,
101 capture_input);
102
103 test::CopyVectorToAudioBuffer(capture_config, capture_input,
104 &capture_buffer);
105
106 ProcessOneFrame(sample_rate_hz, &capture_buffer, &beamformer);
107 }
108
109 // Extract and verify the test results.
110 std::vector<float> capture_output;
111 test::ExtractVectorFromAudioBuffer(capture_config, &capture_buffer,
112 &capture_output);
113
114 const float kElementErrorBound = 1.f / static_cast<float>(1 << 15);
115
116 // Compare the output with the reference. Only the first values of the output
117 // from last frame processed are compared in order not having to specify all
118 // preceeding frames as testvectors. As the algorithm being tested has a
119 // memory, testing only the last frame implicitly also tests the preceeding
120 // frames.
121 EXPECT_TRUE(test::VerifyDeinterleavedArray(
122 capture_config.num_frames(), capture_config.num_channels(),
123 output_reference, capture_output, kElementErrorBound));
124 }
125
126 // TODO(peah): Add bitexactness tests for scenarios with more than 2 input
127 // channels.
CreateArrayGeometry(int variant)128 std::vector<Point> CreateArrayGeometry(int variant) {
129 std::vector<Point> array_geometry;
130 switch (variant) {
131 case 1:
132 array_geometry.push_back(Point(-0.025f, 0.f, 0.f));
133 array_geometry.push_back(Point(0.025f, 0.f, 0.f));
134 break;
135 case 2:
136 array_geometry.push_back(Point(-0.035f, 0.f, 0.f));
137 array_geometry.push_back(Point(0.035f, 0.f, 0.f));
138 break;
139 case 3:
140 array_geometry.push_back(Point(-0.5f, 0.f, 0.f));
141 array_geometry.push_back(Point(0.5f, 0.f, 0.f));
142 break;
143 default:
144 RTC_CHECK(false);
145 }
146 return array_geometry;
147 }
148
149 const SphericalPointf TargetDirection1(0.4f * static_cast<float>(M_PI) / 2.f,
150 0.f,
151 1.f);
152 const SphericalPointf TargetDirection2(static_cast<float>(M_PI) / 2.f,
153 1.f,
154 2.f);
155
156 } // namespace
157
TEST(NonlinearBeamformerTest,AimingModifiesBeam)158 TEST(NonlinearBeamformerTest, AimingModifiesBeam) {
159 std::vector<Point> array_geometry;
160 array_geometry.push_back(Point(-0.025f, 0.f, 0.f));
161 array_geometry.push_back(Point(0.025f, 0.f, 0.f));
162 NonlinearBeamformer bf(array_geometry, 1u);
163 bf.Initialize(kChunkSizeMs, kSampleRateHz);
164 // The default constructor parameter sets the target angle to PI / 2.
165 Verify(&bf, static_cast<float>(M_PI) / 2.f);
166 AimAndVerify(&bf, static_cast<float>(M_PI) / 3.f);
167 AimAndVerify(&bf, 3.f * static_cast<float>(M_PI) / 4.f);
168 AimAndVerify(&bf, static_cast<float>(M_PI) / 6.f);
169 AimAndVerify(&bf, static_cast<float>(M_PI));
170 }
171
TEST(NonlinearBeamformerTest,InterfAnglesTakeAmbiguityIntoAccount)172 TEST(NonlinearBeamformerTest, InterfAnglesTakeAmbiguityIntoAccount) {
173 {
174 // For linear arrays there is ambiguity.
175 std::vector<Point> array_geometry;
176 array_geometry.push_back(Point(-0.1f, 0.f, 0.f));
177 array_geometry.push_back(Point(0.f, 0.f, 0.f));
178 array_geometry.push_back(Point(0.2f, 0.f, 0.f));
179 NonlinearBeamformer bf(array_geometry, 1u);
180 bf.Initialize(kChunkSizeMs, kSampleRateHz);
181 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
182 EXPECT_FLOAT_EQ(M_PI / 2.f - bf.away_radians_,
183 bf.interf_angles_radians_[0]);
184 EXPECT_FLOAT_EQ(M_PI / 2.f + bf.away_radians_,
185 bf.interf_angles_radians_[1]);
186 bf.AimAt(AzimuthToSphericalPoint(bf.away_radians_ / 2.f));
187 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
188 EXPECT_FLOAT_EQ(M_PI - bf.away_radians_ / 2.f,
189 bf.interf_angles_radians_[0]);
190 EXPECT_FLOAT_EQ(3.f * bf.away_radians_ / 2.f, bf.interf_angles_radians_[1]);
191 }
192 {
193 // For planar arrays with normal in the xy-plane there is ambiguity.
194 std::vector<Point> array_geometry;
195 array_geometry.push_back(Point(-0.1f, 0.f, 0.f));
196 array_geometry.push_back(Point(0.f, 0.f, 0.f));
197 array_geometry.push_back(Point(0.2f, 0.f, 0.f));
198 array_geometry.push_back(Point(0.1f, 0.f, 0.2f));
199 array_geometry.push_back(Point(0.f, 0.f, -0.1f));
200 NonlinearBeamformer bf(array_geometry, 1u);
201 bf.Initialize(kChunkSizeMs, kSampleRateHz);
202 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
203 EXPECT_FLOAT_EQ(M_PI / 2.f - bf.away_radians_,
204 bf.interf_angles_radians_[0]);
205 EXPECT_FLOAT_EQ(M_PI / 2.f + bf.away_radians_,
206 bf.interf_angles_radians_[1]);
207 bf.AimAt(AzimuthToSphericalPoint(bf.away_radians_ / 2.f));
208 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
209 EXPECT_FLOAT_EQ(M_PI - bf.away_radians_ / 2.f,
210 bf.interf_angles_radians_[0]);
211 EXPECT_FLOAT_EQ(3.f * bf.away_radians_ / 2.f, bf.interf_angles_radians_[1]);
212 }
213 {
214 // For planar arrays with normal not in the xy-plane there is no ambiguity.
215 std::vector<Point> array_geometry;
216 array_geometry.push_back(Point(0.f, 0.f, 0.f));
217 array_geometry.push_back(Point(0.2f, 0.f, 0.f));
218 array_geometry.push_back(Point(0.f, 0.1f, -0.2f));
219 NonlinearBeamformer bf(array_geometry, 1u);
220 bf.Initialize(kChunkSizeMs, kSampleRateHz);
221 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
222 EXPECT_FLOAT_EQ(M_PI / 2.f - bf.away_radians_,
223 bf.interf_angles_radians_[0]);
224 EXPECT_FLOAT_EQ(M_PI / 2.f + bf.away_radians_,
225 bf.interf_angles_radians_[1]);
226 bf.AimAt(AzimuthToSphericalPoint(bf.away_radians_ / 2.f));
227 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
228 EXPECT_FLOAT_EQ(-bf.away_radians_ / 2.f, bf.interf_angles_radians_[0]);
229 EXPECT_FLOAT_EQ(3.f * bf.away_radians_ / 2.f, bf.interf_angles_radians_[1]);
230 }
231 {
232 // For arrays which are not linear or planar there is no ambiguity.
233 std::vector<Point> array_geometry;
234 array_geometry.push_back(Point(0.f, 0.f, 0.f));
235 array_geometry.push_back(Point(0.1f, 0.f, 0.f));
236 array_geometry.push_back(Point(0.f, 0.2f, 0.f));
237 array_geometry.push_back(Point(0.f, 0.f, 0.3f));
238 NonlinearBeamformer bf(array_geometry, 1u);
239 bf.Initialize(kChunkSizeMs, kSampleRateHz);
240 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
241 EXPECT_FLOAT_EQ(M_PI / 2.f - bf.away_radians_,
242 bf.interf_angles_radians_[0]);
243 EXPECT_FLOAT_EQ(M_PI / 2.f + bf.away_radians_,
244 bf.interf_angles_radians_[1]);
245 bf.AimAt(AzimuthToSphericalPoint(bf.away_radians_ / 2.f));
246 EXPECT_EQ(2u, bf.interf_angles_radians_.size());
247 EXPECT_FLOAT_EQ(-bf.away_radians_ / 2.f, bf.interf_angles_radians_[0]);
248 EXPECT_FLOAT_EQ(3.f * bf.away_radians_ / 2.f, bf.interf_angles_radians_[1]);
249 }
250 }
251
252 // TODO(peah): Investigate why the nonlinear_beamformer.cc causes a DCHECK in
253 // this setup.
TEST(BeamformerBitExactnessTest,DISABLED_Stereo8kHz_ArrayGeometry1_TargetDirection1)254 TEST(BeamformerBitExactnessTest,
255 DISABLED_Stereo8kHz_ArrayGeometry1_TargetDirection1) {
256 const float kOutputReference[] = {0.001318f, -0.001091f, 0.000990f,
257 0.001318f, -0.001091f, 0.000990f};
258
259 RunBitExactnessTest(AudioProcessing::kSampleRate8kHz, CreateArrayGeometry(1),
260 TargetDirection1, kOutputReference);
261 }
262
TEST(BeamformerBitExactnessTest,Stereo16kHz_ArrayGeometry1_TargetDirection1)263 TEST(BeamformerBitExactnessTest,
264 Stereo16kHz_ArrayGeometry1_TargetDirection1) {
265 const float kOutputReference[] = {-0.000077f, -0.000147f, -0.000138f,
266 -0.000077f, -0.000147f, -0.000138f};
267
268 RunBitExactnessTest(AudioProcessing::kSampleRate16kHz, CreateArrayGeometry(1),
269 TargetDirection1, kOutputReference);
270 }
271
TEST(BeamformerBitExactnessTest,Stereo32kHz_ArrayGeometry1_TargetDirection1)272 TEST(BeamformerBitExactnessTest,
273 Stereo32kHz_ArrayGeometry1_TargetDirection1) {
274 const float kOutputReference[] = {-0.000061f, -0.000061f, -0.000061f,
275 -0.000061f, -0.000061f, -0.000061f};
276
277 RunBitExactnessTest(AudioProcessing::kSampleRate32kHz, CreateArrayGeometry(1),
278 TargetDirection1, kOutputReference);
279 }
280
TEST(BeamformerBitExactnessTest,Stereo48kHz_ArrayGeometry1_TargetDirection1)281 TEST(BeamformerBitExactnessTest,
282 Stereo48kHz_ArrayGeometry1_TargetDirection1) {
283 const float kOutputReference[] = {0.000450f, 0.000436f, 0.000433f,
284 0.000450f, 0.000436f, 0.000433f};
285
286 RunBitExactnessTest(AudioProcessing::kSampleRate48kHz, CreateArrayGeometry(1),
287 TargetDirection1, kOutputReference);
288 }
289
290 // TODO(peah): Investigate why the nonlinear_beamformer.cc causes a DCHECK in
291 // this setup.
TEST(BeamformerBitExactnessTest,DISABLED_Stereo8kHz_ArrayGeometry1_TargetDirection2)292 TEST(BeamformerBitExactnessTest,
293 DISABLED_Stereo8kHz_ArrayGeometry1_TargetDirection2) {
294 const float kOutputReference[] = {0.001144f, -0.001026f, 0.001074f,
295 -0.016205f, -0.007324f, -0.015656f};
296
297 RunBitExactnessTest(AudioProcessing::kSampleRate8kHz, CreateArrayGeometry(1),
298 TargetDirection2, kOutputReference);
299 }
300
TEST(BeamformerBitExactnessTest,Stereo16kHz_ArrayGeometry1_TargetDirection2)301 TEST(BeamformerBitExactnessTest,
302 Stereo16kHz_ArrayGeometry1_TargetDirection2) {
303 const float kOutputReference[] = {0.000221f, -0.000249f, 0.000140f,
304 0.000221f, -0.000249f, 0.000140f};
305
306 RunBitExactnessTest(AudioProcessing::kSampleRate16kHz, CreateArrayGeometry(1),
307 TargetDirection2, kOutputReference);
308 }
309
TEST(BeamformerBitExactnessTest,Stereo32kHz_ArrayGeometry1_TargetDirection2)310 TEST(BeamformerBitExactnessTest,
311 Stereo32kHz_ArrayGeometry1_TargetDirection2) {
312 const float kOutputReference[] = {0.000763f, -0.000336f, 0.000549f,
313 0.000763f, -0.000336f, 0.000549f};
314
315 RunBitExactnessTest(AudioProcessing::kSampleRate32kHz, CreateArrayGeometry(1),
316 TargetDirection2, kOutputReference);
317 }
318
TEST(BeamformerBitExactnessTest,Stereo48kHz_ArrayGeometry1_TargetDirection2)319 TEST(BeamformerBitExactnessTest,
320 Stereo48kHz_ArrayGeometry1_TargetDirection2) {
321 const float kOutputReference[] = {-0.000004f, -0.000494f, 0.000255f,
322 -0.000004f, -0.000494f, 0.000255f};
323
324 RunBitExactnessTest(AudioProcessing::kSampleRate48kHz, CreateArrayGeometry(1),
325 TargetDirection2, kOutputReference);
326 }
327
TEST(BeamformerBitExactnessTest,Stereo8kHz_ArrayGeometry2_TargetDirection2)328 TEST(BeamformerBitExactnessTest,
329 Stereo8kHz_ArrayGeometry2_TargetDirection2) {
330 const float kOutputReference[] = {-0.000914f, 0.002170f, -0.002382f,
331 -0.000914f, 0.002170f, -0.002382f};
332
333 RunBitExactnessTest(AudioProcessing::kSampleRate8kHz, CreateArrayGeometry(2),
334 TargetDirection2, kOutputReference);
335 }
336
TEST(BeamformerBitExactnessTest,Stereo16kHz_ArrayGeometry2_TargetDirection2)337 TEST(BeamformerBitExactnessTest,
338 Stereo16kHz_ArrayGeometry2_TargetDirection2) {
339 const float kOutputReference[] = {0.000179f, -0.000179f, 0.000081f,
340 0.000179f, -0.000179f, 0.000081f};
341
342 RunBitExactnessTest(AudioProcessing::kSampleRate16kHz, CreateArrayGeometry(2),
343 TargetDirection2, kOutputReference);
344 }
345
TEST(BeamformerBitExactnessTest,Stereo32kHz_ArrayGeometry2_TargetDirection2)346 TEST(BeamformerBitExactnessTest,
347 Stereo32kHz_ArrayGeometry2_TargetDirection2) {
348 const float kOutputReference[] = {0.000549f, -0.000214f, 0.000366f,
349 0.000549f, -0.000214f, 0.000366f};
350
351 RunBitExactnessTest(AudioProcessing::kSampleRate32kHz, CreateArrayGeometry(2),
352 TargetDirection2, kOutputReference);
353 }
354
TEST(BeamformerBitExactnessTest,Stereo48kHz_ArrayGeometry2_TargetDirection2)355 TEST(BeamformerBitExactnessTest,
356 Stereo48kHz_ArrayGeometry2_TargetDirection2) {
357 const float kOutputReference[] = {0.000019f, -0.000310f, 0.000182f,
358 0.000019f, -0.000310f, 0.000182f};
359
360 RunBitExactnessTest(AudioProcessing::kSampleRate48kHz, CreateArrayGeometry(2),
361 TargetDirection2, kOutputReference);
362 }
363
364 // TODO(peah): Investigate why the nonlinear_beamformer.cc causes a DCHECK in
365 // this setup.
TEST(BeamformerBitExactnessTest,DISABLED_Stereo16kHz_ArrayGeometry3_TargetDirection1)366 TEST(BeamformerBitExactnessTest,
367 DISABLED_Stereo16kHz_ArrayGeometry3_TargetDirection1) {
368 const float kOutputReference[] = {-0.000161f, 0.000171f, -0.000096f,
369 0.001007f, 0.000427f, 0.000977f};
370
371 RunBitExactnessTest(AudioProcessing::kSampleRate16kHz, CreateArrayGeometry(3),
372 TargetDirection1, kOutputReference);
373 }
374
375 } // namespace webrtc
376