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 |