blob: c02ede62faa12e8f50db252a8f54bc5465c59aa9 [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/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
#include "content/browser/renderer_host/media/video_capture_controller.h"
#include "content/browser/renderer_host/media/video_capture_device_client.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/base/limits.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Mock;
using ::testing::InSequence;
using ::testing::SaveArg;
namespace content {
namespace {
class MockVideoCaptureController : public VideoCaptureController {
public:
explicit MockVideoCaptureController(int max_buffers)
: VideoCaptureController(max_buffers) {}
~MockVideoCaptureController() override {}
MOCK_METHOD1(MockDoIncomingCapturedVideoFrameOnIOThread,
void(const gfx::Size&));
MOCK_METHOD0(DoErrorOnIOThread, void());
MOCK_METHOD1(DoLogOnIOThread, void(const std::string& message));
MOCK_METHOD1(DoBufferDestroyedOnIOThread, void(int buffer_id_to_drop));
void DoIncomingCapturedVideoFrameOnIOThread(
scoped_ptr<media::VideoCaptureDevice::Client::Buffer> buffer,
const scoped_refptr<media::VideoFrame>& frame,
const base::TimeTicks& timestamp) override {
MockDoIncomingCapturedVideoFrameOnIOThread(frame->coded_size());
}
};
class VideoCaptureDeviceClientTest : public ::testing::Test {
public:
VideoCaptureDeviceClientTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
controller_(new MockVideoCaptureController(1)),
device_client_(
controller_->NewDeviceClient(base::ThreadTaskRunnerHandle::Get())) {
}
~VideoCaptureDeviceClientTest() override {}
void TearDown() override { base::RunLoop().RunUntilIdle(); }
protected:
const content::TestBrowserThreadBundle thread_bundle_;
const scoped_ptr<MockVideoCaptureController> controller_;
const scoped_ptr<media::VideoCaptureDevice::Client> device_client_;
private:
DISALLOW_COPY_AND_ASSIGN(VideoCaptureDeviceClientTest);
};
} // namespace
// A small test for reference and to verify VideoCaptureDeviceClient is
// minimally functional.
TEST_F(VideoCaptureDeviceClientTest, Minimal) {
const size_t kScratchpadSizeInBytes = 400;
unsigned char data[kScratchpadSizeInBytes] = {};
const media::VideoCaptureFormat kFrameFormat(
gfx::Size(10, 10), 30.0f /*frame_rate*/,
media::VideoCapturePixelFormat::VIDEO_CAPTURE_PIXEL_FORMAT_I420,
media::VideoPixelStorage::PIXEL_STORAGE_CPU);
DCHECK(device_client_.get());
EXPECT_CALL(*controller_, MockDoIncomingCapturedVideoFrameOnIOThread(_))
.Times(1);
device_client_->OnIncomingCapturedData(data, kScratchpadSizeInBytes,
kFrameFormat, 0 /*clockwise rotation*/,
base::TimeTicks());
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(controller_.get());
}
// Tests that we don't try to pass on frames with an invalid frame format.
TEST_F(VideoCaptureDeviceClientTest, FailsSilentlyGivenInvalidFrameFormat) {
const size_t kScratchpadSizeInBytes = 400;
unsigned char data[kScratchpadSizeInBytes] = {};
// kFrameFormat is invalid in a number of ways.
const media::VideoCaptureFormat kFrameFormat(
gfx::Size(media::limits::kMaxDimension + 1, media::limits::kMaxDimension),
media::limits::kMaxFramesPerSecond + 1,
media::VideoCapturePixelFormat::VIDEO_CAPTURE_PIXEL_FORMAT_I420,
media::VideoPixelStorage::PIXEL_STORAGE_CPU);
DCHECK(device_client_.get());
// Expect the the call to fail silently inside the VideoCaptureDeviceClient.
EXPECT_CALL(*controller_, MockDoIncomingCapturedVideoFrameOnIOThread(_))
.Times(0);
device_client_->OnIncomingCapturedData(data, kScratchpadSizeInBytes,
kFrameFormat, 0 /*clockwise rotation*/,
base::TimeTicks());
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(controller_.get());
}
// Tests that we fail silently if no available buffers to use.
TEST_F(VideoCaptureDeviceClientTest, DropsFrameIfNoBuffer) {
const size_t kScratchpadSizeInBytes = 400;
unsigned char data[kScratchpadSizeInBytes] = {};
const media::VideoCaptureFormat kFrameFormat(
gfx::Size(10, 10), 30.0f /*frame_rate*/,
media::VideoCapturePixelFormat::VIDEO_CAPTURE_PIXEL_FORMAT_I420,
media::VideoPixelStorage::PIXEL_STORAGE_CPU);
// We expect the second frame to be silently dropped, so these should
// only be called once despite the two frames.
EXPECT_CALL(*controller_, MockDoIncomingCapturedVideoFrameOnIOThread(_))
.Times(1);
// Pass two frames. The second will be dropped.
device_client_->OnIncomingCapturedData(data, kScratchpadSizeInBytes,
kFrameFormat, 0 /*clockwise rotation*/,
base::TimeTicks());
device_client_->OnIncomingCapturedData(data, kScratchpadSizeInBytes,
kFrameFormat, 0 /*clockwise rotation*/,
base::TimeTicks());
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(controller_.get());
}
// Tests that buffer-based capture API accepts all memory-backed pixel formats.
TEST_F(VideoCaptureDeviceClientTest, DataCaptureInEachVideoFormatInSequence) {
// The usual ReserveOutputBuffer() -> OnIncomingCapturedVideoFrame() cannot
// be used since it does not accept all pixel formats. The memory backed
// buffer OnIncomingCapturedData() is used instead, with a dummy scratchpad
// buffer.
const size_t kScratchpadSizeInBytes = 400;
unsigned char data[kScratchpadSizeInBytes] = {};
const gfx::Size capture_resolution(10, 10);
ASSERT_GE(kScratchpadSizeInBytes, capture_resolution.GetArea() * 4u)
<< "Scratchpad is too small to hold the largest pixel format (ARGB).";
for (int format = 0; format < media::VIDEO_CAPTURE_PIXEL_FORMAT_MAX;
++format) {
// Conversion from MJPEG to I420 seems to be unsupported.
if (format == media::VIDEO_CAPTURE_PIXEL_FORMAT_UNKNOWN ||
format == media::VIDEO_CAPTURE_PIXEL_FORMAT_MJPEG) {
continue;
}
#if !defined(OS_LINUX) && !defined(OS_WIN)
if (format == media::VIDEO_CAPTURE_PIXEL_FORMAT_RGB24){
continue;
}
#endif
media::VideoCaptureParams params;
params.requested_format = media::VideoCaptureFormat(
capture_resolution, 30.0f, media::VideoCapturePixelFormat(format));
EXPECT_CALL(*controller_, MockDoIncomingCapturedVideoFrameOnIOThread(_))
.Times(1);
device_client_->OnIncomingCapturedData(
data, params.requested_format.ImageAllocationSize(),
params.requested_format, 0 /* clockwise_rotation */, base::TimeTicks());
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(controller_.get());
}
}
// Test that we receive the expected resolution for a given captured frame
// resolution and rotation. Odd resolutions are also cropped.
TEST_F(VideoCaptureDeviceClientTest, CheckRotationsAndCrops) {
const struct SizeAndRotation {
gfx::Size input_resolution;
int rotation;
gfx::Size output_resolution;
} kSizeAndRotations[] = {{{6, 4}, 0, {6, 4}},
{{6, 4}, 90, {4, 6}},
{{6, 4}, 180, {6, 4}},
{{6, 4}, 270, {4, 6}},
{{7, 4}, 0, {6, 4}},
{{7, 4}, 90, {4, 6}},
{{7, 4}, 180, {6, 4}},
{{7, 4}, 270, {4, 6}}};
// The usual ReserveOutputBuffer() -> OnIncomingCapturedVideoFrame() cannot
// be used since it does not resolve rotations or crops. The memory backed
// buffer OnIncomingCapturedData() is used instead, with a dummy scratchpad
// buffer.
const size_t kScratchpadSizeInBytes = 400;
unsigned char data[kScratchpadSizeInBytes] = {};
media::VideoCaptureParams params;
for (const auto& size_and_rotation : kSizeAndRotations) {
ASSERT_GE(kScratchpadSizeInBytes,
size_and_rotation.input_resolution.GetArea() * 4u)
<< "Scratchpad is too small to hold the largest pixel format (ARGB).";
params.requested_format =
media::VideoCaptureFormat(size_and_rotation.input_resolution, 30.0f,
media::VIDEO_CAPTURE_PIXEL_FORMAT_ARGB);
gfx::Size coded_size;
EXPECT_CALL(*controller_, MockDoIncomingCapturedVideoFrameOnIOThread(_))
.Times(1)
.WillOnce(SaveArg<0>(&coded_size));
device_client_->OnIncomingCapturedData(
data, params.requested_format.ImageAllocationSize(),
params.requested_format, size_and_rotation.rotation, base::TimeTicks());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(coded_size.width(), size_and_rotation.output_resolution.width());
EXPECT_EQ(coded_size.height(),
size_and_rotation.output_resolution.height());
Mock::VerifyAndClearExpectations(controller_.get());
}
}
} // namespace content