lumen-engine-ffmpeg 0.2.2

FFmpeg integration for media decode, encode, muxing, and GPU interop in Lumen.
Documentation
use std::ffi::CString;

use crate::ffi::sys;
use crate::gpu::GpuBackend;
use crate::video::{EncodeMode, VideoCodec};
use crate::{FfmpegError, Result};

use super::audio::AudioEncoderConfig;
use super::hardware::hardware_texture_encoder_name;
use super::video::VideoEncoderConfig;

pub(crate) fn find_encoder(config: &VideoEncoderConfig) -> Result<*const sys::AVCodec> {
    let encoder_name = match config.mode {
        EncodeMode::GpuTexture(backend) => {
            Some(hardware_encoder_name(config.codec, backend)?.to_string())
        }
        EncodeMode::CpuUpload => config.encoder_name.clone(),
    };

    if let Some(name) = encoder_name.as_deref() {
        encoder_by_name(name).map_err(|error| error.with_codec(config.codec))
    } else {
        let codec = unsafe { sys::avcodec_find_encoder(config.codec.to_av_codec_id()) };
        if codec.is_null() {
            Err(
                FfmpegError::new("avcodec_find_encoder", "requested encoder is unavailable")
                    .with_codec(config.codec),
            )
        } else {
            Ok(codec)
        }
    }
}

pub(crate) fn encoder_by_name(name: &str) -> Result<*const sys::AVCodec> {
    let c_name = CString::new(name).map_err(|_| {
        FfmpegError::new(
            "avcodec_find_encoder_by_name",
            "encoder name contains NUL byte",
        )
    })?;
    let codec = unsafe { sys::avcodec_find_encoder_by_name(c_name.as_ptr()) };
    if codec.is_null() {
        Err(FfmpegError::new(
            "avcodec_find_encoder_by_name",
            format!("requested encoder `{name}` is unavailable"),
        ))
    } else {
        Ok(codec)
    }
}

pub(crate) fn hardware_encoder_name(
    codec: VideoCodec,
    backend: GpuBackend,
) -> Result<&'static str> {
    hardware_texture_encoder_name(backend, codec).ok_or_else(|| {
        FfmpegError::new(
            "VideoEncoder::create",
            if matches!(backend, GpuBackend::Vulkan) {
                format!("{backend:?} hardware texture encode is not available yet")
            } else {
                format!("{backend:?} hardware encode is unavailable for {codec:?}")
            },
        )
        .with_backend(backend)
        .with_codec(codec)
    })
}

pub(crate) fn find_audio_encoder(config: &AudioEncoderConfig) -> Result<*const sys::AVCodec> {
    if let Some(name) = config.encoder_name.as_deref() {
        return encoder_by_name(name);
    }
    let codec = unsafe { sys::avcodec_find_encoder(sys::AVCodecID::AV_CODEC_ID_AAC) };
    if codec.is_null() {
        Err(FfmpegError::new(
            "avcodec_find_encoder",
            "requested AAC encoder is unavailable",
        ))
    } else {
        Ok(codec)
    }
}

pub(crate) fn select_audio_sample_format(
    codec: *const sys::AVCodec,
) -> Result<sys::AVSampleFormat> {
    use sys::AVSampleFormat::{AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE};

    unsafe {
        let formats = (*codec).sample_fmts;
        if formats.is_null() {
            return Ok(AV_SAMPLE_FMT_FLTP);
        }
        let mut index = 0;
        let mut fallback = AV_SAMPLE_FMT_NONE;
        loop {
            let format = *formats.add(index);
            if format == AV_SAMPLE_FMT_NONE {
                break;
            }
            if format == AV_SAMPLE_FMT_FLTP {
                return Ok(format);
            }
            if format == AV_SAMPLE_FMT_FLT {
                fallback = format;
            }
            index += 1;
        }
        if fallback != AV_SAMPLE_FMT_NONE {
            Ok(fallback)
        } else {
            Err(FfmpegError::new(
                "AudioEncoder::create",
                "AAC encoder does not support f32 input",
            ))
        }
    }
}