Files
platform-external-webrtc/modules/desktop_capture/screen_capturer_fuchsia.cc
Hunter Freyer 1571258ca6 Fix a couple bugs in Fuchsia screen capture.
1. Use ComponentContext::Create instead of

   ComponentContext::CreateAndServeOutgoingDirectory. We're not
   actually serving an outgoing directory here, and trying to causes
   conflicts when this code is linked into a Fuchsia component.
2. Mark the whole screen as having been updated on each frame. Some
   codecs were assuming that nothing on the screen was changing, and
   so only the first frame would be shared.

Change-Id: Icb02a2cc097947b85cceddec49291e666257ed81
Bug: webrtc:14681
Bug: webrtc:14682
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/283920
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Reviewed-by: Sarah Pham <smpham@google.com>
Commit-Queue: Hunter Freyer <hjfreyer@google.com>
Cr-Commit-Position: refs/heads/main@{#38682}
2022-11-18 14:53:09 +00:00

418 lines
15 KiB
C++

/*
* 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 <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#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> DesktopCapturer::CreateRawScreenCapturer(
const DesktopCaptureOptions& options) {
if (ScreenCapturerFuchsia::CheckRequirements()) {
std::unique_ptr<ScreenCapturerFuchsia> 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<uintptr_t>(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<sys::ComponentContext> 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<BasicDesktopFrame> 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<uintptr_t*>(
&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