Start compressing captured frames as H264

This commit is contained in:
Antonio Scandurra 2022-08-31 15:49:55 +02:00
parent f621d290fe
commit 7054fa61f2
4 changed files with 195 additions and 3 deletions

View file

@ -17,8 +17,9 @@ use gpui::{
}; };
use log::LevelFilter; use log::LevelFilter;
use media::{ use media::{
core_media::{CMSampleBuffer, CMSampleBufferRef, CMTimeMake}, core_media::{kCMVideoCodecType_H264, CMSampleBuffer, CMSampleBufferRef, CMTimeMake},
core_video::{self, CVImageBuffer}, core_video::{self, CVImageBuffer},
video_toolbox::VTCompressionSession,
}; };
use objc::{ use objc::{
class, class,
@ -85,6 +86,14 @@ impl ScreenCaptureView {
let display: id = displays.objectAtIndex(0); let display: id = displays.objectAtIndex(0);
let display_width: usize = msg_send![display, width]; let display_width: usize = msg_send![display, width];
let display_height: usize = msg_send![display, height]; let display_height: usize = msg_send![display, height];
let compression_session = VTCompressionSession::new(
display_width,
display_height,
kCMVideoCodecType_H264,
None,
ptr::null(),
)
.unwrap();
let mut decl = ClassDecl::new("CaptureOutput", class!(NSObject)).unwrap(); let mut decl = ClassDecl::new("CaptureOutput", class!(NSObject)).unwrap();
decl.add_ivar::<*mut c_void>("callback"); decl.add_ivar::<*mut c_void>("callback");
@ -114,7 +123,15 @@ impl ScreenCaptureView {
return; return;
} }
let timing_info = buffer.sample_timing_info(0).unwrap();
let image_buffer = buffer.image_buffer(); let image_buffer = buffer.image_buffer();
compression_session
.encode_frame(
image_buffer.as_concrete_TypeRef(),
timing_info.presentationTimeStamp,
timing_info.duration,
)
.unwrap();
*surface_tx.lock().borrow_mut() = Some(image_buffer); *surface_tx.lock().borrow_mut() = Some(image_buffer);
}) as Box<dyn FnMut(CMSampleBufferRef)>; }) as Box<dyn FnMut(CMSampleBufferRef)>;
let callback = Box::into_raw(Box::new(callback)); let callback = Box::into_raw(Box::new(callback));

View file

