| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/capture/video/fuchsia/video_capture_device_factory_fuchsia.h" |
| |
| #include <lib/sys/cpp/component_context.h> |
| |
| #include "base/check_op.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/system/system_monitor.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "media/capture/video/fuchsia/video_capture_device_fuchsia.h" |
| |
| namespace media { |
| |
| // Helper class that calls GetIdentifier() and GetConfigurations() for a |
| // fuchsia::camera3::Device instance and stores the results afterwards. |
| class VideoCaptureDeviceFactoryFuchsia::DeviceConfigFetcher { |
| public: |
| DeviceConfigFetcher(uint64_t device_id, fuchsia::camera3::DevicePtr device) |
| : device_id_(device_id), device_(std::move(device)) { |
| device_.set_error_handler( |
| fit::bind_member(this, &DeviceConfigFetcher::OnError)); |
| } |
| |
| void Fetch(base::OnceClosure on_fetched_callback) { |
| DCHECK(device_); |
| DCHECK(!have_results()); |
| DCHECK(!on_fetched_callback_); |
| |
| on_fetched_callback_ = std::move(on_fetched_callback); |
| device_->GetIdentifier( |
| fit::bind_member(this, &DeviceConfigFetcher::OnDescription)); |
| device_->GetConfigurations( |
| fit::bind_member(this, &DeviceConfigFetcher::OnConfigurations)); |
| } |
| |
| ~DeviceConfigFetcher() = default; |
| |
| DeviceConfigFetcher(const DeviceConfigFetcher&) = delete; |
| const DeviceConfigFetcher& operator=(const DeviceConfigFetcher&) = delete; |
| |
| bool is_pending() const { return !!device_; } |
| bool have_results() const { return description_ && formats_; } |
| bool is_usable() const { return device_ || have_results(); } |
| |
| uint64_t device_id() const { return device_id_; } |
| const std::string& description() const { return description_.value(); } |
| const VideoCaptureFormats& formats() const { return formats_.value(); } |
| |
| private: |
| void OnError(zx_status_t status) { |
| ZX_LOG(ERROR, status) << "fuchsia.camera3.Device disconnected"; |
| |
| if (on_fetched_callback_) |
| std::move(on_fetched_callback_).Run(); |
| } |
| |
| void OnDescription(fidl::StringPtr identifier) { |
| description_ = identifier.value_or(""); |
| MaybeSignalDone(); |
| } |
| |
| void OnConfigurations(std::vector<fuchsia::camera3::Configuration> configs) { |
| VideoCaptureFormats formats; |
| for (auto& config : configs) { |
| for (auto& props : config.streams) { |
| VideoCaptureFormat format; |
| format.frame_size = gfx::Size(props.image_format.display_width, |
| props.image_format.display_height); |
| format.frame_rate = static_cast<float>(props.frame_rate.numerator) / |
| props.frame_rate.denominator; |
| format.pixel_format = |
| VideoCaptureDeviceFuchsia::GetConvertedPixelFormat( |
| props.image_format.pixel_format.type); |
| if (format.pixel_format == PIXEL_FORMAT_UNKNOWN) |
| continue; |
| formats.push_back(format); |
| } |
| } |
| |
| formats_ = std::move(formats); |
| MaybeSignalDone(); |
| } |
| |
| void MaybeSignalDone() { |
| if (!have_results()) |
| return; |
| |
| device_.Unbind(); |
| |
| std::move(on_fetched_callback_).Run(); |
| } |
| |
| uint64_t device_id_; |
| fuchsia::camera3::DevicePtr device_; |
| absl::optional<std::string> description_; |
| absl::optional<VideoCaptureFormats> formats_; |
| base::OnceClosure on_fetched_callback_; |
| }; |
| |
| VideoCaptureDeviceFactoryFuchsia::VideoCaptureDeviceFactoryFuchsia() { |
| DETACH_FROM_THREAD(thread_checker_); |
| } |
| |
| VideoCaptureDeviceFactoryFuchsia::~VideoCaptureDeviceFactoryFuchsia() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| } |
| |
| std::unique_ptr<VideoCaptureDevice> |
| VideoCaptureDeviceFactoryFuchsia::CreateDevice( |
| const VideoCaptureDeviceDescriptor& device_descriptor) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| uint64_t device_id; |
| bool converted = |
| base::StringToUint64(device_descriptor.device_id, &device_id); |
| |
| // Test may call CreateDevice() with an invalid |device_id|. |
| if (!converted) |
| return nullptr; |
| |
| // CreateDevice() may be called before GetDeviceDescriptors(). Make sure |
| // |device_watcher_| is initialized. |
| if (!device_watcher_) |
| Initialize(); |
| |
| fidl::InterfaceHandle<fuchsia::camera3::Device> device; |
| device_watcher_->ConnectToDevice(device_id, device.NewRequest()); |
| return std::make_unique<VideoCaptureDeviceFuchsia>(std::move(device)); |
| } |
| |
| void VideoCaptureDeviceFactoryFuchsia::GetDevicesInfo( |
| GetDevicesInfoCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // If the list hasn't been received, then wait until it's received. |
| if (!devices_ || num_pending_device_info_requests_) { |
| pending_devices_info_requests_.push_back(std::move(callback)); |
| |
| if (!device_watcher_) |
| Initialize(); |
| |
| return; |
| } |
| |
| std::move(callback).Run(MakeDevicesInfo()); |
| } |
| |
| void VideoCaptureDeviceFactoryFuchsia::Initialize() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!device_watcher_); |
| DCHECK(!devices_); |
| |
| base::ComponentContextForProcess()->svc()->Connect( |
| device_watcher_.NewRequest()); |
| |
| device_watcher_.set_error_handler(fit::bind_member( |
| this, &VideoCaptureDeviceFactoryFuchsia::OnDeviceWatcherDisconnected)); |
| |
| WatchDevices(); |
| } |
| |
| void VideoCaptureDeviceFactoryFuchsia::OnDeviceWatcherDisconnected( |
| zx_status_t status) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // CastRunner may close the channel with ZX_ERR_UNAVAILABLE error code when |
| // none of the running applications have access to camera. No need to log the |
| // error in that case. |
| if (status != ZX_ERR_UNAVAILABLE) |
| ZX_LOG(ERROR, status) << "fuchsia.camera3.DeviceWatcher disconnected."; |
| |
| // Clear the list of devices, so we don't report any camera devices while |
| // DeviceWatcher is disconnected. We will try connecting DeviceWatcher again |
| // when GetDevicesInfo() is called. |
| devices_ = absl::nullopt; |
| num_pending_device_info_requests_ = 0; |
| |
| MaybeResolvePendingDeviceInfoCallbacks(); |
| } |
| |
| void VideoCaptureDeviceFactoryFuchsia::WatchDevices() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| device_watcher_->WatchDevices(fit::bind_member( |
| this, &VideoCaptureDeviceFactoryFuchsia::OnWatchDevicesResult)); |
| } |
| |
| void VideoCaptureDeviceFactoryFuchsia::OnWatchDevicesResult( |
| std::vector<fuchsia::camera3::WatchDevicesEvent> events) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!devices_) |
| devices_.emplace(); |
| |
| for (auto& e : events) { |
| if (e.is_removed()) { |
| auto it = devices_->find(e.removed()); |
| if (it == devices_->end()) { |
| LOG(WARNING) << "Received device removed event for a device that " |
| "wasn't previously registered."; |
| continue; |
| } |
| if (it->second->is_pending()) { |
| // If the device info request was still pending then consider it |
| // complete now. If this was the only device in pending state then all |
| // callbacks will be resolved in |
| // MaybeResolvePendingDeviceInfoCallbacks() called below. |
| DCHECK_GT(num_pending_device_info_requests_, 0U); |
| num_pending_device_info_requests_--; |
| } |
| devices_->erase(it); |
| continue; |
| } |
| |
| uint64_t id; |
| if (e.is_added()) { |
| id = e.added(); |
| if (devices_->find(id) != devices_->end()) { |
| LOG(WARNING) << "Received device added event for a device that was " |
| "previously registered."; |
| continue; |
| } |
| } else { |
| id = e.existing(); |
| if (devices_->find(id) != devices_->end()) { |
| continue; |
| } |
| LOG(WARNING) << "Received device exists event for a device that wasn't " |
| "previously registered."; |
| } |
| |
| fuchsia::camera3::DevicePtr device; |
| device_watcher_->ConnectToDevice(id, device.NewRequest()); |
| |
| auto fetcher = std::make_unique<DeviceConfigFetcher>(id, std::move(device)); |
| |
| // base::Unretained() is safe because the |fetcher| is owned by |this|. |
| fetcher->Fetch( |
| base::BindOnce(&VideoCaptureDeviceFactoryFuchsia::OnDeviceInfoFetched, |
| base::Unretained(this))); |
| num_pending_device_info_requests_++; |
| |
| devices_->emplace(id, std::move(fetcher)); |
| } |
| |
| // Watch for further updates. |
| WatchDevices(); |
| |
| // Calls callbacks, which may delete |this|. |
| MaybeResolvePendingDeviceInfoCallbacks(); |
| } |
| |
| void VideoCaptureDeviceFactoryFuchsia::OnDeviceInfoFetched() { |
| DCHECK_GT(num_pending_device_info_requests_, 0U); |
| num_pending_device_info_requests_--; |
| MaybeResolvePendingDeviceInfoCallbacks(); |
| } |
| |
| std::vector<VideoCaptureDeviceInfo> |
| VideoCaptureDeviceFactoryFuchsia::MakeDevicesInfo() { |
| std::vector<VideoCaptureDeviceInfo> devices_info; |
| // Leave the list empty if |devices_| isn't set (e.g. after DeviceWatcher |
| // has disconnected). |
| if (devices_) { |
| for (auto& device : devices_.value()) { |
| DCHECK(!device.second->is_pending()); |
| if (device.second->is_usable()) { |
| devices_info.emplace_back(VideoCaptureDeviceDescriptor( |
| device.second->description(), base::NumberToString(device.first), |
| VideoCaptureApi::FUCHSIA_CAMERA3)); |
| devices_info.back().supported_formats = device.second->formats(); |
| } |
| } |
| } |
| return devices_info; |
| } |
| |
| void VideoCaptureDeviceFactoryFuchsia:: |
| MaybeResolvePendingDeviceInfoCallbacks() { |
| if (num_pending_device_info_requests_ > 0) |
| return; |
| |
| // Notify system monitor if devices have changed. This will indirectly update |
| // media device manager and the web app eventually. |
| auto* system_monitor = base::SystemMonitor::Get(); |
| if (system_monitor) { |
| system_monitor->ProcessDevicesChanged( |
| base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE); |
| } |
| |
| std::vector<GetDevicesInfoCallback> callbacks; |
| callbacks.swap(pending_devices_info_requests_); |
| |
| auto weak_this = weak_factory_.GetWeakPtr(); |
| for (auto& c : callbacks) { |
| auto devices_info = MakeDevicesInfo(); |
| std::move(c).Run(devices_info); |
| if (!weak_this) |
| return; |
| } |
| } |
| |
| } // namespace media |