blob: 9e4fd30925952a3cb895c0ca8285d43e92ea4d83 [file] [log] [blame]
// Copyright 2018 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/media/capture/frame_sink_video_capture_device.h"
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/flat_map.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "media/base/video_frame.h"
#include "media/capture/video/video_frame_receiver.h"
#include "media/capture/video_capture_types.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/native_widget_types.h"
using testing::_;
using testing::ByRef;
using testing::Eq;
using testing::Expectation;
using testing::Ge;
using testing::NiceMock;
using testing::NotNull;
using testing::SaveArg;
using testing::Sequence;
using testing::StrNe;
namespace content {
namespace {
// Threading notes: Throughout these tests, the UI thread (the main test
// thread) represents the executor of all external-to-device operations. This
// means that it represents everything that runs on the UI thread in the browser
// process, plus anything that would run in the VIZ process. The IO thread is
// used as the "device thread" for content::FrameSinkVideoCaptureDevice.
#define DCHECK_ON_DEVICE_THREAD() DCHECK_CURRENTLY_ON(BrowserThread::IO)
#define DCHECK_NOT_ON_DEVICE_THREAD() DCHECK_CURRENTLY_ON(BrowserThread::UI)
// Convenience macro to block the test procedure and run all pending UI tasks.
#define RUN_UI_TASKS() base::RunLoop().RunUntilIdle()
// Convenience macro to post a task to run on the device thread.
#define POST_DEVICE_TASK(closure) \
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO}, closure)
// Convenience macro to block the test procedure until all pending tasks have
// run on the device thread.
#define WAIT_FOR_DEVICE_TASKS() \
browser_threads_.RunIOThreadUntilIdle(); \
RUN_UI_TASKS()
// Capture parameters.
constexpr gfx::Size kResolution = gfx::Size(320, 180);
constexpr int kMaxFrameRate = 25; // It evenly divides 1 million usec.
constexpr base::TimeDelta kMinCapturePeriod = base::TimeDelta::FromMicroseconds(
base::Time::kMicrosecondsPerSecond / kMaxFrameRate);
constexpr media::VideoPixelFormat kFormat = media::PIXEL_FORMAT_I420;
// Helper to return the capture parameters packaged in a VideoCaptureParams.
media::VideoCaptureParams GetCaptureParams() {
media::VideoCaptureParams params;
params.requested_format =
media::VideoCaptureFormat(kResolution, kMaxFrameRate, kFormat);
return params;
}
// Mock for the FrameSinkVideoCapturer running in the VIZ process.
class MockFrameSinkVideoCapturer : public viz::mojom::FrameSinkVideoCapturer {
public:
MockFrameSinkVideoCapturer() : binding_(this) {}
bool is_bound() const { return binding_.is_bound(); }
void Bind(viz::mojom::FrameSinkVideoCapturerRequest request) {
DCHECK_NOT_ON_DEVICE_THREAD();
binding_.Bind(std::move(request));
}
MOCK_METHOD2(SetFormat,
void(media::VideoPixelFormat format,
const gfx::ColorSpace& color_space));
MOCK_METHOD1(SetMinCapturePeriod, void(base::TimeDelta min_period));
MOCK_METHOD1(SetMinSizeChangePeriod, void(base::TimeDelta));
MOCK_METHOD3(SetResolutionConstraints,
void(const gfx::Size& min_size,
const gfx::Size& max_size,
bool use_fixed_aspect_ratio));
MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
void ChangeTarget(
const base::Optional<viz::FrameSinkId>& frame_sink_id) final {
DCHECK_NOT_ON_DEVICE_THREAD();
MockChangeTarget(frame_sink_id ? *frame_sink_id : viz::FrameSinkId());
}
MOCK_METHOD1(MockChangeTarget, void(const viz::FrameSinkId& frame_sink_id));
void Start(viz::mojom::FrameSinkVideoConsumerPtr consumer) final {
DCHECK_NOT_ON_DEVICE_THREAD();
consumer_ = std::move(consumer);
MockStart(consumer_.get());
}
MOCK_METHOD1(MockStart, void(viz::mojom::FrameSinkVideoConsumer* consumer));
void Stop() final {
DCHECK_NOT_ON_DEVICE_THREAD();
consumer_.reset();
MockStop();
}
MOCK_METHOD0(MockStop, void());
MOCK_METHOD0(RequestRefreshFrame, void());
MOCK_METHOD2(CreateOverlay,
void(int32_t stacking_index,
viz::mojom::FrameSinkVideoCaptureOverlayRequest request));
private:
mojo::Binding<viz::mojom::FrameSinkVideoCapturer> binding_;
viz::mojom::FrameSinkVideoConsumerPtr consumer_;
};
// Represents the FrameSinkVideoConsumerFrameCallbacks instance in the VIZ
// process.
class MockFrameSinkVideoConsumerFrameCallbacks
: public viz::mojom::FrameSinkVideoConsumerFrameCallbacks {
public:
MockFrameSinkVideoConsumerFrameCallbacks() : binding_(this) {}
void Bind(viz::mojom::FrameSinkVideoConsumerFrameCallbacksRequest request) {
DCHECK_NOT_ON_DEVICE_THREAD();
binding_.Bind(std::move(request));
}
MOCK_METHOD0(Done, void());
MOCK_METHOD1(ProvideFeedback, void(double utilization));
private:
mojo::Binding<viz::mojom::FrameSinkVideoConsumerFrameCallbacks> binding_;
};
// Mock for the VideoFrameReceiver, the point-of-injection of video frames into
// the video capture stack. It's mocked methods are called on the device thread.
// Some methods stash objects of interest, which test code must grab via the
// TakeXYZ() utility methods (called on the main thread).
class MockVideoFrameReceiver : public media::VideoFrameReceiver {
public:
using Buffer = media::VideoCaptureDevice::Client::Buffer;
~MockVideoFrameReceiver() override {
DCHECK_ON_DEVICE_THREAD();
EXPECT_TRUE(buffer_handles_.empty());
EXPECT_TRUE(feedback_ids_.empty());
EXPECT_TRUE(access_permissions_.empty());
EXPECT_TRUE(frame_infos_.empty());
}
void OnNewBuffer(int buffer_id,
media::mojom::VideoBufferHandlePtr buffer_handle) final {
DCHECK_ON_DEVICE_THREAD();
auto* const raw_pointer = buffer_handle.get();
buffer_handles_[buffer_id] = std::move(buffer_handle);
MockOnNewBuffer(buffer_id, raw_pointer);
}
MOCK_METHOD2(MockOnNewBuffer,
void(int buffer_id,
media::mojom::VideoBufferHandle* buffer_handle));
void OnFrameReadyInBuffer(
int buffer_id,
int frame_feedback_id,
std::unique_ptr<Buffer::ScopedAccessPermission> buffer_read_permission,
media::mojom::VideoFrameInfoPtr frame_info) final {
DCHECK_ON_DEVICE_THREAD();
feedback_ids_[buffer_id] = frame_feedback_id;
auto* const raw_pointer_to_permission = buffer_read_permission.get();
access_permissions_[buffer_id] = std::move(buffer_read_permission);
auto* const raw_pointer_to_info = frame_info.get();
frame_infos_[buffer_id] = std::move(frame_info);
MockOnFrameReadyInBuffer(buffer_id, frame_feedback_id,
raw_pointer_to_permission, raw_pointer_to_info);
}
MOCK_METHOD4(MockOnFrameReadyInBuffer,
void(int buffer_id,
int frame_feedback_id,
Buffer::ScopedAccessPermission* buffer_read_permission,
const media::mojom::VideoFrameInfo* frame_info));
MOCK_METHOD1(OnBufferRetired, void(int buffer_id));
MOCK_METHOD1(OnError, void(media::VideoCaptureError error));
MOCK_METHOD1(OnFrameDropped, void(media::VideoCaptureFrameDropReason reason));
MOCK_METHOD1(OnLog, void(const std::string& message));
MOCK_METHOD0(OnStarted, void());
MOCK_METHOD0(OnStopped, void());
void OnStartedUsingGpuDecode() final { NOTREACHED(); }
base::ReadOnlySharedMemoryRegion TakeBufferHandle(int buffer_id) {
DCHECK_NOT_ON_DEVICE_THREAD();
const auto it = buffer_handles_.find(buffer_id);
if (it == buffer_handles_.end()) {
ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
return base::ReadOnlySharedMemoryRegion();
}
CHECK(it->second->is_read_only_shmem_region());
auto buffer = std::move(it->second->get_read_only_shmem_region());
buffer_handles_.erase(it);
return buffer;
}
int TakeFeedbackId(int buffer_id) {
DCHECK_NOT_ON_DEVICE_THREAD();
const auto it = feedback_ids_.find(buffer_id);
if (it == feedback_ids_.end()) {
ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
return -1;
}
const int feedback_id = it->second;
feedback_ids_.erase(it);
return feedback_id;
}
void ReleaseAccessPermission(int buffer_id) {
DCHECK_NOT_ON_DEVICE_THREAD();
const auto it = access_permissions_.find(buffer_id);
if (it == access_permissions_.end()) {
ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
return;
}
access_permissions_.erase(it);
}
media::mojom::VideoFrameInfoPtr TakeVideoFrameInfo(int buffer_id) {
DCHECK_NOT_ON_DEVICE_THREAD();
const auto it = frame_infos_.find(buffer_id);
if (it == frame_infos_.end()) {
ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
return media::mojom::VideoFrameInfoPtr();
}
media::mojom::VideoFrameInfoPtr info = std::move(it->second);
frame_infos_.erase(it);
return info;
}
private:
base::flat_map<int, media::mojom::VideoBufferHandlePtr> buffer_handles_;
base::flat_map<int, int> feedback_ids_;
base::flat_map<int, std::unique_ptr<Buffer::ScopedAccessPermission>>
access_permissions_;
base::flat_map<int, media::mojom::VideoFrameInfoPtr> frame_infos_;
};
// A FrameSinkVideoCaptureDevice, but with CreateCapturer() overridden to bind
// to a MockFrameSinkVideoCapturer instead of the real thing.
class FrameSinkVideoCaptureDeviceForTest : public FrameSinkVideoCaptureDevice {
public:
explicit FrameSinkVideoCaptureDeviceForTest(
MockFrameSinkVideoCapturer* capturer)
: capturer_(capturer) {}
protected:
void CreateCapturer(viz::mojom::FrameSinkVideoCapturerRequest request) final {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(
[](MockFrameSinkVideoCapturer* capturer,
viz::mojom::FrameSinkVideoCapturerRequest request) {
capturer->Bind(std::move(request));
},
capturer_, std::move(request)));
}
MockFrameSinkVideoCapturer* const capturer_;
};
// Convenience macros to make a non-blocking FrameSinkVideoCaptureDevice method
// call on the device thread.
#define POST_DEVICE_METHOD_CALL0(method) \
POST_DEVICE_TASK(base::BindOnce(&FrameSinkVideoCaptureDevice::method, \
base::Unretained(device_.get())))
#define POST_DEVICE_METHOD_CALL(method, ...) \
POST_DEVICE_TASK(base::BindOnce(&FrameSinkVideoCaptureDevice::method, \
base::Unretained(device_.get()), \
__VA_ARGS__))
class FrameSinkVideoCaptureDeviceTest : public testing::Test {
public:
FrameSinkVideoCaptureDeviceTest()
: browser_threads_(TestBrowserThreadBundle::REAL_IO_THREAD) {}
~FrameSinkVideoCaptureDeviceTest() override { EXPECT_FALSE(device_); }
void SetUp() override {
// Create the FrameSinkVideoCaptureDevice on the device thread, and block
// until complete.
POST_DEVICE_TASK(base::BindOnce(
[](FrameSinkVideoCaptureDeviceTest* test) {
test->device_ = std::make_unique<FrameSinkVideoCaptureDeviceForTest>(
&test->capturer_);
},
this));
WAIT_FOR_DEVICE_TASKS();
}
void TearDown() override {
// Destroy the FrameSinkVideoCaptureDevice on the device thread, and block
// until complete.
POST_DEVICE_TASK(base::BindOnce(
[](FrameSinkVideoCaptureDeviceTest* test) { test->device_.reset(); },
this));
WAIT_FOR_DEVICE_TASKS();
// Some objects owned by the FrameSinkVideoCaptureDevice may need to be
// deleted on the UI thread, so run those tasks now.
RUN_UI_TASKS();
}
// Starts-up the FrameSinkVideoCaptureDevice: Sets a frame sink target,
// creates a capturer, sets the capture parameters; and checks that the mock
// capturer receives the correct mojo method calls.
void AllocateAndStartSynchronouslyWithExpectations(
std::unique_ptr<media::VideoFrameReceiver> receiver) {
EXPECT_CALL(capturer_, SetFormat(kFormat, _));
EXPECT_CALL(capturer_, SetMinCapturePeriod(kMinCapturePeriod));
EXPECT_CALL(capturer_,
SetResolutionConstraints(kResolution, kResolution, _));
constexpr viz::FrameSinkId frame_sink_id(1, 1);
EXPECT_CALL(capturer_, MockChangeTarget(frame_sink_id));
EXPECT_CALL(capturer_, MockStart(NotNull()));
EXPECT_FALSE(capturer_.is_bound());
POST_DEVICE_METHOD_CALL(OnTargetChanged, frame_sink_id);
POST_DEVICE_METHOD_CALL(AllocateAndStartWithReceiver, GetCaptureParams(),
std::move(receiver));
WAIT_FOR_DEVICE_TASKS();
RUN_UI_TASKS(); // Run the task to create the capturer.
EXPECT_TRUE(capturer_.is_bound());
WAIT_FOR_DEVICE_TASKS(); // Run the task where the interface is bound, etc.
}
// Stops the FrameSinkVideoCaptureDevice and optionally checks that the mock
// capturer received the Stop() call.
void StopAndDeAllocateSynchronouslyWithExpectations(
bool capturer_stopped_also) {
EXPECT_CALL(capturer_, MockStop()).Times(capturer_stopped_also ? 1 : 0);
POST_DEVICE_METHOD_CALL0(StopAndDeAllocate);
WAIT_FOR_DEVICE_TASKS();
}
// Simulates what the VIZ capturer would do: Allocates a shared memory buffer,
// populates it with video content, and calls OnFrameCaptured().
void SimulateFrameCapture(
int frame_number,
MockFrameSinkVideoConsumerFrameCallbacks* callbacks) {
// Allocate a buffer and fill it with values based on |frame_number|.
base::MappedReadOnlyRegion region = mojo::CreateReadOnlySharedMemoryRegion(
media::VideoFrame::AllocationSize(kFormat, kResolution));
CHECK(region.IsValid());
memset(region.mapping.memory(), GetFrameFillValue(frame_number),
region.mapping.size());
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks_ptr;
callbacks->Bind(mojo::MakeRequest(&callbacks_ptr));
// |callbacks_ptr| is bound on the main thread, so it needs to be re-bound
// to the device thread before calling OnFrameCaptured().
POST_DEVICE_TASK(base::BindOnce(
[](FrameSinkVideoCaptureDevice* device,
base::ReadOnlySharedMemoryRegion data, int frame_number,
mojo::InterfacePtrInfo<
viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
callbacks_info) {
device->OnFrameCaptured(
std::move(data),
media::mojom::VideoFrameInfo::New(
kMinCapturePeriod * frame_number,
base::Value(base::Value::Type::DICTIONARY), kFormat,
kResolution, gfx::Rect(kResolution),
gfx::ColorSpace::CreateREC709(), nullptr),
gfx::Rect(kResolution),
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr(
std::move(callbacks_info)));
},
base::Unretained(device_.get()), std::move(region.region), frame_number,
callbacks_ptr.PassInterface()));
}
// Returns a byte value based on the given |frame_number|.
static constexpr uint8_t GetFrameFillValue(int frame_number) {
return (frame_number % 0x3f) << 2;
}
// Returns true if the |buffer| is filled with the correct byte value for the
// given |frame_number|.
static bool IsExpectedBufferContentForFrame(
int frame_number,
base::ReadOnlySharedMemoryRegion buffer) {
const auto mapping = buffer.Map();
const size_t frame_allocation_size =
media::VideoFrame::AllocationSize(kFormat, kResolution);
CHECK_LE(frame_allocation_size, mapping.size());
const uint8_t* src = mapping.GetMemoryAs<const uint8_t>();
const uint8_t expected_value = GetFrameFillValue(frame_number);
for (size_t i = 0; i < frame_allocation_size; ++i) {
if (src[i] != expected_value) {
return false;
}
}
return true;
}
protected:
// See the threading notes at top of this file.
TestBrowserThreadBundle browser_threads_;
NiceMock<MockFrameSinkVideoCapturer> capturer_;
std::unique_ptr<FrameSinkVideoCaptureDevice> device_;
};
// Tests a normal session, progressing through the start, frame capture, and
// stop phases.
TEST_F(FrameSinkVideoCaptureDeviceTest, CapturesAndDeliversFrames) {
auto receiver_ptr = std::make_unique<MockVideoFrameReceiver>();
auto* const receiver = receiver_ptr.get();
EXPECT_CALL(*receiver, OnStarted());
EXPECT_CALL(*receiver, OnError(_)).Times(0);
AllocateAndStartSynchronouslyWithExpectations(std::move(receiver_ptr));
// From this point, there is no reason the capturer should be re-started.
EXPECT_CALL(capturer_, MockStart(_)).Times(0);
// Run 24 frames through the pipeline, one at a time. Then, run 24 more, two
// at a time. Then, run 24 more, three at a time.
constexpr int kNumFramesToDeliver = 24;
constexpr int kMaxSimultaneousFrames = 3;
int next_frame_number = 0;
for (int in_flight_count = 1; in_flight_count <= kMaxSimultaneousFrames;
++in_flight_count) {
for (int iteration = 0; iteration < kNumFramesToDeliver; ++iteration) {
int buffer_ids[kMaxSimultaneousFrames] = {-1};
MockFrameSinkVideoConsumerFrameCallbacks
callbackses[kMaxSimultaneousFrames];
// Simulate |in_flight_count| frame captures and expect the frames to be
// delivered to the VideoFrameReceiver.
const int first_frame_number = next_frame_number;
for (int i = 0; i < in_flight_count; ++i) {
Expectation new_buffer_called =
EXPECT_CALL(*receiver, MockOnNewBuffer(Ge(0), NotNull()))
.WillOnce(SaveArg<0>(&buffer_ids[i]));
EXPECT_CALL(*receiver,
MockOnFrameReadyInBuffer(Eq(ByRef(buffer_ids[i])), Ge(0),
NotNull(), NotNull()))
.After(new_buffer_called);
SimulateFrameCapture(next_frame_number, &callbackses[i]);
++next_frame_number;
WAIT_FOR_DEVICE_TASKS();
}
// Confirm the VideoFrameReceiver was provided the correct buffer and
// VideoFrameInfo struct for each frame in this batch.
for (int frame_number = first_frame_number;
frame_number < next_frame_number; ++frame_number) {
const int buffer_id = buffer_ids[frame_number - first_frame_number];
auto buffer = receiver->TakeBufferHandle(buffer_id);
ASSERT_TRUE(buffer.IsValid());
EXPECT_TRUE(
IsExpectedBufferContentForFrame(frame_number, std::move(buffer)));
const auto info = receiver->TakeVideoFrameInfo(buffer_id);
ASSERT_TRUE(info);
EXPECT_EQ(kMinCapturePeriod * frame_number, info->timestamp);
EXPECT_EQ(kFormat, info->pixel_format);
EXPECT_EQ(kResolution, info->coded_size);
EXPECT_EQ(gfx::Rect(kResolution), info->visible_rect);
}
// Simulate the receiver providing the feedback and done notifications for
// each frame and expect the FrameSinkVideoCaptureDevice to process these
// notifications.
for (int frame_number = first_frame_number;
frame_number < next_frame_number; ++frame_number) {
const int buffer_id = buffer_ids[frame_number - first_frame_number];
MockFrameSinkVideoConsumerFrameCallbacks& callbacks =
callbackses[frame_number - first_frame_number];
const double fake_utilization =
static_cast<double>(frame_number) / kNumFramesToDeliver;
EXPECT_CALL(callbacks, ProvideFeedback(fake_utilization));
EXPECT_CALL(callbacks, Done());
EXPECT_CALL(*receiver, OnBufferRetired(buffer_id));
const int feedback_id = receiver->TakeFeedbackId(buffer_id);
POST_DEVICE_METHOD_CALL(OnUtilizationReport, feedback_id,
fake_utilization);
receiver->ReleaseAccessPermission(buffer_id);
WAIT_FOR_DEVICE_TASKS();
}
}
}
StopAndDeAllocateSynchronouslyWithExpectations(true /* capturer will stop */);
}
// Tests that a client request to Suspend() should stop consumption and ignore
// all refresh requests. Likewise, a client request to Resume() will
// re-establish consumption and allow refresh requests to propagate to the
// capturer again.
TEST_F(FrameSinkVideoCaptureDeviceTest, SuspendsAndResumes) {
AllocateAndStartSynchronouslyWithExpectations(
std::make_unique<NiceMock<MockVideoFrameReceiver>>());
// A started device should have started the capturer, and any refresh frame
// requests from the client should be propagated to it.
{
EXPECT_CALL(capturer_, RequestRefreshFrame());
POST_DEVICE_METHOD_CALL0(RequestRefreshFrame);
WAIT_FOR_DEVICE_TASKS();
}
// Simulate a client request that capture be suspended. The capturer should
// receive a Stop() message.
{
EXPECT_CALL(capturer_, MockStart(_)).Times(0);
EXPECT_CALL(capturer_, MockStop());
POST_DEVICE_METHOD_CALL0(MaybeSuspend);
WAIT_FOR_DEVICE_TASKS();
}
// A suspended device should not propagate any refresh frame requests.
{
EXPECT_CALL(capturer_, RequestRefreshFrame()).Times(0);
POST_DEVICE_METHOD_CALL0(RequestRefreshFrame);
WAIT_FOR_DEVICE_TASKS();
}
// Simulate a client request that capture be resumed. The capturer should
// receive a Start() message.
{
EXPECT_CALL(capturer_, MockStart(NotNull()));
EXPECT_CALL(capturer_, MockStop()).Times(0);
POST_DEVICE_METHOD_CALL0(Resume);
WAIT_FOR_DEVICE_TASKS();
}
// Now refresh frame requests should propagate again.
{
EXPECT_CALL(capturer_, RequestRefreshFrame());
POST_DEVICE_METHOD_CALL0(RequestRefreshFrame);
WAIT_FOR_DEVICE_TASKS();
}
StopAndDeAllocateSynchronouslyWithExpectations(true /* capturer will stop */);
}
// Tests that the FrameSinkVideoCaptureDevice will shutdown on a fatal error and
// refuse to be started again.
TEST_F(FrameSinkVideoCaptureDeviceTest, ShutsDownOnFatalError) {
auto receiver_ptr = std::make_unique<MockVideoFrameReceiver>();
auto* receiver = receiver_ptr.get();
Sequence sequence;
EXPECT_CALL(*receiver, OnStarted()).InSequence(sequence);
EXPECT_CALL(*receiver, OnLog(StrNe(""))).InSequence(sequence);
EXPECT_CALL(*receiver, OnError(_)).InSequence(sequence);
AllocateAndStartSynchronouslyWithExpectations(std::move(receiver_ptr));
// Notify the device that the target frame sink was lost. This should stop
// consumption, unbind the capturer, log an error with the VideoFrameReceiver,
// and destroy the VideoFrameReceiver.
{
EXPECT_CALL(capturer_, MockChangeTarget(viz::FrameSinkId()));
EXPECT_CALL(capturer_, MockStop());
POST_DEVICE_METHOD_CALL0(OnTargetPermanentlyLost);
WAIT_FOR_DEVICE_TASKS();
}
// Shutdown the device. However, the fatal error already stopped consumption,
// so don't expect the capturer to be stopped again.
StopAndDeAllocateSynchronouslyWithExpectations(false);
// Now, any further attempts to start the FrameSinkVideoCaptureDevice again
// should fail. The VideoFrameReceiver will be provided the same error
// message.
receiver_ptr = std::make_unique<MockVideoFrameReceiver>();
receiver = receiver_ptr.get();
{
EXPECT_CALL(*receiver, OnStarted()).Times(0);
EXPECT_CALL(*receiver, OnLog(StrNe("")));
EXPECT_CALL(*receiver, OnError(_));
EXPECT_CALL(capturer_, MockStart(_)).Times(0);
POST_DEVICE_METHOD_CALL(AllocateAndStartWithReceiver, GetCaptureParams(),
std::move(receiver_ptr));
WAIT_FOR_DEVICE_TASKS();
}
}
} // namespace
} // namespace content