lumen-engine-ffmpeg 0.2.0

FFmpeg integration for media decode, encode, muxing, and GPU interop in Lumen.
Documentation
use std::{
    ffi::{CStr, CString},
    ptr::NonNull,
    sync::Once,
};

use crate::{FfmpegError, Result};

pub(crate) use ffmpeg_sys_next as sys;

static INIT: Once = Once::new();

pub(crate) fn init() {
    INIT.call_once(|| unsafe {
        sys::av_log_set_level(sys::AV_LOG_ERROR);
    });
}

pub(crate) fn cstring(operation: &'static str, value: &str) -> Result<CString> {
    CString::new(value)
        .map_err(|_| FfmpegError::new(operation, "string contains an interior NUL byte"))
}

pub(crate) fn check(code: i32, operation: &'static str) -> Result<()> {
    if code < 0 {
        Err(error_from_code(operation, code))
    } else {
        Ok(())
    }
}

pub(crate) fn error_from_code(operation: &'static str, code: i32) -> FfmpegError {
    FfmpegError {
        operation,
        path: None,
        code: Some(code),
        message: av_error_string(code),
        backend: None,
        codec: None,
        stream_index: None,
    }
}

pub(crate) fn av_error_string(code: i32) -> String {
    let mut buffer = [0 as libc::c_char; 256];
    unsafe {
        let result = sys::av_strerror(code, buffer.as_mut_ptr(), buffer.len());
        if result < 0 {
            return format!("FFmpeg error {code}");
        }
        CStr::from_ptr(buffer.as_ptr())
            .to_string_lossy()
            .into_owned()
    }
}

pub(crate) struct AvPacket {
    ptr: NonNull<sys::AVPacket>,
}

impl AvPacket {
    pub(crate) fn new() -> Result<Self> {
        let ptr = unsafe { sys::av_packet_alloc() };
        let ptr = NonNull::new(ptr)
            .ok_or_else(|| FfmpegError::new("av_packet_alloc", "failed to allocate packet"))?;
        Ok(Self { ptr })
    }

    pub(crate) fn as_ptr(&self) -> *const sys::AVPacket {
        self.ptr.as_ptr()
    }

    pub(crate) fn as_mut_ptr(&mut self) -> *mut sys::AVPacket {
        self.ptr.as_ptr()
    }

    pub(crate) fn stream_index(&self) -> usize {
        unsafe { (*self.ptr.as_ptr()).stream_index as usize }
    }

    pub(crate) fn pts(&self) -> Option<i64> {
        let pts = unsafe { (*self.ptr.as_ptr()).pts };
        (pts != sys::AV_NOPTS_VALUE).then_some(pts)
    }
}

impl Drop for AvPacket {
    fn drop(&mut self) {
        unsafe {
            let mut ptr = self.ptr.as_ptr();
            sys::av_packet_free(&mut ptr);
        }
    }
}

pub(crate) struct AvFrame {
    ptr: NonNull<sys::AVFrame>,
}

impl AvFrame {
    pub(crate) fn new() -> Result<Self> {
        let ptr = unsafe { sys::av_frame_alloc() };
        let ptr = NonNull::new(ptr)
            .ok_or_else(|| FfmpegError::new("av_frame_alloc", "failed to allocate frame"))?;
        Ok(Self { ptr })
    }

    pub(crate) fn as_ptr(&self) -> *const sys::AVFrame {
        self.ptr.as_ptr()
    }

    pub(crate) fn as_mut_ptr(&mut self) -> *mut sys::AVFrame {
        self.ptr.as_ptr()
    }

    pub(crate) fn format(&self) -> sys::AVPixelFormat {
        unsafe { std::mem::transmute::<i32, sys::AVPixelFormat>((*self.ptr.as_ptr()).format) }
    }

    pub(crate) fn sample_format(&self) -> sys::AVSampleFormat {
        unsafe { std::mem::transmute::<i32, sys::AVSampleFormat>((*self.ptr.as_ptr()).format) }
    }

    pub(crate) fn pts(&self) -> Option<i64> {
        let pts = unsafe { (*self.ptr.as_ptr()).pts };
        (pts != sys::AV_NOPTS_VALUE).then_some(pts)
    }

    pub(crate) fn data(&self, plane: usize) -> *mut u8 {
        unsafe { (*self.ptr.as_ptr()).data[plane] }
    }

    #[cfg(feature = "cuda")]
    pub(crate) fn line_size(&self, plane: usize) -> i32 {
        unsafe { (*self.ptr.as_ptr()).linesize[plane] }
    }

    #[cfg(feature = "cuda")]
    pub(crate) fn hw_sw_format(&self) -> Option<sys::AVPixelFormat> {
        let frame = unsafe { self.ptr.as_ref() };
        if frame.hw_frames_ctx.is_null() {
            return None;
        }
        let frames = unsafe { (*frame.hw_frames_ctx).data }.cast::<sys::AVHWFramesContext>();
        if frames.is_null() {
            return None;
        }
        Some(unsafe { (*frames).sw_format })
    }

    pub(crate) fn width(&self) -> u32 {
        unsafe { (*self.ptr.as_ptr()).width.max(0) as u32 }
    }

    pub(crate) fn height(&self) -> u32 {
        unsafe { (*self.ptr.as_ptr()).height.max(0) as u32 }
    }

    pub(crate) fn nb_samples(&self) -> usize {
        unsafe { (*self.ptr.as_ptr()).nb_samples.max(0) as usize }
    }
}

impl Drop for AvFrame {
    fn drop(&mut self) {
        unsafe {
            let mut ptr = self.ptr.as_ptr();
            sys::av_frame_free(&mut ptr);
        }
    }
}

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

    #[test]
    fn formats_known_error_code() {
        let error = error_from_code("test", sys::AVERROR_EOF);
        assert!(error.message.to_lowercase().contains("end"));
    }

    #[test]
    fn owns_packet_and_frame() {
        let packet = AvPacket::new().expect("packet");
        let frame = AvFrame::new().expect("frame");
        assert!(!packet.as_ptr().is_null());
        assert!(!frame.as_ptr().is_null());
    }
}