blob: 9f503edeebc98bb51c691e48c2b40750b5375ec5 [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.
// Unit test for VideoCaptureController.
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "content/browser/renderer_host/media/media_stream_provider.h"
#include "content/browser/renderer_host/media/video_capture_controller.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/common/gpu/client/gl_helper.h"
#include "content/common/media/media_stream_options.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "gpu/command_buffer/common/mailbox_holder.h"
#include "media/base/video_util.h"
#include "media/video/capture/video_capture_types.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_ANDROID)
#include "content/browser/renderer_host/test/no_transport_image_transport_factory_android.h"
#else
#include "content/browser/compositor/test/no_transport_image_transport_factory.h"
#endif
using ::testing::InSequence;
using ::testing::Mock;
namespace content {
class MockVideoCaptureControllerEventHandler
: public VideoCaptureControllerEventHandler {
public:
explicit MockVideoCaptureControllerEventHandler(
VideoCaptureController* controller)
: controller_(controller) {}
virtual ~MockVideoCaptureControllerEventHandler() {}
// These mock methods are delegated to by our fake implementation of
// VideoCaptureControllerEventHandler, to be used in EXPECT_CALL().
MOCK_METHOD1(DoBufferCreated, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoBufferDestroyed, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoBufferReady, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoMailboxBufferReady, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoEnded, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoError, void(const VideoCaptureControllerID&));
virtual void OnError(const VideoCaptureControllerID& id) override {
DoError(id);
}
virtual void OnBufferCreated(const VideoCaptureControllerID& id,
base::SharedMemoryHandle handle,
int length, int buffer_id) override {
DoBufferCreated(id);
}
virtual void OnBufferDestroyed(const VideoCaptureControllerID& id,
int buffer_id) override {
DoBufferDestroyed(id);
}
virtual void OnBufferReady(const VideoCaptureControllerID& id,
int buffer_id,
const media::VideoCaptureFormat& format,
const gfx::Rect& visible_rect,
base::TimeTicks timestamp) override {
DoBufferReady(id);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureController::ReturnBuffer,
base::Unretained(controller_),
id,
this,
buffer_id,
0));
}
virtual void OnMailboxBufferReady(const VideoCaptureControllerID& id,
int buffer_id,
const gpu::MailboxHolder& mailbox_holder,
const media::VideoCaptureFormat& format,
base::TimeTicks timestamp) override {
DoMailboxBufferReady(id);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureController::ReturnBuffer,
base::Unretained(controller_),
id,
this,
buffer_id,
mailbox_holder.sync_point));
}
virtual void OnEnded(const VideoCaptureControllerID& id) override {
DoEnded(id);
// OnEnded() must respond by (eventually) unregistering the client.
base::MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(base::IgnoreResult(&VideoCaptureController::RemoveClient),
base::Unretained(controller_), id, this));
}
VideoCaptureController* controller_;
};
// Test class.
class VideoCaptureControllerTest : public testing::Test {
public:
VideoCaptureControllerTest() {}
virtual ~VideoCaptureControllerTest() {}
protected:
static const int kPoolSize = 3;
virtual void SetUp() override {
controller_.reset(new VideoCaptureController(kPoolSize));
device_ = controller_->NewDeviceClient().Pass();
client_a_.reset(new MockVideoCaptureControllerEventHandler(
controller_.get()));
client_b_.reset(new MockVideoCaptureControllerEventHandler(
controller_.get()));
}
virtual void TearDown() override {
base::RunLoop().RunUntilIdle();
}
scoped_refptr<media::VideoFrame> WrapI420Buffer(
const scoped_refptr<media::VideoCaptureDevice::Client::Buffer>& buffer,
gfx::Size dimensions) {
return media::VideoFrame::WrapExternalPackedMemory(
media::VideoFrame::I420,
dimensions,
gfx::Rect(dimensions),
dimensions,
reinterpret_cast<uint8*>(buffer->data()),
media::VideoFrame::AllocationSize(media::VideoFrame::I420, dimensions),
base::SharedMemory::NULLHandle(),
base::TimeDelta(),
base::Closure());
}
scoped_refptr<media::VideoFrame> WrapMailboxBuffer(
const scoped_refptr<media::VideoCaptureDevice::Client::Buffer>& buffer,
scoped_ptr<gpu::MailboxHolder> holder,
const media::VideoFrame::ReleaseMailboxCB& release_cb,
gfx::Size dimensions) {
return media::VideoFrame::WrapNativeTexture(
holder.Pass(),
release_cb,
dimensions,
gfx::Rect(dimensions),
dimensions,
base::TimeDelta(),
media::VideoFrame::ReadPixelsCB());
}
TestBrowserThreadBundle bundle_;
scoped_ptr<MockVideoCaptureControllerEventHandler> client_a_;
scoped_ptr<MockVideoCaptureControllerEventHandler> client_b_;
scoped_ptr<VideoCaptureController> controller_;
scoped_ptr<media::VideoCaptureDevice::Client> device_;
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(0, controller_->GetClientCount())
<< "Client count should initially be zero.";
controller_->AddClient(client_a_route_1,
client_a_.get(),
base::kNullProcessHandle,
100,
session_100);
// Clients in controller: [A/1]
ASSERT_EQ(1, controller_->GetClientCount())
<< "Adding client A/1 should bump client count.";
controller_->AddClient(client_a_route_2,
client_a_.get(),
base::kNullProcessHandle,
200,
session_200);
// Clients in controller: [A/1, A/2]
ASSERT_EQ(2, controller_->GetClientCount())
<< "Adding client A/2 should bump client count.";
controller_->AddClient(client_b_route_1,
client_b_.get(),
base::kNullProcessHandle,
300,
session_300);
// Clients in controller: [A/1, A/2, B/1]
ASSERT_EQ(3, 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(2, 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(2, 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(1, controller_->GetClientCount());
controller_->AddClient(client_b_route_2,
client_b_.get(),
base::kNullProcessHandle,
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(2, 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(1, 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(1, controller_->GetClientCount())
<< "Stopping non-existant session 200 should be a no-op.";
controller_->StopSession(256); // Session 256 never existed.
// Clients in controller: [B/2]
ASSERT_EQ(1, controller_->GetClientCount())
<< "Stopping non-existant 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(1, controller_->GetClientCount())
<< "Removing non-existant 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(0, controller_->GetClientCount())
<< "Client count should return to zero after all clients are gone.";
}
static void CacheSyncPoint(uint32* called_release_sync_point,
uint32 release_sync_point) {
*called_release_sync_point = release_sync_point;
}
// 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_F(VideoCaptureControllerTest, NormalCaptureMultipleClients) {
// VideoCaptureController::ReturnBuffer() uses ImageTransportFactory.
#if defined(OS_ANDROID)
ImageTransportFactoryAndroid::InitializeForUnitTests(
scoped_ptr<ImageTransportFactoryAndroid>(
new NoTransportImageTransportFactoryAndroid));
#else
ImageTransportFactory::InitializeForUnitTests(
scoped_ptr<ImageTransportFactory>(new NoTransportImageTransportFactory));
#endif
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_1 = session_100;
gfx::Size capture_resolution(444, 200);
// The device format needn't match the VideoCaptureParams (the camera can do
// what it wants). Pick something random.
media::VideoCaptureFormat device_format(
gfx::Size(10, 10), 25, media::PIXEL_FORMAT_RGB24);
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);
// Start with two clients.
controller_->AddClient(client_a_route_1,
client_a_.get(),
base::kNullProcessHandle,
100,
session_100);
controller_->AddClient(client_b_route_1,
client_b_.get(),
base::kNullProcessHandle,
300,
session_300);
controller_->AddClient(client_a_route_2,
client_a_.get(),
base::kNullProcessHandle,
200,
session_200);
ASSERT_EQ(3, 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 buffer_no = 1;
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer;
buffer =
device_->ReserveOutputBuffer(media::VideoFrame::I420, capture_resolution);
ASSERT_TRUE(buffer.get());
memset(buffer->data(), buffer_no++, buffer->size());
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_1)).Times(1);
EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_1)).Times(1);
}
{
InSequence s;
EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1)).Times(1);
EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_1)).Times(1);
}
{
InSequence s;
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2)).Times(1);
EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_2)).Times(1);
}
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(capture_resolution,
device_format.frame_rate,
media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, capture_resolution),
base::TimeTicks());
buffer = NULL;
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(client_b_.get());
// 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.
buffer =
device_->ReserveOutputBuffer(media::VideoFrame::I420, capture_resolution);
ASSERT_TRUE(buffer.get());
memset(buffer->data(), buffer_no++, buffer->size());
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(capture_resolution,
device_format.frame_rate,
media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, capture_resolution),
base::TimeTicks());
buffer = NULL;
// The buffer should be delivered to the clients in any order.
EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_1)).Times(1);
EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_1)).Times(1);
EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_2)).Times(1);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(client_b_.get());
// Add a fourth client now that some buffers have come through.
controller_->AddClient(client_b_route_2,
client_b_.get(),
base::kNullProcessHandle,
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++) {
buffer = device_->ReserveOutputBuffer(media::VideoFrame::I420,
capture_resolution);
ASSERT_TRUE(buffer.get());
memset(buffer->data(), buffer_no++, buffer->size());
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(capture_resolution,
device_format.frame_rate,
media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, capture_resolution),
base::TimeTicks());
buffer = NULL;
}
// ReserveOutputBuffer ought to fail now, because the pool is depleted.
ASSERT_FALSE(device_->ReserveOutputBuffer(media::VideoFrame::I420,
capture_resolution).get());
// The new client needs to be told of 3 buffers; the old clients only 2.
EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_2)).Times(kPoolSize);
EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_2)).Times(kPoolSize);
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_1))
.Times(kPoolSize - 1);
EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_1)).Times(kPoolSize);
EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2))
.Times(kPoolSize - 1);
EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_2)).Times(kPoolSize);
EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1))
.Times(kPoolSize - 1);
EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_1)).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.
buffer =
device_->ReserveOutputBuffer(media::VideoFrame::I420, capture_resolution);
ASSERT_TRUE(buffer.get());
memset(buffer->data(), buffer_no++, buffer->size());
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(capture_resolution,
device_format.frame_rate,
media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, capture_resolution),
base::TimeTicks());
buffer = NULL;
buffer =
device_->ReserveOutputBuffer(media::VideoFrame::I420, capture_resolution);
{
// 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_TRUE(buffer.get());
memset(buffer->data(), buffer_no++, buffer->size());
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(capture_resolution,
device_format.frame_rate,
media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, capture_resolution),
base::TimeTicks());
buffer = NULL;
// 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)).Times(2);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_a_.get());
Mock::VerifyAndClearExpectations(client_b_.get());
// Allocate all buffers from the buffer pool, half as SHM buffer and half as
// mailbox buffers. Make sure of different counts though.
int shm_buffers = kPoolSize / 2;
int mailbox_buffers = kPoolSize - shm_buffers;
if (shm_buffers == mailbox_buffers) {
shm_buffers--;
mailbox_buffers++;
}
for (int i = 0; i < shm_buffers; ++i) {
buffer = device_->ReserveOutputBuffer(media::VideoFrame::I420,
capture_resolution);
ASSERT_TRUE(buffer.get());
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(capture_resolution,
device_format.frame_rate,
media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, capture_resolution),
base::TimeTicks());
buffer = NULL;
}
std::vector<uint32> mailbox_syncpoints(mailbox_buffers);
std::vector<uint32> release_syncpoints(mailbox_buffers);
#if defined(OS_ANDROID)
GLHelper* gl_helper =
ImageTransportFactoryAndroid::GetInstance()->GetGLHelper();
#else
GLHelper* gl_helper = ImageTransportFactory::GetInstance()->GetGLHelper();
#endif
for (int i = 0; i < mailbox_buffers; ++i) {
buffer = device_->ReserveOutputBuffer(media::VideoFrame::NATIVE_TEXTURE,
gfx::Size(0, 0));
ASSERT_TRUE(buffer.get());
mailbox_syncpoints[i] = gl_helper->InsertSyncPoint();
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(capture_resolution,
device_format.frame_rate,
media::PIXEL_FORMAT_TEXTURE),
WrapMailboxBuffer(buffer,
make_scoped_ptr(new gpu::MailboxHolder(
gpu::Mailbox(), 0, mailbox_syncpoints[i])),
base::Bind(&CacheSyncPoint, &release_syncpoints[i]),
capture_resolution),
base::TimeTicks());
buffer = NULL;
}
// ReserveOutputBuffers ought to fail now regardless of buffer format, because
// the pool is depleted.
ASSERT_FALSE(device_->ReserveOutputBuffer(media::VideoFrame::I420,
capture_resolution).get());
ASSERT_FALSE(device_->ReserveOutputBuffer(media::VideoFrame::NATIVE_TEXTURE,
gfx::Size(0, 0)).get());
EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_2)).Times(shm_buffers);
EXPECT_CALL(*client_b_, DoMailboxBufferReady(client_b_route_2))
.Times(mailbox_buffers);
base::RunLoop().RunUntilIdle();
for (size_t i = 0; i < mailbox_syncpoints.size(); ++i) {
// A new release sync point must be inserted when the video frame is
// returned to the Browser process.
// See: MockVideoCaptureControllerEventHandler::OnMailboxBufferReady() and
// VideoCaptureController::ReturnBuffer()
ASSERT_NE(mailbox_syncpoints[i], release_syncpoints[i]);
}
Mock::VerifyAndClearExpectations(client_b_.get());
#if defined(OS_ANDROID)
ImageTransportFactoryAndroid::TerminateForUnitTests();
#else
ImageTransportFactory::Terminate();
#endif
}
// 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(), base::kNullProcessHandle, 100, session_100);
device_->OnError("Test Error");
EXPECT_CALL(*client_a_, DoError(route_id)).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)).Times(1);
controller_->AddClient(
route_id, client_b_.get(), base::kNullProcessHandle, 200, session_200);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClearExpectations(client_b_.get());
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer =
device_->ReserveOutputBuffer(media::VideoFrame::I420, capture_resolution);
ASSERT_TRUE(buffer.get());
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(
capture_resolution, 30, media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, capture_resolution),
base::TimeTicks());
buffer = NULL;
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(), base::kNullProcessHandle, 100, session_100);
media::VideoCaptureFormat device_format(
gfx::Size(10, 10), 25, media::PIXEL_FORMAT_ARGB);
// 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());
const gfx::Size dims(320, 240);
scoped_refptr<media::VideoCaptureDevice::Client::Buffer> buffer =
device_->ReserveOutputBuffer(media::VideoFrame::I420, dims);
ASSERT_TRUE(buffer.get());
device_->OnError("Test error");
device_->OnIncomingCapturedVideoFrame(
buffer,
media::VideoCaptureFormat(
dims, device_format.frame_rate, media::PIXEL_FORMAT_I420),
WrapI420Buffer(buffer, dims),
base::TimeTicks());
buffer = NULL;
EXPECT_CALL(*client_a_, DoError(route_id)).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)).Times(1);
controller_->AddClient(
route_id, client_b_.get(), base::kNullProcessHandle, 200, session_200);
Mock::VerifyAndClearExpectations(client_b_.get());
}
} // namespace content