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

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: REBASE + Workaround to ensure MediaStreamAudioProcessor is destroyed on the main thread. 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 = 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
OLDNEW
« no previous file with comments | « content/renderer/media/media_stream_audio_track_sink.h ('k') | content/renderer/media/media_stream_center.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698