📄 kinect2-sys/src/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
//! A small C ABI over the parts of libfreenect2's C++ API the capture
//! pipeline needs (see `shim.cpp`). Every call catches C++ exceptions
//! and reports through a thread-local message buffer instead.
//!
//! No bindgen: the surface is tiny and hand-declared in
//! [`crate::ffi`], with layout `static_assert`s on the C++ side.

/// Depth frames are 512x424 float millimetres (invalid: non-positive,
/// NaN, infinity — libfreenect2's convention).
pub const DEPTH_WIDTH: usize = 512;
pub const DEPTH_HEIGHT: usize = 424;

/// The format of a frame's bytes: libfreenect2 `Frame::Format`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameFormat {
    /// 4 bytes: B, G, R, unused.
    Bgrx,
    /// 4 bytes: R, G, B, unused.
    Rgbx,
    /// 4-byte float per pixel.
    Float,
    /// Anything else.
    Other(i32),
}

impl FrameFormat {
    fn from_raw(raw: i32) -> Self {
        match raw {
            4 => Self::Bgrx,
            5 => Self::Rgbx,
            2 => Self::Float,
            other => Self::Other(other),
        }
    }
}

mod ffi {
    use std::ffi::c_char;
    use std::ffi::c_void;

    #[repr(C)]
    #[derive(Debug, Clone, Copy)]
    pub struct FrameView {
        /// Opaque `libfreenect2::Frame*`; owned by whoever allocated it.
        pub frame: *mut c_void,
        pub width: usize,
        pub height: usize,
        pub bytes_per_pixel: usize,
        pub data: *mut u8,
        pub format: i32,
        pub timestamp: u32,
    }

    #[repr(C)]
    #[derive(Debug, Clone, Copy, Default, PartialEq)]
    pub struct IrParams {
        pub fx: f32,
        pub fy: f32,
        pub cx: f32,
        pub cy: f32,
        pub k1: f32,
        pub k2: f32,
        pub k3: f32,
        pub p1: f32,
        pub p2: f32,
    }

    // Layout: keep in sync with shim.cpp's static_asserts.
    pub type Device = c_void;
    pub type Listener = c_void;
    pub type Registration = c_void;

    unsafe extern "C" {
        pub fn lf2_take_error() -> *const c_char;
        pub fn lf2_count_devices() -> i32;
        pub fn lf2_open_default() -> *mut Device;
        pub fn lf2_device_close(device: *mut Device);
        pub fn lf2_start(device: *mut Device) -> i32;
        pub fn lf2_stop(device: *mut Device) -> i32;
        pub fn lf2_get_ir_params(device: *mut Device, out: *mut IrParams);
        pub fn lf2_get_serial(device: *mut Device, buf: *mut c_char, len: usize);
        pub fn lf2_frame_new(width: i32, height: i32, bpp: i32) -> *mut FrameView;
        pub fn lf2_frame_free(frame: *mut FrameView);
        pub fn lf2_listener_new() -> *mut Listener;
        pub fn lf2_listener_free(listener: *mut Listener);
        pub fn lf2_listener_attach(device: *mut Device, listener: *mut Listener) -> i32;
        pub fn lf2_wait_for_frame(
            listener: *mut Listener,
            timeout_ms: i32,
            out_color: *mut FrameView,
            out_depth: *mut FrameView,
        ) -> i32;
        pub fn lf2_release(listener: *mut Listener);
        pub fn lf2_registration_new(device: *mut Device) -> *mut Registration;
        pub fn lf2_registration_free(registration: *mut Registration);
        pub fn lf2_register(
            registration: *mut Registration,
            color: *mut FrameView,
            depth: *mut FrameView,
            out_undistorted: *mut FrameView,
            out_registered: *mut FrameView,
        ) -> i32;
    }
}

/// The last error reported by a failing call on this thread.
pub fn last_error() -> String {
    unsafe {
        let ptr = ffi::lf2_take_error();
        if ptr.is_null() {
            "unknown libfreenect2 failure".into()
        } else {
            std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned()
        }
    }
}

macro_rules! call {
    ($expr:expr) => {{
        let result = unsafe { $expr };
        if result < 0 {
            anyhow::bail!("{}", last_error());
        }
        result
    }};
}

