diff --git a/Cargo.lock b/Cargo.lock index 172b5dc004..0c13be8493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7533,6 +7533,8 @@ dependencies = [ "lyon", "num_cpus", "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-video", "objc2-metal 0.3.2", "parking", "parking_lot", @@ -7651,7 +7653,6 @@ dependencies = [ "accesskit_macos", "anyhow", "async-task", - "block", "block2 0.6.2", "cocoa 0.26.0", "collections", @@ -7671,11 +7672,11 @@ dependencies = [ "libc", "log", "mach2 0.5.0", - "media", - "metal", "objc", "objc2 0.6.3", "objc2-app-kit 0.3.2", + "objc2-core-graphics", + "objc2-core-media", "objc2-foundation 0.3.2", "objc2-screen-capture-kit", "objc2-user-notifications", @@ -10306,6 +10307,7 @@ dependencies = [ "livekit_api", "log", "nanoid", + "objc2-core-foundation", "parking_lot", "postage", "rodio", @@ -10863,19 +10865,6 @@ dependencies = [ "warp", ] -[[package]] -name = "media" -version = "0.1.0" -dependencies = [ - "anyhow", - "bindgen", - "core-foundation 0.10.0", - "core-video", - "foreign-types 0.5.0", - "metal", - "objc", -] - [[package]] name = "memchr" version = "2.8.3" @@ -12048,6 +12037,21 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2 0.6.3", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", +] + [[package]] name = "objc2-core-services" version = "0.3.2" @@ -12058,6 +12062,19 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -12180,8 +12197,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394" dependencies = [ "block2 0.6.2", + "dispatch2", "objc2 0.6.3", "objc2-core-graphics", + "objc2-core-media", "objc2-foundation 0.3.2", ] diff --git a/Cargo.toml b/Cargo.toml index e9d58f7560..c47feca207 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,7 +136,6 @@ members = [ "crates/lsp_locations", "crates/markdown", "crates/markdown_preview", - "crates/media", "crates/menu", "crates/mermaid_render", "crates/migrator", @@ -405,7 +404,6 @@ lsp_command_selector = { path = "crates/lsp_command_selector" } lsp_locations = { path = "crates/lsp_locations" } markdown = { path = "crates/markdown" } markdown_preview = { path = "crates/markdown_preview" } -media = { path = "crates/media" } menu = { path = "crates/menu" } mermaid_render = { path = "crates/mermaid_render" } migrator = { path = "crates/migrator" } @@ -717,6 +715,10 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [ "block2", "objc2-core-foundation", ] } +objc2-core-foundation = { version = "=0.3.2", default-features = false } +objc2-core-graphics = { version = "=0.3.2", default-features = false, features = ["CGDirectDisplay"] } +objc2-core-media = { version = "=0.3.2", default-features = false, features = ["CMSampleBuffer", "objc2-core-video"] } +objc2-core-video = { version = "=0.3.2", default-features = false, features = ["CVBuffer", "CVImageBuffer"] } objc2-foundation = { version = "=0.3.2", default-features = false, features = [ "NSArray", "NSAttributedString", @@ -745,8 +747,11 @@ objc2-foundation = { version = "=0.3.2", default-features = false, features = [ ] } objc2-screen-capture-kit = { version = "=0.3.2", default-features = false, features = [ "block2", + "dispatch2", "objc2-core-graphics", + "objc2-core-media", "SCShareableContent", + "SCStream", "std" ] } objc2-user-notifications = "0.3" diff --git a/corgi.toml b/corgi.toml index ed25f62776..b2050713d2 100644 --- a/corgi.toml +++ b/corgi.toml @@ -63,7 +63,6 @@ profiles = ["release"] # Own non-.rs files read at build time (protobufs, bindgen headers, doc/data). proto = ["proto/**"] livekit_api = ["vendored/protocol/**"] -media = ["src/bindings.h"] gpui = [ "README.md", "examples/image/exif-orientation-rotate-180.jpg", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index b07cab6a60..7ab3121d08 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -120,6 +120,8 @@ uuid = { workspace = true, features = ["js"] } [target.'cfg(target_os = "macos")'.dependencies] core-video.workspace = true +objc2-core-foundation.workspace = true +objc2-core-video.workspace = true # WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "94b0f28166665e8fd2f53ff6d268a14955c82269", package = "zed-font-kit", version = "0.14.1-zed", optional = true } log.workspace = true diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index f3d3c05499..ccf97a29f9 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -32,7 +32,8 @@ pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame; #[cfg(not(feature = "screen-capture"))] pub(crate) type PlatformScreenCaptureFrame = (); #[cfg(all(target_os = "macos", feature = "screen-capture"))] -pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer; +pub(crate) type PlatformScreenCaptureFrame = + objc2_core_foundation::CFRetained; use crate::{ Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds, diff --git a/crates/gpui_macos/Cargo.toml b/crates/gpui_macos/Cargo.toml index a628f3579b..009bf6f0a0 100644 --- a/crates/gpui_macos/Cargo.toml +++ b/crates/gpui_macos/Cargo.toml @@ -27,7 +27,6 @@ accesskit.workspace = true accesskit_macos.workspace = true anyhow.workspace = true async-task.workspace = true -block.workspace = true block2.workspace = true cocoa.workspace = true collections.workspace = true @@ -48,11 +47,11 @@ itertools.workspace = true libc.workspace = true log.workspace = true mach2.workspace = true -media.workspace = true -metal.workspace = true objc.workspace = true objc2.workspace = true objc2-app-kit.workspace = true +objc2-core-graphics.workspace = true +objc2-core-media.workspace = true objc2-foundation.workspace = true objc2-screen-capture-kit.workspace = true objc2-user-notifications.workspace = true diff --git a/crates/gpui_macos/src/screen_capture.rs b/crates/gpui_macos/src/screen_capture.rs index 076731efb9..6545d473e7 100644 --- a/crates/gpui_macos/src/screen_capture.rs +++ b/crates/gpui_macos/src/screen_capture.rs @@ -1,36 +1,25 @@ use anyhow::{Result, anyhow}; -use block::ConcreteBlock; use block2::RcBlock; -use cocoa::{ - base::{id, nil}, - foundation::NSArray, -}; use collections::HashMap; -use core_foundation::base::TCFType; -use core_graphics::display::{ - CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayModeGetPixelHeight, - CGDisplayModeGetPixelWidth, CGDisplayModeRelease, -}; -use ctor::ctor; use futures::channel::oneshot; use gpui::{ DevicePixels, ForegroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SharedString, SourceMetadata, size, }; -use media::core_media::{CMSampleBuffer, CMSampleBufferRef}; -use metal::NSInteger; -use objc::{ - class, - declare::ClassDecl, - msg_send, - runtime::{Class, Object, Sel}, - sel, sel_impl, +use objc2::{ + AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send, + rc::Retained, + runtime::{NSObject, ProtocolObject}, }; -use objc2::{MainThreadMarker, rc::Retained}; use objc2_app_kit::NSScreen; -use objc2_foundation::{NSError, NSNumber, NSString}; -use objc2_screen_capture_kit::{SCDisplay, SCShareableContent}; -use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc}; +use objc2_core_graphics::{CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayMode}; +use objc2_core_media::CMSampleBuffer; +use objc2_foundation::{NSArray, NSError, NSNumber, NSObjectProtocol, NSString}; +use objc2_screen_capture_kit::{ + SCContentFilter, SCDisplay, SCShareableContent, SCStream, SCStreamConfiguration, + SCStreamOutput, SCStreamOutputType, SCWindow, +}; +use std::{cell::RefCell, rc::Rc}; #[derive(Clone)] pub struct MacScreenCaptureSource { @@ -39,49 +28,36 @@ pub struct MacScreenCaptureSource { } pub struct MacScreenCaptureStream { - sc_stream: id, - sc_stream_output: id, + sc_stream: Retained, + sc_stream_output: Retained, meta: SourceMetadata, } -static mut DELEGATE_CLASS: *const Class = ptr::null(); -static mut OUTPUT_CLASS: *const Class = ptr::null(); -const FRAME_CALLBACK_IVAR: &str = "frame_callback"; - -#[allow(non_upper_case_globals)] -const SCStreamOutputTypeScreen: NSInteger = 0; - -impl ScreenCaptureSource for MacScreenCaptureSource { - fn metadata(&self) -> Result { - let (display_id, size) = unsafe { - // SAFETY: `SCDisplay` is an Objective-C object, so objc2's object pointer has the - // same ABI as objc 0.2's `id`. This only casts a borrowed pointer; it does not - // transfer ownership or change the retain count. `self.sc_display` remains alive - // for the entire message send, and `displayID` does not retain the receiver. - let sc_display: id = Retained::as_ptr(&self.sc_display).cast_mut().cast(); - let display_id: CGDirectDisplayID = msg_send![sc_display, displayID]; - let display_mode_ref = CGDisplayCopyDisplayMode(display_id); - let width = CGDisplayModeGetPixelWidth(display_mode_ref); - let height = CGDisplayModeGetPixelHeight(display_mode_ref); - CGDisplayModeRelease(display_mode_ref); - - ( - display_id, - size(DevicePixels(width as i32), DevicePixels(height as i32)), - ) - }; +impl MacScreenCaptureSource { + fn metadata(&self) -> SourceMetadata { + let display_id = unsafe { self.sc_display.displayID() }; + let display_mode = CGDisplayCopyDisplayMode(display_id); + let width = CGDisplayMode::pixel_width(display_mode.as_deref()); + let height = CGDisplayMode::pixel_height(display_mode.as_deref()); + let size = size(DevicePixels(width as i32), DevicePixels(height as i32)); let (label, is_main) = self .meta .clone() .map(|meta| (meta.label, meta.is_main)) .unzip(); - Ok(SourceMetadata { + SourceMetadata { id: display_id as u64, label, is_main, resolution: size, - }) + } + } +} + +impl ScreenCaptureSource for MacScreenCaptureSource { + fn metadata(&self) -> Result { + Ok(self.metadata()) } fn stream( @@ -89,86 +65,68 @@ impl ScreenCaptureSource for MacScreenCaptureSource { _foreground_executor: &ForegroundExecutor, frame_callback: Box, ) -> oneshot::Receiver>> { + let (sender, receiver) = oneshot::channel(); + let metadata = self.metadata(); + unsafe { - // SAFETY: `SCDisplay` is an Objective-C object, so objc2's object pointer has the - // same ABI as objc 0.2's `id`. The cast removes pointer constness only because the - // legacy `id` alias is mutable; it does not mutate the display, transfer ownership, - // or change the retain count. The generated objc2 binding takes `display` as - // `&SCDisplay`, confirming that this initializer borrows rather than consumes it. - // `self.sc_display` remains alive through the call, and `SCContentFilter` must retain - // the display or copy any state that it needs after initialization. - let sc_display: id = Retained::as_ptr(&self.sc_display).cast_mut().cast(); - let stream: id = msg_send![class!(SCStream), alloc]; - let filter: id = msg_send![class!(SCContentFilter), alloc]; - let configuration: id = msg_send![class!(SCStreamConfiguration), alloc]; - let delegate: id = msg_send![DELEGATE_CLASS, alloc]; - let output: id = msg_send![OUTPUT_CLASS, alloc]; - - let excluded_windows = NSArray::array(nil); - let filter: id = - msg_send![filter, initWithDisplay:sc_display excludingWindows:excluded_windows]; - let configuration: id = msg_send![configuration, init]; - let _: id = msg_send![configuration, setScalesToFit: true]; - let _: id = msg_send![configuration, setPixelFormat: 0x42475241]; - // let _: id = msg_send![configuration, setShowsCursor: false]; - // let _: id = msg_send![configuration, setCaptureResolution: 3]; - let delegate: id = msg_send![delegate, init]; - let output: id = msg_send![output, init]; - - output.as_mut().unwrap().set_ivar( - FRAME_CALLBACK_IVAR, - Box::into_raw(Box::new(frame_callback)) as *mut c_void, + let excluded_windows = NSArray::::new(); + let filter = SCContentFilter::initWithDisplay_excludingWindows( + SCContentFilter::alloc(), + &self.sc_display, + &excluded_windows, ); + let configuration = SCStreamConfiguration::new(); + configuration.setScalesToFit(true); + configuration.setPixelFormat(0x42475241); + configuration.setWidth(metadata.resolution.width.0 as usize); + configuration.setHeight(metadata.resolution.height.0 as usize); - let meta = self.metadata().unwrap(); - let _: id = msg_send![configuration, setWidth: meta.resolution.width.0 as i64]; - let _: id = msg_send![configuration, setHeight: meta.resolution.height.0 as i64]; - let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate]; + let stream = SCStream::initWithFilter_configuration_delegate( + SCStream::alloc(), + &filter, + &configuration, + None, + ); + let output = StreamOutput::new(frame_callback); + let output_protocol = ProtocolObject::from_ref(&*output); - // Stream contains filter, configuration, and delegate internally so we release them here - // to prevent a memory leak when steam is dropped - let _: () = msg_send![filter, release]; - let _: () = msg_send![configuration, release]; - let _: () = msg_send![delegate, release]; + if let Err(error) = stream.addStreamOutput_type_sampleHandlerQueue_error( + output_protocol, + SCStreamOutputType::Screen, + None, + ) { + _ = sender.send(Err(anyhow!( + "failed to add stream output: {}", + error.localizedDescription() + ))); - let (tx, rx) = oneshot::channel(); - - let mut error: id = nil; - let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id]; - if error != nil { - let message: id = msg_send![error, localizedDescription]; - let _: () = msg_send![stream, release]; - let _: () = msg_send![output, release]; - tx.send(Err(anyhow!("failed to add stream output {message:?}"))) - .ok(); - return rx; + return receiver; } - let tx = Rc::new(RefCell::new(Some(tx))); - let handler = ConcreteBlock::new({ - move |error: id| { - let result = if error == nil { - let stream = MacScreenCaptureStream { - meta: meta.clone(), - sc_stream: stream, - sc_stream_output: output, - }; - Ok(Box::new(stream) as Box) - } else { - let _: () = msg_send![stream, release]; - let _: () = msg_send![output, release]; - let message: id = msg_send![error, localizedDescription]; - Err(anyhow!("failed to start screen capture stream {message:?}")) - }; - if let Some(tx) = tx.borrow_mut().take() { - tx.send(result).ok(); - } - } + let state = Rc::new(RefCell::new(Some((sender, stream.clone(), output)))); + let handler = RcBlock::new(move |error: *mut NSError| { + let Some((sender, stream, output)) = state.borrow_mut().take() else { + return; + }; + let result = if let Some(error) = error.as_ref() { + Err(anyhow!( + "failed to start screen capture stream: {}", + error.localizedDescription() + )) + } else { + Ok(Box::new(MacScreenCaptureStream { + meta: metadata.clone(), + sc_stream: stream, + sc_stream_output: output, + }) as Box) + }; + + _ = sender.send(result); }); - let handler = handler.copy(); - let _: () = msg_send![stream, startCaptureWithCompletionHandler:handler]; - rx + stream.startCaptureWithCompletionHandler(Some(&handler)); } + + receiver } } @@ -181,23 +139,27 @@ impl ScreenCaptureStream for MacScreenCaptureStream { impl Drop for MacScreenCaptureStream { fn drop(&mut self) { unsafe { - let mut error: id = nil; - let _: () = msg_send![self.sc_stream, removeStreamOutput:self.sc_stream_output type:SCStreamOutputTypeScreen error:&mut error as *mut _]; - if error != nil { - let message: id = msg_send![error, localizedDescription]; - log::error!("failed to add stream output {message:?}"); + let output = ProtocolObject::from_ref(&*self.sc_stream_output); + if let Err(error) = self + .sc_stream + .removeStreamOutput_type_error(output, SCStreamOutputType::Screen) + { + log::error!( + "failed to remove screen capture stream output: {}", + error.localizedDescription() + ); } - let handler = ConcreteBlock::new(move |error: id| { - if error != nil { - let message: id = msg_send![error, localizedDescription]; - log::error!("failed to stop screen capture stream {message:?}"); + let handler = RcBlock::new(move |error: *mut NSError| { + if let Some(error) = error.as_ref() { + log::error!( + "failed to stop screen capture stream: {}", + error.localizedDescription() + ); } }); - let block = handler.copy(); - let _: () = msg_send![self.sc_stream, stopCaptureWithCompletionHandler:block]; - let _: () = msg_send![self.sc_stream, release]; - let _: () = msg_send![self.sc_stream_output, release]; + self.sc_stream + .stopCaptureWithCompletionHandler(Some(&handler)); } } } @@ -288,61 +250,42 @@ pub(crate) fn get_sources( rx } -#[ctor(unsafe)] -unsafe fn build_classes() { - let mut decl = ClassDecl::new("GPUIStreamDelegate", class!(NSObject)).unwrap(); - unsafe { - decl.add_method( - sel!(outputVideoEffectDidStartForStream:), - output_video_effect_did_start_for_stream as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(outputVideoEffectDidStopForStream:), - output_video_effect_did_stop_for_stream as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(stream:didStopWithError:), - stream_did_stop_with_error as extern "C" fn(&Object, Sel, id, id), - ); - DELEGATE_CLASS = decl.register(); - - let mut decl = ClassDecl::new("GPUIStreamOutput", class!(NSObject)).unwrap(); - decl.add_method( - sel!(stream:didOutputSampleBuffer:ofType:), - stream_did_output_sample_buffer_of_type - as extern "C" fn(&Object, Sel, id, id, NSInteger), - ); - decl.add_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR); - - OUTPUT_CLASS = decl.register(); - } +struct StreamOutputIvars { + frame_callback: Box, } -extern "C" fn output_video_effect_did_start_for_stream(_this: &Object, _: Sel, _stream: id) {} +define_class!( + // SAFETY: `NSObject` has no subclassing requirements, and `StreamOutput` does not + // implement `Drop`; `define_class!` manages destruction of its ivars. + #[unsafe(super(NSObject))] + #[ivars = StreamOutputIvars] + struct StreamOutput; -extern "C" fn output_video_effect_did_stop_for_stream(_this: &Object, _: Sel, _stream: id) {} + unsafe impl NSObjectProtocol for StreamOutput {} -extern "C" fn stream_did_stop_with_error(_this: &Object, _: Sel, _stream: id, _error: id) {} + unsafe impl SCStreamOutput for StreamOutput { + #[unsafe(method(stream:didOutputSampleBuffer:ofType:))] + fn stream_did_output_sample_buffer_of_type( + &self, + _stream: &SCStream, + sample_buffer: &CMSampleBuffer, + buffer_type: SCStreamOutputType, + ) { + if buffer_type != SCStreamOutputType::Screen { + return; + } -extern "C" fn stream_did_output_sample_buffer_of_type( - this: &Object, - _: Sel, - _stream: id, - sample_buffer: id, - buffer_type: NSInteger, -) { - if buffer_type != SCStreamOutputTypeScreen { - return; - } - - unsafe { - let sample_buffer = sample_buffer as CMSampleBufferRef; - let sample_buffer = CMSampleBuffer::wrap_under_get_rule(sample_buffer); - if let Some(buffer) = sample_buffer.image_buffer() { - let callback: Box> = - Box::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _); - callback(ScreenCaptureFrame(buffer)); - mem::forget(callback); + let Some(image_buffer) = (unsafe { sample_buffer.image_buffer() }) else { + return; + }; + (self.ivars().frame_callback)(ScreenCaptureFrame(image_buffer)); } } +); + +impl StreamOutput { + fn new(frame_callback: Box) -> Retained { + let this = Self::alloc().set_ivars(StreamOutputIvars { frame_callback }); + unsafe { msg_send![super(this), init] } + } } diff --git a/crates/livekit_client/Cargo.toml b/crates/livekit_client/Cargo.toml index 3aa30a7e85..72d717730a 100644 --- a/crates/livekit_client/Cargo.toml +++ b/crates/livekit_client/Cargo.toml @@ -57,6 +57,7 @@ scap.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true core-video.workspace = true +objc2-core-foundation.workspace = true coreaudio-rs = "0.12.1" [dev-dependencies] diff --git a/crates/livekit_client/src/livekit_client/playback.rs b/crates/livekit_client/src/livekit_client/playback.rs index 1febcd4e86..069995cd0b 100644 --- a/crates/livekit_client/src/livekit_client/playback.rs +++ b/crates/livekit_client/src/livekit_client/playback.rs @@ -753,11 +753,15 @@ fn video_frame_buffer_from_webrtc(buffer: Box) -> Option Option> { use livekit::webrtc; + use objc2_core_foundation::CFRetained; - let pixel_buffer = frame.0.as_concrete_TypeRef(); - std::mem::forget(frame.0); + let pixel_buffer = CFRetained::into_raw(frame.0); unsafe { - Some(webrtc::video_frame::native::NativeBuffer::from_cv_pixel_buffer(pixel_buffer as _)) + Some( + webrtc::video_frame::native::NativeBuffer::from_cv_pixel_buffer( + pixel_buffer.as_ptr().cast(), + ), + ) } } diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml deleted file mode 100644 index 908dd86368..0000000000 --- a/crates/media/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "media" -version = "0.1.0" -edition.workspace = true -publish = false -license = "Apache-2.0" -description = "Bindings to macos media handling APIs for Zed" - -[lints] -workspace = true - -[lib] -path = "src/media.rs" -doctest = false - -[dependencies] -anyhow.workspace = true - -[target.'cfg(target_os = "macos")'.dependencies] -core-foundation.workspace = true -foreign-types.workspace = true -metal.workspace = true -core-video.workspace = true -objc.workspace = true - -[build-dependencies] -bindgen = "0.72" diff --git a/crates/media/LICENSE-APACHE b/crates/media/LICENSE-APACHE deleted file mode 120000 index 1cd601d0a3..0000000000 --- a/crates/media/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/media/build.rs b/crates/media/build.rs deleted file mode 100644 index 090002e9e9..0000000000 --- a/crates/media/build.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")] -#[cfg(target_os = "macos")] -fn main() { - use std::{env, path::PathBuf, process::Command}; - - let sdk_path = String::from_utf8( - Command::new("xcrun") - .args(["--sdk", "macosx", "--show-sdk-path"]) - .output() - .unwrap() - .stdout, - ) - .unwrap(); - let sdk_path = sdk_path.trim_end(); - - println!("cargo:rerun-if-changed=src/bindings.h"); - let bindings = bindgen::Builder::default() - .header("src/bindings.h") - .clang_arg(format!("-isysroot{}", sdk_path)) - .clang_arg("-xobjective-c") - .allowlist_type("CMItemIndex") - .allowlist_type("CMSampleTimingInfo") - .allowlist_type("CMVideoCodecType") - .allowlist_type("VTEncodeInfoFlags") - .allowlist_function("CMTimeMake") - .allowlist_var("kCVPixelFormatType_.*") - .allowlist_var("kCVReturn.*") - .allowlist_var("VTEncodeInfoFlags_.*") - .allowlist_var("kCMVideoCodecType_.*") - .allowlist_var("kCMTime.*") - .allowlist_var("kCMSampleAttachmentKey_.*") - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .layout_tests(false) - .generate() - .expect("unable to generate bindings"); - - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("couldn't write dispatch bindings"); -} - -#[cfg(not(target_os = "macos"))] -fn main() {} diff --git a/crates/media/src/bindings.h b/crates/media/src/bindings.h deleted file mode 100644 index 4df283d0c1..0000000000 --- a/crates/media/src/bindings.h +++ /dev/null @@ -1,5 +0,0 @@ -#import -#import -#import -#import -#import diff --git a/crates/media/src/bindings.rs b/crates/media/src/bindings.rs deleted file mode 100644 index a1c78c17c4..0000000000 --- a/crates/media/src/bindings.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(unused)] - -#[cfg(target_os = "macos")] -use objc::*; - -#[cfg(target_os = "macos")] -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/crates/media/src/media.rs b/crates/media/src/media.rs deleted file mode 100644 index c42bad62e7..0000000000 --- a/crates/media/src/media.rs +++ /dev/null @@ -1,352 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] - -mod bindings; - -#[cfg(target_os = "macos")] -pub mod core_media { - #![allow(non_snake_case)] - - pub use crate::bindings::{ - CMItemIndex, CMSampleTimingInfo, CMTime, CMTimeMake, CMVideoCodecType, - kCMSampleAttachmentKey_NotSync, kCMTimeInvalid, kCMVideoCodecType_H264, - }; - use anyhow::Result; - use core_foundation::{ - array::{CFArray, CFArrayRef}, - base::{CFTypeID, OSStatus, TCFType}, - declare_TCFType, - dictionary::CFDictionary, - impl_CFTypeDescription, impl_TCFType, - string::CFString, - }; - use core_video::image_buffer::{CVImageBuffer, CVImageBufferRef}; - use std::{ffi::c_void, ptr}; - - #[repr(C)] - pub struct __CMSampleBuffer(c_void); - // The ref type must be a pointer to the underlying struct. - pub type CMSampleBufferRef = *const __CMSampleBuffer; - - declare_TCFType!(CMSampleBuffer, CMSampleBufferRef); - impl_TCFType!(CMSampleBuffer, CMSampleBufferRef, CMSampleBufferGetTypeID); - impl_CFTypeDescription!(CMSampleBuffer); - - impl CMSampleBuffer { - pub fn attachments(&self) -> Vec> { - unsafe { - let attachments = - CMSampleBufferGetSampleAttachmentsArray(self.as_concrete_TypeRef(), true); - CFArray::::wrap_under_get_rule(attachments) - .into_iter() - .map(|attachments| { - CFDictionary::wrap_under_get_rule(attachments.as_concrete_TypeRef()) - }) - .collect() - } - } - - pub fn image_buffer(&self) -> Option { - unsafe { - let ptr = CMSampleBufferGetImageBuffer(self.as_concrete_TypeRef()); - if ptr.is_null() { - None - } else { - Some(CVImageBuffer::wrap_under_get_rule(ptr)) - } - } - } - - pub fn sample_timing_info(&self, index: usize) -> Result { - 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, - ); - anyhow::ensure!( - result == 0, - "error getting sample timing info, code {result}" - ); - Ok(timing_info) - } - } - - pub fn format_description(&self) -> CMFormatDescription { - unsafe { - CMFormatDescription::wrap_under_get_rule(CMSampleBufferGetFormatDescription( - self.as_concrete_TypeRef(), - )) - } - } - - pub fn data(&self) -> CMBlockBuffer { - unsafe { - CMBlockBuffer::wrap_under_get_rule(CMSampleBufferGetDataBuffer( - self.as_concrete_TypeRef(), - )) - } - } - } - - #[link(name = "CoreMedia", kind = "framework")] - unsafe extern "C" { - fn CMSampleBufferGetTypeID() -> CFTypeID; - fn CMSampleBufferGetSampleAttachmentsArray( - buffer: CMSampleBufferRef, - create_if_necessary: bool, - ) -> CFArrayRef; - fn CMSampleBufferGetImageBuffer(buffer: CMSampleBufferRef) -> CVImageBufferRef; - fn CMSampleBufferGetSampleTimingInfo( - buffer: CMSampleBufferRef, - index: CMItemIndex, - timing_info_out: *mut CMSampleTimingInfo, - ) -> OSStatus; - fn CMSampleBufferGetFormatDescription(buffer: CMSampleBufferRef) -> CMFormatDescriptionRef; - fn CMSampleBufferGetDataBuffer(sample_buffer: CMSampleBufferRef) -> CMBlockBufferRef; - } - - #[repr(C)] - pub struct __CMFormatDescription(c_void); - pub type CMFormatDescriptionRef = *const __CMFormatDescription; - - declare_TCFType!(CMFormatDescription, CMFormatDescriptionRef); - impl_TCFType!( - CMFormatDescription, - CMFormatDescriptionRef, - CMFormatDescriptionGetTypeID - ); - impl_CFTypeDescription!(CMFormatDescription); - - impl CMFormatDescription { - pub fn h264_parameter_set_count(&self) -> usize { - unsafe { - let mut count = 0; - let result = CMVideoFormatDescriptionGetH264ParameterSetAtIndex( - self.as_concrete_TypeRef(), - 0, - ptr::null_mut(), - ptr::null_mut(), - &mut count, - ptr::null_mut(), - ); - assert_eq!(result, 0); - count - } - } - - pub fn h264_parameter_set_at_index(&self, index: usize) -> Result<&[u8]> { - unsafe { - let mut bytes = ptr::null(); - let mut len = 0; - let result = CMVideoFormatDescriptionGetH264ParameterSetAtIndex( - self.as_concrete_TypeRef(), - index, - &mut bytes, - &mut len, - ptr::null_mut(), - ptr::null_mut(), - ); - anyhow::ensure!(result == 0, "error getting parameter set, code: {result}"); - Ok(std::slice::from_raw_parts(bytes, len)) - } - } - } - - #[link(name = "CoreMedia", kind = "framework")] - unsafe extern "C" { - fn CMFormatDescriptionGetTypeID() -> CFTypeID; - fn CMVideoFormatDescriptionGetH264ParameterSetAtIndex( - video_desc: CMFormatDescriptionRef, - parameter_set_index: usize, - parameter_set_pointer_out: *mut *const u8, - parameter_set_size_out: *mut usize, - parameter_set_count_out: *mut usize, - NALUnitHeaderLengthOut: *mut isize, - ) -> OSStatus; - } - - #[repr(C)] - pub struct __CMBlockBuffer(c_void); - pub type CMBlockBufferRef = *const __CMBlockBuffer; - - declare_TCFType!(CMBlockBuffer, CMBlockBufferRef); - impl_TCFType!(CMBlockBuffer, CMBlockBufferRef, CMBlockBufferGetTypeID); - impl_CFTypeDescription!(CMBlockBuffer); - - impl CMBlockBuffer { - pub fn bytes(&self) -> &[u8] { - unsafe { - let mut bytes = ptr::null(); - let mut len = 0; - let result = CMBlockBufferGetDataPointer( - self.as_concrete_TypeRef(), - 0, - &mut 0, - &mut len, - &mut bytes, - ); - assert!(result == 0, "could not get block buffer data"); - std::slice::from_raw_parts(bytes, len) - } - } - } - - #[link(name = "CoreMedia", kind = "framework")] - unsafe extern "C" { - fn CMBlockBufferGetTypeID() -> CFTypeID; - fn CMBlockBufferGetDataPointer( - buffer: CMBlockBufferRef, - offset: usize, - length_at_offset_out: *mut usize, - total_length_out: *mut usize, - data_pointer_out: *mut *const u8, - ) -> OSStatus; - } -} - -#[cfg(target_os = "macos")] -pub mod core_video { - #![allow(non_snake_case)] - - #[cfg(target_os = "macos")] - use core_foundation::{ - base::{CFTypeID, TCFType}, - declare_TCFType, impl_CFTypeDescription, impl_TCFType, - }; - #[cfg(target_os = "macos")] - use std::ffi::c_void; - - use crate::bindings::{CVReturn, kCVReturnSuccess}; - pub use crate::bindings::{ - kCVPixelFormatType_32BGRA, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, - kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, kCVPixelFormatType_420YpCbCr8Planar, - }; - use anyhow::Result; - use core_foundation::{ - base::kCFAllocatorDefault, dictionary::CFDictionaryRef, mach_port::CFAllocatorRef, - }; - use foreign_types::ForeignTypeRef; - - use metal::{MTLDevice, MTLPixelFormat}; - use std::ptr; - - #[repr(C)] - pub struct __CVMetalTextureCache(c_void); - pub type CVMetalTextureCacheRef = *const __CVMetalTextureCache; - - declare_TCFType!(CVMetalTextureCache, CVMetalTextureCacheRef); - impl_TCFType!( - CVMetalTextureCache, - CVMetalTextureCacheRef, - CVMetalTextureCacheGetTypeID - ); - impl_CFTypeDescription!(CVMetalTextureCache); - - impl CVMetalTextureCache { - /// # Safety - /// - /// metal_device must be valid according to CVMetalTextureCacheCreate - pub unsafe fn new(metal_device: *mut MTLDevice) -> Result { - let mut this = ptr::null(); - let result = unsafe { - CVMetalTextureCacheCreate( - kCFAllocatorDefault, - ptr::null(), - metal_device, - ptr::null(), - &mut this, - ) - }; - anyhow::ensure!( - result == kCVReturnSuccess, - "could not create texture cache, code: {result}" - ); - unsafe { Ok(CVMetalTextureCache::wrap_under_create_rule(this)) } - } - - /// # Safety - /// - /// The arguments to this function must be valid according to CVMetalTextureCacheCreateTextureFromImage - pub unsafe fn create_texture_from_image( - &self, - source: ::core_video::image_buffer::CVImageBufferRef, - texture_attributes: CFDictionaryRef, - pixel_format: MTLPixelFormat, - width: usize, - height: usize, - plane_index: usize, - ) -> Result { - let mut this = ptr::null(); - let result = unsafe { - CVMetalTextureCacheCreateTextureFromImage( - kCFAllocatorDefault, - self.as_concrete_TypeRef(), - source, - texture_attributes, - pixel_format, - width, - height, - plane_index, - &mut this, - ) - }; - anyhow::ensure!( - result == kCVReturnSuccess, - "could not create texture, code: {result}" - ); - unsafe { Ok(CVMetalTexture::wrap_under_create_rule(this)) } - } - } - - #[link(name = "CoreVideo", kind = "framework")] - unsafe extern "C" { - fn CVMetalTextureCacheGetTypeID() -> CFTypeID; - fn CVMetalTextureCacheCreate( - allocator: CFAllocatorRef, - cache_attributes: CFDictionaryRef, - metal_device: *const MTLDevice, - texture_attributes: CFDictionaryRef, - cache_out: *mut CVMetalTextureCacheRef, - ) -> CVReturn; - fn CVMetalTextureCacheCreateTextureFromImage( - allocator: CFAllocatorRef, - texture_cache: CVMetalTextureCacheRef, - source_image: ::core_video::image_buffer::CVImageBufferRef, - texture_attributes: CFDictionaryRef, - pixel_format: MTLPixelFormat, - width: usize, - height: usize, - plane_index: usize, - texture_out: *mut CVMetalTextureRef, - ) -> CVReturn; - } - - #[repr(C)] - pub struct __CVMetalTexture(c_void); - pub type CVMetalTextureRef = *const __CVMetalTexture; - - declare_TCFType!(CVMetalTexture, CVMetalTextureRef); - impl_TCFType!(CVMetalTexture, CVMetalTextureRef, CVMetalTextureGetTypeID); - impl_CFTypeDescription!(CVMetalTexture); - - impl CVMetalTexture { - pub fn as_texture_ref(&self) -> &metal::TextureRef { - unsafe { - let texture = CVMetalTextureGetTexture(self.as_concrete_TypeRef()); - metal::TextureRef::from_ptr(texture as *mut _) - } - } - } - - #[link(name = "CoreVideo", kind = "framework")] - unsafe extern "C" { - fn CVMetalTextureGetTypeID() -> CFTypeID; - fn CVMetalTextureGetTexture(texture: CVMetalTextureRef) -> *mut c_void; - } -}