/* * 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) { if (ScreenCapturerFuchsia::CheckRequirements()) { std::unique_ptr capturer( new ScreenCapturerFuchsia()); return capturer; } return nullptr; } ScreenCapturerFuchsia::ScreenCapturerFuchsia() : component_context_(sys::ComponentContext::Create()) { RTC_DCHECK(CheckRequirements()); } 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); } } // TODO(fxbug.dev/100303): Remove this function when Flatland is the only API. bool ScreenCapturerFuchsia::CheckRequirements() { std::unique_ptr component_context = sys::ComponentContext::Create(); fuchsia::ui::scenic::ScenicSyncPtr scenic; zx_status_t status = component_context->svc()->Connect(scenic.NewRequest()); if (status != ZX_OK) { RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status; return false; } bool scenic_uses_flatland = false; scenic->UsesFlatland(&scenic_uses_flatland); if (!scenic_uses_flatland) { RTC_LOG(LS_ERROR) << "Screen capture not supported without Flatland."; } return scenic_uses_flatland; } 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_)); // Mark the whole screen as having been updated. frame->mutable_updated_region()->SetRect( 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; } 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