screen_capturer_fuchsia.cc (15116B)
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/sysmem2/cpp/fidl.h> 14 #include <fuchsia/ui/composition/cpp/fidl.h> 15 #include <fuchsia/ui/display/singleton/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::images2::ColorSpace kSRGBColorSpace = 46 fuchsia::images2::ColorSpace::SRGB; 47 static constexpr fuchsia::images2::PixelFormat kBGRA32PixelFormatType = 48 fuchsia::images2::PixelFormat::B8G8R8A8; 49 50 // Round |value| up to the closest multiple of |multiple| 51 size_t RoundUpToMultiple(size_t value, size_t multiple) { 52 return DivideRoundUp(value, multiple) * multiple; 53 } 54 55 } // namespace 56 57 std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawScreenCapturer( 58 const DesktopCaptureOptions& options) { 59 RTC_LOG(LS_INFO) 60 << "video capture: DesktopCapturer::CreateRawScreenCapturer creates " 61 "DesktopCapturer of type ScreenCapturerFuchsia"; 62 std::unique_ptr<ScreenCapturerFuchsia> capturer(new ScreenCapturerFuchsia()); 63 return capturer; 64 } 65 66 ScreenCapturerFuchsia::ScreenCapturerFuchsia() 67 : component_context_(sys::ComponentContext::Create()) {} 68 69 ScreenCapturerFuchsia::~ScreenCapturerFuchsia() { 70 // unmap virtual memory mapped pointers 71 uint32_t virt_mem_bytes = 72 buffer_collection_info_.settings().buffer_settings().size_bytes(); 73 for (uint32_t buffer_index = 0; 74 buffer_index < buffer_collection_info_.buffers().size(); 75 buffer_index++) { 76 uintptr_t address = 77 reinterpret_cast<uintptr_t>(virtual_memory_mapped_addrs_[buffer_index]); 78 zx_status_t status = zx::vmar::root_self()->unmap(address, virt_mem_bytes); 79 RTC_DCHECK(status == ZX_OK); 80 } 81 } 82 83 void ScreenCapturerFuchsia::Start(Callback* callback) { 84 RTC_DCHECK(!callback_); 85 RTC_DCHECK(callback); 86 callback_ = callback; 87 88 fatal_error_ = false; 89 90 SetupBuffers(); 91 } 92 93 void ScreenCapturerFuchsia::CaptureFrame() { 94 if (fatal_error_) { 95 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); 96 return; 97 } 98 99 int64_t capture_start_time_nanos = TimeNanos(); 100 101 zx::event event; 102 zx::event dup; 103 zx_status_t status = zx::event::create(0, &event); 104 if (status != ZX_OK) { 105 RTC_LOG(LS_ERROR) << "Failed to create event: " << status; 106 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); 107 return; 108 } 109 event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); 110 111 fuchsia::ui::composition::GetNextFrameArgs next_frame_args; 112 next_frame_args.set_event(std::move(dup)); 113 114 fuchsia::ui::composition::ScreenCapture_GetNextFrame_Result result; 115 screen_capture_->GetNextFrame(std::move(next_frame_args), &result); 116 if (result.is_err()) { 117 RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.GetNextFrame() failed: " 118 << result.err() << "\n"; 119 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); 120 return; 121 } 122 123 status = event.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(kEventDelay), 124 nullptr); 125 if (status != ZX_OK) { 126 RTC_LOG(LS_ERROR) << "Timed out waiting for ScreenCapture to render frame: " 127 << status; 128 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); 129 return; 130 } 131 uint32_t buffer_index = result.response().buffer_id(); 132 133 // TODO(bugs.webrtc.org/14097): Use SharedMemoryDesktopFrame and 134 // ScreenCaptureFrameQueue 135 std::unique_ptr<BasicDesktopFrame> frame( 136 new BasicDesktopFrame(DesktopSize(width_, height_))); 137 138 uint32_t pixels_per_row = GetPixelsPerRow( 139 buffer_collection_info_.settings().image_format_constraints()); 140 uint32_t stride = kFuchsiaBytesPerPixel * pixels_per_row; 141 frame->CopyPixelsFrom(virtual_memory_mapped_addrs_[buffer_index], stride, 142 DesktopRect::MakeWH(width_, height_)); 143 // Mark the whole screen as having been updated. 144 frame->mutable_updated_region()->SetRect( 145 DesktopRect::MakeWH(width_, height_)); 146 147 fuchsia::ui::composition::ScreenCapture_ReleaseFrame_Result release_result; 148 screen_capture_->ReleaseFrame(buffer_index, &release_result); 149 if (release_result.is_err()) { 150 RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.ReleaseFrame() failed: " 151 << release_result.err(); 152 } 153 154 int capture_time_ms = 155 (TimeNanos() - capture_start_time_nanos) / kNumNanosecsPerMillisec; 156 frame->set_capture_time_ms(capture_time_ms); 157 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); 158 } 159 160 bool ScreenCapturerFuchsia::GetSourceList(SourceList* screens) { 161 RTC_DCHECK(screens->size() == 0); 162 // Fuchsia only supports single monitor display at this point 163 screens->push_back({kFuchsiaScreenId, std::string("Fuchsia monitor")}); 164 return true; 165 } 166 167 bool ScreenCapturerFuchsia::SelectSource(SourceId id) { 168 if (id == kFuchsiaScreenId || id == kFullDesktopScreenId) { 169 return true; 170 } 171 return false; 172 } 173 174 fuchsia::sysmem2::BufferCollectionConstraints 175 ScreenCapturerFuchsia::GetBufferConstraints() { 176 fuchsia::sysmem2::BufferCollectionConstraints constraints; 177 constraints.mutable_usage()->set_cpu(fuchsia::sysmem2::CPU_USAGE_READ | 178 fuchsia::sysmem2::CPU_USAGE_WRITE); 179 constraints.set_min_buffer_count(kMinBufferCount); 180 181 auto& memory_constraints = *constraints.mutable_buffer_memory_constraints(); 182 memory_constraints.set_ram_domain_supported(true); 183 memory_constraints.set_cpu_domain_supported(true); 184 185 fuchsia::sysmem2::ImageFormatConstraints& image_constraints = 186 constraints.mutable_image_format_constraints()->emplace_back(); 187 image_constraints.mutable_color_spaces()->emplace_back(kSRGBColorSpace); 188 image_constraints.set_pixel_format(kBGRA32PixelFormatType); 189 image_constraints.set_pixel_format_modifier( 190 fuchsia::images2::PixelFormatModifier::LINEAR); 191 192 image_constraints.set_required_min_size( 193 fuchsia::math::SizeU{width_, height_}); 194 image_constraints.set_required_max_size( 195 fuchsia::math::SizeU{width_, height_}); 196 197 image_constraints.set_bytes_per_row_divisor(kFuchsiaBytesPerPixel); 198 199 return constraints; 200 } 201 202 void ScreenCapturerFuchsia::SetupBuffers() { 203 fuchsia::ui::display::singleton::InfoSyncPtr display_info; 204 zx_status_t status = 205 component_context_->svc()->Connect(display_info.NewRequest()); 206 if (status != ZX_OK) { 207 fatal_error_ = true; 208 RTC_LOG(LS_ERROR) 209 << "Failed to connect to fuchsia.ui.display.singleton.Info: " << status; 210 return; 211 } 212 213 fuchsia::ui::display::singleton::Metrics metrics; 214 status = display_info->GetMetrics(&metrics); 215 if (status != ZX_OK) { 216 fatal_error_ = true; 217 RTC_LOG(LS_ERROR) << "Failed to connect to get display dimensions: " 218 << status; 219 return; 220 } 221 width_ = metrics.extent_in_px().width; 222 height_ = metrics.extent_in_px().height; 223 224 status = component_context_->svc()->Connect(sysmem_allocator_.NewRequest()); 225 if (status != ZX_OK) { 226 fatal_error_ = true; 227 RTC_LOG(LS_ERROR) << "Failed to connect to fuchsia.sysmem2.Allocator: " 228 << status; 229 return; 230 } 231 232 fuchsia::sysmem2::BufferCollectionTokenSyncPtr sysmem_token; 233 status = sysmem_allocator_->AllocateSharedCollection( 234 std::move(fuchsia::sysmem2::AllocatorAllocateSharedCollectionRequest{} 235 .set_token_request(sysmem_token.NewRequest()))); 236 if (status != ZX_OK) { 237 fatal_error_ = true; 238 RTC_LOG(LS_ERROR) 239 << "fuchsia.sysmem2.Allocator.AllocateSharedCollection() failed: " 240 << status; 241 return; 242 } 243 244 fuchsia::sysmem2::BufferCollectionTokenSyncPtr flatland_token; 245 status = sysmem_token->Duplicate( 246 std::move(fuchsia::sysmem2::BufferCollectionTokenDuplicateRequest{} 247 .set_rights_attenuation_mask(ZX_RIGHT_SAME_RIGHTS) 248 .set_token_request(flatland_token.NewRequest()))); 249 if (status != ZX_OK) { 250 fatal_error_ = true; 251 RTC_LOG(LS_ERROR) 252 << "fuchsia.sysmem2.BufferCollectionToken.Duplicate() failed: " 253 << status; 254 return; 255 } 256 257 fuchsia::sysmem2::Node_Sync_Result sync_result; 258 status = sysmem_token->Sync(&sync_result); 259 if (status != ZX_OK) { 260 fatal_error_ = true; 261 RTC_LOG(LS_ERROR) << "fuchsia.sysmem2.BufferCollectionToken.Sync() failed: " 262 << status; 263 return; 264 } 265 266 status = sysmem_allocator_->BindSharedCollection( 267 std::move(fuchsia::sysmem2::AllocatorBindSharedCollectionRequest{} 268 .set_token(std::move(sysmem_token)) 269 .set_buffer_collection_request(collection_.NewRequest()))); 270 if (status != ZX_OK) { 271 fatal_error_ = true; 272 RTC_LOG(LS_ERROR) 273 << "fuchsia.sysmem2.Allocator.BindSharedCollection() failed: " 274 << status; 275 return; 276 } 277 278 status = collection_->SetConstraints(std::move( 279 fuchsia::sysmem2::BufferCollectionSetConstraintsRequest{}.set_constraints( 280 GetBufferConstraints()))); 281 if (status != ZX_OK) { 282 fatal_error_ = true; 283 RTC_LOG(LS_ERROR) 284 << "fuchsia.sysmem2.BufferCollection.SetConstraints() failed: " 285 << status; 286 return; 287 } 288 289 fuchsia::ui::composition::BufferCollectionImportToken import_token; 290 fuchsia::ui::composition::BufferCollectionExportToken export_token; 291 status = zx::eventpair::create(0, &export_token.value, &import_token.value); 292 if (status != ZX_OK) { 293 fatal_error_ = true; 294 RTC_LOG(LS_ERROR) 295 << "Failed to create BufferCollection import and export tokens: " 296 << status; 297 return; 298 } 299 300 status = component_context_->svc()->Connect(flatland_allocator_.NewRequest()); 301 if (status != ZX_OK) { 302 fatal_error_ = true; 303 RTC_LOG(LS_ERROR) << "Failed to connect to Flatland Allocator: " << status; 304 return; 305 } 306 307 fuchsia::ui::composition::RegisterBufferCollectionArgs buffer_collection_args; 308 buffer_collection_args.set_export_token(std::move(export_token)); 309 buffer_collection_args.set_buffer_collection_token2( 310 std::move(flatland_token)); 311 buffer_collection_args.set_usage( 312 fuchsia::ui::composition::RegisterBufferCollectionUsage::SCREENSHOT); 313 314 fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result 315 buffer_collection_result; 316 flatland_allocator_->RegisterBufferCollection( 317 std::move(buffer_collection_args), &buffer_collection_result); 318 if (buffer_collection_result.is_err()) { 319 fatal_error_ = true; 320 RTC_LOG(LS_ERROR) << "fuchsia.ui.composition.Allocator." 321 "RegisterBufferCollection() failed."; 322 return; 323 } 324 325 fuchsia::sysmem2::BufferCollection_WaitForAllBuffersAllocated_Result 326 wait_result; 327 status = collection_->WaitForAllBuffersAllocated(&wait_result); 328 if (status != ZX_OK) { 329 fatal_error_ = true; 330 RTC_LOG(LS_ERROR) << "Failed to wait for buffer collection info: " 331 << status; 332 return; 333 } 334 if (!wait_result.is_response()) { 335 if (wait_result.is_framework_err()) { 336 RTC_LOG(LS_ERROR) 337 << "Failed to allocate buffer collection (framework_err): " 338 << fidl::ToUnderlying(wait_result.framework_err()); 339 } else { 340 RTC_LOG(LS_ERROR) << "Failed to allocate buffer collection (err): " 341 << static_cast<uint32_t>(wait_result.err()); 342 } 343 fatal_error_ = true; 344 return; 345 } 346 buffer_collection_info_ = 347 std::move(*wait_result.response().mutable_buffer_collection_info()); 348 349 status = collection_->Release(); 350 if (status != ZX_OK) { 351 fatal_error_ = true; 352 RTC_LOG(LS_ERROR) << "Failed to close buffer collection token: " << status; 353 return; 354 } 355 356 status = component_context_->svc()->Connect(screen_capture_.NewRequest()); 357 if (status != ZX_OK) { 358 fatal_error_ = true; 359 RTC_LOG(LS_ERROR) << "Failed to connect to Screen Capture: " << status; 360 return; 361 } 362 363 // Configure buffers in ScreenCapture client. 364 fuchsia::ui::composition::ScreenCaptureConfig configure_args; 365 configure_args.set_import_token(std::move(import_token)); 366 configure_args.set_buffer_count(buffer_collection_info_.buffers().size()); 367 configure_args.set_size({width_, height_}); 368 369 fuchsia::ui::composition::ScreenCapture_Configure_Result configure_result; 370 screen_capture_->Configure(std::move(configure_args), &configure_result); 371 if (configure_result.is_err()) { 372 fatal_error_ = true; 373 RTC_LOG(LS_ERROR) 374 << "fuchsia.ui.composition.ScreenCapture.Configure() failed: " 375 << configure_result.err(); 376 return; 377 } 378 379 // We have a collection of virtual memory objects which the ScreenCapture 380 // client will write the frame data to when requested. We map each of these 381 // onto a pointer stored in virtual_memory_mapped_addrs_ which we can use to 382 // access this data. 383 uint32_t virt_mem_bytes = 384 buffer_collection_info_.settings().buffer_settings().size_bytes(); 385 RTC_DCHECK(virt_mem_bytes > 0); 386 for (uint32_t buffer_index = 0; 387 buffer_index < buffer_collection_info_.buffers().size(); 388 buffer_index++) { 389 const zx::vmo& virt_mem = 390 buffer_collection_info_.buffers()[buffer_index].vmo(); 391 virtual_memory_mapped_addrs_[buffer_index] = nullptr; 392 status = zx::vmar::root_self()->map( 393 ZX_VM_PERM_READ, /*vmar_offset*/ 0, virt_mem, 394 /*vmo_offset*/ 0, virt_mem_bytes, 395 reinterpret_cast<uintptr_t*>( 396 &virtual_memory_mapped_addrs_[buffer_index])); 397 if (status != ZX_OK) { 398 fatal_error_ = true; 399 RTC_LOG(LS_ERROR) << "Failed to map virtual memory: " << status; 400 return; 401 } 402 } 403 } 404 405 uint32_t ScreenCapturerFuchsia::GetPixelsPerRow( 406 const fuchsia::sysmem2::ImageFormatConstraints& constraints) { 407 uint32_t stride = RoundUpToMultiple( 408 std::max(constraints.min_bytes_per_row(), width_ * kFuchsiaBytesPerPixel), 409 constraints.bytes_per_row_divisor()); 410 uint32_t pixels_per_row = stride / kFuchsiaBytesPerPixel; 411 412 return pixels_per_row; 413 } 414 415 } // namespace webrtc