blob: 2d57bd7ce9da7e3cabed49e6f3b332ab997410b2 [file] [log] [blame]
// 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 "content/renderer/media/media_stream_video_capturer_source.h"
#include <utility>
#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_track.h"
#include "content/renderer/media/mock_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_METHOD0(RequestRefreshFrame, void());
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) {
std::unique_ptr<MockVideoCapturerSource> delegate(
new MockVideoCapturerSource());
delegate_ = delegate.get();
source_ = new MediaStreamVideoCapturerSource(
base::Bind(&MediaStreamVideoCapturerSourceTest::OnSourceStopped,
base::Unretained(this)),
std::move(delegate));
source_->SetDeviceInfo(device_info);
webkit_source_.initialize(blink::WebString::fromASCII("dummy_source_id"),
blink::WebMediaStreamSource::TypeVideo,
blink::WebString::fromASCII("dummy_source_name"));
webkit_source_.setExtraData(source_);
webkit_source_id_ = webkit_source_.id();
}
MockConstraintFactory* 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_->OnRunStateChanged(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_;
std::unique_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_;
MockConstraintFactory 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()->basic().width.setMax(1920);
constraint_factory()->basic().height.setMax(1080);
constraint_factory()->basic().width.setMin(854);
constraint_factory()->basic().height.setMin(480);
constraint_factory()->basic().frameRate.setMax(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()->basic().width.setMax(1920);
constraint_factory()->basic().height.setMax(1080);
constraint_factory()->basic().width.setMin(0);
constraint_factory()->basic().height.setMin(0);
constraint_factory()->basic().frameRate.setMax(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_.Reset();
constraint_factory()->AddAdvanced().googPowerLineFrequency.setExact(
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, Ended) {
std::unique_ptr<MockVideoCapturerSource> delegate(
new MockVideoCapturerSource());
delegate_ = delegate.get();
source_ = new MediaStreamVideoCapturerSource(
base::Bind(&MediaStreamVideoCapturerSourceTest::OnSourceStopped,
base::Unretained(this)),
std::move(delegate));
webkit_source_.initialize(blink::WebString::fromASCII("dummy_source_id"),
blink::WebMediaStreamSource::TypeVideo,
blink::WebString::fromASCII("dummy_source_name"));
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();
base::RunLoop().RunUntilIdle();
OnStarted(true);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::WebMediaStreamSource::ReadyStateLive,
webkit_source_.getReadyState());
EXPECT_FALSE(source_stopped_);
EXPECT_CALL(mock_delegate(), StopCapture());
OnStarted(false);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::WebMediaStreamSource::ReadyStateEnded,
webkit_source_.getReadyState());
// 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 ConnectToTrack(const blink::WebMediaStreamTrack& track) {
MediaStreamVideoSink::ConnectToTrack(
track, base::Bind(&FakeMediaStreamVideoSink::OnVideoFrame,
base::Unretained(this)),
true);
}
void DisconnectFromTrack() {
MediaStreamVideoSink::DisconnectFromTrack();
}
void OnVideoFrame(const scoped_refptr<media::VideoFrame>& frame,
base::TimeTicks capture_time) {
*capture_time_ = capture_time;
metadata_->Clear();
metadata_->MergeMetadataFrom(frame->metadata());
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(), RequestRefreshFrame());
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()));
fake_sink.ConnectToTrack(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();
fake_sink.DisconnectFromTrack();
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