Start compressing captured frames as H264
This commit is contained in:
parent
f621d290fe
commit
7054fa61f2
4 changed files with 195 additions and 3 deletions
|
@ -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));
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue