xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/screen_capturer_fuchsia.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright 2022 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "modules/desktop_capture/screen_capturer_fuchsia.h"
12 
13 #include <fuchsia/sysmem/cpp/fidl.h>
14 #include <fuchsia/ui/composition/cpp/fidl.h>
15 #include <fuchsia/ui/scenic/cpp/fidl.h>
16 #include <lib/sys/cpp/component_context.h>
17 
18 #include <algorithm>
19 #include <cstdint>
20 #include <memory>
21 #include <string>
22 #include <utility>
23 
24 #include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h"
25 #include "modules/desktop_capture/desktop_capture_options.h"
26 #include "modules/desktop_capture/desktop_capture_types.h"
27 #include "modules/desktop_capture/desktop_capturer.h"
28 #include "modules/desktop_capture/desktop_frame.h"
29 #include "modules/desktop_capture/desktop_geometry.h"
30 #include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h"
31 #include "rtc_base/checks.h"
32 #include "rtc_base/logging.h"
33 #include "rtc_base/numerics/divide_round.h"
34 #include "rtc_base/time_utils.h"
35 
36 namespace webrtc {
37 
38 namespace {
39 
40 static constexpr uint32_t kMinBufferCount = 2;
41 static constexpr uint32_t kFuchsiaBytesPerPixel = 4;
42 static constexpr DesktopCapturer::SourceId kFuchsiaScreenId = 1;
43 // 500 milliseconds
44 static constexpr zx::duration kEventDelay = zx::msec(500);
45 static constexpr fuchsia::sysmem::ColorSpaceType kSRGBColorSpace =
46     fuchsia::sysmem::ColorSpaceType::SRGB;
47 static constexpr fuchsia::sysmem::PixelFormatType kBGRA32PixelFormatType =
48     fuchsia::sysmem::PixelFormatType::BGRA32;
49 
50 // Round |value| up to the closest multiple of |multiple|
RoundUpToMultiple(size_t value,size_t multiple)51 size_t RoundUpToMultiple(size_t value, size_t multiple) {
52   return DivideRoundUp(value, multiple) * multiple;
53 }
54 
55 }  // namespace
56 
CreateRawScreenCapturer(const DesktopCaptureOptions & options)57 std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer(
58     const DesktopCaptureOptions& options) {
59   if (ScreenCapturerFuchsia::CheckRequirements()) {
60     std::unique_ptr<ScreenCapturerFuchsia> capturer(
61         new ScreenCapturerFuchsia());
62     return capturer;
63   }
64   return nullptr;
65 }
66 
ScreenCapturerFuchsia()67 ScreenCapturerFuchsia::ScreenCapturerFuchsia()
68     : component_context_(sys::ComponentContext::Create()) {
69   RTC_DCHECK(CheckRequirements());
70 }
71 
~ScreenCapturerFuchsia()72 ScreenCapturerFuchsia::~ScreenCapturerFuchsia() {
73   // unmap virtual memory mapped pointers
74   uint32_t virt_mem_bytes =
75       buffer_collection_info_.settings.buffer_settings.size_bytes;
76   for (uint32_t buffer_index = 0;
77        buffer_index < buffer_collection_info_.buffer_count; buffer_index++) {
78     uintptr_t address =
79         reinterpret_cast<uintptr_t>(virtual_memory_mapped_addrs_[buffer_index]);
80     zx_status_t status = zx::vmar::root_self()->unmap(address, virt_mem_bytes);
81     RTC_DCHECK(status == ZX_OK);
82   }
83 }
84 
85 // TODO(fxbug.dev/100303): Remove this function when Flatland is the only API.
CheckRequirements()86 bool ScreenCapturerFuchsia::CheckRequirements() {
87   std::unique_ptr<sys::ComponentContext> component_context =
88       sys::ComponentContext::Create();
89   fuchsia::ui::scenic::ScenicSyncPtr scenic;
90   zx_status_t status = component_context->svc()->Connect(scenic.NewRequest());
91   if (status != ZX_OK) {
92     RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status;
93     return false;
94   }
95 
96   bool scenic_uses_flatland = false;
97   scenic->UsesFlatland(&scenic_uses_flatland);
98   if (!scenic_uses_flatland) {
99     RTC_LOG(LS_ERROR) << "Screen capture not supported without Flatland.";
100   }
101 
102   return scenic_uses_flatland;
103 }
104 
Start(Callback * callback)105 void ScreenCapturerFuchsia::Start(Callback* callback) {
106   RTC_DCHECK(!callback_);
107   RTC_DCHECK(callback);
108   callback_ = callback;
109 
110   fatal_error_ = false;
111 
112   SetupBuffers();
113 }
114 
CaptureFrame()115 void ScreenCapturerFuchsia::CaptureFrame() {
116   if (fatal_error_) {
117     callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
118     return;
119   }
120 
121   int64_t capture_start_time_nanos = rtc::TimeNanos();
122 
123   zx::event event;
124   zx::event dup;
125   zx_status_t status = zx::event::create(0, &event);
126   if (status != ZX_OK) {
127     RTC_LOG(LS_ERROR) << "Failed to create event: " << status;
128     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
129     return;
130   }
131   event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
132 
133   fuchsia::ui::composition::GetNextFrameArgs next_frame_args;
134   next_frame_args.set_event(std::move(dup));
135 
136   fuchsia::ui::composition::ScreenCapture_GetNextFrame_Result result;
137   screen_capture_->GetNextFrame(std::move(next_frame_args), &result);
138   if (result.is_err()) {
139     RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.GetNextFrame() failed: "
140                       << result.err() << "\n";
141     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
142     return;
143   }
144 
145   status = event.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(kEventDelay),
146                           nullptr);
147   if (status != ZX_OK) {
148     RTC_LOG(LS_ERROR) << "Timed out waiting for ScreenCapture to render frame: "
149                       << status;
150     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
151     return;
152   }
153   uint32_t buffer_index = result.response().buffer_id();
154 
155   // TODO(bugs.webrtc.org/14097): Use SharedMemoryDesktopFrame and
156   // ScreenCaptureFrameQueue
157   std::unique_ptr<BasicDesktopFrame> frame(
158       new BasicDesktopFrame(DesktopSize(width_, height_)));
159 
160   uint32_t pixels_per_row = GetPixelsPerRow(
161       buffer_collection_info_.settings.image_format_constraints);
162   uint32_t stride = kFuchsiaBytesPerPixel * pixels_per_row;
163   frame->CopyPixelsFrom(virtual_memory_mapped_addrs_[buffer_index], stride,
164                         DesktopRect::MakeWH(width_, height_));
165   // Mark the whole screen as having been updated.
166   frame->mutable_updated_region()->SetRect(
167       DesktopRect::MakeWH(width_, height_));
168 
169   fuchsia::ui::composition::ScreenCapture_ReleaseFrame_Result release_result;
170   screen_capture_->ReleaseFrame(buffer_index, &release_result);
171   if (release_result.is_err()) {
172     RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.ReleaseFrame() failed: "
173                       << release_result.err();
174   }
175 
176   int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
177                         rtc::kNumNanosecsPerMillisec;
178   frame->set_capture_time_ms(capture_time_ms);
179   callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
180 }
181 
GetSourceList(SourceList * screens)182 bool ScreenCapturerFuchsia::GetSourceList(SourceList* screens) {
183   RTC_DCHECK(screens->size() == 0);
184   // Fuchsia only supports single monitor display at this point
185   screens->push_back({kFuchsiaScreenId, std::string("Fuchsia monitor")});
186   return true;
187 }
188 
SelectSource(SourceId id)189 bool ScreenCapturerFuchsia::SelectSource(SourceId id) {
190   if (id == kFuchsiaScreenId || id == kFullDesktopScreenId) {
191     return true;
192   }
193   return false;
194 }
195 
196 fuchsia::sysmem::BufferCollectionConstraints
GetBufferConstraints()197 ScreenCapturerFuchsia::GetBufferConstraints() {
198   fuchsia::sysmem::BufferCollectionConstraints constraints;
199   constraints.usage.cpu =
200       fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite;
201   constraints.min_buffer_count = kMinBufferCount;
202 
203   constraints.has_buffer_memory_constraints = true;
204   constraints.buffer_memory_constraints.ram_domain_supported = true;
205   constraints.buffer_memory_constraints.cpu_domain_supported = true;
206 
207   constraints.image_format_constraints_count = 1;
208   fuchsia::sysmem::ImageFormatConstraints& image_constraints =
209       constraints.image_format_constraints[0];
210   image_constraints.color_spaces_count = 1;
211   image_constraints.color_space[0] =
212       fuchsia::sysmem::ColorSpace{.type = kSRGBColorSpace};
213   image_constraints.pixel_format.type = kBGRA32PixelFormatType;
214   image_constraints.pixel_format.has_format_modifier = true;
215   image_constraints.pixel_format.format_modifier.value =
216       fuchsia::sysmem::FORMAT_MODIFIER_LINEAR;
217 
218   image_constraints.required_min_coded_width = width_;
219   image_constraints.required_min_coded_height = height_;
220   image_constraints.required_max_coded_width = width_;
221   image_constraints.required_max_coded_height = height_;
222 
223   image_constraints.bytes_per_row_divisor = kFuchsiaBytesPerPixel;
224 
225   return constraints;
226 }
227 
SetupBuffers()228 void ScreenCapturerFuchsia::SetupBuffers() {
229   fuchsia::ui::scenic::ScenicSyncPtr scenic;
230   zx_status_t status = component_context_->svc()->Connect(scenic.NewRequest());
231   if (status != ZX_OK) {
232     fatal_error_ = true;
233     RTC_LOG(LS_ERROR) << "Failed to connect to Scenic: " << status;
234     return;
235   }
236 
237   fuchsia::ui::gfx::DisplayInfo display_info;
238   status = scenic->GetDisplayInfo(&display_info);
239   if (status != ZX_OK) {
240     fatal_error_ = true;
241     RTC_LOG(LS_ERROR) << "Failed to connect to get display dimensions: "
242                       << status;
243     return;
244   }
245   width_ = display_info.width_in_px;
246   height_ = display_info.height_in_px;
247 
248   status = component_context_->svc()->Connect(sysmem_allocator_.NewRequest());
249   if (status != ZX_OK) {
250     fatal_error_ = true;
251     RTC_LOG(LS_ERROR) << "Failed to connect to Sysmem Allocator: " << status;
252     return;
253   }
254 
255   fuchsia::sysmem::BufferCollectionTokenSyncPtr sysmem_token;
256   status =
257       sysmem_allocator_->AllocateSharedCollection(sysmem_token.NewRequest());
258   if (status != ZX_OK) {
259     fatal_error_ = true;
260     RTC_LOG(LS_ERROR)
261         << "fuchsia.sysmem.Allocator.AllocateSharedCollection() failed: "
262         << status;
263     return;
264   }
265 
266   fuchsia::sysmem::BufferCollectionTokenSyncPtr flatland_token;
267   status = sysmem_token->Duplicate(ZX_RIGHT_SAME_RIGHTS,
268                                    flatland_token.NewRequest());
269   if (status != ZX_OK) {
270     fatal_error_ = true;
271     RTC_LOG(LS_ERROR)
272         << "fuchsia.sysmem.BufferCollectionToken.Duplicate() failed: "
273         << status;
274     return;
275   }
276 
277   status = sysmem_token->Sync();
278   if (status != ZX_OK) {
279     fatal_error_ = true;
280     RTC_LOG(LS_ERROR) << "fuchsia.sysmem.BufferCollectionToken.Sync() failed: "
281                       << status;
282     return;
283   }
284 
285   status = sysmem_allocator_->BindSharedCollection(std::move(sysmem_token),
286                                                    collection_.NewRequest());
287   if (status != ZX_OK) {
288     fatal_error_ = true;
289     RTC_LOG(LS_ERROR)
290         << "fuchsia.sysmem.Allocator.BindSharedCollection() failed: " << status;
291     return;
292   }
293 
294   status = collection_->SetConstraints(/*has_constraints=*/true,
295                                        GetBufferConstraints());
296   if (status != ZX_OK) {
297     fatal_error_ = true;
298     RTC_LOG(LS_ERROR)
299         << "fuchsia.sysmem.BufferCollection.SetConstraints() failed: "
300         << status;
301     return;
302   }
303 
304   fuchsia::ui::composition::BufferCollectionImportToken import_token;
305   fuchsia::ui::composition::BufferCollectionExportToken export_token;
306   status = zx::eventpair::create(0, &export_token.value, &import_token.value);
307   if (status != ZX_OK) {
308     fatal_error_ = true;
309     RTC_LOG(LS_ERROR)
310         << "Failed to create BufferCollection import and export tokens: "
311         << status;
312     return;
313   }
314 
315   status = component_context_->svc()->Connect(flatland_allocator_.NewRequest());
316   if (status != ZX_OK) {
317     fatal_error_ = true;
318     RTC_LOG(LS_ERROR) << "Failed to connect to Flatland Allocator: " << status;
319     return;
320   }
321 
322   fuchsia::ui::composition::RegisterBufferCollectionArgs buffer_collection_args;
323   buffer_collection_args.set_export_token(std::move(export_token));
324   buffer_collection_args.set_buffer_collection_token(std::move(flatland_token));
325   buffer_collection_args.set_usage(
326       fuchsia::ui::composition::RegisterBufferCollectionUsage::SCREENSHOT);
327 
328   fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result
329       buffer_collection_result;
330   flatland_allocator_->RegisterBufferCollection(
331       std::move(buffer_collection_args), &buffer_collection_result);
332   if (buffer_collection_result.is_err()) {
333     fatal_error_ = true;
334     RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.Allocator."
335                          "RegisterBufferCollection() failed.";
336     return;
337   }
338 
339   zx_status_t allocation_status;
340   status = collection_->WaitForBuffersAllocated(&allocation_status,
341                                                 &buffer_collection_info_);
342   if (status != ZX_OK) {
343     fatal_error_ = true;
344     RTC_LOG(LS_ERROR) << "Failed to wait for buffer collection info: "
345                       << status;
346     return;
347   }
348   if (allocation_status != ZX_OK) {
349     fatal_error_ = true;
350     RTC_LOG(LS_ERROR) << "Failed to allocate buffer collection: " << status;
351     return;
352   }
353   status = collection_->Close();
354   if (status != ZX_OK) {
355     fatal_error_ = true;
356     RTC_LOG(LS_ERROR) << "Failed to close buffer collection token: " << status;
357     return;
358   }
359 
360   status = component_context_->svc()->Connect(screen_capture_.NewRequest());
361   if (status != ZX_OK) {
362     fatal_error_ = true;
363     RTC_LOG(LS_ERROR) << "Failed to connect to Screen Capture: " << status;
364     return;
365   }
366 
367   // Configure buffers in ScreenCapture client.
368   fuchsia::ui::composition::ScreenCaptureConfig configure_args;
369   configure_args.set_import_token(std::move(import_token));
370   configure_args.set_buffer_count(buffer_collection_info_.buffer_count);
371   configure_args.set_size({width_, height_});
372 
373   fuchsia::ui::composition::ScreenCapture_Configure_Result configure_result;
374   screen_capture_->Configure(std::move(configure_args), &configure_result);
375   if (configure_result.is_err()) {
376     fatal_error_ = true;
377     RTC_LOG(LS_ERROR)
378         << "fuchsia.ui.composition.ScreenCapture.Configure() failed: "
379         << configure_result.err();
380     return;
381   }
382 
383   // We have a collection of virtual memory objects which the ScreenCapture
384   // client will write the frame data to when requested. We map each of these
385   // onto a pointer stored in virtual_memory_mapped_addrs_ which we can use to
386   // access this data.
387   uint32_t virt_mem_bytes =
388       buffer_collection_info_.settings.buffer_settings.size_bytes;
389   RTC_DCHECK(virt_mem_bytes > 0);
390   for (uint32_t buffer_index = 0;
391        buffer_index < buffer_collection_info_.buffer_count; buffer_index++) {
392     const zx::vmo& virt_mem = buffer_collection_info_.buffers[buffer_index].vmo;
393     virtual_memory_mapped_addrs_[buffer_index] = nullptr;
394     auto status = zx::vmar::root_self()->map(
395         ZX_VM_PERM_READ, /*vmar_offset*/ 0, virt_mem,
396         /*vmo_offset*/ 0, virt_mem_bytes,
397         reinterpret_cast<uintptr_t*>(
398             &virtual_memory_mapped_addrs_[buffer_index]));
399     if (status != ZX_OK) {
400       fatal_error_ = true;
401       RTC_LOG(LS_ERROR) << "Failed to map virtual memory: " << status;
402       return;
403     }
404   }
405 }
406 
GetPixelsPerRow(const fuchsia::sysmem::ImageFormatConstraints & constraints)407 uint32_t ScreenCapturerFuchsia::GetPixelsPerRow(
408     const fuchsia::sysmem::ImageFormatConstraints& constraints) {
409   uint32_t stride = RoundUpToMultiple(
410       std::max(constraints.min_bytes_per_row, width_ * kFuchsiaBytesPerPixel),
411       constraints.bytes_per_row_divisor);
412   uint32_t pixels_per_row = stride / kFuchsiaBytesPerPixel;
413 
414   return pixels_per_row;
415 }
416 
417 }  // namespace webrtc
418