Commit f042205d authored by Christopher Cameron's avatar Christopher Cameron Committed by Chromium LUCI CQ

Add DesktopCaptureDeviceMacV2 feature

This adds a zero-copy desktop capture mechanism to macOS. The frames
are captured as NV12 IOSurfaces, packaged in IOSurfaces, and passed
directly to the consumers (e.g, compositor, encoders, etc).

This implementation is a trimmed-down version of the existing system,
which can be found in the DesktopCaptureDevice class and the WebRTC
class ScreenCapturerMac. The CGDisplayStream code is identical to
the WebRTC code except for the following changes:
- It requests NV12 as the capture format
- It specifies the requested capture resolution (rather than capturing
  at retina resolution and downsampling in software)
- It specifies kCGDisplayStreamMinimumFrameTime and
  kCGDisplayStreamColorSpace.
Otherwise, all calls are identical.

Bug: 1164736
Change-Id: I962911aa9b1863d730c333866f5644bbbd2b7164
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2616802
Commit-Queue: ccameron <ccameron@chromium.org>
Reviewed-by: default avatarMarkus Handell <handellm@google.com>
Reviewed-by: default avatarGuido Urdaneta <guidou@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#843989}
parent 9da2aae7
......@@ -2273,7 +2273,11 @@ source_set("browser") {
]
}
if (is_mac) {
sources += [ "media/capture/mouse_cursor_overlay_controller_mac.mm" ]
sources += [
"media/capture/desktop_capture_device_mac.cc",
"media/capture/desktop_capture_device_mac.h",
"media/capture/mouse_cursor_overlay_controller_mac.mm",
]
deps += [
"//sandbox/mac:seatbelt",
"//sandbox/mac:seatbelt_extension",
......@@ -2677,6 +2681,7 @@ source_set("browser") {
]
frameworks += [
"Carbon.framework",
"CoreGraphics.framework",
"QuartzCore.framework",
"IOSurface.framework",
]
......
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/capture/desktop_capture_device_mac.h"
#include <CoreGraphics/CoreGraphics.h>
#include "base/threading/thread.h"
#include "media/capture/video/video_capture_device.h"
#include "ui/gfx/native_widget_types.h"
namespace content {
namespace {
class DesktopCaptureDeviceMac : public media::VideoCaptureDevice {
public:
DesktopCaptureDeviceMac(CGDirectDisplayID display_id)
: display_id_(display_id), weak_factory_(this) {}
~DesktopCaptureDeviceMac() override = default;
// media::VideoCaptureDevice:
void AllocateAndStart(const media::VideoCaptureParams& params,
std::unique_ptr<Client> client) override {
DCHECK(client && !client_);
client_ = std::move(client);
requested_format_ = params.requested_format;
requested_format_.pixel_format = media::PIXEL_FORMAT_NV12;
DCHECK_GT(requested_format_.frame_size.GetArea(), 0);
DCHECK_GT(requested_format_.frame_rate, 0);
auto task_runner = base::SequencedTaskRunnerHandle::Get();
CGDisplayStreamFrameAvailableHandler handler = ^(
CGDisplayStreamFrameStatus status, uint64_t display_time,
IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef update_ref) {
if (status == kCGDisplayStreamFrameStatusFrameComplete) {
task_runner->PostTask(
FROM_HERE,
base::BindOnce(&DesktopCaptureDeviceMac::OnReceivedIOSurface,
weak_factory_.GetWeakPtr(),
gfx::ScopedInUseIOSurface(
frame_surface, base::scoped_policy::RETAIN)));
}
};
base::ScopedCFTypeRef<CFDictionaryRef> properties;
{
float max_frame_time = 1.f / requested_format_.frame_rate;
base::ScopedCFTypeRef<CFNumberRef> cf_max_frame_time(
CFNumberCreate(nullptr, kCFNumberFloat32Type, &max_frame_time));
base::ScopedCFTypeRef<CGColorSpaceRef> cg_color_space(
CGColorSpaceCreateWithName(kCGColorSpaceSRGB));
const size_t kNumKeys = 3;
const void* keys[kNumKeys] = {
kCGDisplayStreamShowCursor,
kCGDisplayStreamMinimumFrameTime,
kCGDisplayStreamColorSpace,
};
const void* values[kNumKeys] = {
kCFBooleanFalse,
cf_max_frame_time.get(),
cg_color_space.get(),
};
properties.reset(CFDictionaryCreate(
kCFAllocatorDefault, keys, values, kNumKeys,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
}
display_stream_.reset(CGDisplayStreamCreate(
display_id_, requested_format_.frame_size.width(),
requested_format_.frame_size.height(),
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, properties, handler));
if (!display_stream_) {
client_->OnError(
media::VideoCaptureError::kDesktopCaptureDeviceMacFailedStreamCreate,
FROM_HERE, "CGDisplayStreamCreate failed");
return;
}
CGError error = CGDisplayStreamStart(display_stream_);
if (error != kCGErrorSuccess) {
client_->OnError(
media::VideoCaptureError::kDesktopCaptureDeviceMacFailedStreamStart,
FROM_HERE, "CGDisplayStreamStart failed");
return;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(),
CGDisplayStreamGetRunLoopSource(display_stream_),
kCFRunLoopCommonModes);
client_->OnStarted();
}
void StopAndDeAllocate() override {
weak_factory_.InvalidateWeakPtrs();
if (display_stream_) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
CGDisplayStreamGetRunLoopSource(display_stream_),
kCFRunLoopCommonModes);
CGDisplayStreamStop(display_stream_);
}
display_stream_.reset();
}
private:
void OnReceivedIOSurface(gfx::ScopedInUseIOSurface io_surface) {
// Package |io_surface| as a GpuMemoryBuffer.
gfx::GpuMemoryBufferHandle handle;
handle.id.id = -1;
handle.type = gfx::GpuMemoryBufferType::IO_SURFACE_BUFFER;
handle.io_surface.reset(io_surface, base::scoped_policy::RETAIN);
const auto now = base::TimeTicks::Now();
if (first_frame_time_.is_null())
first_frame_time_ = now;
client_->OnIncomingCapturedExternalBuffer(
std::move(handle), requested_format_, gfx::ColorSpace::CreateSRGB(),
now, now - first_frame_time_);
}
const CGDirectDisplayID display_id_;
std::unique_ptr<Client> client_;
base::ScopedCFTypeRef<CGDisplayStreamRef> display_stream_;
media::VideoCaptureFormat requested_format_;
// The time of the first call to OnReceivedIOSurface. Used to compute the
// timestamp of subsequent frames.
base::TimeTicks first_frame_time_;
base::WeakPtrFactory<DesktopCaptureDeviceMac> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(DesktopCaptureDeviceMac);
};
} // namespace
std::unique_ptr<media::VideoCaptureDevice> CreateDesktopCaptureDeviceMac(
const DesktopMediaID& source) {
CHECK_EQ(source.type, DesktopMediaID::TYPE_SCREEN);
IncrementDesktopCaptureCounter(SCREEN_CAPTURER_CREATED);
IncrementDesktopCaptureCounter(source.audio_share
? SCREEN_CAPTURER_CREATED_WITH_AUDIO
: SCREEN_CAPTURER_CREATED_WITHOUT_AUDIO);
return std::make_unique<DesktopCaptureDeviceMac>(source.id);
}
} // namespace content
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_MEDIA_CAPTURE_DESKTOP_CAPTURE_DEVICE_MAC_H_
#define CONTENT_BROWSER_MEDIA_CAPTURE_DESKTOP_CAPTURE_DEVICE_MAC_H_
#include <stdint.h>
#include <memory>
#include "content/browser/media/capture/desktop_capture_device_uma_types.h"
#include "content/common/content_export.h"
#include "content/public/browser/desktop_media_id.h"
namespace media {
class VideoCaptureDevice;
} // namespace media
namespace content {
std::unique_ptr<media::VideoCaptureDevice> CONTENT_EXPORT
CreateDesktopCaptureDeviceMac(const DesktopMediaID& source);
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_CAPTURE_DESKTOP_CAPTURE_DEVICE_MAC_H_
......@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
......@@ -19,6 +20,7 @@
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/common/content_features.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/media_switches.h"
#include "media/capture/video/fake_video_capture_device.h"
......@@ -41,6 +43,9 @@
#endif
#include "content/browser/media/capture/desktop_capture_device.h"
#endif // defined(OS_ANDROID)
#if defined(OS_MAC)
#include "content/browser/media/capture/desktop_capture_device_mac.h"
#endif
#endif // BUILDFLAG(ENABLE_SCREEN_CAPTURE)
#if BUILDFLAG(IS_CHROMEOS_ASH)
......@@ -390,9 +395,13 @@ void InProcessVideoCaptureDeviceLauncher::DoStartDesktopCaptureOnDeviceThread(
#if defined(OS_ANDROID)
video_capture_device = std::make_unique<ScreenCaptureDeviceAndroid>();
#else
#if defined(OS_MAC)
if (base::FeatureList::IsEnabled(features::kDesktopCaptureMacV2))
video_capture_device = CreateDesktopCaptureDeviceMac(desktop_id);
#endif
if (!video_capture_device)
video_capture_device = DesktopCaptureDevice::Create(desktop_id);
#endif // defined (OS_ANDROID)
#endif
if (video_capture_device)
video_capture_device->AllocateAndStart(params, std::move(device_client));
......
......@@ -911,6 +911,10 @@ const base::Feature kMacV2GPUSandbox{"MacV2GPUSandbox",
// cameras.
const base::Feature kRetryGetVideoCaptureDeviceInfos{
"RetryGetVideoCaptureDeviceInfos", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kDesktopCaptureMacV2{"DesktopCaptureMacV2",
base::FEATURE_DISABLED_BY_DEFAULT};
#endif // defined(OS_MAC)
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
......
......@@ -207,6 +207,7 @@ CONTENT_EXPORT extern const base::Feature kWebNfc;
#endif // defined(OS_ANDROID)
#if defined(OS_MAC)
CONTENT_EXPORT extern const base::Feature kDesktopCaptureMacV2;
CONTENT_EXPORT extern const base::Feature kDeviceMonitorMac;
CONTENT_EXPORT extern const base::Feature kIOSurfaceCapturer;
CONTENT_EXPORT extern const base::Feature kMacSyscallSandbox;
......
......@@ -230,6 +230,8 @@ enum VideoCaptureError {
kFuchsiaUnsupportedPixelFormat,
kFuchsiaFailedToMapSysmemBuffer,
kCrosHalV3DeviceContextDuplicatedClient,
kDesktopCaptureDeviceMacFailedStreamCreate,
kDesktopCaptureDeviceMacFailedStreamStart,
};
enum VideoCaptureFrameDropReason {
......
......@@ -704,6 +704,12 @@ EnumTraits<media::mojom::VideoCaptureError, media::VideoCaptureError>::ToMojom(
case media::VideoCaptureError::kCrosHalV3DeviceContextDuplicatedClient:
return media::mojom::VideoCaptureError::
kCrosHalV3DeviceContextDuplicatedClient;
case media::VideoCaptureError::kDesktopCaptureDeviceMacFailedStreamCreate:
return media::mojom::VideoCaptureError::
kDesktopCaptureDeviceMacFailedStreamCreate;
case media::VideoCaptureError::kDesktopCaptureDeviceMacFailedStreamStart:
return media::mojom::VideoCaptureError::
kDesktopCaptureDeviceMacFailedStreamStart;
}
NOTREACHED();
return media::mojom::VideoCaptureError::kNone;
......@@ -1251,6 +1257,16 @@ bool EnumTraits<media::mojom::VideoCaptureError, media::VideoCaptureError>::
*output =
media::VideoCaptureError::kCrosHalV3DeviceContextDuplicatedClient;
return true;
case media::mojom::VideoCaptureError::
kDesktopCaptureDeviceMacFailedStreamCreate:
*output =
media::VideoCaptureError::kDesktopCaptureDeviceMacFailedStreamCreate;
return true;
case media::mojom::VideoCaptureError::
kDesktopCaptureDeviceMacFailedStreamStart:
*output =
media::VideoCaptureError::kDesktopCaptureDeviceMacFailedStreamStart;
return true;
}
NOTREACHED();
return false;
......
......@@ -190,7 +190,9 @@ enum class VideoCaptureError {
kFuchsiaUnsupportedPixelFormat = 121,
kFuchsiaFailedToMapSysmemBuffer = 122,
kCrosHalV3DeviceContextDuplicatedClient = 123,
kMaxValue = 123
kDesktopCaptureDeviceMacFailedStreamCreate = 124,
kDesktopCaptureDeviceMacFailedStreamStart = 125,
kMaxValue = 125
};
// WARNING: Do not change the values assigned to the entries. They are used for
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment