lumen-engine-ffmpeg 0.2.2

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

use crate::gpu::{GpuBackend, GpuVideoInput};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GpuEncodeTelemetry {
    pub upload_attempts: u64,
    pub upload_successes: u64,
    pub upload_failures: u64,
    pub encode_attempts: u64,
    pub encode_successes: u64,
    pub encode_failures: u64,
    pub cuda_frames: u64,
    pub metal_frames: u64,
    pub vulkan_frames: u64,
    pub estimated_upload_bytes: u64,
    pub upload_time_us: u128,
    pub encode_time_us: u128,
    pub last_error: Option<String>,
    pub recent_events: Vec<GpuEncodeEvent>,
}

impl GpuEncodeTelemetry {
    const MAX_RECENT_EVENTS: usize = 128;

    pub fn record_upload_started(&mut self, descriptor: &GpuUploadDescriptor) {
        self.upload_attempts = self.upload_attempts.saturating_add(1);
        self.estimated_upload_bytes = self
            .estimated_upload_bytes
            .saturating_add(descriptor.estimated_bytes);
        match descriptor.backend {
            GpuBackend::Cuda => self.cuda_frames = self.cuda_frames.saturating_add(1),
            GpuBackend::Metal => self.metal_frames = self.metal_frames.saturating_add(1),
            GpuBackend::Vulkan => self.vulkan_frames = self.vulkan_frames.saturating_add(1),
        }
        self.push_event(GpuEncodeEvent::started(GpuEncodeStage::Upload, descriptor));
    }

    pub fn record_upload_finished(&mut self, descriptor: &GpuUploadDescriptor, elapsed: Duration) {
        self.upload_successes = self.upload_successes.saturating_add(1);
        self.upload_time_us = self.upload_time_us.saturating_add(elapsed.as_micros());
        self.push_event(GpuEncodeEvent::finished(
            GpuEncodeStage::Upload,
            descriptor,
            elapsed,
        ));
    }

    pub fn record_upload_failed(
        &mut self,
        descriptor: &GpuUploadDescriptor,
        elapsed: Duration,
        message: impl Into<String>,
    ) {
        let message = message.into();
        self.upload_failures = self.upload_failures.saturating_add(1);
        self.upload_time_us = self.upload_time_us.saturating_add(elapsed.as_micros());
        self.last_error = Some(message.clone());
        self.push_event(GpuEncodeEvent::failed(
            GpuEncodeStage::Upload,
            descriptor,
            elapsed,
            message,
        ));
    }

    pub fn record_encode_started(&mut self, descriptor: &GpuUploadDescriptor) {
        self.encode_attempts = self.encode_attempts.saturating_add(1);
        self.push_event(GpuEncodeEvent::started(GpuEncodeStage::Encode, descriptor));
    }

    pub fn record_encode_finished(&mut self, descriptor: &GpuUploadDescriptor, elapsed: Duration) {
        self.encode_successes = self.encode_successes.saturating_add(1);
        self.encode_time_us = self.encode_time_us.saturating_add(elapsed.as_micros());
        self.push_event(GpuEncodeEvent::finished(
            GpuEncodeStage::Encode,
            descriptor,
            elapsed,
        ));
    }

    pub fn record_encode_failed(
        &mut self,
        descriptor: &GpuUploadDescriptor,
        elapsed: Duration,
        message: impl Into<String>,
    ) {
        let message = message.into();
        self.encode_failures = self.encode_failures.saturating_add(1);
        self.encode_time_us = self.encode_time_us.saturating_add(elapsed.as_micros());
        self.last_error = Some(message.clone());
        self.push_event(GpuEncodeEvent::failed(
            GpuEncodeStage::Encode,
            descriptor,
            elapsed,
            message,
        ));
    }

    fn push_event(&mut self, event: GpuEncodeEvent) {
        if self.recent_events.len() == Self::MAX_RECENT_EVENTS {
            self.recent_events.remove(0);
        }
        self.recent_events.push(event);
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GpuUploadDescriptor {
    pub backend: GpuBackend,
    pub width: u32,
    pub height: u32,
    pub estimated_bytes: u64,
}

impl GpuUploadDescriptor {
    pub fn from_frame(frame: &GpuVideoInput<'_>) -> Self {
        let (width, height) = frame.dimensions();
        Self {
            backend: frame.backend(),
            width,
            height,
            estimated_bytes: frame.estimated_rgba_bytes(),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpuEncodeStage {
    Upload,
    Encode,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GpuEncodeEvent {
    pub stage: GpuEncodeStage,
    pub outcome: GpuEncodeOutcome,
    pub backend: GpuBackend,
    pub width: u32,
    pub height: u32,
    pub estimated_bytes: u64,
    pub elapsed_us: Option<u128>,
    pub message: Option<String>,
}

impl GpuEncodeEvent {
    fn started(stage: GpuEncodeStage, descriptor: &GpuUploadDescriptor) -> Self {
        Self::new(stage, GpuEncodeOutcome::Started, descriptor, None, None)
    }

    fn finished(
        stage: GpuEncodeStage,
        descriptor: &GpuUploadDescriptor,
        elapsed: Duration,
    ) -> Self {
        Self::new(
            stage,
            GpuEncodeOutcome::Finished,
            descriptor,
            Some(elapsed),
            None,
        )
    }

    fn failed(
        stage: GpuEncodeStage,
        descriptor: &GpuUploadDescriptor,
        elapsed: Duration,
        message: String,
    ) -> Self {
        Self::new(
            stage,
            GpuEncodeOutcome::Failed,
            descriptor,
            Some(elapsed),
            Some(message),
        )
    }

    fn new(
        stage: GpuEncodeStage,
        outcome: GpuEncodeOutcome,
        descriptor: &GpuUploadDescriptor,
        elapsed: Option<Duration>,
        message: Option<String>,
    ) -> Self {
        Self {
            stage,
            outcome,
            backend: descriptor.backend,
            width: descriptor.width,
            height: descriptor.height,
            estimated_bytes: descriptor.estimated_bytes,
            elapsed_us: elapsed.map(|duration| duration.as_micros()),
            message,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpuEncodeOutcome {
    Started,
    Finished,
    Failed,
}