| // Copyright 2014 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 "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/child/child_process.h" |
| #include "content/public/renderer/media_stream_video_sink.h" |
| #include "content/renderer/media/media_stream_video_capturer_source.h" |
| #include "content/renderer/media/media_stream_video_track.h" |
| #include "content/renderer/media/mock_media_constraint_factory.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/web/WebHeap.h" |
| |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::WithArgs; |
| |
| namespace content { |
| |
| class MockVideoCapturerSource : public media::VideoCapturerSource { |
| public: |
| MockVideoCapturerSource() { |
| ON_CALL(*this, GetCurrentSupportedFormats(_, _, _, _)) |
| .WillByDefault(WithArgs<3>( |
| Invoke(this, &MockVideoCapturerSource::EnumerateDeviceFormats))); |
| } |
| |
| MOCK_METHOD4(GetCurrentSupportedFormats, |
| void(int max_requested_width, |
| int max_requested_height, |
| double max_requested_frame_rate, |
| const VideoCaptureDeviceFormatsCB& callback)); |
| MOCK_METHOD3(StartCapture, |
| void(const media::VideoCaptureParams& params, |
| const VideoCaptureDeliverFrameCB& new_frame_callback, |
| const RunningCallback& running_callback)); |
| MOCK_METHOD0(StopCapture, void()); |
| |
| void EnumerateDeviceFormats(const VideoCaptureDeviceFormatsCB& callback) { |
| media::VideoCaptureFormat kFormatSmall(gfx::Size(640, 480), 30.0, |
| media::PIXEL_FORMAT_I420); |
| media::VideoCaptureFormat kFormatLarge(gfx::Size(1920, 1080), 30.0, |
| media::PIXEL_FORMAT_I420); |
| media::VideoCaptureFormats formats; |
| formats.push_back(kFormatSmall); |
| formats.push_back(kFormatLarge); |
| callback.Run(formats); |
| } |
| |
| }; |
| |
| class MediaStreamVideoCapturerSourceTest : public testing::Test { |
| public: |
| MediaStreamVideoCapturerSourceTest() |
| : child_process_(new ChildProcess()), |
| source_(nullptr), |
| delegate_(nullptr), |
| source_stopped_(false) {} |
| |
| void TearDown() override { |
| webkit_source_.reset(); |
| blink::WebHeap::collectAllGarbageForTesting(); |
| } |
| |
| void InitWithDeviceInfo(const StreamDeviceInfo& device_info) { |
| scoped_ptr<MockVideoCapturerSource> delegate(new MockVideoCapturerSource()); |
| delegate_ = delegate.get(); |
| source_ = new MediaStreamVideoCapturerSource( |
| base::Bind(&MediaStreamVideoCapturerSourceTest::OnSourceStopped, |
| base::Unretained(this)), |
| delegate.Pass()); |
| source_->SetDeviceInfo(device_info); |
| |
| webkit_source_.initialize(base::UTF8ToUTF16("dummy_source_id"), |
| blink::WebMediaStreamSource::TypeVideo, |
| base::UTF8ToUTF16("dummy_source_name"), |
| false /* remote */, |
| true /* readonly */); |
| webkit_source_.setExtraData(source_); |
| webkit_source_id_ = webkit_source_.id(); |
| } |
| |
| MockMediaConstraintFactory* constraint_factory() { |
| return &constraint_factory_; |
| } |
| |
| blink::WebMediaStreamTrack StartSource() { |
| bool enabled = true; |
| // CreateVideoTrack will trigger OnConstraintsApplied. |
| return MediaStreamVideoTrack::CreateVideoTrack( |
| source_, constraint_factory_.CreateWebMediaConstraints(), |
| base::Bind(&MediaStreamVideoCapturerSourceTest::OnConstraintsApplied, |
| base::Unretained(this)), |
| enabled); |
| } |
| |
| MockVideoCapturerSource& mock_delegate() { return *delegate_; } |
| |
| const char* GetPowerLineFrequencyForTesting() const { |
| return source_->GetPowerLineFrequencyForTesting(); |
| } |
| |
| void OnSourceStopped(const blink::WebMediaStreamSource& source) { |
| source_stopped_ = true; |
| EXPECT_EQ(source.id(), webkit_source_id_); |
| } |
| void OnStarted(bool result) { source_->OnStarted(result); } |
| |
| protected: |
| void OnConstraintsApplied(MediaStreamSource* source, |
| MediaStreamRequestResult result, |
| const blink::WebString& result_name) {} |
| |
| // A ChildProcess and a MessageLoopForUI are both needed to fool the Tracks |
| // and Sources below into believing they are on the right threads. |
| base::MessageLoopForUI message_loop_; |
| scoped_ptr<ChildProcess> child_process_; |
| |
| blink::WebMediaStreamSource webkit_source_; |
| MediaStreamVideoCapturerSource* source_; // owned by |webkit_source_|. |
| MockVideoCapturerSource* delegate_; // owned by |source|. |
| blink::WebString webkit_source_id_; |
| bool source_stopped_; |
| MockMediaConstraintFactory constraint_factory_; |
| }; |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, TabCaptureFixedResolutionByDefault) { |
| StreamDeviceInfo device_info; |
| device_info.device.type = MEDIA_TAB_VIDEO_CAPTURE; |
| InitWithDeviceInfo(device_info); |
| |
| // No constraints are being provided to the implementation, so expect only |
| // default values. |
| media::VideoCaptureParams expected_params; |
| expected_params.requested_format.frame_size.SetSize( |
| MediaStreamVideoSource::kDefaultWidth, |
| MediaStreamVideoSource::kDefaultHeight); |
| expected_params.requested_format.frame_rate = |
| MediaStreamVideoSource::kDefaultFrameRate; |
| expected_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
| expected_params.resolution_change_policy = |
| media::RESOLUTION_POLICY_FIXED_RESOLUTION; |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL(mock_delegate(), StartCapture(expected_params, _, _)); |
| blink::WebMediaStreamTrack track = StartSource(); |
| // When the track goes out of scope, the source will be stopped. |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| } |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, |
| DesktopCaptureAllowAnyResolutionChangeByDefault) { |
| StreamDeviceInfo device_info; |
| device_info.device.type = MEDIA_DESKTOP_VIDEO_CAPTURE; |
| InitWithDeviceInfo(device_info); |
| |
| // No constraints are being provided to the implementation, so expect only |
| // default values. |
| media::VideoCaptureParams expected_params; |
| expected_params.requested_format.frame_size.SetSize( |
| MediaStreamVideoSource::kDefaultWidth, |
| MediaStreamVideoSource::kDefaultHeight); |
| expected_params.requested_format.frame_rate = |
| MediaStreamVideoSource::kDefaultFrameRate; |
| expected_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
| expected_params.resolution_change_policy = |
| media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT; |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL(mock_delegate(), StartCapture(expected_params, _, _)); |
| blink::WebMediaStreamTrack track = StartSource(); |
| // When the track goes out of scope, the source will be stopped. |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| } |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, |
| TabCaptureConstraintsImplyFixedAspectRatio) { |
| StreamDeviceInfo device_info; |
| device_info.device.type = MEDIA_TAB_VIDEO_CAPTURE; |
| InitWithDeviceInfo(device_info); |
| |
| // Specify max and min size constraints that have the same ~16:9 aspect ratio. |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMaxWidth, 1920); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMaxHeight, 1080); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMinWidth, 854); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMinHeight, 480); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMaxFrameRate, |
| 60.0); |
| |
| media::VideoCaptureParams expected_params; |
| expected_params.requested_format.frame_size.SetSize(1920, 1080); |
| expected_params.requested_format.frame_rate = 60.0; |
| expected_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
| expected_params.resolution_change_policy = |
| media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO; |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL( |
| mock_delegate(), |
| StartCapture( |
| testing::Field(&media::VideoCaptureParams::resolution_change_policy, |
| media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO), |
| _, _)) |
| ; |
| blink::WebMediaStreamTrack track = StartSource(); |
| // When the track goes out of scope, the source will be stopped. |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| } |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, |
| TabCaptureConstraintsImplyAllowingAnyResolutionChange) { |
| StreamDeviceInfo device_info; |
| device_info.device.type = MEDIA_TAB_VIDEO_CAPTURE; |
| InitWithDeviceInfo(device_info); |
| |
| // Specify max and min size constraints with different aspect ratios. |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMaxWidth, 1920); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMaxHeight, 1080); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMinWidth, 0); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMinHeight, 0); |
| constraint_factory()->AddMandatory(MediaStreamVideoSource::kMaxFrameRate, |
| 60.0); |
| |
| media::VideoCaptureParams expected_params; |
| expected_params.requested_format.frame_size.SetSize(1920, 1080); |
| expected_params.requested_format.frame_rate = 60.0; |
| expected_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
| expected_params.resolution_change_policy = |
| media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT; |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL( |
| mock_delegate(), |
| StartCapture( |
| testing::Field(&media::VideoCaptureParams::resolution_change_policy, |
| media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT), |
| _, _)) |
| ; |
| blink::WebMediaStreamTrack track = StartSource(); |
| // When the track goes out of scope, the source will be stopped. |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| } |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, |
| DeviceCaptureConstraintsSupportPowerLineFrequency) { |
| for (int frequency = -100; frequency < 100; ++frequency) { |
| StreamDeviceInfo device_info; |
| device_info.device.type = MEDIA_DEVICE_VIDEO_CAPTURE; |
| InitWithDeviceInfo(device_info); |
| constraint_factory_ = MockMediaConstraintFactory(); |
| |
| constraint_factory()->AddOptional(GetPowerLineFrequencyForTesting(), |
| frequency); |
| |
| media::VideoCaptureParams expected_params; |
| expected_params.requested_format.frame_size.SetSize( |
| MediaStreamVideoSource::kDefaultWidth, |
| MediaStreamVideoSource::kDefaultHeight); |
| expected_params.requested_format.frame_rate = |
| MediaStreamVideoSource::kDefaultFrameRate; |
| expected_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
| expected_params.resolution_change_policy = |
| media::RESOLUTION_POLICY_FIXED_RESOLUTION; |
| if (frequency == 50) { |
| expected_params.power_line_frequency = |
| media::PowerLineFrequency::FREQUENCY_50HZ; |
| } else if (frequency == 60) { |
| expected_params.power_line_frequency = |
| media::PowerLineFrequency::FREQUENCY_60HZ; |
| } else { |
| expected_params.power_line_frequency = |
| media::PowerLineFrequency::FREQUENCY_DEFAULT; |
| } |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL(mock_delegate(), StartCapture(expected_params, _, _)); |
| blink::WebMediaStreamTrack track = StartSource(); |
| // When the track goes out of scope, the source will be stopped. |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| } |
| } |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, |
| InvalidPowerLineFrequencyHandledProperly) { |
| // Test out other varieties of invalid input, like non-numeric strings. |
| StreamDeviceInfo device_info; |
| device_info.device.type = MEDIA_DEVICE_VIDEO_CAPTURE; |
| InitWithDeviceInfo(device_info); |
| constraint_factory_ = MockMediaConstraintFactory(); |
| |
| constraint_factory()->AddOptional(GetPowerLineFrequencyForTesting(), |
| std::string("this is not a frequency")); |
| |
| media::VideoCaptureParams expected_params; |
| expected_params.requested_format.frame_size.SetSize( |
| MediaStreamVideoSource::kDefaultWidth, |
| MediaStreamVideoSource::kDefaultHeight); |
| expected_params.requested_format.frame_rate = |
| MediaStreamVideoSource::kDefaultFrameRate; |
| expected_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; |
| expected_params.resolution_change_policy = |
| media::RESOLUTION_POLICY_FIXED_RESOLUTION; |
| // Invalid frequencies should result in default setting. |
| expected_params.power_line_frequency = |
| media::PowerLineFrequency::FREQUENCY_DEFAULT; |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL(mock_delegate(), StartCapture(expected_params, _, _)); |
| blink::WebMediaStreamTrack track = StartSource(); |
| // When the track goes out of scope, the source will be stopped. |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| } |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, Ended) { |
| scoped_ptr<MockVideoCapturerSource> delegate(new MockVideoCapturerSource()); |
| delegate_ = delegate.get(); |
| source_ = new MediaStreamVideoCapturerSource( |
| base::Bind(&MediaStreamVideoCapturerSourceTest::OnSourceStopped, |
| base::Unretained(this)), |
| delegate.Pass()); |
| webkit_source_.initialize(base::UTF8ToUTF16("dummy_source_id"), |
| blink::WebMediaStreamSource::TypeVideo, |
| base::UTF8ToUTF16("dummy_source_name"), |
| false /* remote */, true /* readonly */); |
| webkit_source_.setExtraData(source_); |
| webkit_source_id_ = webkit_source_.id(); |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL(mock_delegate(), StartCapture(_, _, _)); |
| blink::WebMediaStreamTrack track = StartSource(); |
| message_loop_.RunUntilIdle(); |
| |
| OnStarted(true); |
| message_loop_.RunUntilIdle(); |
| EXPECT_EQ(blink::WebMediaStreamSource::ReadyStateLive, |
| webkit_source_.readyState()); |
| |
| EXPECT_FALSE(source_stopped_); |
| |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| OnStarted(false); |
| message_loop_.RunUntilIdle(); |
| EXPECT_EQ(blink::WebMediaStreamSource::ReadyStateEnded, |
| webkit_source_.readyState()); |
| // Verify that MediaStreamSource::SourceStoppedCallback has been triggered. |
| EXPECT_TRUE(source_stopped_); |
| } |
| |
| class FakeMediaStreamVideoSink : public MediaStreamVideoSink { |
| public: |
| FakeMediaStreamVideoSink(base::TimeTicks* capture_time, |
| media::VideoFrameMetadata* metadata, |
| base::Closure got_frame_cb) |
| : capture_time_(capture_time), |
| metadata_(metadata), |
| got_frame_cb_(got_frame_cb) {} |
| |
| void OnVideoFrame(const scoped_refptr<media::VideoFrame>& frame, |
| base::TimeTicks capture_time) { |
| *capture_time_ = capture_time; |
| metadata_->Clear(); |
| base::DictionaryValue tmp; |
| frame->metadata()->MergeInternalValuesInto(&tmp); |
| metadata_->MergeInternalValuesFrom(tmp); |
| base::ResetAndReturn(&got_frame_cb_).Run(); |
| } |
| |
| private: |
| base::TimeTicks* const capture_time_; |
| media::VideoFrameMetadata* const metadata_; |
| base::Closure got_frame_cb_; |
| }; |
| |
| TEST_F(MediaStreamVideoCapturerSourceTest, CaptureTimeAndMetadataPlumbing) { |
| StreamDeviceInfo device_info; |
| device_info.device.type = MEDIA_DESKTOP_VIDEO_CAPTURE; |
| InitWithDeviceInfo(device_info); |
| |
| VideoCaptureDeliverFrameCB deliver_frame_cb; |
| media::VideoCapturerSource::RunningCallback running_cb; |
| |
| InSequence s; |
| EXPECT_CALL(mock_delegate(), GetCurrentSupportedFormats(_, _, _, _)); |
| EXPECT_CALL(mock_delegate(), StartCapture(_, _, _)) |
| .WillOnce(testing::DoAll(testing::SaveArg<1>(&deliver_frame_cb), |
| testing::SaveArg<2>(&running_cb))); |
| EXPECT_CALL(mock_delegate(), StopCapture()); |
| blink::WebMediaStreamTrack track = StartSource(); |
| running_cb.Run(true); |
| |
| base::RunLoop run_loop; |
| base::TimeTicks reference_capture_time = |
| base::TimeTicks::FromInternalValue(60013); |
| base::TimeTicks capture_time; |
| media::VideoFrameMetadata metadata; |
| FakeMediaStreamVideoSink fake_sink( |
| &capture_time, &metadata, |
| media::BindToCurrentLoop(run_loop.QuitClosure())); |
| FakeMediaStreamVideoSink::AddToVideoTrack( |
| &fake_sink, base::Bind(&FakeMediaStreamVideoSink::OnVideoFrame, |
| base::Unretained(&fake_sink)), |
| track); |
| const scoped_refptr<media::VideoFrame> frame = |
| media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2)); |
| frame->metadata()->SetDouble(media::VideoFrameMetadata::FRAME_RATE, 30.0); |
| child_process_->io_task_runner()->PostTask( |
| FROM_HERE, base::Bind(deliver_frame_cb, frame, reference_capture_time)); |
| run_loop.Run(); |
| FakeMediaStreamVideoSink::RemoveFromVideoTrack(&fake_sink, track); |
| EXPECT_EQ(reference_capture_time, capture_time); |
| double metadata_value; |
| EXPECT_TRUE(metadata.GetDouble(media::VideoFrameMetadata::FRAME_RATE, |
| &metadata_value)); |
| EXPECT_EQ(30.0, metadata_value); |
| } |
| |
| } // namespace content |