macro_rules! opt_call {
    ($expr:expr) => {{
        let result = unsafe { $expr };
        if result.is_null() {
            anyhow::bail!("{}", last_error());
        }
        result
    }};
}

/// A borrow of frame data — valid only until the listener releases the
/// frame or the owning object is dropped.
#[derive(Debug, Clone, Copy)]
pub struct FrameRef {
    view: ffi::FrameView,
}

impl FrameRef {
    pub fn width(&self) -> usize {
        self.view.width
    }

    pub fn height(&self) -> usize {
        self.view.height
    }

    pub fn format(&self) -> FrameFormat {
        FrameFormat::from_raw(self.view.format)
    }

    /// The raw bytes of the frame.
    ///
    /// # Safety
    /// Valid for the borrow lifetime of the view; the caller must not
    /// use them after the source frame is released.
    pub unsafe fn bytes(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(
                self.view.data,
                self.view.width * self.view.height * self.view.bytes_per_pixel,
            )
        }
    }

    /// The floats of a `Float`-format frame.
    ///
    /// # Safety
    /// Valid for the borrow lifetime; `format` must be `Float`.
    pub unsafe fn floats(&self) -> &[f32] {
        unsafe {
            std::slice::from_raw_parts(
                self.view.data.cast::<f32>(),
                self.view.width * self.view.height,
            )
        }
    }
}

/// A Kinect v2 device, via libfreenect2. Attach a [`Listener`] before
/// [`Device::start`].
#[derive(Debug)]
pub struct Device {
    ptr: *mut ffi::Device,
}

impl Device {
    /// How many Kinects are connected; `Err` on failure.
    pub fn count() -> anyhow::Result<usize> {
        Ok(call!(ffi::lf2_count_devices()) as usize)
    }

    /// Open the first connected device (CPU depth pipeline).
    pub fn open_default() -> anyhow::Result<Self> {
        Ok(Self {
            ptr: opt_call!(ffi::lf2_open_default()),
        })
    }

    pub fn start(&self) -> anyhow::Result<()> {
        call!(ffi::lf2_start(self.ptr));
        Ok(())
    }

    pub fn stop(&self) -> anyhow::Result<()> {
        call!(ffi::lf2_stop(self.ptr));
        Ok(())
    }

    /// The depth (IR) camera's factory intrinsics, at 512x424.
    pub fn ir_params(&self) -> IrParams {
        let mut params = IrParams::default();
        unsafe { ffi::lf2_get_ir_params(self.ptr, &mut params.0) };
        params
    }

    /// The device serial number, for logging.
    pub fn serial(&self) -> String {
        let mut buf = [0 as std::ffi::c_char; 64];
        unsafe { ffi::lf2_get_serial(self.ptr, buf.as_mut_ptr(), buf.len()) };
        let cstr = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
        cstr.to_string_lossy().into_owned()
    }

    fn close(&self) {
        unsafe { ffi::lf2_device_close(self.ptr) };
    }
}

impl Drop for Device {
    fn drop(&mut self) {
        self.close();
    }
}

/// Factory-calibrated depth intrinsics: focal length and principal
/// point in pixels, plus the distortion model.
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct IrParams(ffi::IrParams);

impl IrParams {
    pub fn fx(&self) -> f32 {
        self.0.fx
    }

    pub fn fy(&self) -> f32 {
        self.0.fy
    }

    pub fn cx(&self) -> f32 {
        self.0.cx
    }

    pub fn cy(&self) -> f32 {
        self.0.cy
    }
}

/// A synchronized color+depth listener attached to a [`Device`].
///
/// Frames are borrowed: read them inside
/// [`Listener::with_frames`] and copy what you need; the frames are
/// released when the closure returns.
pub struct Listener {
    ptr: *mut ffi::Listener,
}

impl Listener {
    /// Create and attach a `Color | Depth` sync listener.
    pub fn attach(device: &Device) -> anyhow::Result<Self> {
        let listener = opt_call!(ffi::lf2_listener_new());
        call!(ffi::lf2_listener_attach(device.ptr, listener));
        Ok(Self { ptr: listener })
    }

