blob: 97a4ee3bb2038df90f89054083a2053a92d94273 [file] [log] [blame]
// 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 "content/browser/media/capture/io_surface_capture_device_base_mac.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
#include "ui/gfx/native_widget_types.h"
namespace content {
namespace {
class DesktopCaptureDeviceMac : public IOSurfaceCaptureDeviceBase {
public:
DesktopCaptureDeviceMac(CGDirectDisplayID display_id)
: display_id_(display_id),
device_task_runner_(base::ThreadTaskRunnerHandle::Get()),
weak_factory_(this) {}
DesktopCaptureDeviceMac(const DesktopCaptureDeviceMac&) = delete;
DesktopCaptureDeviceMac& operator=(const DesktopCaptureDeviceMac&) = delete;
~DesktopCaptureDeviceMac() override = default;
// IOSurfaceCaptureDeviceBase:
void OnStart() override {
requested_format_ = capture_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);
base::RepeatingCallback<void(gfx::ScopedInUseIOSurface)>
received_io_surface_callback = base::BindRepeating(
&DesktopCaptureDeviceMac::OnFrame, weak_factory_.GetWeakPtr());
CGDisplayStreamFrameAvailableHandler handler =
^(CGDisplayStreamFrameStatus status, uint64_t display_time,
IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef update_ref) {
gfx::ScopedInUseIOSurface io_surface(frame_surface,
base::scoped_policy::RETAIN);
if (status == kCGDisplayStreamFrameStatusFrameComplete) {
device_task_runner_->PostTask(
FROM_HERE,
base::BindRepeating(received_io_surface_callback, io_surface));
}
};
// Retrieve the source display's size.
base::ScopedCFTypeRef<CGDisplayModeRef> mode(
CGDisplayCopyDisplayMode(display_id_));
const gfx::Size source_size = mode ? gfx::Size(CGDisplayModeGetWidth(mode),
CGDisplayModeGetHeight(mode))
: requested_format_.frame_size;
// Compute the destination frame size using CaptureResolutionChooser.
gfx::RectF dest_rect_in_frame;
ComputeFrameSizeAndDestRect(source_size, requested_format_.frame_size,
dest_rect_in_frame);
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));
base::ScopedCFTypeRef<CFDictionaryRef> dest_rect_in_frame_dict(
CGRectCreateDictionaryRepresentation(dest_rect_in_frame.ToCGRect()));
const size_t kNumKeys = 5;
const void* keys[kNumKeys] = {
kCGDisplayStreamShowCursor, kCGDisplayStreamPreserveAspectRatio,
kCGDisplayStreamMinimumFrameTime, kCGDisplayStreamColorSpace,
kCGDisplayStreamDestinationRect,
};
const void* values[kNumKeys] = {
kCFBooleanTrue,
kCFBooleanFalse,
cf_max_frame_time.get(),
cg_color_space.get(),
dest_rect_in_frame_dict.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;
}
// Use CFRunLoopGetMain instead of CFRunLoopGetCurrent because in some
// circumstances (e.g, streaming to ChromeCast), this is called on a
// worker thread where the CFRunLoop does not get serviced.
// https://crbug.com/1185388
CFRunLoopAddSource(CFRunLoopGetMain(),
CGDisplayStreamGetRunLoopSource(display_stream_),
kCFRunLoopCommonModes);
client()->OnStarted();
}
void OnStop() override {
weak_factory_.InvalidateWeakPtrs();
if (display_stream_) {
CFRunLoopRemoveSource(CFRunLoopGetMain(),
CGDisplayStreamGetRunLoopSource(display_stream_),
kCFRunLoopCommonModes);
CGDisplayStreamStop(display_stream_);
}
display_stream_.reset();
}
private:
void OnFrame(gfx::ScopedInUseIOSurface io_surface) {
OnReceivedIOSurfaceFromStream(io_surface, requested_format_);
}
const CGDirectDisplayID display_id_;
const scoped_refptr<base::SingleThreadTaskRunner> device_task_runner_;
base::ScopedCFTypeRef<CGDisplayStreamRef> display_stream_;
media::VideoCaptureFormat requested_format_;
base::WeakPtrFactory<DesktopCaptureDeviceMac> weak_factory_;
};
} // namespace
std::unique_ptr<media::VideoCaptureDevice> CreateDesktopCaptureDeviceMac(
const DesktopMediaID& source) {
// DesktopCaptureDeviceMac supports only TYPE_SCREEN.
// https://crbug.com/1176900
if (source.type != DesktopMediaID::TYPE_SCREEN)
return nullptr;
// DesktopCaptureDeviceMac only supports a single display at a time. It
// will not stitch desktops together.
// https://crbug.com/1178360
if (source.id == webrtc::kFullDesktopScreenId ||
source.id == webrtc::kInvalidScreenId) {
return nullptr;
}
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