Skip to content

Instantly share code, notes, and snippets.

@anselm
Last active September 26, 2022 18:14
Show Gist options
  • Save anselm/6c0e34d85508a2fe498677d57df84e56 to your computer and use it in GitHub Desktop.
Save anselm/6c0e34d85508a2fe498677d57df84e56 to your computer and use it in GitHub Desktop.
avfoundation rust webcam experiment
// the below is a rust port of https://gist.github.com/bellbind/6954679
// capture image from webcam(e.g. face time)
// some helpful examples
// https://kyle.space/posts/cocoa-apps-in-rust-eventually/
// msg_send
// https://github.com/SSheldon/rust-objc/blob/master/examples/example.rs
// silence a few warnings
#![allow(non_snake_case)]
// don't need to specify these by hand...
//extern crate dispatch;
//extern crate cocoa;
// do get macros
#[macro_use] extern crate objc;
use std::ffi::CString;
// not even sure this is will work - is this the right dispatcher?
use dispatch::{Queue,QueueAttribute};
use cocoa::base::{SEL,selector, nil, id, NO, YES};
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSAutoreleasePool, NSProcessInfo, NSString};
use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSWindow,
NSBackingStoreBuffered, NSMenu, NSMenuItem, NSWindowStyleMask,
NSRunningApplication, NSApplicationActivateIgnoringOtherApps};
use objc::runtime::{Class, Object, Sel, BOOL, Protocol};
use objc::declare::ClassDecl;
use objc::Encode;
use objc::rc::StrongPtr;
#[link(name = "AVFoundation", kind = "framework")]
#[link(name = "CoreMedia", kind = "framework")]
#[link(name = "CoreImage", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "Foundation", kind = "framework")]
extern {
// objc
//pub fn objc_getClass(name: *const libc::c_char) -> Class;
//pub fn objc_msgSend(obj: id, sel: SEL, ...) -> id;
//pub fn sel_registerName(name: *const libc::c_char) -> SEL;
// Foundation
pub fn NSLog(fmt: id, ...);
//pub fn NSStringFromClass(cls: Class) -> id;
//pub fn NSStringFromSelector(sel: SEL) -> id;
}
extern "C" {
pub fn CMSampleBufferGetImageBuffer(buffer: id) -> id;
// pub fn CMSampleBufferGetOutputPresentationTimeStamp(buffer:id) -> id;
// pub fn CMTimeGetSeconds(time:id) -> f64;
}
/// startup
fn main() {
// CALLBACK
extern fn myCaptureOutput(_this: &Object, _cmd: Sel, _id1: id, buffer: id, _id3: id) {
unsafe {
// this crashes
//let time = CMSampleBufferGetOutputPresentationTimeStamp(buffer);
//let time = CMTimeGetSeconds(time);
//println!("Time is {}",time);
let frame = CMSampleBufferGetImageBuffer(buffer);
let image: *mut Object = msg_send![class!(CIImage), imageWithCVImageBuffer: frame];
let bitmap: *mut Object = msg_send![class!(NSBitmapImageRep), alloc];
let _: () = msg_send![bitmap,initWithCIImage: image];
// build a png - I use simple-counter as a crate to do this
if true {
let filename = format!("result{}.png",Counter::next() );
let filename = NSString::alloc(nil).init_str(filename.as_str());
let data: *mut Object = msg_send![bitmap, representationUsingType:4 properties: nil];
let _: () = msg_send![data, writeToFile: filename atomically: YES];
}
}
}
unsafe {
// some types
let NSString = Class::get("NSString").unwrap();
// make secret enum type "vide" - not really documented anywhere but i did find a C# citation of this
//let AVMediaTypeVideo = CString::new("vide").unwrap();
//let AVMediaTypeVideo = AVMediaTypeVideo.as_ptr();
//let AVMediaTypeVideo: *mut Object = msg_send![class!(NSString), stringWithUTF8String:AVMediaTypeVideo];
let AVMediaTypeVideo = NSString::alloc(nil).init_str(&"vide".to_string()).autorelease();
// make the device
let device: *mut Object = msg_send![class!(AVCaptureDevice), defaultDeviceWithMediaType:AVMediaTypeVideo ];
NSLog(NSString::alloc(nil).init_str("Device is %@"),device);
// make the input
let input: *mut Object = msg_send![class!(AVCaptureDeviceInput), deviceInputWithDevice:device error:0 ];
NSLog(NSString::alloc(nil).init_str("Input is %@"),input);
// make the output thing
let output: *mut Object = msg_send![class!(AVCaptureVideoDataOutput),alloc];
let output: *mut Object = msg_send![output,init];
// make a capture class
// how do i implement AVCaptureVideoDataOutputSampleBufferDelegate?
// is that a polymorphic method?
// https://developer.apple.com/documentation/avfoundation/avcapturevideodataoutputsamplebufferdelegate
let mut Capture = ClassDecl::new("MyCapture", class!(NSObject)).unwrap();
let protocol = &Protocol::get("AVCaptureVideoDataOutputSampleBufferDelegate").unwrap();
Capture.add_protocol(protocol);
let magic = sel!(captureOutput: didOutputSampleBuffer: fromConnection:);
Capture.add_method(magic, myCaptureOutput as extern fn(&Object,Sel, id, id, id));
Capture.register();
Capture.add_method(sel!(captureOutput), myCaptureOutput as extern fn(&Object,Sel));
Capture.register();
let Capture = Class::get("MyCapture").unwrap(); // why?
let capture: *mut Object = msg_send![Capture,alloc];
let capture: *mut Object = msg_send![capture,init];
NSLog(NSString::alloc(nil).init_str("Capture is %@"),capture);
// how msg_send works and its notation could have been clarified more...
// 2 params? https://developer.apple.com/documentation/avfoundation/avcapturevideodataoutput/1389008-setsamplebufferdelegate
// or ?? [output setSampleBufferDelegate: capture queue: dispatch_get_main_queue()];
// also - no commas on msg_send?
// is there a non macro version?
// make some kind of dispatch?
let queue = dispatch::ffi::dispatch_get_main_queue();
NSLog(NSString::alloc(nil).init_str("queue is %@"),queue);
//let _: () = msg_send![output, sampleBufferDelegate:capture sampleBufferCallBackQueue:queue];
let _: () = msg_send![output, setSampleBufferDelegate:capture queue:queue];
// make and start the session itself
let session: *mut Object = msg_send![class!(AVCaptureSession),alloc];
let session: *mut Object = msg_send![session,init];
let _: () = msg_send![session,addInput:input];
let _: () = msg_send![session,addOutput:output];
let _: () = msg_send![session,startRunning];
NSLog(NSString::alloc(nil).init_str("Session is %@"),session);
// TODO - i think i somehow need to let this thing now fire events
// right now my capture class is not being invoked (i think)
// is that because it is not of type AVCaptureVideoDataOutputSampleBufferDelegate
// or is it missing a selector?
// or is that the dispatcher needs to run?
// how does dispatch work? what does it mean?
// https://github.com/SSheldon/rust-dispatch/blob/master/examples/main.rs
// https://faq.sealedabstract.com/rust/#dispatch
// dispatch::ffi::dispatch_main();
// this crashes
//let app = NSRunningApplication::currentApplication(nil);
//app.run();
// maybe this?
println!("running");
core_foundation::runloop::CFRunLoopRun();
// hmm
//std::thread::sleep(std::time::Duration::from_millis(3000));
}
}
// links:
// some other library https://lib.rs/crates/objrs
fn oldmain() {
fn register_button() {
unsafe {
let superclass = class!(NSButton);
let mut decl = ClassDecl::new("HelloWorldButton", superclass).unwrap();
extern fn clicked(_this: &Object, _cmd: Sel) {
unsafe {
println!("clicked {:?}", _this);
/*
let alert:*const Object = msg_send!(class!(NSAlert), alloc);
let alert:*const Object = msg_send!(alert, init);
let alert_title = NSString::alloc(nil).init_str(&"Hello World".to_string()).autorelease();
let alert_body = NSString::alloc(nil).init_str(&"You Clicked Me!").autorelease();
let _alert_id: id = msg_send!(alert, setMessageText:alert_title);
let _alert_id: id = msg_send!(alert, setInformativeText:alert_body);
let _alert_id: id = msg_send!(alert, runModal);
*/
}
}
let clicked: extern fn(&Object, Sel) = clicked;
decl.add_method(sel!(clicked), clicked);
decl.register();
}
}
fn create_button(frame: NSRect, title:String) -> *mut Object {
unsafe {
let button: *const Object = msg_send![class!(HelloWorldButton), alloc];
let button_with_frame: *mut Object = msg_send![button, initWithFrame:frame];
let title_as_nsstring = NSString::alloc(nil).init_str(&title.to_string()).autorelease();
let _title_return: id = msg_send![button_with_frame, setTitle:title_as_nsstring];
let _hello_world_button_msg: id = msg_send![button_with_frame, setTarget:button_with_frame];
let _hello_world_button_msg: id = msg_send![button_with_frame, setAction:sel!(clicked)];
button_with_frame
}
}
unsafe {
register_button();
let hello_world_button_frame:NSRect = NSRect::new(NSPoint::new( 0., 30.), NSSize::new(20., 10.));
let hello_world_button = create_button(hello_world_button_frame, "Click Me".to_string());
let _pool = NSAutoreleasePool::new(nil);
let app = NSApp();
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
// create Menu Bar
let menubar = NSMenu::new(nil).autorelease();
let app_menu_item = NSMenuItem::new(nil).autorelease();
menubar.addItem_(app_menu_item);
app.setMainMenu_(menubar);
// create Application menu
let app_menu = NSMenu::new(nil).autorelease();
let quit_prefix = NSString::alloc(nil).init_str("Quit ");
let quit_title = quit_prefix.stringByAppendingString_(NSProcessInfo::processInfo(nil).processName());
let quit_action = selector("terminate:");
let quit_key = NSString::alloc(nil).init_str("q");
let quit_item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(quit_title, quit_action, quit_key)
.autorelease();
app_menu.addItem_(quit_item);
app_menu_item.setSubmenu_(app_menu);
// create Window
let window = NSWindow::alloc(nil)
.initWithContentRect_styleMask_backing_defer_(NSRect::new(NSPoint::new(0., 0.),
NSSize::new(200., 200.)),
NSWindowStyleMask::NSTitledWindowMask,
NSBackingStoreBuffered,
NO)
.autorelease();
window.cascadeTopLeftFromPoint_(NSPoint::new(20., 20.));
window.center();
let title = NSString::alloc(nil).init_str("Hello World!");
window.setTitle_(title);
window.makeKeyAndOrderFront_(nil);
let current_app = NSRunningApplication::currentApplication(nil);
current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps);
window.setContentView_(hello_world_button);
app.run();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment