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