From 39b6cb651ed158630cde4d11f5e981cad599f215 Mon Sep 17 00:00:00 2001 From: Sarah Pham Date: Tue, 24 May 2022 11:18:58 +1000 Subject: [PATCH] Add Fuchsia desktop capturer. This enables screen sharing on Fuchsia. Bug: chromium:1322341 Change-Id: I2f52f6bfe7406b5fe36ae904a0cdf30e8168cac5 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/262340 Reviewed-by: Emircan Uysaler Commit-Queue: Sarah Pham Reviewed-by: Alexander Cooper Cr-Commit-Position: refs/heads/main@{#37029} --- modules/desktop_capture/BUILD.gn | 20 +- .../screen_capturer_fuchsia.cc | 398 ++++++++++++++++++ .../desktop_capture/screen_capturer_fuchsia.h | 63 +++ 3 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 modules/desktop_capture/screen_capturer_fuchsia.cc create mode 100644 modules/desktop_capture/screen_capturer_fuchsia.h diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn index 3aa1b982b6..04d8cd5285 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -478,7 +478,8 @@ rtc_library("desktop_capture_generic") { ] } - if (!is_win && !is_mac && !rtc_use_x11_extensions && !rtc_use_pipewire) { + if (!is_win && !is_mac && !rtc_use_x11_extensions && !rtc_use_pipewire && + !is_fuchsia) { sources += [ "mouse_cursor_monitor_null.cc", "screen_capturer_null.cc", @@ -509,6 +510,23 @@ rtc_library("desktop_capture_generic") { "../../system_wrappers:metrics", ] + if (is_fuchsia) { + sources += [ + "mouse_cursor_monitor_null.cc", + "screen_capturer_fuchsia.cc", + "screen_capturer_fuchsia.h", + "window_capturer_null.cc", + ] + deps += [ + "../../rtc_base:rtc_numerics", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.sysmem", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.ui.composition", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.ui.scenic", + "//third_party/fuchsia-sdk/sdk/pkg/scenic_cpp", + "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp", + ] + } + if (is_win) { sources += [ "cropping_window_capturer_win.cc", diff --git a/modules/desktop_capture/screen_capturer_fuchsia.cc b/modules/desktop_capture/screen_capturer_fuchsia.cc new file mode 100644 index 0000000000..6ccde6d3ab --- /dev/null +++ b/modules/desktop_capture/screen_capturer_fuchsia.cc @@ -0,0 +1,398 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/screen_capturer_fuchsia.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/divide_round.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +namespace { + +static constexpr uint32_t kMinBufferCount = 2; +static constexpr uint32_t kFuchsiaBytesPerPixel = 4; +static constexpr DesktopCapturer::SourceId kFuchsiaScreenId = 1; +// 500 milliseconds +static constexpr zx::duration kEventDelay = zx::msec(500); +static constexpr fuchsia::sysmem::ColorSpaceType kSRGBColorSpace = + fuchsia::sysmem::ColorSpaceType::SRGB; +static constexpr fuchsia::sysmem::PixelFormatType kBGRA32PixelFormatType = + fuchsia::sysmem::PixelFormatType::BGRA32; + +// Round |value| up to the closest multiple of |multiple| +size_t RoundUpToMultiple(size_t value, size_t multiple) { + return DivideRoundUp(value, multiple) * multiple; +} + +} // namespace + +std::unique_ptr DesktopCapturer::CreateRawScreenCapturer( + const DesktopCaptureOptions& options) { + std::unique_ptr capturer(new ScreenCapturerFuchsia()); + return capturer; +} + +ScreenCapturerFuchsia::ScreenCapturerFuchsia() + : component_context_( + sys::ComponentContext::CreateAndServeOutgoingDirectory()) {} + +ScreenCapturerFuchsia::~ScreenCapturerFuchsia() { + // unmap virtual memory mapped pointers + uint32_t virt_mem_bytes = + buffer_collection_info_.settings.buffer_settings.size_bytes; + for (uint32_t buffer_index = 0; + buffer_index < buffer_collection_info_.buffer_count; buffer_index++) { + uintptr_t address = + reinterpret_cast(virtual_memory_mapped_addrs_[buffer_index]); + zx_status_t status = zx::vmar::root_self()->unmap(address, virt_mem_bytes); + RTC_DCHECK(status == ZX_OK); + } +} + +void ScreenCapturerFuchsia::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + callback_ = callback; + + fatal_error_ = false; + + SetupBuffers(); +} + +void ScreenCapturerFuchsia::CaptureFrame() { + if (fatal_error_) { + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + zx::event event; + zx::event dup; + zx_status_t status = zx::event::create(0, &event); + if (status != ZX_OK) { + RTC_LOG(LS_ERROR) << "Failed to create event: " << status; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); + + fuchsia::ui::composition::GetNextFrameArgs next_frame_args; + next_frame_args.set_event(std::move(dup)); + + fuchsia::ui::composition::ScreenCapture_GetNextFrame_Result result; + screen_capture_->GetNextFrame(std::move(next_frame_args), &result); + if (result.is_err()) { + RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.GetNextFrame() failed: " + << result.err() << "\n"; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + + status = event.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(kEventDelay), + nullptr); + if (status != ZX_OK) { + RTC_LOG(LS_ERROR) << "Timed out waiting for ScreenCapture to render frame: " + << status; + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + uint32_t buffer_index = result.response().buffer_id(); + + // TODO(bugs.webrtc.org/14097): Use SharedMemoryDesktopFrame and + // ScreenCaptureFrameQueue + std::unique_ptr frame( + new BasicDesktopFrame(DesktopSize(width_, height_))); + + uint32_t pixels_per_row = GetPixelsPerRow( + buffer_collection_info_.settings.image_format_constraints); + uint32_t stride = kFuchsiaBytesPerPixel * pixels_per_row; + frame->CopyPixelsFrom(virtual_memory_mapped_addrs_[buffer_index], stride, + DesktopRect::MakeWH(width_, height_)); + + fuchsia::ui::composition::ScreenCapture_ReleaseFrame_Result release_result; + screen_capture_->ReleaseFrame(buffer_index, &release_result); + if (release_result.is_err()) { + RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.ReleaseFrame() failed: " + << release_result.err(); + } + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + frame->set_capture_time_ms(capture_time_ms); + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +bool ScreenCapturerFuchsia::GetSourceList(SourceList* screens) { + RTC_DCHECK(screens->size() == 0); + // Fuchsia only supports single monitor display at this point + screens->push_back({kFuchsiaScreenId, std::string("Fuchsia monitor")}); + return true; +} + +bool ScreenCapturerFuchsia::SelectSource(SourceId id) { + if (id == kFuchsiaScreenId || id == kFullDesktopScreenId) { + return true; + } + return false; +} + +fuchsia::sysmem::BufferCollectionConstraints +ScreenCapturerFuchsia::GetBufferConstraints() { + fuchsia::sysmem::BufferCollectionConstraints constraints; + constraints.usage.cpu = + fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite; + constraints.min_buffer_count = kMinBufferCount; + + constraints.has_buffer_memory_constraints = true; + constraints.buffer_memory_constraints.ram_domain_supported = true; + constraints.buffer_memory_constraints.cpu_domain_supported = true; + + constraints.image_format_constraints_count = 1; + fuchsia::sysmem::ImageFormatConstraints& image_constraints = + constraints.image_format_constraints[0]; + image_constraints.color_spaces_count = 1; + image_constraints.color_space[0] = + fuchsia::sysmem::ColorSpace{.type = kSRGBColorSpace}; + image_constraints.pixel_format.type = kBGRA32PixelFormatType; + image_constraints.pixel_format.has_format_modifier = true; + image_constraints.pixel_format.format_modifier.value = + fuchsia::sysmem::FORMAT_MODIFIER_LINEAR; + + image_constraints.required_min_coded_width = width_; + image_constraints.required_min_coded_height = height_; + image_constraints.required_max_coded_width = width_; + image_constraints.required_max_coded_height = height_; + + image_constraints.bytes_per_row_divisor = kFuchsiaBytesPerPixel; + + return constraints; +} + +void ScreenCapturerFuchsia::SetupBuffers() { + fuchsia::ui::scenic::ScenicSyncPtr scenic; + zx_status_t status = component_context_->svc()->Connect(scenic.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status; + return; + } + + bool scenic_uses_flatland = false; + // TODO(fxbug.dev/100303): Remove when Flatland is the only API. + scenic->UsesFlatland(&scenic_uses_flatland); + if (!scenic_uses_flatland) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Screen capture not supported without Flatland."; + return; + } + + fuchsia::ui::gfx::DisplayInfo display_info; + status = scenic->GetDisplayInfo(&display_info); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to get display dimensions: " + << status; + return; + } + width_ = display_info.width_in_px; + height_ = display_info.height_in_px; + + status = component_context_->svc()->Connect(sysmem_allocator_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Sysmem Allocator: " << status; + return; + } + + fuchsia::sysmem::BufferCollectionTokenSyncPtr sysmem_token; + status = + sysmem_allocator_->AllocateSharedCollection(sysmem_token.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.Allocator.AllocateSharedCollection() failed: " + << status; + return; + } + + fuchsia::sysmem::BufferCollectionTokenSyncPtr flatland_token; + status = sysmem_token->Duplicate(ZX_RIGHT_SAME_RIGHTS, + flatland_token.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.BufferCollectionToken.Duplicate() failed: " + << status; + return; + } + + status = sysmem_token->Sync(); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "fuchsia.sysmem.BufferCollectionToken.Sync() failed: " + << status; + return; + } + + status = sysmem_allocator_->BindSharedCollection(std::move(sysmem_token), + collection_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.Allocator.BindSharedCollection() failed: " << status; + return; + } + + status = collection_->SetConstraints(/*has_constraints=*/true, + GetBufferConstraints()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.sysmem.BufferCollection.SetConstraints() failed: " + << status; + return; + } + + fuchsia::ui::composition::BufferCollectionImportToken import_token; + fuchsia::ui::composition::BufferCollectionExportToken export_token; + status = zx::eventpair::create(0, &export_token.value, &import_token.value); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "Failed to create BufferCollection import and export tokens: " + << status; + return; + } + + status = component_context_->svc()->Connect(flatland_allocator_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Flatland Allocator: " << status; + return; + } + + fuchsia::ui::composition::RegisterBufferCollectionArgs buffer_collection_args; + buffer_collection_args.set_export_token(std::move(export_token)); + buffer_collection_args.set_buffer_collection_token(std::move(flatland_token)); + buffer_collection_args.set_usage( + fuchsia::ui::composition::RegisterBufferCollectionUsage::SCREENSHOT); + + fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result + buffer_collection_result; + flatland_allocator_->RegisterBufferCollection( + std::move(buffer_collection_args), &buffer_collection_result); + if (buffer_collection_result.is_err()) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.Allocator." + "RegisterBufferCollection() failed."; + return; + } + + zx_status_t allocation_status; + status = collection_->WaitForBuffersAllocated(&allocation_status, + &buffer_collection_info_); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to wait for buffer collection info: " + << status; + return; + } + if (allocation_status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to allocate buffer collection: " << status; + return; + } + status = collection_->Close(); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to close buffer collection token: " << status; + return; + } + + status = component_context_->svc()->Connect(screen_capture_.NewRequest()); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to connect to Screen Capture: " << status; + return; + } + + // Configure buffers in ScreenCapture client. + fuchsia::ui::composition::ScreenCaptureConfig configure_args; + configure_args.set_import_token(std::move(import_token)); + configure_args.set_buffer_count(buffer_collection_info_.buffer_count); + configure_args.set_size({width_, height_}); + + fuchsia::ui::composition::ScreenCapture_Configure_Result configure_result; + screen_capture_->Configure(std::move(configure_args), &configure_result); + if (configure_result.is_err()) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) + << "fuchsia.ui.composition.ScreenCapture.Configure() failed: " + << configure_result.err(); + return; + } + + // We have a collection of virtual memory objects which the ScreenCapture + // client will write the frame data to when requested. We map each of these + // onto a pointer stored in virtual_memory_mapped_addrs_ which we can use to + // access this data. + uint32_t virt_mem_bytes = + buffer_collection_info_.settings.buffer_settings.size_bytes; + RTC_DCHECK(virt_mem_bytes > 0); + for (uint32_t buffer_index = 0; + buffer_index < buffer_collection_info_.buffer_count; buffer_index++) { + const zx::vmo& virt_mem = buffer_collection_info_.buffers[buffer_index].vmo; + virtual_memory_mapped_addrs_[buffer_index] = nullptr; + auto status = zx::vmar::root_self()->map( + ZX_VM_PERM_READ, /*vmar_offset*/ 0, virt_mem, + /*vmo_offset*/ 0, virt_mem_bytes, + reinterpret_cast( + &virtual_memory_mapped_addrs_[buffer_index])); + if (status != ZX_OK) { + fatal_error_ = true; + RTC_LOG(LS_ERROR) << "Failed to map virtual memory: " << status; + return; + } + } +} + +uint32_t ScreenCapturerFuchsia::GetPixelsPerRow( + const fuchsia::sysmem::ImageFormatConstraints& constraints) { + uint32_t stride = RoundUpToMultiple( + std::max(constraints.min_bytes_per_row, width_ * kFuchsiaBytesPerPixel), + constraints.bytes_per_row_divisor); + uint32_t pixels_per_row = stride / kFuchsiaBytesPerPixel; + + return pixels_per_row; +} + +} // namespace webrtc diff --git a/modules/desktop_capture/screen_capturer_fuchsia.h b/modules/desktop_capture/screen_capturer_fuchsia.h new file mode 100644 index 0000000000..6e0f87cc58 --- /dev/null +++ b/modules/desktop_capture/screen_capturer_fuchsia.h @@ -0,0 +1,63 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_ +#define MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_ + +#include +#include +#include + +#include +#include + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" + +namespace webrtc { + +class ScreenCapturerFuchsia final : public DesktopCapturer { + public: + ScreenCapturerFuchsia(); + ~ScreenCapturerFuchsia() override; + + // DesktopCapturer interface. + void Start(Callback* callback) override; + void CaptureFrame() override; + bool GetSourceList(SourceList* screens) override; + bool SelectSource(SourceId id) override; + + private: + fuchsia::sysmem::BufferCollectionConstraints GetBufferConstraints(); + void SetupBuffers(); + uint32_t GetPixelsPerRow( + const fuchsia::sysmem::ImageFormatConstraints& constraints); + + Callback* callback_ = nullptr; + + std::unique_ptr component_context_; + fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator_; + fuchsia::ui::composition::AllocatorSyncPtr flatland_allocator_; + fuchsia::ui::composition::ScreenCaptureSyncPtr screen_capture_; + fuchsia::sysmem::BufferCollectionSyncPtr collection_; + fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info_; + std::unordered_map virtual_memory_mapped_addrs_; + + bool fatal_error_; + + // Dimensions of the screen we are capturing + uint32_t width_; + uint32_t height_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_