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