1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "services/audio/loopback_stream.h"
6 
7 #include <algorithm>
8 #include <cmath>
9 #include <cstdint>
10 #include <memory>
11 
12 #include "base/bind.h"
13 #include "base/containers/unique_ptr_adapters.h"
14 #include "base/memory/scoped_refptr.h"
15 #include "base/test/task_environment.h"
16 #include "base/unguessable_token.h"
17 #include "media/base/audio_parameters.h"
18 #include "media/base/audio_timestamp_helper.h"
19 #include "media/base/channel_layout.h"
20 #include "mojo/public/cpp/bindings/pending_receiver.h"
21 #include "mojo/public/cpp/bindings/pending_remote.h"
22 #include "mojo/public/cpp/bindings/receiver.h"
23 #include "mojo/public/cpp/bindings/remote.h"
24 #include "services/audio/loopback_coordinator.h"
25 #include "services/audio/loopback_group_member.h"
26 #include "services/audio/test/fake_consumer.h"
27 #include "services/audio/test/fake_loopback_group_member.h"
28 #include "testing/gmock/include/gmock/gmock.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 
31 using testing::_;
32 using testing::Mock;
33 using testing::NiceMock;
34 using testing::StrictMock;
35 
36 namespace audio {
37 namespace {
38 
39 // Volume settings for the FakeLoopbackGroupMember (source) and LoopbackStream.
40 constexpr double kSnoopVolume = 0.25;
41 constexpr double kLoopbackVolume = 0.5;
42 
43 // Piano key frequencies.
44 constexpr double kMiddleAFreq = 440;
45 constexpr double kMiddleCFreq = 261.626;
46 
47 // Audio buffer duration.
48 constexpr base::TimeDelta kBufferDuration =
49     base::TimeDelta::FromMilliseconds(10);
50 
51 // Local audio output delay.
52 constexpr base::TimeDelta kDelayUntilOutput =
53     base::TimeDelta::FromMilliseconds(20);
54 
55 // The amount of audio signal to record each time PumpAudioAndTakeNewRecording()
56 // is called.
57 constexpr base::TimeDelta kTestRecordingDuration =
58     base::TimeDelta::FromMilliseconds(250);
59 
GetLoopbackStreamParams()60 const media::AudioParameters& GetLoopbackStreamParams() {
61   // 48 kHz, 2-channel audio, with 10 ms buffers.
62   static const media::AudioParameters params(
63       media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
64       media::CHANNEL_LAYOUT_STEREO, 48000, 480);
65   return params;
66 }
67 
68 class MockClientAndObserver : public media::mojom::AudioInputStreamClient,
69                               public media::mojom::AudioInputStreamObserver {
70  public:
71   MockClientAndObserver() = default;
72   ~MockClientAndObserver() override = default;
73 
Bind(mojo::PendingReceiver<media::mojom::AudioInputStreamClient> client_receiver,mojo::PendingReceiver<media::mojom::AudioInputStreamObserver> observer_receiver)74   void Bind(mojo::PendingReceiver<media::mojom::AudioInputStreamClient>
75                 client_receiver,
76             mojo::PendingReceiver<media::mojom::AudioInputStreamObserver>
77                 observer_receiver) {
78     client_receiver_.Bind(std::move(client_receiver));
79     observer_receiver_.Bind(std::move(observer_receiver));
80   }
81 
CloseClientBinding()82   void CloseClientBinding() { client_receiver_.reset(); }
CloseObserverBinding()83   void CloseObserverBinding() { observer_receiver_.reset(); }
84 
85   MOCK_METHOD0(OnError, void());
86   MOCK_METHOD0(DidStartRecording, void());
OnMutedStateChanged(bool)87   void OnMutedStateChanged(bool) override { NOTREACHED(); }
88 
89  private:
90   mojo::Receiver<media::mojom::AudioInputStreamClient> client_receiver_{this};
91   mojo::Receiver<media::mojom::AudioInputStreamObserver> observer_receiver_{
92       this};
93 };
94 
95 // Subclass of FakeConsumer that adapts the SyncWriter interface to allow the
96 // tests to record and analyze the audio data from the LoopbackStream.
97 class FakeSyncWriter : public FakeConsumer, public InputController::SyncWriter {
98  public:
FakeSyncWriter(int channels,int sample_rate)99   FakeSyncWriter(int channels, int sample_rate)
100       : FakeConsumer(channels, sample_rate) {}
101 
102   ~FakeSyncWriter() override = default;
103 
Clear()104   void Clear() {
105     FakeConsumer::Clear();
106     last_capture_time_ = base::TimeTicks();
107   }
108 
109   // media::AudioInputController::SyncWriter implementation.
Write(const media::AudioBus * data,double volume,bool key_pressed,base::TimeTicks capture_time)110   void Write(const media::AudioBus* data,
111              double volume,
112              bool key_pressed,
113              base::TimeTicks capture_time) final {
114     FakeConsumer::Consume(*data);
115 
116     // Capture times should be monotonically increasing.
117     if (!last_capture_time_.is_null()) {
118       CHECK_LT(last_capture_time_, capture_time);
119     }
120     last_capture_time_ = capture_time;
121   }
122 
Close()123   void Close() final {}
124 
125   base::TimeTicks last_capture_time_;
126 };
127 
128 class LoopbackStreamTest : public testing::Test {
129  public:
LoopbackStreamTest()130   LoopbackStreamTest() : group_id_(base::UnguessableToken::Create()) {}
131 
132   ~LoopbackStreamTest() override = default;
133 
TearDown()134   void TearDown() override {
135     stream_ = nullptr;
136 
137     for (const auto& source : sources_) {
138       coordinator_.UnregisterMember(group_id_, source.get());
139     }
140     sources_.clear();
141 
142     task_environment_.FastForwardUntilNoTasksRemain();
143   }
144 
client()145   MockClientAndObserver* client() { return &client_; }
stream()146   LoopbackStream* stream() { return stream_.get(); }
consumer()147   FakeSyncWriter* consumer() { return consumer_; }
148 
RunMojoTasks()149   void RunMojoTasks() { task_environment_.RunUntilIdle(); }
150 
AddSource(int channels,int sample_rate)151   FakeLoopbackGroupMember* AddSource(int channels, int sample_rate) {
152     sources_.emplace_back(std::make_unique<FakeLoopbackGroupMember>(
153         media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
154                                media::GuessChannelLayout(channels), sample_rate,
155                                (sample_rate * kBufferDuration).InSeconds())));
156     coordinator_.RegisterMember(group_id_, sources_.back().get());
157     return sources_.back().get();
158   }
159 
RemoveSource(FakeLoopbackGroupMember * source)160   void RemoveSource(FakeLoopbackGroupMember* source) {
161     const auto it = std::find_if(sources_.begin(), sources_.end(),
162                                  base::MatchesUniquePtr(source));
163     if (it != sources_.end()) {
164       coordinator_.UnregisterMember(group_id_, source);
165       sources_.erase(it);
166     }
167   }
168 
CreateLoopbackStream()169   void CreateLoopbackStream() {
170     CHECK(!stream_);
171 
172     mojo::PendingRemote<media::mojom::AudioInputStreamClient> client;
173     mojo::PendingRemote<media::mojom::AudioInputStreamObserver> observer;
174     client_.Bind(client.InitWithNewPipeAndPassReceiver(),
175                  observer.InitWithNewPipeAndPassReceiver());
176 
177     stream_ = std::make_unique<LoopbackStream>(
178         base::BindOnce([](media::mojom::ReadOnlyAudioDataPipePtr pipe) {
179           EXPECT_TRUE(pipe->shared_memory.IsValid());
180           EXPECT_TRUE(pipe->socket.is_valid());
181         }),
182         base::BindOnce([](LoopbackStreamTest* self,
183                           LoopbackStream* stream) { self->stream_ = nullptr; },
184                        this),
185         task_environment_.GetMainThreadTaskRunner(),
186         remote_input_stream_.BindNewPipeAndPassReceiver(), std::move(client),
187         std::move(observer), GetLoopbackStreamParams(),
188         // The following argument is the |shared_memory_count|, which does not
189         // matter because the SyncWriter will be overridden with FakeSyncWriter
190         // below.
191         1, &coordinator_, group_id_);
192 
193     // Override the clock used by the LoopbackStream so that everything is
194     // single-threaded and synchronized with the driving code in these tests.
195     stream_->set_clock_for_testing(task_environment_.GetMockTickClock());
196 
197     // Redirect the output of the LoopbackStream to a FakeSyncWriter.
198     // LoopbackStream takes ownership of the FakeSyncWriter.
199     auto consumer = std::make_unique<FakeSyncWriter>(
200         GetLoopbackStreamParams().channels(),
201         GetLoopbackStreamParams().sample_rate());
202     CHECK(!consumer_);
203     consumer_ = consumer.get();
204     stream_->set_sync_writer_for_testing(std::move(consumer));
205 
206     // Set the volume for the LoopbackStream.
207     remote_input_stream_->SetVolume(kLoopbackVolume);
208 
209     // Allow all pending mojo tasks for all of the above to run and propagate
210     // state.
211     RunMojoTasks();
212 
213     ASSERT_TRUE(remote_input_stream_);
214   }
215 
StartLoopbackRecording()216   void StartLoopbackRecording() {
217     ASSERT_EQ(0, consumer_->GetRecordedFrameCount());
218     remote_input_stream_->Record();
219     RunMojoTasks();
220   }
221 
SetLoopbackVolume(double volume)222   void SetLoopbackVolume(double volume) {
223     remote_input_stream_->SetVolume(volume);
224     RunMojoTasks();
225   }
226 
PumpAudioAndTakeNewRecording()227   void PumpAudioAndTakeNewRecording() {
228     consumer_->Clear();
229 
230     const int min_frames_to_record = media::AudioTimestampHelper::TimeToFrames(
231         kTestRecordingDuration, GetLoopbackStreamParams().sample_rate());
232     do {
233       // Render audio meant for local output at some point in the near
234       // future.
235       const base::TimeTicks output_timestamp =
236           task_environment_.NowTicks() + kDelayUntilOutput;
237       for (const auto& source : sources_) {
238         source->RenderMoreAudio(output_timestamp);
239       }
240 
241       // Move the task runner forward, which will cause the FlowNetwork's
242       // delayed tasks to run, which will generate output for the consumer.
243       task_environment_.FastForwardBy(kBufferDuration);
244     } while (consumer_->GetRecordedFrameCount() < min_frames_to_record);
245   }
246 
CloseInputStreamPtr()247   void CloseInputStreamPtr() {
248     remote_input_stream_.reset();
249     RunMojoTasks();
250   }
251 
252  private:
253   base::test::TaskEnvironment task_environment_{
254       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
255   LoopbackCoordinator coordinator_;
256   const base::UnguessableToken group_id_;
257   std::vector<std::unique_ptr<FakeLoopbackGroupMember>> sources_;
258   NiceMock<MockClientAndObserver> client_;
259   std::unique_ptr<LoopbackStream> stream_;
260   FakeSyncWriter* consumer_ = nullptr;  // Owned by |stream_|.
261 
262   mojo::Remote<media::mojom::AudioInputStream> remote_input_stream_;
263 
264   DISALLOW_COPY_AND_ASSIGN(LoopbackStreamTest);
265 };
266 
TEST_F(LoopbackStreamTest,ShutsDownStreamWhenInterfacePtrIsClosed)267 TEST_F(LoopbackStreamTest, ShutsDownStreamWhenInterfacePtrIsClosed) {
268   CreateLoopbackStream();
269   EXPECT_CALL(*client(), DidStartRecording());
270   StartLoopbackRecording();
271   PumpAudioAndTakeNewRecording();
272   EXPECT_CALL(*client(), OnError());
273   CloseInputStreamPtr();
274   EXPECT_FALSE(stream());
275   Mock::VerifyAndClearExpectations(client());
276 }
277 
TEST_F(LoopbackStreamTest,ShutsDownStreamWhenClientBindingIsClosed)278 TEST_F(LoopbackStreamTest, ShutsDownStreamWhenClientBindingIsClosed) {
279   CreateLoopbackStream();
280   EXPECT_CALL(*client(), DidStartRecording());
281   StartLoopbackRecording();
282   PumpAudioAndTakeNewRecording();
283   // Note: Expect no call to client::OnError() because it is the client binding
284   // that is being closed and causing the error.
285   EXPECT_CALL(*client(), OnError()).Times(0);
286   client()->CloseClientBinding();
287   RunMojoTasks();
288   EXPECT_FALSE(stream());
289   Mock::VerifyAndClearExpectations(client());
290 }
291 
TEST_F(LoopbackStreamTest,ShutsDownStreamWhenObserverBindingIsClosed)292 TEST_F(LoopbackStreamTest, ShutsDownStreamWhenObserverBindingIsClosed) {
293   CreateLoopbackStream();
294   EXPECT_CALL(*client(), DidStartRecording());
295   StartLoopbackRecording();
296   PumpAudioAndTakeNewRecording();
297   EXPECT_CALL(*client(), OnError());
298   client()->CloseObserverBinding();
299   RunMojoTasks();
300   EXPECT_FALSE(stream());
301   Mock::VerifyAndClearExpectations(client());
302 }
303 
TEST_F(LoopbackStreamTest,ProducesSilenceWhenNoMembersArePresent)304 TEST_F(LoopbackStreamTest, ProducesSilenceWhenNoMembersArePresent) {
305   CreateLoopbackStream();
306   EXPECT_CALL(*client(), DidStartRecording());
307   StartLoopbackRecording();
308   PumpAudioAndTakeNewRecording();
309   for (int ch = 0; ch < GetLoopbackStreamParams().channels(); ++ch) {
310     SCOPED_TRACE(testing::Message() << "ch=" << ch);
311     EXPECT_TRUE(consumer()->IsSilent(ch));
312   }
313 }
314 
315 // Syntatic sugar to confirm a tone exists and its amplitude matches
316 // expectations.
317 #define EXPECT_TONE(ch, frequency, expected_amplitude)                     \
318   {                                                                        \
319     SCOPED_TRACE(testing::Message() << "ch=" << ch);                       \
320     const double amplitude = consumer()->ComputeAmplitudeAt(               \
321         ch, frequency, consumer()->GetRecordedFrameCount());               \
322     VLOG(1) << "For ch=" << ch << ", amplitude at frequency=" << frequency \
323             << " is " << amplitude;                                        \
324     EXPECT_NEAR(expected_amplitude, amplitude, 0.01);                      \
325   }
326 
TEST_F(LoopbackStreamTest,ProducesAudioFromASingleSource)327 TEST_F(LoopbackStreamTest, ProducesAudioFromASingleSource) {
328   FakeLoopbackGroupMember* const source =
329       AddSource(1, 48000);  // Monaural, 48 kHz.
330   source->SetChannelTone(0, kMiddleAFreq);
331   source->SetVolume(kSnoopVolume);
332 
333   CreateLoopbackStream();
334   EXPECT_CALL(*client(), DidStartRecording());
335   StartLoopbackRecording();
336   PumpAudioAndTakeNewRecording();
337 
338   // Expect to have recorded middle-A in all of the loopback stream's channels.
339   for (int ch = 0; ch < GetLoopbackStreamParams().channels(); ++ch) {
340     EXPECT_TONE(ch, kMiddleAFreq, kSnoopVolume * kLoopbackVolume);
341   }
342 }
343 
TEST_F(LoopbackStreamTest,ProducesAudioFromTwoSources)344 TEST_F(LoopbackStreamTest, ProducesAudioFromTwoSources) {
345   // Start the first source (of a middle-A note) before creating the loopback
346   // stream.
347   const int channels = GetLoopbackStreamParams().channels();
348   FakeLoopbackGroupMember* const source1 = AddSource(channels, 48000);
349   source1->SetChannelTone(0, kMiddleAFreq);
350   source1->SetVolume(kSnoopVolume);
351 
352   CreateLoopbackStream();
353   EXPECT_CALL(*client(), DidStartRecording());
354   StartLoopbackRecording();
355   PumpAudioAndTakeNewRecording();
356 
357   // Start the second source (of a middle-C note) while the loopback stream is
358   // running. The second source has a different sample rate than the first.
359   FakeLoopbackGroupMember* const source2 = AddSource(channels, 44100);
360   source2->SetChannelTone(1, kMiddleCFreq);
361   source2->SetVolume(kSnoopVolume);
362 
363   PumpAudioAndTakeNewRecording();
364 
365   // Expect to have recorded both middle-A and middle-C in all of the loopback
366   // stream's channels.
367   EXPECT_TONE(0, kMiddleAFreq, kSnoopVolume * kLoopbackVolume);
368   EXPECT_TONE(1, kMiddleCFreq, kSnoopVolume * kLoopbackVolume);
369 
370   // Switch the channels containig the tone in both sources, and expect to see
371   // the tones have switched channels in the loopback output.
372   source1->SetChannelTone(0, 0.0);
373   source1->SetChannelTone(1, kMiddleAFreq);
374   source2->SetChannelTone(0, kMiddleCFreq);
375   source2->SetChannelTone(1, 0.0);
376   PumpAudioAndTakeNewRecording();
377   EXPECT_TONE(1, kMiddleAFreq, kSnoopVolume * kLoopbackVolume);
378   EXPECT_TONE(0, kMiddleCFreq, kSnoopVolume * kLoopbackVolume);
379 }
380 
TEST_F(LoopbackStreamTest,AudioChangesVolume)381 TEST_F(LoopbackStreamTest, AudioChangesVolume) {
382   FakeLoopbackGroupMember* const source =
383       AddSource(1, 48000);  // Monaural, 48 kHz.
384   source->SetChannelTone(0, kMiddleAFreq);
385   source->SetVolume(kSnoopVolume);
386 
387   CreateLoopbackStream();
388   StartLoopbackRecording();
389   PumpAudioAndTakeNewRecording();
390 
391   // Record and check the amplitude at the default volume settings.
392   double expected_amplitude = kSnoopVolume * kLoopbackVolume;
393   for (int ch = 0; ch < GetLoopbackStreamParams().channels(); ++ch) {
394     EXPECT_TONE(ch, kMiddleAFreq, expected_amplitude);
395   }
396 
397   // Double the volume of the source and expect the output to have also doubled.
398   source->SetVolume(kSnoopVolume * 2);
399   PumpAudioAndTakeNewRecording();
400   expected_amplitude *= 2;
401   for (int ch = 0; ch < GetLoopbackStreamParams().channels(); ++ch) {
402     EXPECT_TONE(ch, kMiddleAFreq, expected_amplitude);
403   }
404 
405   // Drop the LoopbackStream volume by 1/3 and expect the output to also have
406   // dropped by 1/3.
407   SetLoopbackVolume(kLoopbackVolume / 3);
408   PumpAudioAndTakeNewRecording();
409   expected_amplitude /= 3;
410   for (int ch = 0; ch < GetLoopbackStreamParams().channels(); ++ch) {
411     EXPECT_TONE(ch, kMiddleAFreq, expected_amplitude);
412   }
413 }
414 
415 }  // namespace
416 }  // namespace audio
417