| Index: content/renderer/media/media_stream_audio_unittest.cc
|
| diff --git a/content/renderer/media/media_stream_audio_unittest.cc b/content/renderer/media/media_stream_audio_unittest.cc
|
| deleted file mode 100644
|
| index 060dca37b0706d0276decba38199d7792efaf851..0000000000000000000000000000000000000000
|
| --- a/content/renderer/media/media_stream_audio_unittest.cc
|
| +++ /dev/null
|
| @@ -1,450 +0,0 @@
|
| -// Copyright 2016 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include <stdint.h>
|
| -
|
| -#include "base/atomicops.h"
|
| -#include "base/synchronization/lock.h"
|
| -#include "base/synchronization/waitable_event.h"
|
| -#include "base/test/test_timeouts.h"
|
| -#include "base/threading/platform_thread.h"
|
| -#include "base/threading/thread_checker.h"
|
| -#include "content/public/renderer/media_stream_audio_sink.h"
|
| -#include "content/renderer/media/media_stream_audio_source.h"
|
| -#include "content/renderer/media/media_stream_audio_track.h"
|
| -#include "media/base/audio_bus.h"
|
| -#include "media/base/audio_parameters.h"
|
| -#include "testing/gtest/include/gtest/gtest.h"
|
| -#include "third_party/WebKit/public/platform/WebString.h"
|
| -#include "third_party/WebKit/public/web/WebHeap.h"
|
| -
|
| -namespace content {
|
| -
|
| -namespace {
|
| -
|
| -constexpr int kSampleRate = 8000;
|
| -constexpr int kBufferSize = kSampleRate / 100;
|
| -
|
| -// The maximum integer that can be exactly represented by the float data type.
|
| -constexpr int kMaxValueSafelyConvertableToFloat = 1 << 24;
|
| -
|
| -// A simple MediaStreamAudioSource that spawns a real-time audio thread and
|
| -// emits audio samples with monotonically-increasing sample values. Includes
|
| -// hooks for the unit tests to confirm lifecycle status and to change audio
|
| -// format.
|
| -class FakeMediaStreamAudioSource
|
| - : public MediaStreamAudioSource,
|
| - public base::PlatformThread::Delegate {
|
| - public:
|
| - FakeMediaStreamAudioSource()
|
| - : MediaStreamAudioSource(true), stop_event_(true, false),
|
| - next_buffer_size_(kBufferSize), sample_count_(0) {}
|
| -
|
| - ~FakeMediaStreamAudioSource() final {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - EnsureSourceIsStopped();
|
| - }
|
| -
|
| - bool was_started() const {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - return !thread_.is_null();
|
| - }
|
| -
|
| - bool was_stopped() const {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - return stop_event_.IsSignaled();
|
| - }
|
| -
|
| - void SetBufferSize(int new_buffer_size) {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - base::subtle::NoBarrier_Store(&next_buffer_size_, new_buffer_size);
|
| - }
|
| -
|
| - protected:
|
| - bool EnsureSourceIsStarted() final {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - if (was_started())
|
| - return true;
|
| - if (was_stopped())
|
| - return false;
|
| - base::PlatformThread::CreateWithPriority(
|
| - 0, this, &thread_, base::ThreadPriority::REALTIME_AUDIO);
|
| - return true;
|
| - }
|
| -
|
| - void EnsureSourceIsStopped() final {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - if (was_stopped())
|
| - return;
|
| - stop_event_.Signal();
|
| - if (was_started())
|
| - base::PlatformThread::Join(thread_);
|
| - }
|
| -
|
| - void ThreadMain() override {
|
| - while (!stop_event_.IsSignaled()) {
|
| - // If needed, notify of the new format and re-create |audio_bus_|.
|
| - const int buffer_size = base::subtle::NoBarrier_Load(&next_buffer_size_);
|
| - if (!audio_bus_ || audio_bus_->frames() != buffer_size) {
|
| - MediaStreamAudioSource::SetFormat(media::AudioParameters(
|
| - media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
|
| - media::CHANNEL_LAYOUT_MONO, kSampleRate, 16, buffer_size));
|
| - audio_bus_ = media::AudioBus::Create(1, buffer_size);
|
| - }
|
| -
|
| - // Deliver the next chunk of audio data. Each sample value is its offset
|
| - // from the very first sample.
|
| - float* const data = audio_bus_->channel(0);
|
| - for (int i = 0; i < buffer_size; ++i)
|
| - data[i] = ++sample_count_;
|
| - CHECK_LT(sample_count_, kMaxValueSafelyConvertableToFloat);
|
| - MediaStreamAudioSource::DeliverDataToTracks(*audio_bus_,
|
| - base::TimeTicks::Now());
|
| -
|
| - // Sleep before producing the next chunk of audio.
|
| - base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds(
|
| - base::Time::kMicrosecondsPerSecond * buffer_size / kSampleRate));
|
| - }
|
| - }
|
| -
|
| - private:
|
| - base::ThreadChecker main_thread_checker_;
|
| -
|
| - base::PlatformThreadHandle thread_;
|
| - mutable base::WaitableEvent stop_event_;
|
| -
|
| - base::subtle::Atomic32 next_buffer_size_;
|
| - std::unique_ptr<media::AudioBus> audio_bus_;
|
| - int sample_count_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(FakeMediaStreamAudioSource);
|
| -};
|
| -
|
| -// A simple MediaStreamAudioSink that consumes audio and confirms the sample
|
| -// values. Includes hooks for the unit tests to monitor the format and flow of
|
| -// audio, whether the audio is silent, and the propagation of the "enabled"
|
| -// state.
|
| -class FakeMediaStreamAudioSink : public MediaStreamAudioSink {
|
| - public:
|
| - enum EnableState {
|
| - NO_ENABLE_NOTIFICATION,
|
| - WAS_ENABLED,
|
| - WAS_DISABLED
|
| - };
|
| -
|
| - FakeMediaStreamAudioSink()
|
| - : MediaStreamAudioSink(), expected_sample_count_(-1),
|
| - num_on_data_calls_(0), audio_is_silent_(true), was_ended_(false),
|
| - enable_state_(NO_ENABLE_NOTIFICATION) {}
|
| -
|
| - ~FakeMediaStreamAudioSink() final {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - }
|
| -
|
| - media::AudioParameters params() const {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - base::AutoLock auto_lock(params_lock_);
|
| - return params_;
|
| - }
|
| -
|
| - int num_on_data_calls() const {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - return base::subtle::NoBarrier_Load(&num_on_data_calls_);
|
| - }
|
| -
|
| - bool is_audio_silent() const {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - return !!base::subtle::NoBarrier_Load(&audio_is_silent_);
|
| - }
|
| -
|
| - bool was_ended() const {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - return was_ended_;
|
| - }
|
| -
|
| - EnableState enable_state() const {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - return enable_state_;
|
| - }
|
| -
|
| - void OnSetFormat(const media::AudioParameters& params) final {
|
| - ASSERT_TRUE(params.IsValid());
|
| - base::AutoLock auto_lock(params_lock_);
|
| - params_ = params;
|
| - }
|
| -
|
| - void OnData(const media::AudioBus& audio_bus,
|
| - base::TimeTicks estimated_capture_time) final {
|
| - ASSERT_TRUE(params_.IsValid());
|
| - ASSERT_FALSE(was_ended_);
|
| -
|
| - ASSERT_EQ(params_.channels(), audio_bus.channels());
|
| - ASSERT_EQ(params_.frames_per_buffer(), audio_bus.frames());
|
| - if (audio_bus.AreFramesZero()) {
|
| - base::subtle::NoBarrier_Store(&audio_is_silent_, 1);
|
| - expected_sample_count_ = -1; // Reset for when audio comes back.
|
| - } else {
|
| - base::subtle::NoBarrier_Store(&audio_is_silent_, 0);
|
| - const float* const data = audio_bus.channel(0);
|
| - if (expected_sample_count_ == -1)
|
| - expected_sample_count_ = static_cast<int64_t>(data[0]);
|
| - CHECK_LE(expected_sample_count_ + audio_bus.frames(),
|
| - kMaxValueSafelyConvertableToFloat);
|
| - for (int i = 0; i < audio_bus.frames(); ++i) {
|
| - const float expected_sample_value = expected_sample_count_;
|
| - ASSERT_EQ(expected_sample_value, data[i]);
|
| - ++expected_sample_count_;
|
| - }
|
| - }
|
| -
|
| - ASSERT_TRUE(!estimated_capture_time.is_null());
|
| - ASSERT_LT(last_estimated_capture_time_, estimated_capture_time);
|
| - last_estimated_capture_time_ = estimated_capture_time;
|
| -
|
| - base::subtle::NoBarrier_AtomicIncrement(&num_on_data_calls_, 1);
|
| - }
|
| -
|
| - void OnReadyStateChanged(
|
| - blink::WebMediaStreamSource::ReadyState state) final {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - if (state == blink::WebMediaStreamSource::ReadyStateEnded)
|
| - was_ended_ = true;
|
| - }
|
| -
|
| - void OnEnabledChanged(bool enabled) final {
|
| - CHECK(main_thread_checker_.CalledOnValidThread());
|
| - enable_state_ = enabled ? WAS_ENABLED : WAS_DISABLED;
|
| - }
|
| -
|
| - private:
|
| - base::ThreadChecker main_thread_checker_;
|
| -
|
| - mutable base::Lock params_lock_;
|
| - media::AudioParameters params_;
|
| - int expected_sample_count_;
|
| - base::TimeTicks last_estimated_capture_time_;
|
| - base::subtle::Atomic32 num_on_data_calls_;
|
| - base::subtle::Atomic32 audio_is_silent_;
|
| - bool was_ended_;
|
| - EnableState enable_state_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(FakeMediaStreamAudioSink);
|
| -};
|
| -
|
| -} // namespace
|
| -
|
| -class MediaStreamAudioTest : public ::testing::Test {
|
| - protected:
|
| - void SetUp() override {
|
| - blink_audio_source_.initialize(blink::WebString::fromUTF8("audio_id"),
|
| - blink::WebMediaStreamSource::TypeAudio,
|
| - blink::WebString::fromUTF8("audio_track"),
|
| - false /* remote */);
|
| - blink_audio_track_.initialize(blink_audio_source_.id(),
|
| - blink_audio_source_);
|
| - }
|
| -
|
| - void TearDown() override {
|
| - blink_audio_track_.reset();
|
| - blink_audio_source_.reset();
|
| - blink::WebHeap::collectAllGarbageForTesting();
|
| - }
|
| -
|
| - FakeMediaStreamAudioSource* source() const {
|
| - return static_cast<FakeMediaStreamAudioSource*>(
|
| - MediaStreamAudioSource::From(blink_audio_source_));
|
| - }
|
| -
|
| - MediaStreamAudioTrack* track() const {
|
| - return MediaStreamAudioTrack::From(blink_audio_track_);
|
| - }
|
| -
|
| - blink::WebMediaStreamSource blink_audio_source_;
|
| - blink::WebMediaStreamTrack blink_audio_track_;
|
| -};
|
| -
|
| -// Tests that a simple source-->track-->sink connection and audio data flow
|
| -// works.
|
| -TEST_F(MediaStreamAudioTest, BasicUsage) {
|
| - // Create the source, but it should not be started yet.
|
| - ASSERT_FALSE(source());
|
| - blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource());
|
| - ASSERT_TRUE(source());
|
| - EXPECT_FALSE(source()->was_started());
|
| - EXPECT_FALSE(source()->was_stopped());
|
| -
|
| - // Connect a track to the source. This should auto-start the source.
|
| - ASSERT_FALSE(track());
|
| - EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_));
|
| - ASSERT_TRUE(track());
|
| - EXPECT_TRUE(source()->was_started());
|
| - EXPECT_FALSE(source()->was_stopped());
|
| -
|
| - // Connect a sink to the track. This should begin audio flow to the
|
| - // sink. Wait and confirm that three OnData() calls were made from the audio
|
| - // thread.
|
| - FakeMediaStreamAudioSink sink;
|
| - EXPECT_FALSE(sink.was_ended());
|
| - track()->AddSink(&sink);
|
| - const int start_count = sink.num_on_data_calls();
|
| - while (sink.num_on_data_calls() - start_count < 3)
|
| - base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| -
|
| - // Check that the audio parameters propagated to the track and sink.
|
| - const media::AudioParameters expected_params(
|
| - media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
|
| - kSampleRate, 16, kBufferSize);
|
| - EXPECT_TRUE(expected_params.Equals(track()->GetOutputFormat()));
|
| - EXPECT_TRUE(expected_params.Equals(sink.params()));
|
| -
|
| - // Stop the track. Since this was the last track connected to the source, the
|
| - // source should automatically stop. In addition, the sink should receive a
|
| - // ReadyStateEnded notification.
|
| - track()->Stop();
|
| - EXPECT_TRUE(source()->was_started());
|
| - EXPECT_TRUE(source()->was_stopped());
|
| - EXPECT_TRUE(sink.was_ended());
|
| -
|
| - track()->RemoveSink(&sink);
|
| -}
|
| -
|
| -// Tests that "ended" tracks can be connected after the source has stopped.
|
| -TEST_F(MediaStreamAudioTest, ConnectTrackAfterSourceStopped) {
|
| - // Create the source, connect one track, and stop it. This should
|
| - // automatically stop the source.
|
| - blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource());
|
| - ASSERT_TRUE(source());
|
| - EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_));
|
| - track()->Stop();
|
| - EXPECT_TRUE(source()->was_started());
|
| - EXPECT_TRUE(source()->was_stopped());
|
| -
|
| - // Now, connect another track. ConnectToTrack() will return false, but there
|
| - // should be a MediaStreamAudioTrack instance created and owned by the
|
| - // blink::WebMediaStreamTrack.
|
| - blink::WebMediaStreamTrack another_blink_track;
|
| - another_blink_track.initialize(blink_audio_source_.id(), blink_audio_source_);
|
| - EXPECT_FALSE(MediaStreamAudioTrack::From(another_blink_track));
|
| - EXPECT_FALSE(source()->ConnectToTrack(another_blink_track));
|
| - EXPECT_TRUE(MediaStreamAudioTrack::From(another_blink_track));
|
| -}
|
| -
|
| -// Tests that a sink is immediately "ended" when connected to a stopped track.
|
| -TEST_F(MediaStreamAudioTest, AddSinkToStoppedTrack) {
|
| - // Create a track and stop it. Then, when adding a sink, the sink should get
|
| - // the ReadyStateEnded notification immediately.
|
| - MediaStreamAudioTrack track(true);
|
| - track.Stop();
|
| - FakeMediaStreamAudioSink sink;
|
| - EXPECT_FALSE(sink.was_ended());
|
| - track.AddSink(&sink);
|
| - EXPECT_TRUE(sink.was_ended());
|
| - EXPECT_EQ(0, sink.num_on_data_calls());
|
| - track.RemoveSink(&sink);
|
| -}
|
| -
|
| -// Tests that audio format changes at the source propagate to the track and
|
| -// sink.
|
| -TEST_F(MediaStreamAudioTest, FormatChangesPropagate) {
|
| - // Create a source, connect it to track, and connect the track to a
|
| - // sink.
|
| - blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource());
|
| - ASSERT_TRUE(source());
|
| - EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_));
|
| - ASSERT_TRUE(track());
|
| - FakeMediaStreamAudioSink sink;
|
| - ASSERT_TRUE(!sink.params().IsValid());
|
| - track()->AddSink(&sink);
|
| -
|
| - // Wait until valid parameters are propagated to the sink, and then confirm
|
| - // the parameters are correct at the track and the sink.
|
| - while (!sink.params().IsValid())
|
| - base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| - const media::AudioParameters expected_params(
|
| - media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
|
| - kSampleRate, 16, kBufferSize);
|
| - EXPECT_TRUE(expected_params.Equals(track()->GetOutputFormat()));
|
| - EXPECT_TRUE(expected_params.Equals(sink.params()));
|
| -
|
| - // Now, trigger a format change by doubling the buffer size.
|
| - source()->SetBufferSize(kBufferSize * 2);
|
| -
|
| - // Wait until the new buffer size propagates to the sink.
|
| - while (sink.params().frames_per_buffer() == kBufferSize)
|
| - base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| - EXPECT_EQ(kBufferSize * 2, track()->GetOutputFormat().frames_per_buffer());
|
| - EXPECT_EQ(kBufferSize * 2, sink.params().frames_per_buffer());
|
| -
|
| - track()->RemoveSink(&sink);
|
| -}
|
| -
|
| -// Tests that tracks deliver audio when enabled and silent audio when
|
| -// disabled. Whenever a track is enabled or disabled, the sink's
|
| -// OnEnabledChanged() method should be called.
|
| -TEST_F(MediaStreamAudioTest, EnableAndDisableTracks) {
|
| - // Create a source and connect it to track.
|
| - blink_audio_source_.setExtraData(new FakeMediaStreamAudioSource());
|
| - ASSERT_TRUE(source());
|
| - EXPECT_TRUE(source()->ConnectToTrack(blink_audio_track_));
|
| - ASSERT_TRUE(track());
|
| -
|
| - // Connect the track to a sink and expect the sink to be notified that the
|
| - // track is enabled.
|
| - FakeMediaStreamAudioSink sink;
|
| - EXPECT_TRUE(sink.is_audio_silent());
|
| - EXPECT_EQ(FakeMediaStreamAudioSink::NO_ENABLE_NOTIFICATION,
|
| - sink.enable_state());
|
| - track()->AddSink(&sink);
|
| - EXPECT_EQ(FakeMediaStreamAudioSink::WAS_ENABLED, sink.enable_state());
|
| -
|
| - // Wait until non-silent audio reaches the sink.
|
| - while (sink.is_audio_silent())
|
| - base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| -
|
| - // Now, disable the track and expect the sink to be notified.
|
| - track()->SetEnabled(false);
|
| - EXPECT_EQ(FakeMediaStreamAudioSink::WAS_DISABLED, sink.enable_state());
|
| -
|
| - // Wait until silent audio reaches the sink.
|
| - while (!sink.is_audio_silent())
|
| - base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| -
|
| - // Create a second track and a second sink, but this time the track starts out
|
| - // disabled. Expect the sink to be notified at the start that the track is
|
| - // disabled.
|
| - blink::WebMediaStreamTrack another_blink_track;
|
| - another_blink_track.initialize(blink_audio_source_.id(), blink_audio_source_);
|
| - EXPECT_TRUE(source()->ConnectToTrack(another_blink_track));
|
| - MediaStreamAudioTrack::From(another_blink_track)->SetEnabled(false);
|
| - FakeMediaStreamAudioSink another_sink;
|
| - MediaStreamAudioTrack::From(another_blink_track)->AddSink(&another_sink);
|
| - EXPECT_EQ(FakeMediaStreamAudioSink::WAS_DISABLED,
|
| - another_sink.enable_state());
|
| -
|
| - // Wait until OnData() is called on the second sink. Expect the audio to be
|
| - // silent.
|
| - const int start_count = another_sink.num_on_data_calls();
|
| - while (another_sink.num_on_data_calls() == start_count)
|
| - base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| - EXPECT_TRUE(another_sink.is_audio_silent());
|
| -
|
| - // Now, enable the second track and expect the second sink to be notified.
|
| - MediaStreamAudioTrack::From(another_blink_track)->SetEnabled(true);
|
| - EXPECT_EQ(FakeMediaStreamAudioSink::WAS_ENABLED, another_sink.enable_state());
|
| -
|
| - // Wait until non-silent audio reaches the second sink.
|
| - while (another_sink.is_audio_silent())
|
| - base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
|
| -
|
| - // The first track and sink should not have been affected by changing the
|
| - // enabled state of the second track and sink. They should still be disabled,
|
| - // with silent audio being consumed at the sink.
|
| - EXPECT_EQ(FakeMediaStreamAudioSink::WAS_DISABLED, sink.enable_state());
|
| - EXPECT_TRUE(sink.is_audio_silent());
|
| -
|
| - MediaStreamAudioTrack::From(another_blink_track)->RemoveSink(&another_sink);
|
| - track()->RemoveSink(&sink);
|
| -}
|
| -
|
| -} // namespace content
|
|
|