blob: 2cfda36fdd22568969c761495754b98188abccb7 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Unit test for VideoCaptureManager.
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/android_buildflags.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/media/fake_video_capture_provider.h"
#include "content/browser/renderer_host/media/in_process_video_capture_provider.h"
#include "content/browser/renderer_host/media/media_stream_provider.h"
#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
#include "content/browser/renderer_host/media/video_capture_provider_switcher.h"
#include "content/browser/screenlock_monitor/screenlock_monitor.h"
#include "content/browser/screenlock_monitor/screenlock_monitor_source.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/common/buildflags.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "media/base/media_switches.h"
#include "media/capture/video/fake_video_capture_device_factory.h"
#include "media/capture/video/video_capture_system_impl.h"
#include "services/video_effects/public/cpp/buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
#include "services/video_effects/public/mojom/video_effects_processor.mojom-forward.h"
#endif
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SaveArg;
namespace content {
namespace {
// Wraps FakeVideoCaptureDeviceFactory to allow mocking of the
// VideoCaptureDevice MaybeSuspend() and Resume() methods. This is used to check
// that devices are asked to suspend or resume at the correct times.
class WrappedDeviceFactory final : public media::FakeVideoCaptureDeviceFactory {
public:
class WrappedDevice final : public media::VideoCaptureDevice {
public:
WrappedDevice(std::unique_ptr<media::VideoCaptureDevice> device,
WrappedDeviceFactory* factory)
: device_(std::move(device)), factory_(factory) {
factory_->OnDeviceCreated(this);
}
WrappedDevice(const WrappedDevice&) = delete;
WrappedDevice& operator=(const WrappedDevice&) = delete;
~WrappedDevice() override { factory_->OnDeviceDestroyed(this); }
void AllocateAndStart(const media::VideoCaptureParams& params,
std::unique_ptr<Client> client) override {
device_->AllocateAndStart(params, std::move(client));
}
void RequestRefreshFrame() override { device_->RequestRefreshFrame(); }
void MaybeSuspend() override {
factory_->WillSuspendDevice();
device_->MaybeSuspend();
}
void Resume() override {
factory_->WillResumeDevice();
device_->Resume();
}
void StopAndDeAllocate() override { device_->StopAndDeAllocate(); }
void GetPhotoState(GetPhotoStateCallback callback) override {
device_->GetPhotoState(std::move(callback));
}
void TakePhoto(TakePhotoCallback callback) override {
device_->TakePhoto(std::move(callback));
}
private:
const std::unique_ptr<media::VideoCaptureDevice> device_;
const raw_ptr<WrappedDeviceFactory> factory_;
};
static const media::VideoFacingMode DEFAULT_FACING =
media::VideoFacingMode::MEDIA_VIDEO_FACING_USER;
WrappedDeviceFactory() = default;
WrappedDeviceFactory(const WrappedDeviceFactory&) = delete;
WrappedDeviceFactory& operator=(const WrappedDeviceFactory&) = delete;
~WrappedDeviceFactory() override = default;
media::VideoCaptureErrorOrDevice CreateDevice(
const media::VideoCaptureDeviceDescriptor& device_descriptor) override {
auto device = std::make_unique<WrappedDevice>(
FakeVideoCaptureDeviceFactory::CreateDevice(device_descriptor)
.ReleaseDevice(),
this);
return media::VideoCaptureErrorOrDevice(std::move(device));
}
void GetDevicesInfo(GetDevicesInfoCallback callback) override {
media::FakeVideoCaptureDeviceFactory::GetDevicesInfo(
base::BindLambdaForTesting(
[callback =
std::move(callback)](std::vector<media::VideoCaptureDeviceInfo>
devices_info) mutable {
for (auto& device : devices_info) {
if (device.descriptor.facing ==
media::VideoFacingMode::MEDIA_VIDEO_FACING_NONE) {
device.descriptor.facing = DEFAULT_FACING;
}
}
std::move(callback).Run(std::move(devices_info));
}));
}
bool has_active_devices() const { return !devices_.empty(); }
MOCK_METHOD0(WillSuspendDevice, void());
MOCK_METHOD0(WillResumeDevice, void());
private:
void OnDeviceCreated(WrappedDevice* device) { devices_.push_back(device); }
void OnDeviceDestroyed(WrappedDevice* device) {
const auto it = std::ranges::find(devices_, device);
CHECK(it != devices_.end());
devices_.erase(it);
}
std::vector<raw_ptr<WrappedDevice, VectorExperimental>> devices_;
};
// Listener class used to track progress of VideoCaptureManager test.
class MockMediaStreamProviderListener : public MediaStreamProviderListener {
public:
MockMediaStreamProviderListener() {}
~MockMediaStreamProviderListener() override {}
MOCK_METHOD2(Opened,
void(blink::mojom::MediaStreamType,
const base::UnguessableToken&));
MOCK_METHOD2(Closed,
void(blink::mojom::MediaStreamType,
const base::UnguessableToken&));
MOCK_METHOD2(Aborted,
void(blink::mojom::MediaStreamType,
const base::UnguessableToken&));
}; // class MockMediaStreamProviderListener
// Needed as an input argument to ConnectClient().
class MockFrameObserver : public VideoCaptureControllerEventHandler {
public:
MOCK_METHOD2(OnError,
void(const VideoCaptureControllerID& id,
media::VideoCaptureError error));
MOCK_METHOD1(OnStarted, void(const VideoCaptureControllerID& id));
MOCK_METHOD1(OnStartedUsingGpuDecode,
void(const VideoCaptureControllerID& id));
void OnCaptureConfigurationChanged(
const VideoCaptureControllerID& id) override {}
void OnNewBuffer(const VideoCaptureControllerID& id,
media::mojom::VideoBufferHandlePtr buffer_handle,
int buffer_id) override {}
void OnBufferDestroyed(const VideoCaptureControllerID& id,
int buffer_id) override {}
void OnBufferReady(const VideoCaptureControllerID& id,
const ReadyBuffer& buffer) override {}
void OnFrameDropped(const VideoCaptureControllerID& id,
media::VideoCaptureFrameDropReason reason) override {}
void OnNewSubCaptureTargetVersion(
const VideoCaptureControllerID& id,
uint32_t sub_capture_target_version) override {}
void OnFrameWithEmptyRegionCapture(const VideoCaptureControllerID&) override {
}
void OnEnded(const VideoCaptureControllerID& id) override {}
void OnGotControllerCallback(const VideoCaptureControllerID&) {}
};
// Input argument for testing AddVideoCaptureObserver().
class MockVideoCaptureObserver : public media::VideoCaptureObserver {
public:
MOCK_METHOD1(OnVideoCaptureStarted, void(media::VideoFacingMode));
MOCK_METHOD1(OnVideoCaptureStopped, void(media::VideoFacingMode));
};
// Needed to generate ScreenLocked event to |vcm_|.
class ScreenlockMonitorTestSource : public ScreenlockMonitorSource {
public:
ScreenlockMonitorTestSource() = default;
~ScreenlockMonitorTestSource() override = default;
void GenerateScreenLockedEvent() {
ProcessScreenlockEvent(SCREEN_LOCK_EVENT);
base::RunLoop().RunUntilIdle();
}
void GenerateScreenUnlockedEvent() {
ProcessScreenlockEvent(SCREEN_UNLOCK_EVENT);
base::RunLoop().RunUntilIdle();
}
};
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
class MockBrowserClient : public content::ContentBrowserClient {
public:
MOCK_METHOD(void,
BindReadonlyVideoEffectsManager,
(const std::string& device_id,
content::BrowserContext* browser_context,
mojo::PendingReceiver<media::mojom::ReadonlyVideoEffectsManager>
video_effects_manager),
(override));
MOCK_METHOD(
void,
BindVideoEffectsProcessor,
(const std::string& device_id,
content::BrowserContext* browser_context,
mojo::PendingReceiver<video_effects::mojom::VideoEffectsProcessor>
video_effects_processor),
(override));
};
#endif // BUILDFLAG(ENABLE_VIDEO_EFFECTS)
} // namespace
// Test class
class VideoCaptureManagerTest : public testing::Test {
public:
VideoCaptureManagerTest() {}
VideoCaptureManagerTest(const VideoCaptureManagerTest&) = delete;
VideoCaptureManagerTest& operator=(const VideoCaptureManagerTest&) = delete;
~VideoCaptureManagerTest() override {}
void HandleEnumerationResult(
base::OnceClosure quit_closure,
media::mojom::DeviceEnumerationResult result,
const media::VideoCaptureDeviceDescriptors& descriptors) {
blink::MediaStreamDevices devices;
for (const auto& descriptor : descriptors) {
devices.emplace_back(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
descriptor.device_id, descriptor.GetNameAndModel());
}
devices_ = devices;
std::move(quit_closure).Run();
}
void HandleEnumerationResultAsDisplayMediaDevices(
base::OnceClosure quit_closure,
media::mojom::DeviceEnumerationResult result,
const media::VideoCaptureDeviceDescriptors& descriptors) {
blink::MediaStreamDevices devices;
for (const auto& descriptor : descriptors) {
devices.emplace_back(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
DesktopMediaID(DesktopMediaID::TYPE_SCREEN,
DesktopMediaID::kFakeId, false)
.ToString(),
descriptor.GetNameAndModel());
}
devices_ = devices;
std::move(quit_closure).Run();
}
protected:
void SetUp() override {
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
content::SetBrowserClientForTesting(&browser_client_);
#endif
listener_ = std::make_unique<MockMediaStreamProviderListener>();
auto video_capture_device_factory =
std::make_unique<WrappedDeviceFactory>();
video_capture_device_factory_ = video_capture_device_factory.get();
auto fake_video_capture_provider =
std::make_unique<FakeVideoCaptureProvider>(
std::move(video_capture_device_factory));
auto screencapture_video_capture_provider =
InProcessVideoCaptureProvider::CreateInstanceForScreenCapture(
base::SingleThreadTaskRunner::GetCurrentDefault());
auto video_capture_provider_switcher =
std::make_unique<VideoCaptureProviderSwitcher>(
std::move(fake_video_capture_provider),
std::move(screencapture_video_capture_provider));
screenlock_monitor_source_ = new ScreenlockMonitorTestSource();
screenlock_monitor_ = std::make_unique<ScreenlockMonitor>(
std::unique_ptr<ScreenlockMonitorSource>(screenlock_monitor_source_));
vcm_ = new VideoCaptureManager(std::move(video_capture_provider_switcher),
base::DoNothing());
const int32_t kNumberOfFakeDevices = 2;
video_capture_device_factory_->SetToDefaultDevicesConfig(
kNumberOfFakeDevices);
vcm_->RegisterListener(listener_.get());
frame_observer_ = std::make_unique<MockFrameObserver>();
base::RunLoop run_loop;
vcm_->EnumerateDevices(
base::BindOnce(&VideoCaptureManagerTest::HandleEnumerationResult,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_GE(devices_.size(), 2u);
}
void TearDown() override {
screenlock_monitor_source_ = nullptr;
task_environment_.RunUntilIdle();
}
void OnGotControllerCallback(
VideoCaptureControllerID id,
bool expect_success,
const base::WeakPtr<VideoCaptureController>& controller) {
if (expect_success) {
ASSERT_TRUE(controller);
ASSERT_TRUE(0 == controllers_.count(id));
controllers_[id] = controller.get();
} else {
ASSERT_FALSE(controller);
}
}
VideoCaptureControllerID StartClient(const base::UnguessableToken& session_id,
bool expect_success) {
media::VideoCaptureParams params;
params.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
VideoCaptureControllerID client_id = base::UnguessableToken::Create();
vcm_->ConnectClient(
session_id, params, client_id, frame_observer_.get(), std::nullopt,
base::BindOnce(&VideoCaptureManagerTest::OnGotControllerCallback,
base::Unretained(this), client_id, expect_success),
/*browser_context=*/&browser_context_);
base::RunLoop().RunUntilIdle();
return client_id;
}
void StopClient(const VideoCaptureControllerID& client_id) {
ASSERT_TRUE(1 == controllers_.count(client_id));
vcm_->DisconnectClient(controllers_[client_id], client_id,
frame_observer_.get(),
media::VideoCaptureError::kNone);
controllers_.erase(client_id);
}
void ResumeClient(const base::UnguessableToken& session_id,
const VideoCaptureControllerID& client_id) {
ASSERT_EQ(1u, controllers_.count(client_id));
media::VideoCaptureParams params;
params.requested_format = media::VideoCaptureFormat(
gfx::Size(320, 240), 30, media::PIXEL_FORMAT_I420);
vcm_->ResumeCaptureForClient(session_id, params, controllers_[client_id],
client_id, frame_observer_.get());
// Allow possible VideoCaptureDevice::Resume() task to run.
base::RunLoop().RunUntilIdle();
}
void PauseClient(const VideoCaptureControllerID& client_id) {
ASSERT_EQ(1u, controllers_.count(client_id));
vcm_->PauseCaptureForClient(controllers_[client_id], client_id,
frame_observer_.get());
// Allow possible VideoCaptureDevice::MaybeSuspend() task to run.
base::RunLoop().RunUntilIdle();
}
#if BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_DESKTOP_ANDROID)
void ApplicationStateChange(base::android::ApplicationState state) {
vcm_->OnApplicationStateChange(state);
}
#endif
BrowserTaskEnvironment task_environment_;
raw_ptr<ScreenlockMonitorTestSource> screenlock_monitor_source_;
std::unique_ptr<ScreenlockMonitor> screenlock_monitor_;
std::map<VideoCaptureControllerID,
raw_ptr<VideoCaptureController, CtnExperimental>>
controllers_;
scoped_refptr<VideoCaptureManager> vcm_;
std::unique_ptr<MockMediaStreamProviderListener> listener_;
std::unique_ptr<MockFrameObserver> frame_observer_;
raw_ptr<WrappedDeviceFactory> video_capture_device_factory_;
blink::MediaStreamDevices devices_;
content::TestBrowserContext browser_context_;
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
MockBrowserClient browser_client_;
#endif
};
// Test cases
// Try to open, start, stop and close a device.
TEST_F(VideoCaptureManagerTest, CreateAndClose) {
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
EXPECT_CALL(browser_client_, BindReadonlyVideoEffectsManager(_, _, _))
.Times(0);
#endif
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
StopClient(client_id);
vcm_->Close(video_session_id);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
// Try to start and stop a device with an effects processor
TEST_F(VideoCaptureManagerTest, CreateWithVideoEffectsProcessor) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(media::kCameraMicEffects);
mojo::PendingReceiver<media::mojom::ReadonlyVideoEffectsManager> receiver;
EXPECT_CALL(browser_client_, BindVideoEffectsProcessor(devices_.front().id,
&browser_context_, _))
.Times(1);
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
auto client_id = StartClient(video_session_id, true);
StopClient(client_id);
}
#endif // BUILDFLAG(ENABLE_VIDEO_EFFECTS)
TEST_F(VideoCaptureManagerTest, CreateAndCloseMultipleTimes) {
InSequence s;
for (int i = 1; i < 3; ++i) {
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
StopClient(client_id);
vcm_->Close(video_session_id);
}
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Try to open, start, and abort a device.
TEST_F(VideoCaptureManagerTest, CreateAndAbort) {
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Aborted(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
// Wait for device opened.
base::RunLoop().RunUntilIdle();
vcm_->DisconnectClient(
controllers_[client_id], client_id, frame_observer_.get(),
media::VideoCaptureError::kIntentionalErrorRaisedByUnitTest);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
TEST_F(VideoCaptureManagerTest, AddObserver) {
InSequence s;
MockVideoCaptureObserver observer;
vcm_->AddVideoCaptureObserver(&observer);
EXPECT_CALL(observer,
OnVideoCaptureStarted(WrappedDeviceFactory::DEFAULT_FACING));
EXPECT_CALL(observer,
OnVideoCaptureStopped(WrappedDeviceFactory::DEFAULT_FACING));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
StopClient(client_id);
vcm_->Close(video_session_id);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Open the same device twice.
TEST_F(VideoCaptureManagerTest, OpenTwice) {
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _))
.Times(2);
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _))
.Times(2);
base::UnguessableToken video_session_id_first = vcm_->Open(devices_.front());
// This should trigger an error callback with error code
// 'kDeviceAlreadyInUse'.
base::UnguessableToken video_session_id_second = vcm_->Open(devices_.front());
EXPECT_NE(video_session_id_first, video_session_id_second);
vcm_->Close(video_session_id_first);
vcm_->Close(video_session_id_second);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Connect and disconnect devices.
TEST_F(VideoCaptureManagerTest, ConnectAndDisconnectDevices) {
int number_of_devices_keep =
video_capture_device_factory_->number_of_devices();
// Simulate we remove 1 fake device.
video_capture_device_factory_->SetToDefaultDevicesConfig(1);
base::RunLoop run_loop;
vcm_->EnumerateDevices(
base::BindOnce(&VideoCaptureManagerTest::HandleEnumerationResult,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_EQ(devices_.size(), 1u);
// Simulate we add 2 fake devices.
video_capture_device_factory_->SetToDefaultDevicesConfig(3);
base::RunLoop run_loop2;
vcm_->EnumerateDevices(
base::BindOnce(&VideoCaptureManagerTest::HandleEnumerationResult,
base::Unretained(this), run_loop2.QuitClosure()));
run_loop2.Run();
ASSERT_EQ(devices_.size(), 3u);
vcm_->UnregisterListener(listener_.get());
video_capture_device_factory_->SetToDefaultDevicesConfig(
number_of_devices_keep);
}
// Enumerate devices and open the first, then check the list of supported
// formats. Then start the opened device. The capability list should stay the
// same. Finally stop the device and check that the capabilities stay unchanged.
TEST_F(VideoCaptureManagerTest, ManipulateDeviceAndCheckCapabilities) {
// Before enumerating the devices, requesting formats should return false.
base::UnguessableToken video_session_id;
media::VideoCaptureFormats supported_formats;
supported_formats.clear();
EXPECT_FALSE(
vcm_->GetDeviceSupportedFormats(video_session_id, &supported_formats));
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
video_session_id = vcm_->Open(devices_.front());
base::RunLoop().RunUntilIdle();
// Right after opening the device, we should see all its formats.
supported_formats.clear();
EXPECT_TRUE(
vcm_->GetDeviceSupportedFormats(video_session_id, &supported_formats));
ASSERT_GT(supported_formats.size(), 1u);
EXPECT_GT(supported_formats[0].frame_size.width(), 1);
EXPECT_GT(supported_formats[0].frame_size.height(), 1);
EXPECT_GT(supported_formats[0].frame_rate, 1);
EXPECT_GT(supported_formats[1].frame_size.width(), 1);
EXPECT_GT(supported_formats[1].frame_size.height(), 1);
EXPECT_GT(supported_formats[1].frame_rate, 1);
EXPECT_CALL(*frame_observer_, OnStarted(_));
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
base::RunLoop().RunUntilIdle();
// After StartClient(), device's supported formats should stay the same.
supported_formats.clear();
EXPECT_TRUE(
vcm_->GetDeviceSupportedFormats(video_session_id, &supported_formats));
ASSERT_GE(supported_formats.size(), 2u);
EXPECT_GT(supported_formats[0].frame_size.width(), 1);
EXPECT_GT(supported_formats[0].frame_size.height(), 1);
EXPECT_GT(supported_formats[0].frame_rate, 1);
EXPECT_GT(supported_formats[1].frame_size.width(), 1);
EXPECT_GT(supported_formats[1].frame_size.height(), 1);
EXPECT_GT(supported_formats[1].frame_rate, 1);
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
StopClient(client_id);
supported_formats.clear();
EXPECT_TRUE(
vcm_->GetDeviceSupportedFormats(video_session_id, &supported_formats));
ASSERT_GE(supported_formats.size(), 2u);
EXPECT_GT(supported_formats[0].frame_size.width(), 1);
EXPECT_GT(supported_formats[0].frame_size.height(), 1);
EXPECT_GT(supported_formats[0].frame_rate, 1);
EXPECT_GT(supported_formats[1].frame_size.width(), 1);
EXPECT_GT(supported_formats[1].frame_size.height(), 1);
EXPECT_GT(supported_formats[1].frame_rate, 1);
vcm_->Close(video_session_id);
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Enumerate devices, then check the list of supported formats. Then open and
// start the first device. The capability list should stay the same. Finally
// stop the device and check that the capabilities stay unchanged.
TEST_F(VideoCaptureManagerTest,
ManipulateDeviceAndCheckCapabilitiesWithDeviceId) {
// Requesting formats should work even before enumerating/opening devices.
std::string device_id = devices_.front().id;
media::VideoCaptureFormats supported_formats;
supported_formats.clear();
EXPECT_TRUE(vcm_->GetDeviceSupportedFormats(device_id, &supported_formats));
ASSERT_GE(supported_formats.size(), 2u);
EXPECT_GT(supported_formats[0].frame_size.width(), 1);
EXPECT_GT(supported_formats[0].frame_size.height(), 1);
EXPECT_GT(supported_formats[0].frame_rate, 1);
EXPECT_GT(supported_formats[1].frame_size.width(), 1);
EXPECT_GT(supported_formats[1].frame_size.height(), 1);
EXPECT_GT(supported_formats[1].frame_rate, 1);
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
base::RunLoop().RunUntilIdle();
// Right after opening the device, we should see all its formats.
supported_formats.clear();
EXPECT_TRUE(vcm_->GetDeviceSupportedFormats(device_id, &supported_formats));
ASSERT_GE(supported_formats.size(), 2u);
EXPECT_GT(supported_formats[0].frame_size.width(), 1);
EXPECT_GT(supported_formats[0].frame_size.height(), 1);
EXPECT_GT(supported_formats[0].frame_rate, 1);
EXPECT_GT(supported_formats[1].frame_size.width(), 1);
EXPECT_GT(supported_formats[1].frame_size.height(), 1);
EXPECT_GT(supported_formats[1].frame_rate, 1);
EXPECT_CALL(*frame_observer_, OnStarted(_));
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
base::RunLoop().RunUntilIdle();
// After StartClient(), device's supported formats should stay the same.
supported_formats.clear();
EXPECT_TRUE(vcm_->GetDeviceSupportedFormats(device_id, &supported_formats));
ASSERT_GE(supported_formats.size(), 2u);
EXPECT_GT(supported_formats[0].frame_size.width(), 1);
EXPECT_GT(supported_formats[0].frame_size.height(), 1);
EXPECT_GT(supported_formats[0].frame_rate, 1);
EXPECT_GT(supported_formats[1].frame_size.width(), 1);
EXPECT_GT(supported_formats[1].frame_size.height(), 1);
EXPECT_GT(supported_formats[1].frame_rate, 1);
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
StopClient(client_id);
supported_formats.clear();
EXPECT_TRUE(vcm_->GetDeviceSupportedFormats(device_id, &supported_formats));
ASSERT_GE(supported_formats.size(), 2u);
EXPECT_GT(supported_formats[0].frame_size.width(), 1);
EXPECT_GT(supported_formats[0].frame_size.height(), 1);
EXPECT_GT(supported_formats[0].frame_rate, 1);
EXPECT_GT(supported_formats[1].frame_size.width(), 1);
EXPECT_GT(supported_formats[1].frame_size.height(), 1);
EXPECT_GT(supported_formats[1].frame_rate, 1);
vcm_->Close(video_session_id);
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Enumerate devices and open the first, then check the formats currently in
// use, which should be an empty vector. Then start the opened device. The
// format(s) in use should be just one format (the one used when configuring-
// starting the device). Finally stop the device and check that the formats in
// use is an empty vector.
TEST_F(VideoCaptureManagerTest, StartDeviceAndGetDeviceFormatInUse) {
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
base::RunLoop().RunUntilIdle();
// Right after opening the device, we should see no format in use.
media::VideoCaptureFormats formats_in_use;
EXPECT_TRUE(vcm_->GetDeviceFormatsInUse(video_session_id, &formats_in_use));
EXPECT_TRUE(formats_in_use.empty());
EXPECT_CALL(*frame_observer_, OnStarted(_));
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
base::RunLoop().RunUntilIdle();
// After StartClient(), |formats_in_use| should contain one valid format.
EXPECT_TRUE(vcm_->GetDeviceFormatsInUse(video_session_id, &formats_in_use));
EXPECT_EQ(formats_in_use.size(), 1u);
if (formats_in_use.size()) {
media::VideoCaptureFormat& format_in_use = formats_in_use.front();
EXPECT_TRUE(format_in_use.IsValid());
EXPECT_GT(format_in_use.frame_size.width(), 1);
EXPECT_GT(format_in_use.frame_size.height(), 1);
EXPECT_GT(format_in_use.frame_rate, 1);
}
formats_in_use.clear();
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
StopClient(client_id);
base::RunLoop().RunUntilIdle();
// After StopClient(), the device's formats in use should be empty again.
EXPECT_TRUE(vcm_->GetDeviceFormatsInUse(video_session_id, &formats_in_use));
EXPECT_TRUE(formats_in_use.empty());
vcm_->Close(video_session_id);
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Enumerate devices and open the first, then check the formats currently in
// use, which should be an empty vector. Then start the opened device. The
// format(s) in use should be just one format (the one used when configuring-
// starting the device). Finally stop the device and check that the formats in
// use is an empty vector.
TEST_F(VideoCaptureManagerTest,
StartDeviceAndGetDeviceFormatInUseWithDeviceId) {
std::string device_id = devices_.front().id;
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
base::RunLoop().RunUntilIdle();
// Right after opening the device, we should see no format in use.
EXPECT_EQ(
std::nullopt,
vcm_->GetDeviceFormatInUse(
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, device_id));
EXPECT_CALL(*frame_observer_, OnStarted(_));
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
base::RunLoop().RunUntilIdle();
// After StartClient(), device's format in use should be valid.
std::optional<media::VideoCaptureFormat> format_in_use =
vcm_->GetDeviceFormatInUse(
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, device_id);
EXPECT_TRUE(format_in_use.has_value());
EXPECT_TRUE(format_in_use->IsValid());
EXPECT_GT(format_in_use->frame_size.width(), 1);
EXPECT_GT(format_in_use->frame_size.height(), 1);
EXPECT_GT(format_in_use->frame_rate, 1);
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
StopClient(client_id);
base::RunLoop().RunUntilIdle();
// After StopClient(), the device's format in use should be empty again.
EXPECT_EQ(
std::nullopt,
vcm_->GetDeviceFormatInUse(
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, device_id));
vcm_->Close(video_session_id);
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Open two different devices.
TEST_F(VideoCaptureManagerTest, OpenTwo) {
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _))
.Times(2);
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _))
.Times(2);
auto it = devices_.begin();
base::UnguessableToken video_session_id_first = vcm_->Open(*it);
++it;
base::UnguessableToken video_session_id_second = vcm_->Open(*it);
vcm_->Close(video_session_id_first);
vcm_->Close(video_session_id_second);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Try open a non-existing device.
TEST_F(VideoCaptureManagerTest, OpenNotExisting) {
InSequence s;
EXPECT_CALL(*frame_observer_, OnError(_, _));
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
blink::mojom::MediaStreamType stream_type =
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
std::string device_name("device_doesnt_exist");
std::string device_id("id_doesnt_exist");
blink::MediaStreamDevice dummy_device(stream_type, device_id, device_name);
// This should fail with an error to the controller.
base::UnguessableToken session_id = vcm_->Open(dummy_device);
VideoCaptureControllerID client_id = StartClient(session_id, true);
base::RunLoop().RunUntilIdle();
StopClient(client_id);
vcm_->Close(session_id);
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Start a device without calling Open, using a non-magic ID.
TEST_F(VideoCaptureManagerTest, StartInvalidSession) {
StartClient(base::UnguessableToken::Create(), false);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Open and start a device, close it before calling Stop.
TEST_F(VideoCaptureManagerTest, CloseWithoutStop) {
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
// Close will stop the running device, an assert will be triggered in
// VideoCaptureManager destructor otherwise.
vcm_->Close(video_session_id);
StopClient(client_id);
// Wait to check callbacks before removing the listener
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
// Try to open, start, pause and resume a device. Confirm the device is
// paused/resumed at the correct times in both single-client and multiple-client
// scenarios.
TEST_F(VideoCaptureManagerTest, PauseAndResumeClient) {
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
const base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
const VideoCaptureControllerID client_id =
StartClient(video_session_id, true);
// Test pause/resume when only one client is present.
EXPECT_CALL(*video_capture_device_factory_, WillSuspendDevice()).Times(1);
PauseClient(client_id);
EXPECT_CALL(*video_capture_device_factory_, WillResumeDevice()).Times(2);
ResumeClient(video_session_id, client_id);
// Attempting to resume the same client a second time should not cause any
// calls to VideoCaptureDevice::Resume() because VideoCaptureManager will
// not attempt to resume the device if the client is not paused.
ResumeClient(video_session_id, client_id);
// Add a second client that is never paused, then pause/resume the first
// client, and no calls to VideoCaptureDevice::MaybeSuspend() is made.
// However, a call to VideoCaptureDevice::Resume() is still made because
// VideoCaptureManager will attempt to resume the device if the client is
// paused.
EXPECT_CALL(*frame_observer_, OnStarted(_));
const VideoCaptureControllerID client_id2 =
StartClient(video_session_id, true);
PauseClient(client_id);
ResumeClient(video_session_id, client_id);
StopClient(client_id);
StopClient(client_id2);
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
vcm_->Close(video_session_id);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
#if BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_DESKTOP_ANDROID)
// Try to open, start, pause and resume a device.
TEST_F(VideoCaptureManagerTest, PauseAndResumeDevice) {
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
// Release/ResumeDevices according to ApplicationStatus. Should cause no
// problem in any order. Check https://crbug.com/615557 for more details.
ApplicationStateChange(
base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
ApplicationStateChange(
base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
ApplicationStateChange(
base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
ApplicationStateChange(
base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
ApplicationStateChange(
base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
StopClient(client_id);
vcm_->Close(video_session_id);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
#elif !BUILDFLAG(IS_DESKTOP_ANDROID)
TEST_F(VideoCaptureManagerTest, PauseAndResumeDeviceOnScreenLock) {
vcm_->set_idle_close_timeout_for_testing(base::TimeDelta());
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
auto video_session_id = vcm_->Open(devices_.front());
auto client_id = StartClient(video_session_id, true);
ASSERT_TRUE(video_capture_device_factory_->has_active_devices());
// Pretend screen is locked, which should close the device.
screenlock_monitor_source_->GenerateScreenLockedEvent();
ASSERT_FALSE(video_capture_device_factory_->has_active_devices());
// Starting another client while the screen is locked should defer the actual
// start of the device, but appear open. Since the device is already started,
// the OnStarted() will appear before Opened().
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
auto video_session_id2 = vcm_->Open(devices_.front());
auto client_id2 = StartClient(video_session_id2, true);
ASSERT_FALSE(video_capture_device_factory_->has_active_devices());
// Unlock the screen now.
EXPECT_CALL(*frame_observer_, OnStarted(_)).Times(2);
screenlock_monitor_source_->GenerateScreenUnlockedEvent();
ASSERT_TRUE(video_capture_device_factory_->has_active_devices());
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
video_session_id));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
video_session_id2));
StopClient(client_id);
vcm_->Close(video_session_id);
StopClient(client_id2);
vcm_->Close(video_session_id2);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
TEST_F(VideoCaptureManagerTest, ScreenLockDoesNothingBeforeTimeout) {
vcm_->set_idle_close_timeout_for_testing(base::TimeDelta::Max());
InSequence s;
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
auto video_session_id = vcm_->Open(devices_.front());
auto client_id = StartClient(video_session_id, true);
ASSERT_TRUE(video_capture_device_factory_->has_active_devices());
// Pretend screen is locked, which should do nothing since timeout hasn't
// elapsed.
screenlock_monitor_source_->GenerateScreenLockedEvent();
EXPECT_TRUE(video_capture_device_factory_->has_active_devices());
EXPECT_TRUE(vcm_->is_idle_close_timer_running_for_testing());
screenlock_monitor_source_->GenerateScreenUnlockedEvent();
ASSERT_TRUE(video_capture_device_factory_->has_active_devices());
EXPECT_FALSE(vcm_->is_idle_close_timer_running_for_testing());
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
video_session_id));
StopClient(client_id);
vcm_->Close(video_session_id);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
#endif
// Try to open, start a device capture device, and confirm it's not affected by
// the ScreenLocked event.
TEST_F(VideoCaptureManagerTest, DeviceCaptureDeviceNotClosedOnScreenlock) {
InSequence s;
// ScreenLocked event shouldn't affect camera capture device.
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _))
.Times(0);
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
// Pretend screen is locked, which should not close the device.
screenlock_monitor_source_->GenerateScreenLockedEvent();
Mock::VerifyAndClearExpectations(listener_.get());
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, _));
StopClient(client_id);
vcm_->Close(video_session_id);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
#if BUILDFLAG(ENABLE_SCREEN_CAPTURE) && !BUILDFLAG(IS_ANDROID)
// Try to open, start a desktop capture device, and confirm it's closed on
// ScreenLocked event on desktop platforms.
TEST_F(VideoCaptureManagerTest, DesktopCaptureDeviceClosedOnScreenlock) {
InSequence s;
// ScreenLocked event should stop desktop capture device.
EXPECT_CALL(*listener_,
Opened(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, _));
EXPECT_CALL(*frame_observer_, OnStarted(_));
EXPECT_CALL(*listener_,
Closed(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE, _));
// Simulate we add 1 fake display media device.
video_capture_device_factory_->SetToDefaultDevicesConfig(1);
base::RunLoop run_loop;
vcm_->EnumerateDevices(base::BindOnce(
&VideoCaptureManagerTest::HandleEnumerationResultAsDisplayMediaDevices,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_EQ(devices_.size(), 1u);
base::UnguessableToken video_session_id = vcm_->Open(devices_.front());
VideoCaptureControllerID client_id = StartClient(video_session_id, true);
// Pretend screen is locked, which should close the device.
screenlock_monitor_source_->GenerateScreenLockedEvent();
Mock::VerifyAndClearExpectations(listener_.get());
StopClient(client_id);
// Wait to check callbacks before removing the listener.
base::RunLoop().RunUntilIdle();
vcm_->UnregisterListener(listener_.get());
}
#endif // ENABLE_SCREEN_CAPTURE && !BUILDFLAG(IS_ANDROID)
// TODO(mcasas): Add a test to check consolidation of the supported formats
// provided by the device when http://crbug.com/323913 is closed.
} // namespace content