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