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