    /// Wait up to `timeout_ms` for a synchronized color+depth pair and
    /// run `f` with views into both, returning `f`'s value.
    /// Returns `Ok(None)` on timeout.
    pub fn with_frames<T>(
        &mut self,
        timeout_ms: i32,
        mut f: impl FnMut(&FrameRef, &FrameRef) -> T,
    ) -> anyhow::Result<Option<T>> {
        let mut color = ffi::FrameView {
            frame: std::ptr::null_mut(),
            width: 0,
            height: 0,
            bytes_per_pixel: 0,
            data: std::ptr::null_mut(),
            format: 0,
            timestamp: 0,
        };
        let mut depth = color;
        let got = unsafe { ffi::lf2_wait_for_frame(self.ptr, timeout_ms, &mut color, &mut depth) };
        match got {
            1 => {
                let value = f(&FrameRef { view: color }, &FrameRef { view: depth });
                unsafe { ffi::lf2_release(self.ptr) };
                Ok(Some(value))
            }
            0 => Ok(None),
            _ => anyhow::bail!("{}", last_error()),
        }
    }
}

impl Drop for Listener {
    fn drop(&mut self) {
        unsafe { ffi::lf2_listener_free(self.ptr) };
    }
}

/// Registration of color onto depth (libfreenect2's reverse-engineered
/// factory-extrinsics mapping): produces the undistorted depth map
/// (float millimetres) and the color image resampled onto the depth
/// grid, both at 512x424.
pub struct Registration {
    ptr: *mut ffi::Registration,
    undistorted: *mut ffi::FrameView,
    registered: *mut ffi::FrameView,
}

impl Registration {
    /// From the device's factory calibration.
    pub fn from_device(device: &Device) -> anyhow::Result<Self> {
        let registration = opt_call!(ffi::lf2_registration_new(device.ptr));
        let undistorted = opt_call!(ffi::lf2_frame_new(
            DEPTH_WIDTH as i32,
            DEPTH_HEIGHT as i32,
            4
        ));
        let registered = opt_call!(ffi::lf2_frame_new(
            DEPTH_WIDTH as i32,
            DEPTH_HEIGHT as i32,
            4
        ));
        Ok(Self {
            ptr: registration,
            undistorted,
            registered,
        })
    }

    /// Register one frame pair; the returned views are valid until the
    /// next call or drop. The registered image's channel order matches
    /// the color frame's (`FrameRef::format` of `color`).
    pub fn apply(
        &mut self,
        color: &FrameRef,
        depth: &FrameRef,
    ) -> anyhow::Result<(FrameRef, FrameRef)> {
        let mut color_view = color.view;
        let mut depth_view = depth.view;
        call!(ffi::lf2_register(
            self.ptr,
            &mut color_view,
            &mut depth_view,
            self.undistorted,
            self.registered
        ));
        // SAFETY: the out-frames live as long as `self`; the returned
        // views are plain copies pointing at their data, invalidated by
        // the next `apply` or by dropping `self`.
        unsafe { Ok(((*self.undistorted).borrow(), (*self.registered).borrow())) }
    }
}

// SAFETY: libfreenect2 device/listener/registration handles are
// thread-safe across our usage (one worker thread drives them).
unsafe impl Send for Device {}
unsafe impl Send for Listener {}
unsafe impl Send for Registration {}

impl ffi::FrameView {
    /// Reinterpret as a borrow of the frame data.
    ///
    /// # Safety
    /// The view must be alive for `'self`'s lifetime; the caller
    /// guarantees it.
    unsafe fn borrow(&self) -> FrameRef {
        FrameRef { view: *self }
    }
}

impl Drop for Registration {
    fn drop(&mut self) {
        unsafe {
            ffi::lf2_frame_free(self.undistorted);
            ffi::lf2_frame_free(self.registered);
            ffi::lf2_registration_free(self.ptr);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn linkage_smoke_without_a_device() {
        // The library links and its context works even without USB.
        let count = Device::count().expect("enumerating devices works");
        println!("{count} Kinect(s) connected");
    }

    #[test]
    fn open_fails_clearly_without_a_device() {
        // The container has no USB: opening must fail (or succeed only
        // if a Kinect is actually attached, which it is not here).
        if Device::count().unwrap() == 0 {
            let err = Device::open_default().expect_err("no device");
            assert!(!err.to_string().is_empty());
        }
    }
}