blob: e6d5107bf45d02bac3780f680b2cee10a17eac0c [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/video_capture/broadcasting_receiver.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::InvokeWithoutArgs;
namespace video_capture {
static const size_t kArbitraryDummyBufferSize = 8u;
static const int kArbitraryBufferId = 123;
static const int kArbitraryFrameFeedbackId = 456;
class StubReadWritePermission final
: public media::VideoCaptureDevice::Client::Buffer::ScopedAccessPermission {
public:
StubReadWritePermission() = default;
StubReadWritePermission(const StubReadWritePermission&) = delete;
StubReadWritePermission& operator=(const StubReadWritePermission&) = delete;
~StubReadWritePermission() override = default;
};
class BroadcastingReceiverTest : public ::testing::Test {
public:
void SetUp() override {
mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_1;
mojo::PendingRemote<mojom::VideoFrameHandler> video_frame_handler_2;
mock_video_frame_handler_1_ = std::make_unique<MockVideoFrameHandler>(
video_frame_handler_1.InitWithNewPipeAndPassReceiver());
mock_video_frame_handler_2_ = std::make_unique<MockVideoFrameHandler>(
video_frame_handler_2.InitWithNewPipeAndPassReceiver());
client_id_1_ =
broadcaster_.AddClient(std::move(video_frame_handler_1),
media::VideoCaptureBufferType::kSharedMemory);
client_id_2_ =
broadcaster_.AddClient(std::move(video_frame_handler_2),
media::VideoCaptureBufferType::kSharedMemory);
shm_region_ =
base::UnsafeSharedMemoryRegion::Create(kArbitraryDummyBufferSize);
ASSERT_TRUE(shm_region_.IsValid());
media::mojom::VideoBufferHandlePtr buffer_handle =
media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
std::move(shm_region_));
broadcaster_.OnNewBuffer(kArbitraryBufferId, std::move(buffer_handle));
}
size_t HoldBufferContextSize() {
return broadcaster_.scoped_access_permissions_by_buffer_context_id_.size();
}
protected:
BroadcastingReceiver broadcaster_;
std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_1_;
std::unique_ptr<MockVideoFrameHandler> mock_video_frame_handler_2_;
int32_t client_id_1_;
int32_t client_id_2_;
base::UnsafeSharedMemoryRegion shm_region_;
base::test::TaskEnvironment task_environment_;
};
TEST_F(
BroadcastingReceiverTest,
HoldsOnToAccessPermissionForRetiredBufferUntilLastClientFinishedConsuming) {
base::RunLoop frame_arrived_at_video_frame_handler_1;
base::RunLoop frame_arrived_at_video_frame_handler_2;
EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
.WillOnce(InvokeWithoutArgs([&frame_arrived_at_video_frame_handler_1]() {
frame_arrived_at_video_frame_handler_1.Quit();
}));
EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _))
.WillOnce(InvokeWithoutArgs([&frame_arrived_at_video_frame_handler_2]() {
frame_arrived_at_video_frame_handler_2.Quit();
}));
mock_video_frame_handler_1_->HoldAccessPermissions();
mock_video_frame_handler_2_->HoldAccessPermissions();
broadcaster_.OnFrameReadyInBuffer(
media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
std::make_unique<StubReadWritePermission>(),
media::mojom::VideoFrameInfo::New()),
{});
// mock_video_frame_handler_1_ finishes consuming immediately.
// mock_video_frame_handler_2_ continues consuming.
frame_arrived_at_video_frame_handler_1.Run();
frame_arrived_at_video_frame_handler_2.Run();
base::RunLoop buffer_retired_arrived_at_video_frame_handler_1;
base::RunLoop buffer_retired_arrived_at_video_frame_handler_2;
EXPECT_CALL(*mock_video_frame_handler_1_, DoOnBufferRetired(_))
.WillOnce(InvokeWithoutArgs(
[&buffer_retired_arrived_at_video_frame_handler_1]() {
buffer_retired_arrived_at_video_frame_handler_1.Quit();
}));
EXPECT_CALL(*mock_video_frame_handler_2_, DoOnBufferRetired(_))
.WillOnce(InvokeWithoutArgs(
[&buffer_retired_arrived_at_video_frame_handler_2]() {
buffer_retired_arrived_at_video_frame_handler_2.Quit();
}));
// Retiring the buffer results in both receivers getting the retired event.
broadcaster_.OnBufferRetired(kArbitraryBufferId);
buffer_retired_arrived_at_video_frame_handler_1.Run();
buffer_retired_arrived_at_video_frame_handler_2.Run();
base::RunLoop().RunUntilIdle();
// Despite retiring, the access to the buffer is not released because it is
// still in use by both handlers.
DCHECK_EQ(HoldBufferContextSize(), 1u);
// mock_video_frame_handler_2_ finishes consuming. Access is still not
// released because of mock_video_frame_handler_1_.
mock_video_frame_handler_2_->ReleaseAccessedFrames();
base::RunLoop().RunUntilIdle();
DCHECK_EQ(HoldBufferContextSize(), 1u);
// mock_video_frame_handler_1_ finishes consuming. Now the buffer is finally
// released.
mock_video_frame_handler_1_->ReleaseAccessedFrames();
base::RunLoop().RunUntilIdle();
DCHECK_EQ(HoldBufferContextSize(), 0u);
}
TEST_F(BroadcastingReceiverTest,
DoesNotHoldOnToAccessPermissionWhenAllClientsAreSuspended) {
EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
.Times(0);
EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _))
.Times(0);
mock_video_frame_handler_1_->HoldAccessPermissions();
mock_video_frame_handler_2_->HoldAccessPermissions();
broadcaster_.SuspendClient(client_id_1_);
broadcaster_.SuspendClient(client_id_2_);
broadcaster_.OnFrameReadyInBuffer(
media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
std::make_unique<StubReadWritePermission>(),
media::mojom::VideoFrameInfo::New()),
{});
// Because the clients are suspended, frames are automatically released.
base::RunLoop().RunUntilIdle();
DCHECK_EQ(HoldBufferContextSize(), 0u);
// Resume one of the clients and pass another frame.
broadcaster_.ResumeClient(client_id_2_);
EXPECT_CALL(*mock_video_frame_handler_2_, DoOnFrameReadyInBuffer(_, _, _))
.Times(1);
broadcaster_.OnFrameReadyInBuffer(
media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
std::make_unique<StubReadWritePermission>(),
media::mojom::VideoFrameInfo::New()),
{});
// This time the buffer is not released automatically.
base::RunLoop().RunUntilIdle();
DCHECK_EQ(HoldBufferContextSize(), 1u);
// Releasing mock_video_frame_handler_2_'s frame is sufficient for the buffer
// to be released since the frame was never delivered to
// mock_video_frame_handler_1_.
mock_video_frame_handler_2_->ReleaseAccessedFrames();
base::RunLoop().RunUntilIdle();
DCHECK_EQ(HoldBufferContextSize(), 0u);
}
TEST_F(BroadcastingReceiverTest, ForwardsScaledFrames) {
const int kBufferId = 10;
const int kScaledBufferId = 11;
mock_video_frame_handler_1_->HoldAccessPermissions();
media::mojom::VideoBufferHandlePtr buffer_handle =
media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
base::UnsafeSharedMemoryRegion::Create(kArbitraryDummyBufferSize));
broadcaster_.OnNewBuffer(kBufferId, std::move(buffer_handle));
media::mojom::VideoBufferHandlePtr scaled_buffer_handle =
media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
base::UnsafeSharedMemoryRegion::Create(kArbitraryDummyBufferSize));
broadcaster_.OnNewBuffer(kScaledBufferId, std::move(scaled_buffer_handle));
// Suspend the second client so that the first client alone controls buffer
// access.
broadcaster_.SuspendClient(client_id_2_);
base::RunLoop on_buffer_ready;
EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
.WillOnce(
InvokeWithoutArgs([&on_buffer_ready]() { on_buffer_ready.Quit(); }));
media::ReadyFrameInBuffer ready_buffer =
media::ReadyFrameInBuffer(kBufferId, kArbitraryFrameFeedbackId,
std::make_unique<StubReadWritePermission>(),
media::mojom::VideoFrameInfo::New());
std::vector<media::ReadyFrameInBuffer> scaled_ready_buffers;
scaled_ready_buffers.emplace_back(kScaledBufferId, kArbitraryFrameFeedbackId,
std::make_unique<StubReadWritePermission>(),
media::mojom::VideoFrameInfo::New());
broadcaster_.OnFrameReadyInBuffer(std::move(ready_buffer),
std::move(scaled_ready_buffers));
on_buffer_ready.Run();
DCHECK_EQ(HoldBufferContextSize(), 2u);
// Releasing the handler's buffers releases both frame and scaled frame.
mock_video_frame_handler_1_->ReleaseAccessedFrames();
base::RunLoop().RunUntilIdle();
DCHECK_EQ(HoldBufferContextSize(), 0u);
// Scaled buffers also get retired.
base::RunLoop on_both_buffers_retired;
size_t num_buffers_retired = 0u;
EXPECT_CALL(*mock_video_frame_handler_1_, DoOnBufferRetired(_))
.WillRepeatedly(
InvokeWithoutArgs([&on_both_buffers_retired, &num_buffers_retired]() {
++num_buffers_retired;
if (num_buffers_retired == 2u)
on_both_buffers_retired.Quit();
}));
broadcaster_.OnBufferRetired(kBufferId);
broadcaster_.OnBufferRetired(kScaledBufferId);
on_both_buffers_retired.Run();
}
TEST_F(BroadcastingReceiverTest, AccessPermissionsSurviveStop) {
// For simplicitly, we only care about the first client in this test.
broadcaster_.SuspendClient(client_id_2_);
broadcaster_.OnStarted();
// In this test, two frame handlers are used. In order to inspect all frame
// IDs that have released after the first handler is destroyed.
mock_video_frame_handler_1_->HoldAccessPermissions();
EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
.Times(1);
broadcaster_.OnFrameReadyInBuffer(
media::ReadyFrameInBuffer(kArbitraryBufferId, kArbitraryFrameFeedbackId,
std::make_unique<StubReadWritePermission>(),
media::mojom::VideoFrameInfo::New()),
{});
base::RunLoop().RunUntilIdle();
// The first frame has not been released yet.
DCHECK_EQ(HoldBufferContextSize(), 1u);
// Simulate a device restart.
broadcaster_.OnStopped();
broadcaster_.OnStarted();
base::RunLoop().RunUntilIdle();
// The first frame has still not been released yet.
DCHECK_EQ(HoldBufferContextSize(), 1u);
// Receive another frame after device restart.
base::UnsafeSharedMemoryRegion shm_region2 =
base::UnsafeSharedMemoryRegion::Create(kArbitraryDummyBufferSize);
ASSERT_TRUE(shm_region2.IsValid());
media::mojom::VideoBufferHandlePtr buffer_handle2 =
media::mojom::VideoBufferHandle::NewUnsafeShmemRegion(
std::move(shm_region2));
broadcaster_.OnNewBuffer(kArbitraryBufferId + 1, std::move(buffer_handle2));
EXPECT_CALL(*mock_video_frame_handler_1_, DoOnFrameReadyInBuffer(_, _, _))
.Times(1);
broadcaster_.OnFrameReadyInBuffer(
media::ReadyFrameInBuffer(kArbitraryBufferId + 1,
kArbitraryFrameFeedbackId + 1,
std::make_unique<StubReadWritePermission>(),
media::mojom::VideoFrameInfo::New()),
{});
base::RunLoop().RunUntilIdle();
// Neither frame has been released.
DCHECK_EQ(HoldBufferContextSize(), 2u);
// Release all frames. This should inform both the old and the new handler.
mock_video_frame_handler_1_->ReleaseAccessedFrames();
base::RunLoop().RunUntilIdle();
// Both buffers should now be released.
DCHECK_EQ(HoldBufferContextSize(), 0u);
}
} // namespace video_capture