@ -16,9 +16,16 @@ fn main() {
.header("src/bindings.h") .header("src/bindings.h")
.clang_arg(format!("-isysroot{}", sdk_path)) .clang_arg(format!("-isysroot{}", sdk_path))
.clang_arg("-xobjective-c") .clang_arg("-xobjective-c")
.allowlist_type("CMItemIndex")
.allowlist_type("CMSampleTimingInfo")
.allowlist_type("CMVideoCodecType")
.allowlist_type("VTEncodeInfoFlags")
.allowlist_function("CMTimeMake") .allowlist_function("CMTimeMake")
.allowlist_var("kCVPixelFormatType_.*") .allowlist_var("kCVPixelFormatType_.*")
.allowlist_var("kCVReturn.*") .allowlist_var("kCVReturn.*")
.allowlist_var("VTEncodeInfoFlags_.*")
.allowlist_var("kCMVideoCodecType_.*")
.allowlist_var("kCMTime.*")
.parse_callbacks(Box::new(bindgen::CargoCallbacks)) .parse_callbacks(Box::new(bindgen::CargoCallbacks))
.layout_tests(false) .layout_tests(false)
.generate() .generate()

View file

@ -1,3 +1,5 @@
#import <CoreMedia/CMFormatDescription.h>
#import <CoreMedia/CMSampleBuffer.h> #import <CoreMedia/CMSampleBuffer.h>
#import <CoreVideo/CVPixelFormatDescription.h> #import <CoreVideo/CVPixelFormatDescription.h>
#import <CoreVideo/CVReturn.h> #import <CoreVideo/CVReturn.h>
#import <VideoToolbox/VTCompressionSession.h>

View file

@ -194,11 +194,15 @@ pub mod core_video {
pub mod core_media { pub mod core_media {
#![allow(non_snake_case)] #![allow(non_snake_case)]
pub use crate::bindings::CMTimeMake; pub use crate::bindings::{
kCMTimeInvalid, kCMVideoCodecType_H264, CMItemIndex, CMSampleTimingInfo, CMTime,
CMTimeMake, CMVideoCodecType,
};
use crate::core_video::{CVImageBuffer, CVImageBufferRef}; use crate::core_video::{CVImageBuffer, CVImageBufferRef};
use anyhow::{anyhow, Result};
use core_foundation::{ use core_foundation::{
array::{CFArray, CFArrayRef}, array::{CFArray, CFArrayRef},
base::{CFTypeID, TCFType}, base::{CFTypeID, OSStatus, TCFType},
declare_TCFType, declare_TCFType,
dictionary::CFDictionary, dictionary::CFDictionary,
impl_CFTypeDescription, impl_TCFType, impl_CFTypeDescription, impl_TCFType,
@ -236,6 +240,27 @@ pub mod core_media {
)) ))
} }
} }
pub fn sample_timing_info(&self, index: usize) -> Result<CMSampleTimingInfo> {
unsafe {
let mut timing_info = CMSampleTimingInfo {
duration: kCMTimeInvalid,
presentationTimeStamp: kCMTimeInvalid,
decodeTimeStamp: kCMTimeInvalid,
};
let result = CMSampleBufferGetSampleTimingInfo(
self.as_concrete_TypeRef(),
index as CMItemIndex,
&mut timing_info,
);
if result == 0 {
Ok(timing_info)
} else {
Err(anyhow!("error getting sample timing info, code {}", result))
}
}
}
} }
#[link(name = "CoreMedia", kind = "framework")] #[link(name = "CoreMedia", kind = "framework")]
@ -246,5 +271,146 @@ pub mod core_media {
create_if_necessary: bool, create_if_necessary: bool,
) -> CFArrayRef; ) -> CFArrayRef;
fn CMSampleBufferGetImageBuffer(buffer: CMSampleBufferRef) -> CVImageBufferRef; fn CMSampleBufferGetImageBuffer(buffer: CMSampleBufferRef) -> CVImageBufferRef;
fn CMSampleBufferGetSampleTimingInfo(
buffer: CMSampleBufferRef,
index: CMItemIndex,
timing_info_out: *mut CMSampleTimingInfo,
) -> OSStatus;
}
}
pub mod video_toolbox {
#![allow(non_snake_case)]
use super::*;
use crate::{
core_media::{CMSampleBufferRef, CMTime, CMVideoCodecType},
core_video::CVImageBufferRef,
};
use anyhow::{anyhow, Result};
use bindings::VTEncodeInfoFlags;
use core_foundation::{
base::OSStatus,
dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
mach_port::CFAllocatorRef,
};
use std::ptr;
#[repr(C)]
pub struct __VTCompressionSession(c_void);
// The ref type must be a pointer to the underlying struct.
pub type VTCompressionSessionRef = *const __VTCompressionSession;
declare_TCFType!(VTCompressionSession, VTCompressionSessionRef);
impl_TCFType!(
VTCompressionSession,
VTCompressionSessionRef,
VTCompressionSessionGetTypeID
);
impl_CFTypeDescription!(VTCompressionSession);
impl VTCompressionSession {
pub fn new(
width: usize,
height: usize,
codec: CMVideoCodecType,
callback: VTCompressionOutputCallback,
callback_data: *const c_void,
) -> Result<Self> {
unsafe {
let mut this = ptr::null();
let result = VTCompressionSessionCreate(
ptr::null(),
width as i32,
height as i32,
codec,
ptr::null(),
ptr::null(),
ptr::null(),
Some(Self::output),
callback_data,
&mut this,
);
if result == 0 {
Ok(Self::wrap_under_create_rule(this))
} else {
Err(anyhow!(
"error creating compression session, code {}",
result
))
}
}
}
extern "C" fn output(
outputCallbackRefCon: *mut c_void,
sourceFrameRefCon: *mut c_void,
status: OSStatus,
infoFlags: VTEncodeInfoFlags,
sampleBuffer: CMSampleBufferRef,
) {
println!("YO!");
}
pub fn encode_frame(
&self,
buffer: CVImageBufferRef,
presentation_timestamp: CMTime,
duration: CMTime,
) -> Result<()> {
unsafe {
let result = VTCompressionSessionEncodeFrame(
self.as_concrete_TypeRef(),
buffer,
presentation_timestamp,
duration,
ptr::null(),
ptr::null(),
ptr::null_mut(),
);
if result == 0 {
Ok(())
} else {
Err(anyhow!("error encoding frame, code {}", result))
}
}
}
}
type VTCompressionOutputCallback = Option<
unsafe extern "C" fn(
outputCallbackRefCon: *mut c_void,
sourceFrameRefCon: *mut c_void,
status: OSStatus,
infoFlags: VTEncodeInfoFlags,
sampleBuffer: CMSampleBufferRef,
),
>;
#[link(name = "VideoToolbox", kind = "framework")]
extern "C" {
fn VTCompressionSessionGetTypeID() -> CFTypeID;
fn VTCompressionSessionCreate(
allocator: CFAllocatorRef,
width: i32,
height: i32,
codec_type: CMVideoCodecType,
encoder_specification: CFDictionaryRef,
source_image_buffer_attributes: CFDictionaryRef,
compressed_data_allocator: CFAllocatorRef,
output_callback: VTCompressionOutputCallback,
output_callback_ref_con: *const c_void,
compression_session_out: *mut VTCompressionSessionRef,
) -> OSStatus;
fn VTCompressionSessionEncodeFrame(
session: VTCompressionSessionRef,
image_buffer: CVImageBufferRef,
presentation_timestamp: CMTime,
duration: CMTime,
frame_properties: CFDictionaryRef,
source_frame_ref_con: *const c_void,
output_flags: *mut VTEncodeInfoFlags,
) -> OSStatus;
} }
} }