Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(361)

Side by Side Diff: content/renderer/media/media_stream_audio_unittest.cc

Issue 1834323002: MediaStream audio: Refactor 3 separate "glue" implementations into one. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Reworked unit tests around structural changes, and added exhaustive media_stream_audio_unittest.cc. Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698