blob: 288bbc0442f764f1e8c340de70ff4cbcad2c660d [file] [log] [blame]
// Copyright 2015 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/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/thread_task_runner_handle.h"
#include "content/child/child_process.h"
#include "content/renderer/media/canvas_capture_handler.h"
#include "content/renderer/media/media_stream_video_capturer_source.h"
#include "media/base/limits.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebMediaStreamSource.h"
#include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
#include "third_party/WebKit/public/platform/WebSize.h"
#include "third_party/WebKit/public/web/WebHeap.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::SaveArg;
using ::testing::Test;
using ::testing::TestWithParam;
namespace content {
namespace {
static const int kTestCanvasCaptureWidth = 320;
static const int kTestCanvasCaptureHeight = 240;
static const double kTestCanvasCaptureFramesPerSecond = 55.5;
static const int kTestCanvasCaptureFrameEvenSize = 2;
static const int kTestCanvasCaptureFrameOddSize = 3;
static const int kTestCanvasCaptureFrameColorErrorTolerance = 2;
static const int kTestAlphaValue = 175;
ACTION_P(RunClosure, closure) {
closure.Run();
}
} // namespace
class CanvasCaptureHandlerTest
: public TestWithParam<testing::tuple<bool, int, int>> {
public:
CanvasCaptureHandlerTest() {}
void SetUp() override {
canvas_capture_handler_.reset(
CanvasCaptureHandler::CreateCanvasCaptureHandler(
blink::WebSize(kTestCanvasCaptureWidth, kTestCanvasCaptureHeight),
kTestCanvasCaptureFramesPerSecond, message_loop_.task_runner(),
&track_));
}
void TearDown() override {
track_.reset();
blink::WebHeap::collectAllGarbageForTesting();
canvas_capture_handler_.reset();
// Let the message loop run to finish destroying the capturer.
base::RunLoop().RunUntilIdle();
}
// Necessary callbacks and MOCK_METHODS for VideoCapturerSource.
MOCK_METHOD2(DoOnDeliverFrame,
void(const scoped_refptr<media::VideoFrame>&, base::TimeTicks));
void OnDeliverFrame(const scoped_refptr<media::VideoFrame>& video_frame,
base::TimeTicks estimated_capture_time) {
DoOnDeliverFrame(video_frame, estimated_capture_time);
}
MOCK_METHOD1(DoOnVideoCaptureDeviceFormats,
void(const media::VideoCaptureFormats&));
void OnVideoCaptureDeviceFormats(const media::VideoCaptureFormats& formats) {
DoOnVideoCaptureDeviceFormats(formats);
}
MOCK_METHOD1(DoOnRunning, void(bool));
void OnRunning(bool state) { DoOnRunning(state); }
// Verify returned frames.
static sk_sp<SkImage> GenerateTestImage(bool opaque, int width, int height) {
SkBitmap testBitmap;
testBitmap.allocN32Pixels(width, height, opaque);
testBitmap.eraseARGB(kTestAlphaValue, 30, 60, 200);
return SkImage::MakeFromBitmap(testBitmap);
}
void OnVerifyDeliveredFrame(
bool opaque,
int expected_width,
int expected_height,
const scoped_refptr<media::VideoFrame>& video_frame,
base::TimeTicks estimated_capture_time) {
if (opaque)
EXPECT_EQ(media::PIXEL_FORMAT_I420, video_frame->format());
else
EXPECT_EQ(media::PIXEL_FORMAT_YV12A, video_frame->format());
EXPECT_EQ(video_frame->timestamp().InMilliseconds(),
(estimated_capture_time - base::TimeTicks()).InMilliseconds());
const gfx::Size& size = video_frame->visible_rect().size();
EXPECT_EQ(expected_width, size.width());
EXPECT_EQ(expected_height, size.height());
const uint8_t* y_plane =
video_frame->visible_data(media::VideoFrame::kYPlane);
EXPECT_NEAR(74, y_plane[0], kTestCanvasCaptureFrameColorErrorTolerance);
const uint8_t* u_plane =
video_frame->visible_data(media::VideoFrame::kUPlane);
EXPECT_NEAR(193, u_plane[0], kTestCanvasCaptureFrameColorErrorTolerance);
const uint8_t* v_plane =
video_frame->visible_data(media::VideoFrame::kVPlane);
EXPECT_NEAR(105, v_plane[0], kTestCanvasCaptureFrameColorErrorTolerance);
if (!opaque) {
const uint8_t* a_plane =
video_frame->visible_data(media::VideoFrame::kAPlane);
EXPECT_EQ(kTestAlphaValue, a_plane[0]);
}
}
blink::WebMediaStreamTrack track_;
// The Class under test. Needs to be scoped_ptr to force its destruction.
std::unique_ptr<CanvasCaptureHandler> canvas_capture_handler_;
protected:
media::VideoCapturerSource* GetVideoCapturerSource(
MediaStreamVideoCapturerSource* ms_source) {
return ms_source->source_.get();
}
// A ChildProcess and a MessageLoopForUI are both needed to fool the Tracks
// and Sources into believing they are on the right threads.
base::MessageLoopForUI message_loop_;
ChildProcess child_process_;
private:
DISALLOW_COPY_AND_ASSIGN(CanvasCaptureHandlerTest);
};
// Checks that the initialization-destruction sequence works fine.
TEST_F(CanvasCaptureHandlerTest, ConstructAndDestruct) {
EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
base::RunLoop().RunUntilIdle();
}
// Checks that the destruction sequence works fine.
TEST_F(CanvasCaptureHandlerTest, DestructTrack) {
EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
track_.reset();
base::RunLoop().RunUntilIdle();
}
// Checks that the destruction sequence works fine.
TEST_F(CanvasCaptureHandlerTest, DestructHandler) {
EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
canvas_capture_handler_.reset();
base::RunLoop().RunUntilIdle();
}
// Checks that VideoCapturerSource call sequence works fine.
TEST_P(CanvasCaptureHandlerTest, GetFormatsStartAndStop) {
InSequence s;
const blink::WebMediaStreamSource& web_media_stream_source = track_.source();
EXPECT_FALSE(web_media_stream_source.isNull());
MediaStreamVideoCapturerSource* const ms_source =
static_cast<MediaStreamVideoCapturerSource*>(
web_media_stream_source.getExtraData());
EXPECT_TRUE(ms_source != nullptr);
media::VideoCapturerSource* source = GetVideoCapturerSource(ms_source);
EXPECT_TRUE(source != nullptr);
media::VideoCaptureFormats formats;
EXPECT_CALL(*this, DoOnVideoCaptureDeviceFormats(_))
.Times(1)
.WillOnce(SaveArg<0>(&formats));
source->GetCurrentSupportedFormats(
media::limits::kMaxCanvas /* max_requesteed_width */,
media::limits::kMaxCanvas /* max_requesteed_height */,
media::limits::kMaxFramesPerSecond /* max_requested_frame_rate */,
base::Bind(&CanvasCaptureHandlerTest::OnVideoCaptureDeviceFormats,
base::Unretained(this)));
ASSERT_EQ(2u, formats.size());
EXPECT_EQ(kTestCanvasCaptureWidth, formats[0].frame_size.width());
EXPECT_EQ(kTestCanvasCaptureHeight, formats[0].frame_size.height());
media::VideoCaptureParams params;
params.requested_format = formats[0];
base::RunLoop run_loop;
base::Closure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*this, DoOnRunning(true)).Times(1);
EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
.Times(1)
.WillOnce(RunClosure(quit_closure));
source->StartCapture(
params, base::Bind(&CanvasCaptureHandlerTest::OnDeliverFrame,
base::Unretained(this)),
base::Bind(&CanvasCaptureHandlerTest::OnRunning, base::Unretained(this)));
canvas_capture_handler_->sendNewFrame(
GenerateTestImage(testing::get<0>(GetParam()),
testing::get<1>(GetParam()),
testing::get<2>(GetParam()))
.get());
run_loop.Run();
source->StopCapture();
}
// Verifies that SkImage is processed and produces VideoFrame as expected.
TEST_P(CanvasCaptureHandlerTest, VerifyFrame) {
const bool opaque_frame = testing::get<0>(GetParam());
const bool width = testing::get<1>(GetParam());
const bool height = testing::get<1>(GetParam());
InSequence s;
media::VideoCapturerSource* const source =
GetVideoCapturerSource(static_cast<MediaStreamVideoCapturerSource*>(
track_.source().getExtraData()));
EXPECT_TRUE(source != nullptr);
base::RunLoop run_loop;
EXPECT_CALL(*this, DoOnRunning(true)).Times(1);
media::VideoCaptureParams params;
source->StartCapture(
params, base::Bind(&CanvasCaptureHandlerTest::OnVerifyDeliveredFrame,
base::Unretained(this), opaque_frame, width, height),
base::Bind(&CanvasCaptureHandlerTest::OnRunning, base::Unretained(this)));
canvas_capture_handler_->sendNewFrame(
GenerateTestImage(opaque_frame, width, height).get());
run_loop.RunUntilIdle();
}
// Checks that needsNewFrame() works as expected.
TEST_F(CanvasCaptureHandlerTest, CheckNeedsNewFrame) {
InSequence s;
media::VideoCapturerSource* source =
GetVideoCapturerSource(static_cast<MediaStreamVideoCapturerSource*>(
track_.source().getExtraData()));
EXPECT_TRUE(source != nullptr);
EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
source->StopCapture();
EXPECT_FALSE(canvas_capture_handler_->needsNewFrame());
}
INSTANTIATE_TEST_CASE_P(
,
CanvasCaptureHandlerTest,
::testing::Combine(::testing::Bool(),
::testing::Values(kTestCanvasCaptureFrameEvenSize,
kTestCanvasCaptureFrameOddSize),
::testing::Values(kTestCanvasCaptureFrameEvenSize,
kTestCanvasCaptureFrameOddSize)));
} // namespace content