Skip to main content

ez_ffmpeg/core/frame_export/
frame.rs

1//! The owned, packed [`VideoFrame`] output type.
2
3use super::options::PixelLayout;
4
5/// One exported video frame: tightly packed 8-bit pixels plus metadata.
6///
7/// The byte buffer is owned and has no row padding — `as_bytes().len()` equals
8/// `width * height * layout.bytes_per_pixel()` and the row stride equals
9/// `width * layout.bytes_per_pixel()`. This is the shape ML/CV consumers expect
10/// (feed it straight into an ndarray view, a tensor, or an image encoder).
11pub struct VideoFrame {
12    width: u32,
13    height: u32,
14    layout: PixelLayout,
15    pts_us: Option<i64>,
16    index: u64,
17    data: Vec<u8>,
18    /// Drop-time return path to the producing run's buffer pool. `None` once
19    /// [`into_vec`](VideoFrame::into_vec) detaches the buffer (or when the
20    /// frame was built without a pool). Purely an allocation-recycling
21    /// channel: it never blocks and never affects the data contract.
22    recycle: Option<crossbeam_channel::Sender<Vec<u8>>>,
23}
24
25impl VideoFrame {
26    /// Builds a frame from an already-packed, tight buffer. Crate-internal: the
27    /// sink guarantees `data.len() == width * height * layout.bytes_per_pixel()`.
28    /// `recycle` is the sink pool's return path for the buffer (dropped frames
29    /// hand their allocation back for the next frame to reuse).
30    pub(crate) fn new(
31        width: u32,
32        height: u32,
33        layout: PixelLayout,
34        pts_us: Option<i64>,
35        index: u64,
36        data: Vec<u8>,
37        recycle: Option<crossbeam_channel::Sender<Vec<u8>>>,
38    ) -> Self {
39        debug_assert_eq!(
40            data.len(),
41            width as usize * height as usize * layout.bytes_per_pixel(),
42            "VideoFrame buffer must be tightly packed"
43        );
44        Self {
45            width,
46            height,
47            layout,
48            pts_us,
49            index,
50            data,
51            recycle,
52        }
53    }
54
55    /// Frame width in pixels.
56    pub fn width(&self) -> u32 {
57        self.width
58    }
59
60    /// Frame height in pixels.
61    pub fn height(&self) -> u32 {
62        self.height
63    }
64
65    /// The packed pixel layout of [`as_bytes`](VideoFrame::as_bytes).
66    pub fn layout(&self) -> PixelLayout {
67        self.layout
68    }
69
70    /// Post-filter presentation time in microseconds, normalized to the start
71    /// of the extraction window (the stream start when no `start_time_us` was
72    /// set). Vsync passthrough preserves the source timing one-to-one; `None`
73    /// when the frame carried no usable timestamp.
74    pub fn pts_us(&self) -> Option<i64> {
75        self.pts_us
76    }
77
78    /// 0-based export index (counts delivered frames in order).
79    pub fn index(&self) -> u64 {
80        self.index
81    }
82
83    /// The packed pixel bytes. Length is `width * height * bytes_per_pixel`;
84    /// rows are tight (`row_bytes()` each) and top-down.
85    pub fn as_bytes(&self) -> &[u8] {
86        &self.data
87    }
88
89    /// Consumes the frame and returns the owned packed buffer (no copy).
90    pub fn into_vec(mut self) -> Vec<u8> {
91        // Detach from the buffer pool first: `Drop` still runs for `self`, and
92        // with `recycle` cleared it has nothing to send — the caller owns the
93        // allocation outright and the pool simply allocates fresh next time.
94        self.recycle = None;
95        std::mem::take(&mut self.data)
96    }
97
98    /// Bytes per row: `width * layout.bytes_per_pixel()`.
99    pub fn row_bytes(&self) -> usize {
100        self.width as usize * self.layout.bytes_per_pixel()
101    }
102}
103
104impl Drop for VideoFrame {
105    fn drop(&mut self) {
106        if let Some(recycle) = self.recycle.take() {
107            // Non-blocking by design: a full pool or a finished run (receiver
108            // gone) just means the buffer is freed here instead of reused.
109            let _ = recycle.try_send(std::mem::take(&mut self.data));
110        }
111    }
112}
113
114impl std::fmt::Debug for VideoFrame {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("VideoFrame")
117            .field("width", &self.width)
118            .field("height", &self.height)
119            .field("layout", &self.layout)
120            .field("pts_us", &self.pts_us)
121            .field("index", &self.index)
122            .field("bytes", &self.data.len())
123            .finish()
124    }
125}