blob: 1cb1d3a9cdbe34ff435a22b028a28a5599b80039 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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 <optional>
#include <string>
#include <utility>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "build/build_config.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/browser_task_environment.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_pool_util.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 BUILDFLAG(IS_CHROMEOS)
#include "media/capture/video/chromeos/video_capture_jpeg_decoder_impl.h"
#endif // BUILDFLAG(IS_CHROMEOS)
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::SaveArg;
using ::testing::StrEq;
namespace content {
namespace {
struct ControllerIDAndSize {
ControllerIDAndSize(VideoCaptureControllerID id, gfx::Size size)
: id(id), size(std::move(size)) {}
VideoCaptureControllerID id;
gfx::Size size;
};
bool operator==(const ControllerIDAndSize& x, const ControllerIDAndSize& y) {
return x.id == y.id && x.size == y.size;
}
class MockVideoCaptureControllerEventHandler
: public VideoCaptureControllerEventHandler {
public:
explicit MockVideoCaptureControllerEventHandler(
VideoCaptureController* controller)
: controller_(controller) {}
~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(const VideoCaptureControllerID&, int buffer_id));
MOCK_METHOD2(DoBufferDestroyed,
void(const VideoCaptureControllerID&, int buffer_id));
MOCK_METHOD1(DoBufferReady, void(ControllerIDAndSize buffer));
MOCK_METHOD1(OnCaptureConfigurationChanged,
void(const VideoCaptureControllerID&));
MOCK_METHOD1(OnFrameWithEmptyRegionCapture,
void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoEnded, void(const VideoCaptureControllerID&));
MOCK_METHOD2(DoError,
void(const VideoCaptureControllerID&, media::VideoCaptureError));
MOCK_METHOD1(OnStarted, void(const VideoCaptureControllerID&));
MOCK_METHOD1(OnStartedUsingGpuDecode, void(const VideoCaptureControllerID&));
MOCK_METHOD2(OnNewSubCaptureTargetVersion,
void(const VideoCaptureControllerID&, uint32_t));
MOCK_METHOD2(OnFrameDropped,
void(const VideoCaptureControllerID&,
media::VideoCaptureFrameDropReason));
void OnError(const VideoCaptureControllerID& id,
media::VideoCaptureError error) override {
DoError(id, error);
}
void OnNewBuffer(const VideoCaptureControllerID& id,
media::mojom::VideoBufferHandlePtr buffer_handle,
int buffer_id) override {
DoBufferCreated(id, buffer_id);
}
void OnBufferDestroyed(const VideoCaptureControllerID& id,
int buffer_id) override {
DoBufferDestroyed(id, buffer_id);
}
void OnBufferReady(const VideoCaptureControllerID& id,
const ReadyBuffer& buffer) override {
EXPECT_EQ(expected_pixel_format_, buffer.frame_info->pixel_format);
EXPECT_EQ(expected_color_space_, buffer.frame_info->color_space);
EXPECT_TRUE(buffer.frame_info->metadata.reference_time.has_value());
DoBufferReady(ControllerIDAndSize(id, buffer.frame_info->coded_size));
if (enable_auto_return_buffer_on_buffer_ready_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureController::ReturnBuffer,
base::Unretained(controller_), id, this,
buffer.buffer_id, feedback_));
}
}
void OnEnded(const VideoCaptureControllerID& id) override {
DoEnded(id);
// OnEnded() must respond by (eventually) unregistering the client.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(base::IgnoreResult(
&VideoCaptureController::RemoveClient),
base::Unretained(controller_), id, this));
}
raw_ptr<VideoCaptureController, DanglingUntriaged> controller_;
media::VideoPixelFormat expected_pixel_format_ = media::PIXEL_FORMAT_I420;
gfx::ColorSpace expected_color_space_ = gfx::ColorSpace::CreateREC709();
media::VideoCaptureFeedback feedback_;
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_(base::UnguessableToken::Create()),
arbitrary_session_id_(base::UnguessableToken::Create()) {}
VideoCaptureControllerTest(const VideoCaptureControllerTest&) = delete;
VideoCaptureControllerTest& operator=(const VideoCaptureControllerTest&) =
delete;
~VideoCaptureControllerTest() override {}
protected:
static constexpr int kPoolSize = 3;
void SetUp() override {
const std::string arbitrary_device_id = "arbitrary_device_id";
const blink::mojom::MediaStreamType arbitrary_stream_type =
blink::mojom::MediaStreamType::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::DoNothing());
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_ = std::make_unique<MockVideoCaptureControllerEventHandler>(
controller_.get());
client_b_ = std::make_unique<MockVideoCaptureControllerEventHandler>(
controller_.get());
}
void TearDown() override { base::RunLoop().RunUntilIdle(); }
void InitializeNewDeviceClientAndBufferPoolInstances() {
buffer_pool_ = base::MakeRefCounted<media::VideoCaptureBufferPoolImpl>(
media::VideoCaptureBufferType::kSharedMemory, kPoolSize);
#if BUILDFLAG(IS_CHROMEOS)
device_client_ = std::make_unique<media::VideoCaptureDeviceClient>(
std::make_unique<media::VideoFrameReceiverOnTaskRunner>(
controller_->GetWeakPtrForIOThread(), GetIOThreadTaskRunner({})),
buffer_pool_, media::VideoCaptureJpegDecoderFactoryCB());
#else
device_client_ = std::make_unique<media::VideoCaptureDeviceClient>(
std::make_unique<media::VideoFrameReceiverOnTaskRunner>(
controller_->GetWeakPtrForIOThread(), GetIOThreadTaskRunner({})),
buffer_pool_, std::nullopt);
#endif // BUILDFLAG(IS_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, false /* flip_y */, base::TimeTicks(),
base::TimeDelta(), /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt, frame_feedback_id);
}
BrowserTaskEnvironment task_environment_;
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_;
raw_ptr<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_;
};
// A simple test of VideoCaptureController's ability to add, remove, and keep
// track of clients.
TEST_F(VideoCaptureControllerTest, AddAndRemoveClients) {
media::VideoCaptureParams session_params_1;
session_params_1.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_params_2 = session_params_1;
media::VideoCaptureParams session_params_3 = session_params_1;
media::VideoCaptureParams session_params_4 = session_params_1;
// 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 =
base::UnguessableToken::Create();
const VideoCaptureControllerID client_a_route_2 =
base::UnguessableToken::Create();
const VideoCaptureControllerID client_b_route_1 = client_a_route_2;
const VideoCaptureControllerID client_b_route_2 =
base::UnguessableToken::Create();
const media::VideoCaptureSessionId session_id_1 =
base::UnguessableToken::Create();
const media::VideoCaptureSessionId session_id_2 =
base::UnguessableToken::Create();
const media::VideoCaptureSessionId session_id_3 =
base::UnguessableToken::Create();
const media::VideoCaptureSessionId session_id_4 =
base::UnguessableToken::Create();
// Clients in controller: []
ASSERT_EQ(0u, controller_->GetClientCount())
<< "Client count should initially be zero.";
controller_->AddClient(client_a_route_1, client_a_.get(), session_id_1,
session_params_1, std::nullopt);
// 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(), session_id_2,
session_params_2, std::nullopt);
// 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(), session_id_3,
session_params_3, std::nullopt);
// Clients in controller: [A/1, A/2, B/1]
ASSERT_EQ(3u, controller_->GetClientCount())
<< "Adding client B/1 should bump client count.";
ASSERT_EQ(session_id_2,
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_TRUE(
controller_->RemoveClient(client_a_route_2, client_a_.get()).is_empty())
<< "Removing a nonexistant client should fail.";
// Clients in controller: [A/1, B/1]
ASSERT_EQ(2u, controller_->GetClientCount());
ASSERT_EQ(session_id_3,
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(), session_id_4,
session_params_4, std::nullopt);
// Clients in controller: [A/1, B/2]
EXPECT_CALL(*client_a_, DoEnded(client_a_route_1)).Times(1);
controller_->StopSession(session_id_1); // 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(session_id_2); // 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(
base::UnguessableToken::Create()); // Session never existed.
// Clients in controller: [B/2]
ASSERT_EQ(1u, controller_->GetClientCount())
<< "Stopping non-existent session should be a no-op.";
ASSERT_TRUE(
controller_->RemoveClient(client_a_route_1, client_a_.get()).is_empty())
<< "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(session_id_4,
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_params_1;
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_params_1.requested_format =
media::VideoCaptureFormat(gfx::Size(320, 240), 30, format);
media::VideoCaptureParams session_params_2 = session_params_1;
media::VideoCaptureParams session_params_3 = session_params_1;
media::VideoCaptureParams session_params_4 = session_params_1;
media::VideoCaptureFormat device_format(gfx::Size(444, 200), 25, format);
const VideoCaptureControllerID client_a_route_1 =
base::UnguessableToken::Create();
const VideoCaptureControllerID client_a_route_2 =
base::UnguessableToken::Create();
const VideoCaptureControllerID client_b_route_1 =
base::UnguessableToken::Create();
const VideoCaptureControllerID client_b_route_2 =
base::UnguessableToken::Create();
const media::VideoCaptureSessionId session_id_1 =
base::UnguessableToken::Create();
const media::VideoCaptureSessionId session_id_2 =
base::UnguessableToken::Create();
const media::VideoCaptureSessionId session_id_3 =
base::UnguessableToken::Create();
controller_->AddClient(client_a_route_1, client_a_.get(), session_id_1,
session_params_1, std::nullopt);
controller_->AddClient(client_b_route_1, client_b_.get(), session_id_3,
session_params_3, std::nullopt);
controller_->AddClient(client_a_route_2, client_a_.get(), session_id_2,
session_params_2, std::nullopt);
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, /*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code);
auto buffer_access = buffer.handle_provider->GetHandleForInProcessAccess();
ASSERT_EQ(1.0 / kPoolSize, device_client_->GetBufferPoolUtilization());
UNSAFE_TODO(
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(ControllerIDAndSize(
client_a_route_1, device_format.frame_size)));
}
{
InSequence s;
EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1, _));
EXPECT_CALL(*client_b_, DoBufferReady(ControllerIDAndSize(
client_b_route_1, device_format.frame_size)));
}
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2, _));
EXPECT_CALL(*client_a_, DoBufferReady(ControllerIDAndSize(
client_a_route_2, device_format.frame_size)));
}
client_a_->feedback_.resource_utilization = 0.5;
client_b_->feedback_.resource_utilization = -1.0;
// Expect VideoCaptureController to call the load observer with a
// resource utilization of 0.5 (the largest of all reported values).
media::VideoCaptureFeedback kExpectedFeedback =
media::VideoCaptureFeedback(0.5);
kExpectedFeedback.frame_id = arbitrary_frame_feedback_id;
EXPECT_CALL(*mock_launched_device_, OnUtilizationReport(kExpectedFeedback));
device_client_->OnIncomingCapturedBuffer(
std::move(buffer), device_format, arbitrary_reference_time_,
arbitrary_timestamp_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
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,
/*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code_2);
auto buffer2_access = buffer2.handle_provider->GetHandleForInProcessAccess();
UNSAFE_TODO(memset(buffer2_access->data(), buffer_no++,
buffer2_access->mapped_size()));
client_a_->feedback_ = media::VideoCaptureFeedback(0.5, 60, 1000);
client_a_->feedback_.frame_id = arbitrary_frame_feedback_id_2;
client_b_->feedback_ = media::VideoCaptureFeedback(3.14, 30);
client_b_->feedback_.frame_id = arbitrary_frame_feedback_id_2;
// Expect VideoCaptureController to call the load observer with a
// resource utilization of 3.14 (the largest of all reported values) and
// sink constraints being the minimum of all reported values.
auto expected_feedback_2 = media::VideoCaptureFeedback(3.14, 30, 1000);
expected_feedback_2.frame_id = arbitrary_frame_feedback_id_2;
EXPECT_CALL(*mock_launched_device_, OnUtilizationReport(expected_feedback_2));
device_client_->OnIncomingCapturedBuffer(
std::move(buffer2), device_format, arbitrary_reference_time_,
arbitrary_timestamp_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
// The frame should be delivered to the clients in any order.
EXPECT_CALL(*client_a_, DoBufferReady(ControllerIDAndSize(
client_a_route_1, device_format.frame_size)));
EXPECT_CALL(*client_b_, DoBufferReady(ControllerIDAndSize(
client_b_route_1, device_format.frame_size)));
EXPECT_CALL(*client_a_, DoBufferReady(ControllerIDAndSize(
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(),
base::UnguessableToken::Create(), session_params_4,
std::nullopt);
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_3 = 200 + i;
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_3, &buffer3,
/*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code_3);
auto buffer3_access =
buffer3.handle_provider->GetHandleForInProcessAccess();
UNSAFE_TODO(memset(buffer3_access->data(), buffer_no++,
buffer3_access->mapped_size()));
device_client_->OnIncomingCapturedBuffer(
std::move(buffer3), device_format, arbitrary_reference_time_,
arbitrary_timestamp_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
}
// 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,
/*require_new_buffer_id=*/nullptr, /*retire_old_buffer_id=*/nullptr));
// 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(ControllerIDAndSize(
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(ControllerIDAndSize(
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(ControllerIDAndSize(
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(ControllerIDAndSize(
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(session_id_3);
// 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, /*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code_3);
auto buffer3_access = buffer3.handle_provider->GetHandleForInProcessAccess();
UNSAFE_TODO(memset(buffer3_access->data(), buffer_no++,
buffer3_access->mapped_size()));
device_client_->OnIncomingCapturedBuffer(
std::move(buffer3), device_format, arbitrary_reference_time_,
arbitrary_timestamp_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
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, /*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
{
// 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(session_id_2);
}
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code_4);
auto buffer4_access = buffer4.handle_provider->GetHandleForInProcessAccess();
UNSAFE_TODO(memset(buffer4_access->data(), buffer_no++,
buffer4_access->mapped_size()));
device_client_->OnIncomingCapturedBuffer(
std::move(buffer4), device_format, arbitrary_reference_time_,
arbitrary_timestamp_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
// B2 is the only client left, and is the only one that should
// get the buffer.
EXPECT_CALL(*client_b_, DoBufferReady(ControllerIDAndSize(
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(All,
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_params_1;
session_params_1.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_params_2 = session_params_1;
const gfx::Size capture_resolution(320, 240);
const VideoCaptureControllerID route_id(base::UnguessableToken::Create());
const base::UnguessableToken session_id_1 = base::UnguessableToken::Create();
const base::UnguessableToken session_id_2 = base::UnguessableToken::Create();
// Start with one client.
controller_->AddClient(route_id, client_a_.get(), session_id_1,
session_params_1, std::nullopt);
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(), session_id_2,
session_params_2, std::nullopt);
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, /*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
reserve_result);
device_client_->OnIncomingCapturedBuffer(
std::move(buffer), device_format, arbitrary_reference_time_,
arbitrary_timestamp_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
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_params_1;
session_params_1.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_params_2 = session_params_1;
const VideoCaptureControllerID route_id(base::UnguessableToken::Create());
// Start with one client.
controller_->AddClient(route_id, client_a_.get(),
base::UnguessableToken::Create(), session_params_1,
std::nullopt);
// 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, /*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
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_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
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(),
base::UnguessableToken::Create(), session_params_2,
std::nullopt);
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_params_1;
session_params_1.requested_format = arbitrary_format;
const VideoCaptureControllerID route_id = base::UnguessableToken::Create();
controller_->AddClient(route_id, client_a_.get(),
base::UnguessableToken::Create(), session_params_1,
std::nullopt);
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;
media::VideoCaptureFeedback stub_consumer_feedback =
media::VideoCaptureFeedback(static_cast<float>(frame_index) /
kTestFrameSequenceLength);
stub_consumer_feedback.frame_id = stub_frame_feedback_id;
client_a_->feedback_ = stub_consumer_feedback;
EXPECT_CALL(*client_a_, DoBufferReady(ControllerIDAndSize(
route_id, arbitrary_format.frame_size)))
.Times(1);
EXPECT_CALL(*mock_launched_device_,
OnUtilizationReport(stub_consumer_feedback))
.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, /*require_new_buffer_id=*/nullptr,
/*retire_old_buffer_id=*/nullptr);
ASSERT_EQ(media::VideoCaptureDevice::Client::ReserveResult::kSucceeded,
result_code);
device_client_->OnIncomingCapturedBuffer(
std::move(buffer), arbitrary_format, arbitrary_reference_time_,
arbitrary_timestamp_, /*capture_begin_timestamp=*/std::nullopt,
/*metadata=*/std::nullopt);
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, std::nullopt);
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, std::nullopt);
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, std::nullopt);
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 media::VideoCaptureFeedback arbitrary_feedback =
media::VideoCaptureFeedback();
controller_->ReturnBuffer(arbitrary_route_id_, client_a_.get(),
buffer_id_reported_to_client, arbitrary_feedback);
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, std::nullopt);
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 media::VideoCaptureFeedback arbitrary_feedback =
media::VideoCaptureFeedback();
controller_->ReturnBuffer(arbitrary_route_id_, client_a_.get(),
first_buffer_id, arbitrary_feedback);
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_feedback);
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_params_1;
session_params_1.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
media::VideoCaptureParams session_params_2 = session_params_1;
media::VideoCaptureParams session_params_3 = session_params_1;
const VideoCaptureControllerID client_a_route_1 =
base::UnguessableToken::Create();
const VideoCaptureControllerID client_a_route_2 =
base::UnguessableToken::Create();
const VideoCaptureControllerID client_b_route_1 =
base::UnguessableToken::Create();
controller_->AddClient(client_a_route_1, client_a_.get(),
base::UnguessableToken::Create(), session_params_1,
std::nullopt);
controller_->AddClient(client_b_route_1, client_b_.get(),
base::UnguessableToken::Create(), session_params_3,
std::nullopt);
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(),
base::UnguessableToken::Create(), session_params_2,
std::nullopt);
}
}
TEST_F(VideoCaptureControllerTest, OnFrameDroppedIsForwarded) {
media::VideoCaptureParams requested_params;
requested_params.requested_format = arbitrary_format_;
controller_->AddClient(arbitrary_route_id_, client_a_.get(),
arbitrary_session_id_, requested_params, std::nullopt);
EXPECT_CALL(*client_a_, OnFrameDropped(_, _)).Times(1);
controller_->OnFrameDropped(
media::VideoCaptureFrameDropReason::kBufferPoolMaxBufferCountExceeded);
Mock::VerifyAndClearExpectations(client_a_.get());
}
TEST_F(VideoCaptureControllerTest, DeviceClientWithColorSpace) {
// Register |client_a_| at |controller_|.
media::VideoCaptureParams requested_params;
requested_params.requested_format = media::VideoCaptureFormat(
gfx::Size(128, 80), 30, media::PIXEL_FORMAT_ARGB);
const gfx::ColorSpace data_color_space =
gfx::ColorSpace::CreateDisplayP3D65();
const gfx::ColorSpace overriden_color_space =
data_color_space.GetWithMatrixAndRange(
gfx::ColorSpace::MatrixID::SMPTE170M,
gfx::ColorSpace::RangeID::LIMITED);
client_a_->expected_color_space_ = overriden_color_space;
controller_->AddClient(arbitrary_route_id_, client_a_.get(),
arbitrary_session_id_, requested_params, std::nullopt);
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(requested_params.requested_format,
data_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());
}
} // namespace
} // namespace content