blob: ed503e806f37dead9c76c7128ecdc77e83e929e4 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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/service_video_capture_provider.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "content/browser/child_process_host_impl.h"
#include "content/browser/renderer_host/media/service_video_capture_device_launcher.h"
#include "content/browser/renderer_host/media/virtual_video_capture_devices_changed_observer.h"
#include "content/browser/video_capture_service_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/video_capture_service.h"
#include "content/public/common/content_features.h"
#include "gpu/config/gpu_info.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/video_capture/public/mojom/video_capture_service.mojom.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "content/public/browser/chromeos/delegate_to_browser_gpu_service_accelerator_factory.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace {
using GetSourceInfosResult =
video_capture::mojom::VideoSourceProvider::GetSourceInfosResult;
#if BUILDFLAG(IS_CHROMEOS)
std::unique_ptr<video_capture::mojom::AcceleratorFactory>
CreateAcceleratorFactory() {
return std::make_unique<
content::DelegateToBrowserGpuServiceAcceleratorFactory>();
}
#endif // BUILDFLAG(IS_CHROMEOS)
// Do not reorder, used for UMA Media.VideoCapture.GetDeviceInfosResult
enum class GetDeviceInfosResult {
kSucessNoRetry = 0,
kFailureNoRetry = 1,
kSucessAfterRetry = 2,
kFailureAfterRetry = 3,
kMaxValue = kFailureAfterRetry,
};
void LogGetDeviceInfosResult(
std::optional<GetSourceInfosResult> get_source_infos_result,
bool get_device_infos_retried) {
GetDeviceInfosResult result;
if (get_source_infos_result &&
*get_source_infos_result == GetSourceInfosResult::kSuccess) {
result = get_device_infos_retried ? GetDeviceInfosResult::kSucessAfterRetry
: GetDeviceInfosResult::kSucessNoRetry;
} else {
result = get_device_infos_retried ? GetDeviceInfosResult::kFailureAfterRetry
: GetDeviceInfosResult::kFailureNoRetry;
}
base::UmaHistogramEnumeration("Media.VideoCapture.GetDeviceInfosResult",
result);
}
} // anonymous namespace
namespace content {
class ServiceVideoCaptureProvider::ServiceProcessObserver
: public ServiceProcessHost::Observer {
public:
ServiceProcessObserver(base::RepeatingClosure start_callback,
base::RepeatingClosure stop_callback)
: io_task_runner_(GetIOThreadTaskRunner({})),
start_callback_(std::move(start_callback)),
stop_callback_(std::move(stop_callback)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ServiceProcessHost::AddObserver(this);
}
ServiceProcessObserver(const ServiceProcessObserver&) = delete;
ServiceProcessObserver& operator=(const ServiceProcessObserver&) = delete;
~ServiceProcessObserver() override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ServiceProcessHost::RemoveObserver(this);
}
private:
// ServiceProcessHost::Observer implementation.
void OnServiceProcessLaunched(const ServiceProcessInfo& info) override {
if (info.IsService<video_capture::mojom::VideoCaptureService>())
io_task_runner_->PostTask(FROM_HERE, base::BindOnce(start_callback_));
}
void OnServiceProcessTerminatedNormally(
const ServiceProcessInfo& info) override {
if (info.IsService<video_capture::mojom::VideoCaptureService>())
io_task_runner_->PostTask(FROM_HERE, base::BindOnce(stop_callback_));
}
void OnServiceProcessCrashed(const ServiceProcessInfo& info) override {
if (info.IsService<video_capture::mojom::VideoCaptureService>()) {
LOG(WARNING) << "Detected crash of video capture service";
io_task_runner_->PostTask(FROM_HERE, base::BindOnce(stop_callback_));
}
}
const scoped_refptr<base::TaskRunner> io_task_runner_;
const base::RepeatingClosure start_callback_;
const base::RepeatingClosure stop_callback_;
};
#if BUILDFLAG(IS_CHROMEOS)
ServiceVideoCaptureProvider::ServiceVideoCaptureProvider(
base::RepeatingCallback<void(const std::string&)> emit_log_message_cb)
: ServiceVideoCaptureProvider(base::NullCallback(),
std::move(emit_log_message_cb)) {}
ServiceVideoCaptureProvider::ServiceVideoCaptureProvider(
CreateAcceleratorFactoryCallback create_accelerator_factory_cb,
base::RepeatingCallback<void(const std::string&)> emit_log_message_cb)
: create_accelerator_factory_cb_(std::move(create_accelerator_factory_cb)),
emit_log_message_cb_(std::move(emit_log_message_cb)),
launcher_has_connected_to_source_provider_(false) {
#else // BUILDFLAG(IS_CHROMEOS)
ServiceVideoCaptureProvider::ServiceVideoCaptureProvider(
base::RepeatingCallback<void(const std::string&)> emit_log_message_cb)
: emit_log_message_cb_(std::move(emit_log_message_cb)),
launcher_has_connected_to_source_provider_(false) {
#endif // BUILDFLAG(IS_CHROMEOS)
if (features::IsVideoCaptureServiceEnabledForOutOfProcess()) {
service_process_observer_.emplace(
GetUIThreadTaskRunner({}),
base::BindRepeating(&ServiceVideoCaptureProvider::OnServiceStarted,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&ServiceVideoCaptureProvider::OnServiceStopped,
weak_ptr_factory_.GetWeakPtr()));
} else if (features::IsVideoCaptureServiceEnabledForBrowserProcess()) {
// Connect immediately and permanently when the service runs in-process.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ServiceVideoCaptureProvider::OnServiceStarted,
weak_ptr_factory_.GetWeakPtr()));
}
// Must register at the IO thread so that callbacks would be also
// triggered on the IO thread.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ServiceVideoCaptureProvider::RegisterWithGpuDataManager,
weak_ptr_factory_.GetWeakPtr()));
}
ServiceVideoCaptureProvider::~ServiceVideoCaptureProvider() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
OnServiceConnectionClosed(ReasonForDisconnect::kShutdown);
content::GpuDataManager::GetInstance()->RemoveObserver(this);
}
void ServiceVideoCaptureProvider::GetDeviceInfosAsync(
GetDeviceInfosCallback result_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
emit_log_message_cb_.Run("ServiceVideoCaptureProvider::GetDeviceInfosAsync");
get_device_infos_retried_ = false;
get_device_infos_pending_callbacks_.push_back(std::move(result_callback));
GetDeviceInfosAsyncForRetry();
}
std::unique_ptr<VideoCaptureDeviceLauncher>
ServiceVideoCaptureProvider::CreateDeviceLauncher() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
return std::make_unique<ServiceVideoCaptureDeviceLauncher>(
base::BindRepeating(
&ServiceVideoCaptureProvider::OnLauncherConnectingToSourceProvider,
weak_ptr_factory_.GetWeakPtr()));
}
void ServiceVideoCaptureProvider::OpenNativeScreenCapturePicker(
DesktopMediaID::Type type,
base::OnceCallback<void(DesktopMediaID::Id)> created_callback,
base::OnceCallback<void(webrtc::DesktopCapturer::Source)> picker_callback,
base::OnceCallback<void()> cancel_callback,
base::OnceCallback<void()> error_callback) {
NOTREACHED();
}
void ServiceVideoCaptureProvider::CloseNativeScreenCapturePicker(
DesktopMediaID device_id) {
NOTREACHED();
}
void ServiceVideoCaptureProvider::OnServiceStarted() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Whenever the video capture service starts, we register a
// VirtualVideoCaptureDevicesChangedObserver in order to propagate device
// change events when virtual devices are added to or removed from the
// service.
auto service_connection = LazyConnectToService();
mojo::PendingRemote<video_capture::mojom::DevicesChangedObserver> observer;
mojo::MakeSelfOwnedReceiver(
std::make_unique<VirtualVideoCaptureDevicesChangedObserver>(),
observer.InitWithNewPipeAndPassReceiver());
service_connection->source_provider()->RegisterVirtualDevicesChangedObserver(
std::move(observer),
true /*raise_event_if_virtual_devices_already_present*/);
}
void ServiceVideoCaptureProvider::OnServiceStopped() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!get_device_infos_pending_callbacks_.empty()) {
// The service stopped during a device info query.
TRACE_EVENT_INSTANT0(
TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"Video capture service has shut down. Retrying GetDeviceInfos.",
TRACE_EVENT_SCOPE_PROCESS);
GetDeviceInfosAsyncForRetry();
}
}
void ServiceVideoCaptureProvider::OnLauncherConnectingToSourceProvider(
scoped_refptr<RefCountedVideoSourceProvider>* out_provider) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
launcher_has_connected_to_source_provider_ = true;
*out_provider = LazyConnectToService();
}
void ServiceVideoCaptureProvider::RegisterWithGpuDataManager() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
content::GpuDataManager::GetInstance()->AddObserver(this);
}
scoped_refptr<RefCountedVideoSourceProvider>
ServiceVideoCaptureProvider::LazyConnectToService() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (weak_service_connection_) {
// There already is a connection.
return base::WrapRefCounted(weak_service_connection_.get());
}
launcher_has_connected_to_source_provider_ = false;
time_of_last_connect_ = base::TimeTicks::Now();
auto ui_task_runner = GetUIThreadTaskRunner({});
#if BUILDFLAG(IS_CHROMEOS)
mojo::PendingRemote<video_capture::mojom::AcceleratorFactory>
accelerator_factory;
if (!create_accelerator_factory_cb_)
create_accelerator_factory_cb_ =
base::BindRepeating(&CreateAcceleratorFactory);
mojo::MakeSelfOwnedReceiver(
create_accelerator_factory_cb_.Run(),
accelerator_factory.InitWithNewPipeAndPassReceiver());
GetVideoCaptureService().InjectGpuDependencies(
std::move(accelerator_factory));
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)
// Pass active gpu info.
GetVideoCaptureService().OnGpuInfoUpdate(
content::GpuDataManager::GetInstance()->GetGPUInfo().active_gpu().luid);
#endif
mojo::Remote<video_capture::mojom::VideoSourceProvider> source_provider;
#if BUILDFLAG(IS_MAC)
if (get_device_infos_retried_) {
// If the service crashed once during a device info query, enable the
// safe-mode VideoCaptureService.
EnableVideoCaptureServiceSafeMode();
}
#endif
GetVideoCaptureService().ConnectToVideoSourceProvider(
source_provider.BindNewPipeAndPassReceiver());
source_provider.set_disconnect_handler(base::BindOnce(
&ServiceVideoCaptureProvider::OnLostConnectionToSourceProvider,
weak_ptr_factory_.GetWeakPtr()));
auto result = base::MakeRefCounted<RefCountedVideoSourceProvider>(
std::move(source_provider),
base::BindOnce(&ServiceVideoCaptureProvider::OnServiceConnectionClosed,
weak_ptr_factory_.GetWeakPtr(),
ReasonForDisconnect::kUnused));
weak_service_connection_ = result->GetWeakPtr();
return result;
}
void ServiceVideoCaptureProvider::GetDeviceInfosAsyncForRetry() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto service_connection = LazyConnectToService();
// Make sure that the callback gets invoked with an empty result in case
// that the service drops the request.
service_connection->source_provider()->GetSourceInfos(
mojo::WrapCallbackWithDropHandler(
base::BindOnce(&ServiceVideoCaptureProvider::OnDeviceInfosReceived,
weak_ptr_factory_.GetWeakPtr(), service_connection),
base::BindOnce(
&ServiceVideoCaptureProvider::OnDeviceInfosRequestDropped,
weak_ptr_factory_.GetWeakPtr(), service_connection)));
}
void ServiceVideoCaptureProvider::OnDeviceInfosReceived(
scoped_refptr<RefCountedVideoSourceProvider> service_connection,
GetSourceInfosResult result,
const std::vector<media::VideoCaptureDeviceInfo>& infos) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
LogGetDeviceInfosResult(result, get_device_infos_retried_);
for (GetDeviceInfosCallback& callback : get_device_infos_pending_callbacks_) {
media::mojom::DeviceEnumerationResult callback_result;
switch (result) {
case GetSourceInfosResult::kSuccess:
callback_result = media::mojom::DeviceEnumerationResult::kSuccess;
break;
case GetSourceInfosResult::kErrorDroppedRequest:
callback_result = media::mojom::DeviceEnumerationResult::
kErrorCaptureServiceDroppedRequest;
break;
default:
NOTREACHED() << "Invalid GetSourceInfosResult result " << result;
}
std::move(callback).Run(callback_result, infos);
}
get_device_infos_pending_callbacks_.clear();
}
void ServiceVideoCaptureProvider::OnDeviceInfosRequestDropped(
scoped_refptr<RefCountedVideoSourceProvider> service_connection) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (base::FeatureList::IsEnabled(
features::kRetryGetVideoCaptureDeviceInfos)) {
if (!get_device_infos_retried_) {
get_device_infos_retried_ = true;
// Do nothing, OnServiceStopped will retry the query automatically when
// the service has been torn down.
return;
}
LOG(WARNING) << "Too many GetDeviceInfos() retries";
emit_log_message_cb_.Run(
"ServiceVideoCaptureProvider::OnDeviceInfosRequestDropped: Too many "
"retries");
}
LogGetDeviceInfosResult(/*get_source_infos_result=*/std::nullopt,
get_device_infos_retried_);
// After too many retries, we just return an empty list
for (GetDeviceInfosCallback& callback : get_device_infos_pending_callbacks_) {
std::move(callback).Run(
media::mojom::DeviceEnumerationResult::kErrorCaptureServiceCrash,
std::vector<media::VideoCaptureDeviceInfo>());
}
get_device_infos_pending_callbacks_.clear();
}
void ServiceVideoCaptureProvider::OnLostConnectionToSourceProvider() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
emit_log_message_cb_.Run(
"ServiceVideoCaptureProvider::OnLostConnectionToSourceProvider");
// This may indicate that the video capture service has crashed. Uninitialize
// here, so that a new connection will be established when clients try to
// reconnect.
OnServiceConnectionClosed(ReasonForDisconnect::kConnectionLost);
}
void ServiceVideoCaptureProvider::OnServiceConnectionClosed(
ReasonForDisconnect reason) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
time_of_last_uninitialize_ = base::TimeTicks::Now();
}
void ServiceVideoCaptureProvider::OnGpuInfoUpdate() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!weak_service_connection_) {
// Only need to notify the service if it's already running.
return;
}
#if BUILDFLAG(IS_WIN)
GetVideoCaptureService().OnGpuInfoUpdate(
content::GpuDataManager::GetInstance()->GetGPUInfo().active_gpu().luid);
#endif
}
} // namespace content