| // 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 <memory> |
| #include <string> |
| |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop.h" |
| #include "build/build_config.h" |
| #include "content/public/renderer/media_stream_audio_sink.h" |
| #include "content/renderer/media/media_stream_audio_track.h" |
| #include "content/renderer/media/mock_audio_device_factory.h" |
| #include "content/renderer/media/mock_constraint_factory.h" |
| #include "content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h" |
| #include "content/renderer/media/webrtc/processed_local_audio_source.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_parameters.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/platform/WebMediaConstraints.h" |
| #include "third_party/WebKit/public/web/WebHeap.h" |
| |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::Invoke; |
| using ::testing::WithArg; |
| |
| namespace content { |
| |
| namespace { |
| |
| // Audio parameters for the VerifyAudioFlowWithoutAudioProcessing test. |
| constexpr int kSampleRate = 48000; |
| constexpr media::ChannelLayout kChannelLayout = media::CHANNEL_LAYOUT_STEREO; |
| constexpr int kRequestedBufferSize = 512; |
| |
| // On Android, ProcessedLocalAudioSource forces a 20ms buffer size from the |
| // input device. |
| #if defined(OS_ANDROID) |
| constexpr int kExpectedSourceBufferSize = kSampleRate / 50; |
| #else |
| constexpr int kExpectedSourceBufferSize = kRequestedBufferSize; |
| #endif |
| |
| // On both platforms, even though audio processing is turned off, the |
| // MediaStreamAudioProcessor will force the use of 10ms buffer sizes on the |
| // output end of its FIFO. |
| constexpr int kExpectedOutputBufferSize = kSampleRate / 100; |
| |
| class MockMediaStreamAudioSink : public MediaStreamAudioSink { |
| public: |
| MockMediaStreamAudioSink() {} |
| ~MockMediaStreamAudioSink() override {} |
| |
| void OnData(const media::AudioBus& audio_bus, |
| base::TimeTicks estimated_capture_time) override { |
| EXPECT_EQ(audio_bus.channels(), params_.channels()); |
| EXPECT_EQ(audio_bus.frames(), params_.frames_per_buffer()); |
| EXPECT_FALSE(estimated_capture_time.is_null()); |
| OnDataCallback(); |
| } |
| MOCK_METHOD0(OnDataCallback, void()); |
| |
| void OnSetFormat(const media::AudioParameters& params) override { |
| params_ = params; |
| FormatIsSet(params_); |
| } |
| MOCK_METHOD1(FormatIsSet, void(const media::AudioParameters& params)); |
| |
| private: |
| media::AudioParameters params_; |
| }; |
| |
| } // namespace |
| |
| class ProcessedLocalAudioSourceTest : public testing::Test { |
| protected: |
| ProcessedLocalAudioSourceTest() {} |
| |
| ~ProcessedLocalAudioSourceTest() override {} |
| |
| void SetUp() override { |
| blink_audio_source_.initialize(blink::WebString::fromUTF8("audio_label"), |
| blink::WebMediaStreamSource::TypeAudio, |
| blink::WebString::fromUTF8("audio_track")); |
| 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(); |
| } |
| |
| void CreateProcessedLocalAudioSource( |
| const blink::WebMediaConstraints& constraints) { |
| ProcessedLocalAudioSource* const source = new ProcessedLocalAudioSource( |
| -1 /* consumer_render_frame_id is N/A for non-browser tests */, |
| StreamDeviceInfo(MEDIA_DEVICE_AUDIO_CAPTURE, "Mock audio device", |
| "mock_audio_device_id", kSampleRate, kChannelLayout, |
| kRequestedBufferSize), |
| constraints, |
| base::Bind(&ProcessedLocalAudioSourceTest::OnAudioSourceStarted, |
| base::Unretained(this)), |
| &mock_dependency_factory_); |
| source->SetAllowInvalidRenderFrameIdForTesting(true); |
| blink_audio_source_.setExtraData(source); // Takes ownership. |
| } |
| |
| void CheckSourceFormatMatches(const media::AudioParameters& params) { |
| EXPECT_EQ(kSampleRate, params.sample_rate()); |
| EXPECT_EQ(kChannelLayout, params.channel_layout()); |
| EXPECT_EQ(kExpectedSourceBufferSize, params.frames_per_buffer()); |
| } |
| |
| void CheckOutputFormatMatches(const media::AudioParameters& params) { |
| EXPECT_EQ(kSampleRate, params.sample_rate()); |
| EXPECT_EQ(kChannelLayout, params.channel_layout()); |
| EXPECT_EQ(kExpectedOutputBufferSize, params.frames_per_buffer()); |
| } |
| |
| MockAudioDeviceFactory* mock_audio_device_factory() { |
| return &mock_audio_device_factory_; |
| } |
| |
| media::AudioCapturerSource::CaptureCallback* capture_source_callback() const { |
| return static_cast<media::AudioCapturerSource::CaptureCallback*>( |
| ProcessedLocalAudioSource::From(audio_source())); |
| } |
| |
| MediaStreamAudioSource* audio_source() const { |
| return MediaStreamAudioSource::From(blink_audio_source_); |
| } |
| |
| const blink::WebMediaStreamTrack& blink_audio_track() { |
| return blink_audio_track_; |
| } |
| |
| void OnAudioSourceStarted(MediaStreamSource* source, |
| MediaStreamRequestResult result, |
| const blink::WebString& result_name) {} |
| |
| private: |
| base::MessageLoop main_thread_message_loop_; // Needed for MSAudioProcessor. |
| MockAudioDeviceFactory mock_audio_device_factory_; |
| MockPeerConnectionDependencyFactory mock_dependency_factory_; |
| blink::WebMediaStreamSource blink_audio_source_; |
| blink::WebMediaStreamTrack blink_audio_track_; |
| }; |
| |
| // Tests a basic end-to-end start-up, track+sink connections, audio flow, and |
| // shut-down. The unit tests in media_stream_audio_unittest.cc provide more |
| // comprehensive testing of the object graph connections and multi-threading |
| // concerns. |
| TEST_F(ProcessedLocalAudioSourceTest, VerifyAudioFlowWithoutAudioProcessing) { |
| using ThisTest = |
| ProcessedLocalAudioSourceTest_VerifyAudioFlowWithoutAudioProcessing_Test; |
| |
| // Turn off the default constraints so the sink will get audio in chunks of |
| // the native buffer size. |
| MockConstraintFactory constraint_factory; |
| constraint_factory.DisableDefaultAudioConstraints(); |
| |
| CreateProcessedLocalAudioSource( |
| constraint_factory.CreateWebMediaConstraints()); |
| |
| // Connect the track, and expect the MockCapturerSource to be initialized and |
| // started by ProcessedLocalAudioSource. |
| EXPECT_CALL(*mock_audio_device_factory()->mock_capturer_source(), |
| Initialize(_, capture_source_callback(), -1)) |
| .WillOnce(WithArg<0>(Invoke(this, &ThisTest::CheckSourceFormatMatches))); |
| EXPECT_CALL(*mock_audio_device_factory()->mock_capturer_source(), |
| SetAutomaticGainControl(true)); |
| EXPECT_CALL(*mock_audio_device_factory()->mock_capturer_source(), Start()) |
| .WillOnce(Invoke( |
| capture_source_callback(), |
| &media::AudioCapturerSource::CaptureCallback::OnCaptureStarted)); |
| ASSERT_TRUE(audio_source()->ConnectToTrack(blink_audio_track())); |
| CheckOutputFormatMatches(audio_source()->GetAudioParameters()); |
| |
| // Connect a sink to the track. |
| std::unique_ptr<MockMediaStreamAudioSink> sink( |
| new MockMediaStreamAudioSink()); |
| EXPECT_CALL(*sink, FormatIsSet(_)) |
| .WillOnce(Invoke(this, &ThisTest::CheckOutputFormatMatches)); |
| MediaStreamAudioTrack::From(blink_audio_track())->AddSink(sink.get()); |
| |
| // Feed audio data into the ProcessedLocalAudioSource and expect it to reach |
| // the sink. |
| int delay_ms = 65; |
| bool key_pressed = true; |
| double volume = 0.9; |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(2, kExpectedSourceBufferSize); |
| audio_bus->Zero(); |
| EXPECT_CALL(*sink, OnDataCallback()).Times(AtLeast(1)); |
| capture_source_callback()->Capture(audio_bus.get(), delay_ms, volume, |
| key_pressed); |
| |
| // Expect the ProcessedLocalAudioSource to auto-stop the MockCapturerSource |
| // when the track is stopped. |
| EXPECT_CALL(*mock_audio_device_factory()->mock_capturer_source(), Stop()); |
| MediaStreamAudioTrack::From(blink_audio_track())->Stop(); |
| } |
| |
| // Tests that the source is not started when invalid audio constraints are |
| // present. |
| TEST_F(ProcessedLocalAudioSourceTest, FailToStartWithWrongConstraints) { |
| MockConstraintFactory constraint_factory; |
| const std::string dummy_constraint = "dummy"; |
| // Set a non-audio constraint. |
| constraint_factory.basic().width.setExact(240); |
| |
| CreateProcessedLocalAudioSource( |
| constraint_factory.CreateWebMediaConstraints()); |
| |
| // Expect the MockCapturerSource is never initialized/started and the |
| // ConnectToTrack() operation fails due to the invalid constraint. |
| EXPECT_CALL(*mock_audio_device_factory()->mock_capturer_source(), |
| Initialize(_, capture_source_callback(), -1)) |
| .Times(0); |
| EXPECT_CALL(*mock_audio_device_factory()->mock_capturer_source(), |
| SetAutomaticGainControl(true)).Times(0); |
| EXPECT_CALL(*mock_audio_device_factory()->mock_capturer_source(), Start()) |
| .Times(0); |
| EXPECT_FALSE(audio_source()->ConnectToTrack(blink_audio_track())); |
| |
| // Even though ConnectToTrack() failed, there should still have been a new |
| // MediaStreamAudioTrack instance created, owned by the |
| // blink::WebMediaStreamTrack. |
| EXPECT_TRUE(MediaStreamAudioTrack::From(blink_audio_track())); |
| } |
| |
| // TODO(miu): There's a lot of logic in ProcessedLocalAudioSource around |
| // constraints processing and validation that should have unit testing. |
| |
| } // namespace content |