OLD | NEW |
| (Empty) |
1 // Copyright 2016 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 <stdint.h> | |
6 | |
7 #include "base/atomicops.h" | |
8 #include "base/synchronization/lock.h" | |
9 #include "base/synchronization/waitable_event.h" | |
10 #include "base/test/test_timeouts.h" | |
11 #include "base/threading/platform_thread.h" | |
12 #include "base/threading/thread_checker.h" | |
13 #include "content/public/renderer/media_stream_audio_sink.h" | |
14 #include "content/renderer/media/media_stream_audio_source.h" | |
15 #include "content/renderer/media/media_stream_audio_track.h" | |
16 #include "media/base/audio_bus.h" | |
17 #include "media/base/audio_parameters.h" | |
18 #include "testing/gtest/include/gtest/gtest.h" | |
19 #include "third_party/WebKit/public/platform/WebString.h" | |
20 #include "third_party/WebKit/public/web/WebHeap.h" | |
21 | |
22 namespace content { | |
23 | |
24 namespace { | |
25 | |
26 constexpr int kSampleRate = 8000; | |
27 constexpr int kBufferSize = kSampleRate / 100; | |
28 | |
29 // The maximum integer that can be exactly represented by the float data type. | |
30 constexpr int kMaxValueSafelyConvertableToFloat = 1 << 24; | |
31 | |
32 // A simple MediaStreamAudioSource that spawns a real-time audio thread and | |
33 // emits audio samples with monotonically-increasing sample values. Includes | |
34 // hooks for the unit tests to confirm lifecycle status and to change audio | |
35 // format. | |
36 class FakeMediaStreamAudioSource | |
37 : public MediaStreamAudioSource, | |
38 public base::PlatformThread::Delegate { | |
39 public: | |
40 FakeMediaStreamAudioSource() | |
41 : MediaStreamAudioSource(true), stop_event_(true, false), | |
42 next_buffer_size_(kBufferSize), sample_count_(0) {} | |
43 | |
44 ~FakeMediaStreamAudioSource() final { | |
45 CHECK(main_thread_checker_.CalledOnValidThread()); | |
46 EnsureSourceIsStopped(); | |
47 } | |
48 | |
49 bool was_started() const { | |
50 CHECK(main_thread_checker_.CalledOnValidThread()); | |
51 return !thread_.is_null(); | |
52 } | |
53 | |
54 bool was_stopped() const { | |
55 CHECK(main_thread_checker_.CalledOnValidThread()); | |
56 return stop_event_.IsSignaled(); | |
57 } | |
58 | |
59 void SetBufferSize(int new_buffer_size) { | |
60 CHECK(main_thread_checker_.CalledOnValidThread()); | |
61 base::subtle::NoBarrier_Store(&next_buffer_size_, new_buffer_size); | |
62 } | |
63 | |
64 protected: | |
65 bool EnsureSourceIsStarted() final { | |
66 CHECK(main_thread_checker_.CalledOnValidThread()); | |
67 if (was_started()) | |
68 return true; | |
69 if (was_stopped()) | |
70 return false; | |
71 base::PlatformThread::CreateWithPriority( | |
72 0, this, &thread_, base::ThreadPriority::REALTIME_AUDIO); | |
73 return true; | |
74 } | |
75 | |
76 void EnsureSourceIsStopped() final { | |
77 CHECK(main_thread_checker_.CalledOnValidThread()); | |
78 if (was_stopped()) | |
79 return; | |
80 stop_event_.Signal(); | |
81 if (was_started()) | |
82 base::PlatformThread::Join(thread_); | |
83 } | |
84 | |
85 void ThreadMain() override { | |
86 while (!stop_event_.IsSignaled()) { | |
87 // If needed, notify of the new format and re-create |audio_bus_|. | |
88 const int buffer_size = base::subtle::NoBarrier_Load(&next_buffer_size_); | |
89 if (!audio_bus_ || audio_bus_->frames() != buffer_size) { | |
90 MediaStreamAudioSource::SetFormat(media::AudioParameters( | |
91 media::AudioParameters::AUDIO_PCM_LOW_LATENCY, | |
92 media::CHANNEL_LAYOUT_MONO, kSampleRate, 16, buffer_size)); | |
93 audio_bus_ = media::AudioBus::Create(1, buffer_size); | |
94 } | |
95 | |
96 // Deliver the next chunk of audio data. Each sample value is its offset | |
97 // from the very first sample. | |
98 float* const data = audio_bus_->channel(0); | |
99 for (int i = 0; i < buffer_size; ++i) | |
100 data[i] = ++sample_count_; | |
101 CHECK_LT(sample_count_, kMaxValueSafelyConvertableToFloat); | |
102 MediaStreamAudioSource::DeliverDataToTracks(*audio_bus_, | |
103 base::TimeTicks::Now()); | |
104 | |
105 // Sleep before producing the next chunk of audio. | |
106 base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds( | |
107 base::Time::kMicrosecondsPerSecond * buffer_size / kSampleRate)); | |
108 } | |
109 } | |
110 | |
111 private: | |
112 base::ThreadChecker main_thread_checker_; | |
113 | |
114 base::PlatformThreadHandle thread_; | |
115 mutable base::WaitableEvent stop_event_; | |
116 | |
117 base::subtle::Atomic32 next_buffer_size_; | |
118 std::unique_ptr<media::AudioBus> audio_bus_; | |
119 int sample_count_; | |
120 | |
121 DISALLOW_COPY_AND_ASSIGN(FakeMediaStreamAudioSource); | |
122 }; | |
123 | |
124 // A simple MediaStreamAudioSink that consumes audio and confirms the sample | |
125 // values. Includes hooks for the unit tests to monitor the format and flow of | |
126 // audio, whether the audio is silent, and the propagation of the "enabled" | |
127 // state. | |
128 class FakeMediaStreamAudioSink : public MediaStreamAudioSink { | |
129 public: | |
130 enum EnableState { | |
131 NO_ENABLE_NOTIFICATION, | |
132 WAS_ENABLED, | |
133 WAS_DISABLED | |
134 }; | |
135 | |
136 FakeMediaStreamAudioSink() | |
137 : MediaStreamAudioSink(), expected_sample_count_(-1), | |
138 num_on_data_calls_(0), audio_is_silent_(true), was_ended_(false), | |
139 enable_state_(NO_ENABLE_NOTIFICATION) {} | |
140 | |
141 ~FakeMediaStreamAudioSink() final { | |
142 CHECK(main_thread_checker_.CalledOnValidThread()); | |
143 } | |
144 | |
145 media::AudioParameters params() const { | |
146 CHECK(main_thread_checker_.CalledOnValidThread()); | |
147 base::AutoLock auto_lock(params_lock_); | |
148 return params_; | |
149 } | |
150 | |
151 int num_on_data_calls() const { | |
152 CHECK(main_thread_checker_.CalledOnValidThread()); | |
153 return base::subtle::NoBarrier_Load(&num_on_data_calls_); | |
154 } | |
155 | |
156 bool is_audio_silent() const { | |
157 CHECK(main_thread_checker_.CalledOnValidThread()); | |
158 return !!base::subtle::NoBarrier_Load(&audio_is_silent_); | |
159 } | |
160 | |
161 bool was_ended() const { | |
162 CHECK(main_thread_checker_.CalledOnValidThread()); | |
163 return was_ended_; | |
164 } | |
165 | |
166 EnableState enable_state() const { | |
167 CHECK(main_thread_checker_.CalledOnValidThread()); | |
168 return enable_state_; | |
169 } | |
170 | |
171 void OnSetFormat(const media::AudioParameters& params) final { | |
172 ASSERT_TRUE(params.IsValid()); | |
173 base::AutoLock auto_lock(params_lock_); | |
174 params_ = params; | |
175 } | |
176 | |
177 void OnData(const media::AudioBus& audio_bus, | |
178 base::TimeTicks estimated_capture_time) final { | |
179 ASSERT_TRUE(params_.IsValid()); | |
180 ASSERT_FALSE(was_ended_); | |
181 | |
182 ASSERT_EQ(params_.channels(), audio_bus.channels()); | |
183 ASSERT_EQ(params_.frames_per_buffer(), audio_bus.frames()); | |
184 if (audio_bus.AreFramesZero()) { | |
185 base::subtle::NoBarrier_Store(&audio_is_silent_, 1); | |
186 expected_sample_count_ = -1; // Reset for when audio comes back. | |
187 } else { | |
188 base::subtle::NoBarrier_Store(&audio_is_silent_, 0); | |
189 const float* const data = audio_bus.channel(0); | |
190 if (expected_sample_count_ == -1) | |
191 expected_sample_count_ = static_cast<int64_t>(data[0]); | |
192 CHECK_LE(expected_sample_count_ + audio_bus.frames(), | |
193 kMaxValueSafelyConvertableToFloat); | |
194 for (int i = 0; i < audio_bus.frames(); ++i) { | |
195 const float expected_sample_value = expected_sample_count_; | |
196 ASSERT_EQ(expected_sample_value, data[i]); | |
197 ++expected_sample_count_; | |
198 } | |
199 } | |
200 | |
201 ASSERT_TRUE(!estimated_capture_time.is_null()); | |
202 ASSERT_LT(last_estimated_capture_time_, estimated_capture_time); | |
203 last_estimated_capture_time_ = estimated_capture_time; | |
204 | |
205 base::subtle::NoBarrier_AtomicIncrement(&num_on_data_calls_, 1); | |
206 } | |
207 | |
208 void OnReadyStateChanged( | |
209 blink::WebMediaStreamSource::ReadyState state) final { | |
210 CHECK(main_thread_checker_.CalledOnValidThread()); | |
211 if (state == blink::WebMediaStreamSource::ReadyStateEnded) | |
212 was_ended_ = true; | |
213 } | |
214 | |
215 void OnEnabledChanged(bool enabled) final { | |
216 CHECK(main_thread_checker_.CalledOnValidThread()); | |
217 enable_state_ = enabled ? WAS_ENABLED : WAS_DISABLED; | |
218 } | |
219 | |
220 private: | |
221 base::ThreadChecker main_thread_checker_; | |
222 | |
223 mutable base::Lock params_lock_; | |
224 media::AudioParameters params_; | |
225 int expected_sample_count_; | |
226 base::TimeTicks last_estimated_capture_time_; | |
227 base::subtle::Atomic32 num_on_data_calls_; | |
228 base::subtle::Atomic32 audio_is_silent_; | |
229 bool was_ended_; | |
230 EnableState enable_state_; | |
231 | |
232 DISALLOW_COPY_AND_ASSIGN(FakeMediaStreamAudioSink); | |
233 }; | |
234 | |
235 } // namespace | |
236 | |
237 class MediaStreamAudioTest : public ::testing::Test { | |
238 protected: | |
239 void SetUp() override { | |
240 blink_audio_source_.initialize(blink::WebString::fromUTF8("audio_id"), | |
241 blink::WebMediaStreamSource::TypeAudio, | |
242 blink::WebString::fromUTF8("audio_track"), | |
243 false /* remote */); | |
244 blink_audio_track_.initialize(blink_audio_source_.id(), | |
245 blink_audio_source_); | |
246 } | |
247 | |
248 void TearDown() override { | |
249 blink_audio_track_.reset(); | |
250 blink_audio_source_.reset(); | |
251 blink::WebHeap::collectAllGarbageForTesting(); | |
252 } | |
253 | |
254 FakeMediaStreamAudioSource* source() const { | |
255 return static_cast<FakeMediaStreamAudioSource*>( | |
256 MediaStreamAudioSource::From(blink_audio_source_)); | |
257 } | |
258 | |
259 MediaStreamAudioTrack* track() const { | |
260 return MediaStreamAudioTrack::From(blink_audio_track_); | |
261 } | |
262 | |
263 blink::WebMediaStreamSource blink_audio_source_; | |
264 blink::WebMediaStreamTrack blink_audio_track_; | |
265 }; | |
266 | |
267 // Tests that a simple source-->track-->sink connection and audio data flow | |
268 // works. | |
269 TEST_F(MediaStreamAudioTest, BasicUsage) { | |
270 // Create the source, but it should not be started yet. | |
271 ASSERT_FALSE(source()); | |
272 blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource()); | |
273 ASSERT_TRUE(source()); | |
274 EXPECT_FALSE(source()->was_started()); | |
275 EXPECT_FALSE(source()->was_stopped()); | |
276 | |
277 // Connect a track to the source. This should auto-start the source. | |
278 ASSERT_FALSE(track()); | |
279 EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_)); | |
280 ASSERT_TRUE(track()); | |
281 EXPECT_TRUE(source()->was_started()); | |
282 EXPECT_FALSE(source()->was_stopped()); | |
283 | |
284 // Connect a sink to the track. This should begin audio flow to the | |
285 // sink. Wait and confirm that three OnData() calls were made from the audio | |
286 // thread. | |
287 FakeMediaStreamAudioSink sink; | |
288 EXPECT_FALSE(sink.was_ended()); | |
289 track()->AddSink(&sink); | |
290 const int start_count = sink.num_on_data_calls(); | |
291 while (sink.num_on_data_calls() - start_count < 3) | |
292 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
293 | |
294 // Check that the audio parameters propagated to the track and sink. | |
295 const media::AudioParameters expected_params( | |
296 media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO, | |
297 kSampleRate, 16, kBufferSize); | |
298 EXPECT_TRUE(expected_params.Equals(track()->GetOutputFormat())); | |
299 EXPECT_TRUE(expected_params.Equals(sink.params())); | |
300 | |
301 // Stop the track. Since this was the last track connected to the source, the | |
302 // source should automatically stop. In addition, the sink should receive a | |
303 // ReadyStateEnded notification. | |
304 track()->Stop(); | |
305 EXPECT_TRUE(source()->was_started()); | |
306 EXPECT_TRUE(source()->was_stopped()); | |
307 EXPECT_TRUE(sink.was_ended()); | |
308 | |
309 track()->RemoveSink(&sink); | |
310 } | |
311 | |
312 // Tests that "ended" tracks can be connected after the source has stopped. | |
313 TEST_F(MediaStreamAudioTest, ConnectTrackAfterSourceStopped) { | |
314 // Create the source, connect one track, and stop it. This should | |
315 // automatically stop the source. | |
316 blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource()); | |
317 ASSERT_TRUE(source()); | |
318 EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_)); | |
319 track()->Stop(); | |
320 EXPECT_TRUE(source()->was_started()); | |
321 EXPECT_TRUE(source()->was_stopped()); | |
322 | |
323 // Now, connect another track. ConnectToTrack() will return false, but there | |
324 // should be a MediaStreamAudioTrack instance created and owned by the | |
325 // blink::WebMediaStreamTrack. | |
326 blink::WebMediaStreamTrack another_blink_track; | |
327 another_blink_track.initialize(blink_audio_source_.id(), blink_audio_source_); | |
328 EXPECT_FALSE(MediaStreamAudioTrack::From(another_blink_track)); | |
329 EXPECT_FALSE(source()->ConnectToTrack(another_blink_track)); | |
330 EXPECT_TRUE(MediaStreamAudioTrack::From(another_blink_track)); | |
331 } | |
332 | |
333 // Tests that a sink is immediately "ended" when connected to a stopped track. | |
334 TEST_F(MediaStreamAudioTest, AddSinkToStoppedTrack) { | |
335 // Create a track and stop it. Then, when adding a sink, the sink should get | |
336 // the ReadyStateEnded notification immediately. | |
337 MediaStreamAudioTrack track(true); | |
338 track.Stop(); | |
339 FakeMediaStreamAudioSink sink; | |
340 EXPECT_FALSE(sink.was_ended()); | |
341 track.AddSink(&sink); | |
342 EXPECT_TRUE(sink.was_ended()); | |
343 EXPECT_EQ(0, sink.num_on_data_calls()); | |
344 track.RemoveSink(&sink); | |
345 } | |
346 | |
347 // Tests that audio format changes at the source propagate to the track and | |
348 // sink. | |
349 TEST_F(MediaStreamAudioTest, FormatChangesPropagate) { | |
350 // Create a source, connect it to track, and connect the track to a | |
351 // sink. | |
352 blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource()); | |
353 ASSERT_TRUE(source()); | |
354 EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_)); | |
355 ASSERT_TRUE(track()); | |
356 FakeMediaStreamAudioSink sink; | |
357 ASSERT_TRUE(!sink.params().IsValid()); | |
358 track()->AddSink(&sink); | |
359 | |
360 // Wait until valid parameters are propagated to the sink, and then confirm | |
361 // the parameters are correct at the track and the sink. | |
362 while (!sink.params().IsValid()) | |
363 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
364 const media::AudioParameters expected_params( | |
365 media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO, | |
366 kSampleRate, 16, kBufferSize); | |
367 EXPECT_TRUE(expected_params.Equals(track()->GetOutputFormat())); | |
368 EXPECT_TRUE(expected_params.Equals(sink.params())); | |
369 | |
370 // Now, trigger a format change by doubling the buffer size. | |
371 source()->SetBufferSize(kBufferSize * 2); | |
372 | |
373 // Wait until the new buffer size propagates to the sink. | |
374 while (sink.params().frames_per_buffer() == kBufferSize) | |
375 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
376 EXPECT_EQ(kBufferSize * 2, track()->GetOutputFormat().frames_per_buffer()); | |
377 EXPECT_EQ(kBufferSize * 2, sink.params().frames_per_buffer()); | |
378 | |
379 track()->RemoveSink(&sink); | |
380 } | |
381 | |
382 // Tests that tracks deliver audio when enabled and silent audio when | |
383 // disabled. Whenever a track is enabled or disabled, the sink's | |
384 // OnEnabledChanged() method should be called. | |
385 TEST_F(MediaStreamAudioTest, EnableAndDisableTracks) { | |
386 // Create a source and connect it to track. | |
387 blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource()); | |
388 ASSERT_TRUE(source()); | |
389 EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_)); | |
390 ASSERT_TRUE(track()); | |
391 | |
392 // Connect the track to a sink and expect the sink to be notified that the | |
393 // track is enabled. | |
394 FakeMediaStreamAudioSink sink; | |
395 EXPECT_TRUE(sink.is_audio_silent()); | |
396 EXPECT_EQ(FakeMediaStreamAudioSink::NO_ENABLE_NOTIFICATION, | |
397 sink.enable_state()); | |
398 track()->AddSink(&sink); | |
399 EXPECT_EQ(FakeMediaStreamAudioSink::WAS_ENABLED, sink.enable_state()); | |
400 | |
401 // Wait until non-silent audio reaches the sink. | |
402 while (sink.is_audio_silent()) | |
403 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
404 | |
405 // Now, disable the track and expect the sink to be notified. | |
406 track()->SetEnabled(false); | |
407 EXPECT_EQ(FakeMediaStreamAudioSink::WAS_DISABLED, sink.enable_state()); | |
408 | |
409 // Wait until silent audio reaches the sink. | |
410 while (!sink.is_audio_silent()) | |
411 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
412 | |
413 // Create a second track and a second sink, but this time the track starts out | |
414 // disabled. Expect the sink to be notified at the start that the track is | |
415 // disabled. | |
416 blink::WebMediaStreamTrack another_blink_track; | |
417 another_blink_track.initialize(blink_audio_source_.id(), blink_audio_source_); | |
418 EXPECT_TRUE(source()->ConnectToTrack(another_blink_track)); | |
419 MediaStreamAudioTrack::From(another_blink_track)->SetEnabled(false); | |
420 FakeMediaStreamAudioSink another_sink; | |
421 MediaStreamAudioTrack::From(another_blink_track)->AddSink(&another_sink); | |
422 EXPECT_EQ(FakeMediaStreamAudioSink::WAS_DISABLED, | |
423 another_sink.enable_state()); | |
424 | |
425 // Wait until OnData() is called on the second sink. Expect the audio to be | |
426 // silent. | |
427 const int start_count = another_sink.num_on_data_calls(); | |
428 while (another_sink.num_on_data_calls() == start_count) | |
429 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
430 EXPECT_TRUE(another_sink.is_audio_silent()); | |
431 | |
432 // Now, enable the second track and expect the second sink to be notified. | |
433 MediaStreamAudioTrack::From(another_blink_track)->SetEnabled(true); | |
434 EXPECT_EQ(FakeMediaStreamAudioSink::WAS_ENABLED, another_sink.enable_state()); | |
435 | |
436 // Wait until non-silent audio reaches the second sink. | |
437 while (another_sink.is_audio_silent()) | |
438 base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
439 | |
440 // The first track and sink should not have been affected by changing the | |
441 // enabled state of the second track and sink. They should still be disabled, | |
442 // with silent audio being consumed at the sink. | |
443 EXPECT_EQ(FakeMediaStreamAudioSink::WAS_DISABLED, sink.enable_state()); | |
444 EXPECT_TRUE(sink.is_audio_silent()); | |
445 | |
446 MediaStreamAudioTrack::From(another_blink_track)->RemoveSink(&another_sink); | |
447 track()->RemoveSink(&sink); | |
448 } | |
449 | |
450 } // namespace content | |
OLD | NEW |