[feat] optimize macOS native video encoding

This commit is contained in:
dijunkun
2026-09-04 02:06:44 +08:00
parent cd1b53ee65
commit db3b26efe7
13 changed files with 148 additions and 93 deletions
@@ -42,6 +42,65 @@ static std::string NSErrorToString(NSError *error) {
return description ? description : "";
}
static void RetainCapturedPixelBuffer(void *owner) {
if (owner) {
CVPixelBufferRetain(static_cast<CVPixelBufferRef>(owner));
}
}
static void ReleaseCapturedPixelBuffer(void *owner) {
if (owner) {
CVPixelBufferRelease(static_cast<CVPixelBufferRef>(owner));
}
}
static int CopyCapturedPixelBufferToNv12(void *owner, uint8_t *destination,
size_t destination_size) {
auto pixel_buffer = static_cast<CVPixelBufferRef>(owner);
if (!pixel_buffer || !destination ||
!CVPixelBufferIsPlanar(pixel_buffer) ||
CVPixelBufferGetPlaneCount(pixel_buffer) < 2) {
return -1;
}
const OSType pixel_format = CVPixelBufferGetPixelFormatType(pixel_buffer);
if (pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange &&
pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
return -1;
}
const size_t width = CVPixelBufferGetWidth(pixel_buffer);
const size_t height = CVPixelBufferGetHeight(pixel_buffer);
const size_t required_size = width * height * 3U / 2U;
if (destination_size < required_size ||
CVPixelBufferLockBaseAddress(pixel_buffer,
kCVPixelBufferLock_ReadOnly) !=
kCVReturnSuccess) {
return -1;
}
const auto *y_plane = static_cast<const uint8_t *>(
CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 0));
const auto *uv_plane = static_cast<const uint8_t *>(
CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 1));
const size_t y_stride = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 0);
const size_t uv_stride = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 1);
int result = 0;
if (!y_plane || !uv_plane || y_stride < width || uv_stride < width) {
result = -1;
} else {
for (size_t row = 0; row < height; ++row) {
std::memcpy(destination + row * width, y_plane + row * y_stride, width);
}
uint8_t *uv_destination = destination + width * height;
for (size_t row = 0; row < height / 2U; ++row) {
std::memcpy(uv_destination + row * width,
uv_plane + row * uv_stride, width);
}
}
CVPixelBufferUnlockBaseAddress(pixel_buffer, kCVPixelBufferLock_ReadOnly);
return result;
}
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
@@ -92,14 +151,11 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
private:
std::vector<DisplayInfo> display_info_list_;
std::map<int, CGDirectDisplayID> display_id_map_;
unsigned char *nv12_frame_ = nullptr;
size_t nv12_frame_size_ = 0;
int width_ = 0;
int height_ = 0;
int fps_ = 60;
bool show_cursor_ = false;
bool capture_requested_ = false;
bool invalid_stream_id_logged_ = false;
bool native_output_logged_ = false;
public:
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
@@ -119,8 +175,8 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
SckHelper *__strong helper_ = nil;
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's
// thread.
// Callback for returning captured frames to the caller. ScreenCaptureKit may
// invoke it on its sample-handler queue.
cb_desktop_data _on_data = nullptr;
// Signals that a permanent error occurred. This may be set on any thread, and is read by
// CaptureFrame() which runs on the caller's thread.
@@ -244,12 +300,6 @@ ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
display_info_list_.clear();
display_id_map_.clear();
if (nv12_frame_) {
delete[] nv12_frame_;
nv12_frame_ = nullptr;
nv12_frame_size_ = 0;
}
}
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
@@ -262,6 +312,7 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
fps_ = fps > 0 ? fps : 60;
display_info_list_.clear();
display_id_map_.clear();
native_output_logged_ = false;
if (@available(macOS 10.15, *)) {
bool has_permission = CGPreflightScreenCaptureAccess();
@@ -596,81 +647,69 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
return;
}
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
if (width == 0 || height == 0 || CVPixelBufferGetPlaneCount(pixelBuffer) < 2) {
LOG_ERROR("Invalid CVPixelBuffer: width={}, height={}, planes={}", width, height,
const size_t width = CVPixelBufferGetWidth(pixelBuffer);
const size_t height = CVPixelBufferGetHeight(pixelBuffer);
const OSType pixel_format = CVPixelBufferGetPixelFormatType(pixelBuffer);
if (width == 0 || height == 0 || (width & 1U) != 0 ||
(height & 1U) != 0 || !CVPixelBufferIsPlanar(pixelBuffer) ||
CVPixelBufferGetPlaneCount(pixelBuffer) < 2 ||
(pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange &&
pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)) {
LOG_ERROR("Invalid CVPixelBuffer: width={}, height={}, format={}, planes={}",
width, height, pixel_format,
CVPixelBufferGetPlaneCount(pixelBuffer));
return;
}
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
if (status != kCVReturnSuccess) {
LOG_ERROR("Failed to lock CVPixelBuffer base address: %d", status);
return;
}
size_t required_size = width * height * 3 / 2;
const size_t required_size = width * height * 3U / 2U;
if (required_size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
LOG_ERROR("Captured frame is too large: {} bytes", required_size);
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
return;
}
std::lock_guard<std::mutex> lock(lock_);
if (!_on_data || !capture_requested_) {
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
return;
}
if (!nv12_frame_ || nv12_frame_size_ < required_size) {
delete[] nv12_frame_;
nv12_frame_ = new unsigned char[required_size];
nv12_frame_size_ = required_size;
}
width_ = static_cast<int>(width);
height_ = static_cast<int>(height);
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
if (!base_y || !base_uv || stride_y < width || stride_uv < width) {
LOG_ERROR("Invalid CVPixelBuffer planes: base_y={}, base_uv={}, stride_y={}, stride_uv={}",
base_y != nullptr, base_uv != nullptr, stride_y, stride_uv);
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
return;
}
unsigned char *dst_y = nv12_frame_;
for (size_t row = 0; row < height; ++row) {
memcpy(dst_y + row * width, static_cast<unsigned char *>(base_y) + row * stride_y, width);
}
unsigned char *dst_uv = nv12_frame_ + width * height;
for (size_t row = 0; row < height / 2; ++row) {
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
}
const std::string stream_id = ResolveDisplayStreamId(
current_stream_id_.c_str(), display_info_list_.size(),
current_monitor_index_);
if (stream_id.empty()) {
if (!invalid_stream_id_logged_) {
LOG_ERROR("Dropping macOS frames without a registered stream id, "
"display_id={}",
current_display_);
invalid_stream_id_logged_ = true;
cb_desktop_data callback;
std::string stream_id;
bool log_native_output = false;
{
std::lock_guard<std::mutex> lock(lock_);
if (!_on_data || !capture_requested_) {
return;
}
stream_id = ResolveDisplayStreamId(current_stream_id_.c_str(),
display_info_list_.size(),
current_monitor_index_);
if (stream_id.empty()) {
if (!invalid_stream_id_logged_) {
LOG_ERROR("Dropping macOS frames without a registered stream id, "
"display_id={}",
current_display_);
invalid_stream_id_logged_ = true;
}
return;
}
invalid_stream_id_logged_ = false;
callback = _on_data;
if (!native_output_logged_) {
native_output_logged_ = true;
log_native_output = true;
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
return;
}
invalid_stream_id_logged_ = false;
_on_data(nv12_frame_, static_cast<int>(required_size), static_cast<int>(width),
static_cast<int>(height), stream_id.c_str());
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
XNativeVideoFrame native_frame{};
native_frame.struct_size = sizeof(native_frame);
native_frame.type = XNativeVideoFrameCVPixelBuffer;
native_frame.width = static_cast<uint32_t>(width);
native_frame.height = static_cast<uint32_t>(height);
native_frame.payload.cv_pixel_buffer = pixelBuffer;
native_frame.owner = pixelBuffer;
native_frame.retain = &RetainCapturedPixelBuffer;
native_frame.release = &ReleaseCapturedPixelBuffer;
native_frame.copy_to_nv12 = &CopyCapturedPixelBufferToNv12;
if (log_native_output) {
LOG_INFO("macOS ScreenCaptureKit native CVPixelBuffer output enabled");
}
callback(nullptr, static_cast<int>(required_size), static_cast<int>(width),
static_cast<int>(height), stream_id.c_str(), &native_frame);
}
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {