blob: ad1583b67efc40e217dab7e7d9532603b67a2b5c [file] [log] [blame]
// Copyright (c) 2012 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/browser/renderer_host/media/video_capture_controller.h"
#include <stdint.h>
#include <string.h>
#include <memory>
#include <string>
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/task/post_task.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/renderer_host/media/media_stream_provider.h"
#include "content/browser/renderer_host/media/mock_video_capture_provider.h"
#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/base/video_frame_metadata.h"
#include "media/base/video_util.h"
#include "media/capture/video/video_capture_buffer_pool_impl.h"
#include "media/capture/video/video_capture_buffer_tracker_factory_impl.h"
#include "media/capture/video/video_capture_device_client.h"
#include "media/capture/video/video_frame_receiver_on_task_runner.h"
#include "media/capture/video_capture_types.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_CHROMEOS)
#include "media/capture/video/chromeos/video_capture_jpeg_decoder_impl.h"
#endif // defined(OS_CHROMEOS)
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::SaveArg;
namespace content {
class MockVideoCaptureControllerEventHandler
: public VideoCaptureControllerEventHandler {
public:
explicit MockVideoCaptureControllerEventHandler(
VideoCaptureController* controller)
: controller_(controller), resource_utilization_(-1.0) {}
~MockVideoCaptureControllerEventHandler() override {}
void set_enable_auto_return_buffer_on_buffer_ready(bool enable) {
enable_auto_return_buffer_on_buffer_ready_ = enable;
}
// These mock methods are delegated to by our fake implementation of
// VideoCaptureControllerEventHandler, to be used in EXPECT_CALL().
MOCK_METHOD2(DoBufferCreated, void(VideoCaptureControllerID, int buffer_id));
MOCK_METHOD2(DoBufferDestroyed,
void(VideoCaptureControllerID, int buffer_id));
MOCK_METHOD2(DoBufferReady, void(VideoCaptureControllerID, const gfx::Size&));
MOCK_METHOD1(DoEnded, void(VideoCaptureControllerID));
MOCK_METHOD2(DoError,
void(VideoCaptureControllerID, media::VideoCaptureError));
MOCK_METHOD1(OnStarted, void(VideoCaptureControllerID));
MOCK_METHOD1(OnStartedUsingGpuDecode, void(VideoCaptureControllerID));
void OnError(VideoCaptureControllerID id,
media::VideoCaptureError error) override {
DoError(id, error);
}
void OnNewBuffer(VideoCaptureControllerID id,
media::mojom::VideoBufferHandlePtr buffer_handle,
int buffer_id) override {
DoBufferCreated(id, buffer_id);
}
void OnBufferDestroyed(VideoCaptureControllerID id, int buffer_id) override {
DoBufferDestroyed(id, buffer_id);
}
void OnBufferReady(
VideoCaptureControllerID id,
int buffer_id,
const media::mojom::VideoFrameInfoPtr& frame_info) override {
EXPECT_EQ(expected_pixel_format_, frame_info->pixel_format);
EXPECT_EQ(expected_color_space_, frame_info->color_space);
media::VideoFrameMetadata metadata;
metadata.MergeInternalValuesFrom(frame_info->metadata);
base::TimeTicks reference_time;
EXPECT_TRUE(metadata.GetTimeTicks(media::VideoFrameMetadata::REFERENCE_TIME,
&reference_time));
DoBufferReady(id, frame_info->coded_size);
if (enable_auto_return_buffer_on_buffer_ready_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureController::ReturnBuffer,
base::Unretained(controller_), id, this,
buffer_id, resource_utilization_));
}
}
void OnEnded(VideoCaptureControllerID id) override {
DoEnded(id);
// OnEnded() must respond by (eventually) unregistering the client.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(base::IgnoreResult(
&VideoCaptureController::RemoveClient),
base::Unretained(controller_), id, this));
}
VideoCaptureController* controller_;
media::VideoPixelFormat expected_pixel_format_ = media::PIXEL_FORMAT_I420;
gfx::ColorSpace expected_color_space_ = gfx::ColorSpace::CreateREC709();
double resource_utilization_;
bool enable_auto_return_buffer_on_buffer_ready_ = true;
};
// Test fixture for testing a unit consisting of an instance of
// VideoCaptureController connected to an instance of VideoCaptureDeviceClient,
// an instance of VideoCaptureBufferPoolImpl, as well as related threading glue
// that replicates how it is used in production.
class VideoCaptureControllerTest
: public testing::Test,
public testing::WithParamInterface<media::VideoPixelFormat> {
public:
VideoCaptureControllerTest()
: arbitrary_format_(gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420),
arbitrary_color_space_(gfx::ColorSpace::CreateREC709()),
arbitrary_route_id_(0x99),
arbitrary_session_id_(100) {}
~VideoCaptureControllerTest() override {}
protected:
static const int kPoolSize = 3;
void SetUp() override {
const std::string arbitrary_device_id = "arbitrary_device_id";
const blink::MediaStreamType arbitrary_stream_type =
blink::MEDIA_DEVICE_VIDEO_CAPTURE;
const media::VideoCaptureParams arbitrary_params;
auto device_launcher = std::make_unique<MockVideoCaptureDeviceLauncher>();
controller_ = new VideoCaptureController(
arbitrary_device_id, arbitrary_stream_type, arbitrary_params,
std::move(device_launcher),
base::BindRepeating([](const std::string&) {}));
InitializeNewDeviceClientAndBufferPoolInstances();
auto mock_launched_device =
std::make_unique<MockLaunchedVideoCaptureDevice>();
mock_launched_device_ = mock_launched_device.get();
controller_->OnDeviceLaunched(std::move(mock_launched_device));
client_a_.reset(
new MockVideoCaptureControllerEventHandler(controller_.get()));
client_b_.reset(
new MockVideoCaptureControllerEventHandler(controller_.get()));
}
void TearDown() override { base::RunLoop().RunUntilIdle(); }
void InitializeNewDeviceClientAndBufferPoolInstances() {
buffer_pool_ = new media::VideoCaptureBufferPoolImpl(
std::make_unique<media::VideoCaptureBufferTrackerFactoryImpl>(),
kPoolSize);
#if defined(OS_CHROMEOS)
device_client_.reset(new media::VideoCaptureDeviceClient(
media::VideoCaptureBufferType::kSharedMemory,
std::make_unique<media::VideoFrameReceiverOnTaskRunner>(
controller_->GetWeakPtrForIOThread(),
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO})),
buffer_pool_, media::VideoCaptureJpegDecoderFactoryCB()));
#else
device_client_.reset(new media::VideoCaptureDeviceClient(
media::VideoCaptureBufferType::kSharedMemory,
std::make_unique<media::VideoFrameReceiverOnTaskRunner>(
controller_->GetWeakPtrForIOThread(),
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO})),
buffer_pool_));
#endif // defined(OS_CHROMEOS)
}
void SendStubFrameToDeviceClient(const media::VideoCaptureFormat format,
const gfx::ColorSpace& color_space) {
auto stub_frame = media::VideoFrame::CreateZeroInitializedFrame(
format.pixel_format, format.frame_size,
gfx::Rect(format.frame_size.width(), format.frame_size.height()),
format.frame_size, base::TimeDelta());
const int rotation = 0;
const int frame_feedback_id = 0;
device_client_->OnIncomingCapturedData(
stub_frame->data(0),
media::VideoFrame::AllocationSize(stub_frame->format(),
stub_frame->coded_size()),
format, color_space, rotation, base::TimeTicks(), base::TimeDelta(),
frame_feedback_id);
}
TestBrowserThreadBundle bundle_;
scoped_refptr<media::VideoCaptureBufferPool> buffer_pool_;
std::unique_ptr<MockVideoCaptureControllerEventHandler> client_a_;
std::unique_ptr<MockVideoCaptureControllerEventHandler> client_b_;
scoped_refptr<VideoCaptureController> controller_;
std::unique_ptr<media::VideoCaptureDevice::Client> device_client_;
MockLaunchedVideoCaptureDevice* mock_launched_device_;
const float arbitrary_frame_rate_ = 10.0f;
const base::TimeTicks arbitrary_reference_time_ = base::TimeTicks();
const base::TimeDelta arbitrary_timestamp_ = base::TimeDelta();
const media::VideoCaptureFormat arbitrary_format_;
const gfx::ColorSpace arbitrary_color_space_;
const VideoCaptureControllerID arbitrary_route_id_;
const media::VideoCaptureSessionId arbitrary_session_id_;
private:
DISALLOW_COPY_AND_ASSIGN(VideoCaptureControllerTest);
};
// A simple test of VideoCaptureController's ability to add, remove, and keep
// track of clients.
TEST_F(VideoCaptureControllerTest, AddAndRemoveClients) {
media::VideoCaptureParams session_100;
session_100.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_200 = session_100;
media::VideoCaptureParams session_300 = session_100;
media::VideoCaptureParams session_400 = session_100;
// Intentionally use the same route ID for two of the clients: the device_ids
// are a per-VideoCaptureHost namespace, and can overlap across hosts.
const VideoCaptureControllerID client_a_route_1(44);
const VideoCaptureControllerID client_a_route_2(30);
const VideoCaptureControllerID client_b_route_1(30);
const VideoCaptureControllerID client_b_route_2(1);
// Clients in controller: []
ASSERT_EQ(0u, controller_->GetClientCount())
<< "Client count should initially be zero.";
controller_->AddClient(client_a_route_1, client_a_.get(), 100, session_100);
// Clients in controller: [A/1]
ASSERT_EQ(1u, controller_->GetClientCount())
<< "Adding client A/1 should bump client count.";
controller_->AddClient(client_a_route_2, client_a_.get(), 200, session_200);
// Clients in controller: [A/1, A/2]
ASSERT_EQ(2u, controller_->GetClientCount())
<< "Adding client A/2 should bump client count.";
controller_->AddClient(client_b_route_1, client_b_.get(), 300, session_300);
// Clients in controller: [A/1, A/2, B/1]
ASSERT_EQ(3u, controller_->GetClientCount())
<< "Adding client B/1 should bump client count.";
ASSERT_EQ(200, controller_->RemoveClient(client_a_route_2, client_a_.get()))
<< "Removing client A/1 should return its session_id.";
// Clients in controller: [A/1, B/1]
ASSERT_EQ(2u, controller_->GetClientCount());
ASSERT_EQ(static_cast<int>(kInvalidMediaCaptureSessionId),
controller_->RemoveClient(client_a_route_2, client_a_.get()))
<< "Removing a nonexistant client should fail.";
// Clients in controller: [A/1, B/1]
ASSERT_EQ(2u, controller_->GetClientCount());
ASSERT_EQ(300, controller_->RemoveClient(client_b_route_1, client_b_.get()))
<< "Removing client B/1 should return its session_id.";
// Clients in controller: [A/1]
ASSERT_EQ(1u, controller_->GetClientCount());
controller_->AddClient(client_b_route_2, client_b_.get(), 400, session_400);
// Clients in controller: [A/1, B/2]
EXPECT_CALL(*client_a_, DoEnded(client_a_route_1)).Times(1);
controller_->StopSession(100); // Session 100 == client A/1
Mock::VerifyAndClearExpectations(client_a_.get());
ASSERT_EQ(2u, controller_->GetClientCount())
<< "Client should be closed but still exist after StopSession.";
// Clients in controller: [A/1 (closed, removal pending), B/2]
base::RunLoop().RunUntilIdle();
// Clients in controller: [B/2]
ASSERT_EQ(1u, controller_->GetClientCount())
<< "Client A/1 should be deleted by now.";
controller_->StopSession(200); // Session 200 does not exist anymore
// Clients in controller: [B/2]
ASSERT_EQ(1u, controller_->GetClientCount())
<< "Stopping non-existent session 200 should be a no-op.";
controller_->StopSession(256); // Session 256 never existed.
// Clients in controller: [B/2]
ASSERT_EQ(1u, controller_->GetClientCount())
<< "Stopping non-existent session 256 should be a no-op.";
ASSERT_EQ(static_cast<int>(kInvalidMediaCaptureSessionId),
controller_->RemoveClient(client_a_route_1, client_a_.get()))
<< "Removing already-removed client A/1 should fail.";
// Clients in controller: [B/2]
ASSERT_EQ(1u, controller_->GetClientCount())
<< "Removing non-existent session 200 should be a no-op.";
ASSERT_EQ(400, controller_->RemoveClient(client_b_route_2, client_b_.get()))
<< "Removing client B/2 should return its session_id.";
// Clients in controller: []
ASSERT_EQ(0u, controller_->GetClientCount())
<< "Client count should return to zero after all clients are gone.";
}
// This test will connect and disconnect several clients while simulating an
// active capture device being started and generating frames. It runs on one
// thread and is intended to behave deterministically.
TEST_P(VideoCaptureControllerTest, NormalCaptureMultipleClients) {
media::VideoCaptureParams session_100;
const media::VideoPixelFormat format = GetParam();
client_a_->expected_pixel_format_ = format;
client_b_->expected_pixel_format_ = format;
// OnIncomingCapturedBuffer keeps the color space unset. If needed use
// OnIncomingCapturedBufferExt.
client_a_->expected_color_space_ = gfx::ColorSpace();
client_b_->expected_color_space_ = gfx::ColorSpace();
session_100.requested_format =
media::VideoCaptureFormat(gfx::Size(320, 240), 30, format);
media::VideoCaptureParams session_200 = session_100;
media::VideoCaptureParams session_300 = session_100;
media::VideoCaptureParams session_1 = session_100;
media::VideoCaptureFormat device_format(gfx::Size(444, 200), 25, format);
const VideoCaptureControllerID client_a_route_1(0xa1a1a1a1);
const VideoCaptureControllerID client_a_route_2(0xa2a2a2a2);
const VideoCaptureControllerID client_b_route_1(0xb1b1b1b1);
const VideoCaptureControllerID client_b_route_2(0xb2b2b2b2);
controller_->AddClient(client_a_route_1, client_a_.get(), 100, session_100);
controller_->AddClient(client_b_route_1, client_b_.get(), 300, session_300);
controller_->AddClient(client_a_route_2, client_a_.get(), 200, session_200);
ASSERT_EQ(3u, controller_->GetClientCount());
// Now, simulate an incoming captured buffer from the capture device. As a
// side effect this will cause the first buffer to be shared with clients.
uint8_t buffer_no = 1;
const int arbitrary_frame_feedback_id = 101;
ASSERT_EQ(0.0, device_client_->GetBufferPoolUtilization());
media::VideoCaptureDevice::Client::Buffer buffer;
const auto result_code = device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id, &buffer);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code);
auto buffer_access = buffer.handle_provider->GetHandleForInProcessAccess();
ASSERT_EQ(1.0 / kPoolSize, device_client_->GetBufferPoolUtilization());
memset(buffer_access->data(), buffer_no++, buffer_access->mapped_size());
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_1, _));
EXPECT_CALL(*client_a_,
DoBufferReady(client_a_route_1, device_format.frame_size));
}
{
InSequence s;
EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1, _));
EXPECT_CALL(*client_b_,
DoBufferReady(client_b_route_1, device_format.frame_size));
}
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2, _));
EXPECT_CALL(*client_a_,
DoBufferReady(client_a_route_2, device_format.frame_size));
}
client_a_->resource_utilization_ = 0.5;
client_b_->resource_utilization_ = -1.0;
// Expect VideoCaptureController to call the load observer with a
// resource utilization of 0.5 (the largest of all reported values).
EXPECT_CALL(*mock_launched_device_,
OnUtilizationReport(arbitrary_frame_feedback_id, 0.5));
device_client_->OnIncomingCapturedBuffer(std::move(buffer), device_format,
arbitrary_reference_time_,
arbitrary_timestamp_);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(client_b_.get());
Mock::VerifyAndClearExpectations(mock_launched_device_);
// Second buffer which ought to use the same shared memory buffer. In this
// case pretend that the Buffer pointer is held by the device for a long
// delay. This shouldn't affect anything.
const int arbitrary_frame_feedback_id_2 = 102;
media::VideoCaptureDevice::Client::Buffer buffer2;
const auto result_code_2 = device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id_2, &buffer2);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code_2);
auto buffer2_access = buffer2.handle_provider->GetHandleForInProcessAccess();
memset(buffer2_access->data(), buffer_no++, buffer2_access->mapped_size());
client_a_->resource_utilization_ = 0.5;
client_b_->resource_utilization_ = 3.14;
// Expect VideoCaptureController to call the load observer with a
// resource utilization of 3.14 (the largest of all reported values).
EXPECT_CALL(*mock_launched_device_,
OnUtilizationReport(arbitrary_frame_feedback_id_2, 3.14));
device_client_->OnIncomingCapturedBuffer(std::move(buffer2), device_format,
arbitrary_reference_time_,
arbitrary_timestamp_);
// The frame should be delivered to the clients in any order.
EXPECT_CALL(*client_a_,
DoBufferReady(client_a_route_1, device_format.frame_size));
EXPECT_CALL(*client_b_,
DoBufferReady(client_b_route_1, device_format.frame_size));
EXPECT_CALL(*client_a_,
DoBufferReady(client_a_route_2, device_format.frame_size));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(client_b_.get());
Mock::VerifyAndClearExpectations(mock_launched_device_);
// Add a fourth client now that some buffers have come through.
controller_->AddClient(client_b_route_2, client_b_.get(), 1, session_1);
Mock::VerifyAndClearExpectations(client_b_.get());
// Third, fourth, and fifth buffers. Pretend they all arrive at the same time.
for (int i = 0; i < kPoolSize; i++) {
const int arbitrary_frame_feedback_id = 200 + i;
media::VideoCaptureDevice::Client::Buffer buffer;
const auto result_code = device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id, &buffer);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code);
auto buffer_access = buffer.handle_provider->GetHandleForInProcessAccess();
memset(buffer_access->data(), buffer_no++, buffer_access->mapped_size());
device_client_->OnIncomingCapturedBuffer(std::move(buffer), device_format,
arbitrary_reference_time_,
arbitrary_timestamp_);
}
// ReserveOutputBuffer ought to fail now, because the pool is depleted.
media::VideoCaptureDevice::Client::Buffer buffer_fail;
ASSERT_EQ(
media::VideoCaptureDevice::Client::ReserveResult::kMaxBufferCountExceeded,
device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id, &buffer_fail));
// The new client needs to be notified of the creation of |kPoolSize| buffers;
// the old clients only |kPoolSize - 1|.
EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_2, _))
.Times(kPoolSize);
EXPECT_CALL(*client_b_,
DoBufferReady(client_b_route_2, device_format.frame_size))
.Times(kPoolSize);
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_1, _))
.Times(kPoolSize - 1);
EXPECT_CALL(*client_a_,
DoBufferReady(client_a_route_1, device_format.frame_size))
.Times(kPoolSize);
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2, _))
.Times(kPoolSize - 1);
EXPECT_CALL(*client_a_,
DoBufferReady(client_a_route_2, device_format.frame_size))
.Times(kPoolSize);
EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1, _))
.Times(kPoolSize - 1);
EXPECT_CALL(*client_b_,
DoBufferReady(client_b_route_1, device_format.frame_size))
.Times(kPoolSize);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(client_b_.get());
// Now test the interaction of client shutdown and buffer delivery.
// Kill A1 via renderer disconnect (synchronous).
controller_->RemoveClient(client_a_route_1, client_a_.get());
// Kill B1 via session close (posts a task to disconnect).
EXPECT_CALL(*client_b_, DoEnded(client_b_route_1)).Times(1);
controller_->StopSession(300);
// Queue up another buffer.
media::VideoCaptureDevice::Client::Buffer buffer3;
const auto result_code_3 = device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id, &buffer3);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code_3);
auto buffer3_access = buffer3.handle_provider->GetHandleForInProcessAccess();
memset(buffer3_access->data(), buffer_no++, buffer3_access->mapped_size());
device_client_->OnIncomingCapturedBuffer(std::move(buffer3), device_format,
arbitrary_reference_time_,
arbitrary_timestamp_);
media::VideoCaptureDevice::Client::Buffer buffer4;
const auto result_code_4 = device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id, &buffer4);
{
// Kill A2 via session close (posts a task to disconnect, but A2 must not
// be sent either of these two buffers).
EXPECT_CALL(*client_a_, DoEnded(client_a_route_2)).Times(1);
controller_->StopSession(200);
}
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code_4);
auto buffer4_access = buffer4.handle_provider->GetHandleForInProcessAccess();
memset(buffer4_access->data(), buffer_no++, buffer4_access->mapped_size());
device_client_->OnIncomingCapturedBuffer(std::move(buffer4), device_format,
arbitrary_reference_time_,
arbitrary_timestamp_);
// B2 is the only client left, and is the only one that should
// get the buffer.
EXPECT_CALL(*client_b_,
DoBufferReady(client_b_route_2, device_format.frame_size))
.Times(2);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(client_b_.get());
}
INSTANTIATE_TEST_SUITE_P(,
VideoCaptureControllerTest,
::testing::Values(media::PIXEL_FORMAT_I420,
media::PIXEL_FORMAT_Y16));
// Exercises the OnError() codepath of VideoCaptureController, and tests the
// behavior of various operations after the error state has been signalled.
TEST_F(VideoCaptureControllerTest, ErrorBeforeDeviceCreation) {
media::VideoCaptureParams session_100;
session_100.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_200 = session_100;
const gfx::Size capture_resolution(320, 240);
const VideoCaptureControllerID route_id(0x99);
// Start with one client.
controller_->AddClient(route_id, client_a_.get(), 100, session_100);
device_client_->OnError(
media::VideoCaptureError::kIntentionalErrorRaisedByUnitTest, FROM_HERE,
"Test Error");
EXPECT_CALL(
*client_a_,
DoError(route_id,
media::VideoCaptureError::kIntentionalErrorRaisedByUnitTest))
.Times(1);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// Second client connects after the error state. It also should get told of
// the error.
EXPECT_CALL(
*client_b_,
DoError(route_id, media::VideoCaptureError::
kVideoCaptureControllerIsAlreadyInErrorState))
.Times(1);
controller_->AddClient(route_id, client_b_.get(), 200, session_200);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_b_.get());
media::VideoCaptureFormat device_format(
capture_resolution, arbitrary_frame_rate_, media::PIXEL_FORMAT_I420);
const int arbitrary_frame_feedback_id = 101;
media::VideoCaptureDevice::Client::Buffer buffer;
const auto reserve_result = device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id, &buffer);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
reserve_result);
device_client_->OnIncomingCapturedBuffer(std::move(buffer), device_format,
arbitrary_reference_time_,
arbitrary_timestamp_);
base::RunLoop().RunUntilIdle();
}
// Exercises the OnError() codepath of VideoCaptureController, and tests the
// behavior of various operations after the error state has been signalled.
TEST_F(VideoCaptureControllerTest, ErrorAfterDeviceCreation) {
media::VideoCaptureParams session_100;
session_100.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_200 = session_100;
const VideoCaptureControllerID route_id(0x99);
// Start with one client.
controller_->AddClient(route_id, client_a_.get(), 100, session_100);
// Start the device. Then, before the first buffer, signal an error and
// deliver the buffer. The error should be propagated to clients; the buffer
// should not be.
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
media::VideoCaptureFormat device_format(
gfx::Size(10, 10), arbitrary_frame_rate_, media::PIXEL_FORMAT_I420);
const int arbitrary_frame_feedback_id = 101;
media::VideoCaptureDevice::Client::Buffer buffer;
const auto result_code = device_client_->ReserveOutputBuffer(
device_format.frame_size, device_format.pixel_format,
arbitrary_frame_feedback_id, &buffer);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code);
device_client_->OnError(
media::VideoCaptureError::kIntentionalErrorRaisedByUnitTest, FROM_HERE,
"Test Error");
device_client_->OnIncomingCapturedBuffer(std::move(buffer), device_format,
arbitrary_reference_time_,
arbitrary_timestamp_);
EXPECT_CALL(
*client_a_,
DoError(route_id,
media::VideoCaptureError::kIntentionalErrorRaisedByUnitTest))
.Times(1);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// Second client connects after the error state. It also should get told of
// the error.
EXPECT_CALL(
*client_b_,
DoError(route_id, media::VideoCaptureError::
kVideoCaptureControllerIsAlreadyInErrorState))
.Times(1);
controller_->AddClient(route_id, client_b_.get(), 200, session_200);
Mock::VerifyAndClearExpectations(client_b_.get());
}
// Tests that frame feedback provided by consumers is correctly reported back
// to the producing device for a sequence of frames that is longer than the
// number of buffers shared between the device and consumer.
TEST_F(VideoCaptureControllerTest, FrameFeedbackIsReportedForSequenceOfFrames) {
const int kTestFrameSequenceLength = 10;
media::VideoCaptureFormat arbitrary_format(
gfx::Size(320, 240), arbitrary_frame_rate_, media::PIXEL_FORMAT_I420);
// OnIncomingCapturedBuffer keeps the color space unset. If needed use
// OnIncomingCapturedBufferExt.
client_a_->expected_color_space_ = gfx::ColorSpace();
// Register |client_a_| at |controller_|.
media::VideoCaptureParams session_100;
session_100.requested_format = arbitrary_format;
const VideoCaptureControllerID route_id(0x99);
controller_->AddClient(route_id, client_a_.get(), 100, session_100);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
for (int frame_index = 0; frame_index < kTestFrameSequenceLength;
frame_index++) {
const int stub_frame_feedback_id = frame_index;
const float stub_consumer_utilization =
static_cast<float>(frame_index) / kTestFrameSequenceLength;
client_a_->resource_utilization_ = stub_consumer_utilization;
EXPECT_CALL(*client_a_,
DoBufferReady(route_id, arbitrary_format.frame_size))
.Times(1);
EXPECT_CALL(
*mock_launched_device_,
OnUtilizationReport(stub_frame_feedback_id, stub_consumer_utilization))
.Times(1);
// Device prepares and pushes a frame.
// The frame is expected to arrive at |client_a_|.DoBufferReady(), which
// automatically notifies |controller_| that it has finished consuming it.
media::VideoCaptureDevice::Client::Buffer buffer;
const auto result_code = device_client_->ReserveOutputBuffer(
arbitrary_format.frame_size, arbitrary_format.pixel_format,
stub_frame_feedback_id, &buffer);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code);
device_client_->OnIncomingCapturedBuffer(
std::move(buffer), arbitrary_format, arbitrary_reference_time_,
arbitrary_timestamp_);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(mock_launched_device_);
}
}
TEST_F(VideoCaptureControllerTest,
DeviceClientIsReleasedBeforeAnyBufferWasShared) {
// Register |client_a_| at |controller_|.
media::VideoCaptureParams requested_params;
requested_params.requested_format = arbitrary_format_;
controller_->AddClient(arbitrary_route_id_, client_a_.get(),
arbitrary_session_id_, requested_params);
base::RunLoop().RunUntilIdle();
// |device_client_| is released by the device.
EXPECT_CALL(*client_a_, DoBufferDestroyed(arbitrary_route_id_, _)).Times(0);
device_client_.reset();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
}
TEST_F(VideoCaptureControllerTest,
DeviceClientIsReleasedAfterFrameHasBeenConsumed) {
// Register |client_a_| at |controller_|.
media::VideoCaptureParams requested_params;
requested_params.requested_format = arbitrary_format_;
controller_->AddClient(arbitrary_route_id_, client_a_.get(),
arbitrary_session_id_, requested_params);
base::RunLoop().RunUntilIdle();
// Device sends a frame to |device_client_| and |client_a_| reports to
// |controller_| that it has finished consuming the frame.
int buffer_id_reported_to_client = media::VideoCaptureBufferPool::kInvalidId;
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(_, _))
.Times(1)
.WillOnce(SaveArg<1>(&buffer_id_reported_to_client));
EXPECT_CALL(*client_a_, DoBufferReady(_, _)).Times(1);
}
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, _)).Times(0);
SendStubFrameToDeviceClient(arbitrary_format_, arbitrary_color_space_);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// |device_client_| is released by the device.
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, buffer_id_reported_to_client));
device_client_.reset();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
}
TEST_F(VideoCaptureControllerTest,
DeviceClientIsReleasedWhileFrameIsBeingConsumed) {
client_a_->set_enable_auto_return_buffer_on_buffer_ready(false);
// Register |client_a_| at |controller_|.
media::VideoCaptureParams requested_params;
requested_params.requested_format = arbitrary_format_;
controller_->AddClient(arbitrary_route_id_, client_a_.get(),
arbitrary_session_id_, requested_params);
base::RunLoop().RunUntilIdle();
// Device sends a frame to |device_client_|.
int buffer_id_reported_to_client = media::VideoCaptureBufferPool::kInvalidId;
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(_, _))
.Times(1)
.WillOnce(SaveArg<1>(&buffer_id_reported_to_client));
EXPECT_CALL(*client_a_, DoBufferReady(_, _)).Times(1);
}
SendStubFrameToDeviceClient(arbitrary_format_, arbitrary_color_space_);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// |device_client_| is released by the device.
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, _)).Times(0);
device_client_.reset();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// |client_a_| signals to |controller_| that it has finished consuming the
// frame.
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, _)).Times(1);
const double arbitrary_utilization = 0.0;
controller_->ReturnBuffer(arbitrary_route_id_, client_a_.get(),
buffer_id_reported_to_client,
arbitrary_utilization);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
}
TEST_F(VideoCaptureControllerTest,
NewDeviceClientSendsNewBufferWhileRetiredBufferStillBeingConsumed) {
client_a_->set_enable_auto_return_buffer_on_buffer_ready(false);
// Register |client_a_| at |controller_|.
media::VideoCaptureParams requested_params;
requested_params.requested_format = arbitrary_format_;
controller_->AddClient(arbitrary_route_id_, client_a_.get(),
arbitrary_session_id_, requested_params);
base::RunLoop().RunUntilIdle();
// Device sends a frame to |device_client_|.
int first_buffer_id = media::VideoCaptureBufferPool::kInvalidId;
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(_, _))
.Times(1)
.WillOnce(SaveArg<1>(&first_buffer_id));
EXPECT_CALL(*client_a_, DoBufferReady(_, _)).Times(1);
}
SendStubFrameToDeviceClient(arbitrary_format_, arbitrary_color_space_);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// |device_client_| is released by the device.
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, _)).Times(0);
device_client_.reset();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// A new |device_client_| is created with a new buffer pool.
InitializeNewDeviceClientAndBufferPoolInstances();
// Device sends a frame to the new |device_client_|.
int second_buffer_id = media::VideoCaptureBufferPool::kInvalidId;
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(_, _))
.Times(1)
.WillOnce(SaveArg<1>(&second_buffer_id));
EXPECT_CALL(*client_a_, DoBufferReady(_, _)).Times(1);
}
SendStubFrameToDeviceClient(arbitrary_format_, arbitrary_color_space_);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
EXPECT_NE(first_buffer_id, second_buffer_id);
// |client_a_| signals to |controller_| that it has finished consuming the
// first frame.
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, first_buffer_id)).Times(1);
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, second_buffer_id)).Times(0);
const double arbitrary_utilization = 0.0;
controller_->ReturnBuffer(arbitrary_route_id_, client_a_.get(),
first_buffer_id, arbitrary_utilization);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
// |client_a_| signals to |controller_| that it has finished consuming the
// second frame. Since the new |device_client_| is still alive, the second
// buffer is expected to stay alive.
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, first_buffer_id)).Times(0);
EXPECT_CALL(*client_a_, DoBufferDestroyed(_, second_buffer_id)).Times(0);
controller_->ReturnBuffer(arbitrary_route_id_, client_a_.get(),
second_buffer_id, arbitrary_utilization);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
}
// Tests that the VideoCaptureController reports OnStarted() to all clients,
// even if they connect after VideoCaptureController::OnStarted() has been
// invoked.
TEST_F(VideoCaptureControllerTest, OnStartedForMultipleClients) {
media::VideoCaptureParams session_100;
session_100.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_200 = session_100;
media::VideoCaptureParams session_300 = session_100;
const VideoCaptureControllerID client_a_route_1(1);
const VideoCaptureControllerID client_a_route_2(2);
const VideoCaptureControllerID client_b_route_1(3);
controller_->AddClient(client_a_route_1, client_a_.get(), 100, session_100);
controller_->AddClient(client_b_route_1, client_b_.get(), 300, session_300);
ASSERT_EQ(2u, controller_->GetClientCount());
{
InSequence s;
// Simulate the OnStarted event from device.
EXPECT_CALL(*client_a_, OnStarted(_));
EXPECT_CALL(*client_b_, OnStarted(_));
device_client_->OnStarted();
// VideoCaptureController will take care of the OnStarted event for the
// clients who join later.
EXPECT_CALL(*client_a_, OnStarted(_));
controller_->AddClient(client_a_route_2, client_a_.get(), 200, session_200);
}
}
TEST_F(VideoCaptureControllerTest, DroppedFramesGetLoggedInUMA) {
base::HistogramTester histogram_tester;
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kBufferPoolMaxBufferCountExceeded);
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat,
2);
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kBufferPoolMaxBufferCountExceeded, 1);
}
// Tests that too many frames dropped for the same reason emits a special UMA
// log and disables further logging
TEST_F(VideoCaptureControllerTest,
DroppedFrameLoggingGetsDisabledIfTooManyConsecutiveDropsForSameReason) {
base::HistogramTester histogram_tester;
for (int i = 0;
i < VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount;
i++) {
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
}
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat,
VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount);
// Add one more count after already having reached the max allowed.
// This should not get counted.
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat,
VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount);
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.MaxFrameDropExceeded.DeviceCapture",
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat,
1);
}
TEST_F(VideoCaptureControllerTest,
DeliveredFrameInBetweenDroppedFramesResetsCounter) {
base::HistogramTester histogram_tester;
for (int i = 0;
i <
VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount - 1;
i++) {
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
}
SendStubFrameToDeviceClient(arbitrary_format_, arbitrary_color_space_);
base::RunLoop().RunUntilIdle();
for (int i = 0;
i < VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount;
i++) {
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
}
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat,
2 * VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount -
1);
}
TEST_F(VideoCaptureControllerTest, DeliveredFrameReenablesDroppedFrameLogging) {
base::HistogramTester histogram_tester;
// Drop enough frames to disable logging
for (int i = 0;
i <
VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount + 1;
i++) {
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
}
SendStubFrameToDeviceClient(arbitrary_format_, arbitrary_color_space_);
base::RunLoop().RunUntilIdle();
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat,
VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount + 1);
}
TEST_F(VideoCaptureControllerTest,
ChangeInDropReasonReenablesDroppedFrameLogging) {
base::HistogramTester histogram_tester;
// Drop enough frames to disable logging
for (int i = 0;
i <
VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount + 1;
i++) {
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
}
// Drop for a different reason
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kBufferPoolMaxBufferCountExceeded);
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat);
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kDeviceClientFrameHasInvalidFormat,
VideoCaptureController::kMaxConsecutiveFrameDropForSameReasonCount + 1);
histogram_tester.ExpectBucketCount(
"Media.VideoCapture.FrameDrop.DeviceCapture",
media::VideoCaptureFrameDropReason::kBufferPoolMaxBufferCountExceeded, 1);
}
} // namespace content