ndi_sdk_sys/frame/
video.rs

1use num::ToPrimitive;
2use std::fmt::Debug;
3use std::{ffi::CStr, sync::Arc};
4
5use num::Rational32;
6
7pub(crate) use crate::bindings::NDIlib_video_frame_v2_t as NDIRawVideoFrame;
8use crate::receiver::RawReceiver;
9use crate::{
10    bindings,
11    buffer_info::BufferInfo,
12    enums::NDIFieldedFrameMode,
13    four_cc::{BufferInfoError, FourCC, FourCCVideo},
14    resolution::Resolution,
15    sender::RawSender,
16    timecode::NDITime,
17};
18
19use super::{NDIFrame, RawBufferManagement, RawFrame, drop_guard::FrameDataDropGuard};
20
21impl RawBufferManagement for NDIRawVideoFrame {
22    #[inline]
23    unsafe fn drop_with_recv(&mut self, recv: &Arc<RawReceiver>) {
24        unsafe { bindings::NDIlib_recv_free_video_v2(recv.raw_ptr(), self) }
25    }
26
27    #[inline]
28    unsafe fn drop_with_sender(&mut self, _sender: &Arc<RawSender>) {
29        panic!(
30            "NDIRawVideoFrame cannot be dropped with a sender as it cannot be received by the sender."
31        )
32    }
33
34    fn assert_unwritten(&self) {
35        assert!(
36            self.p_data.is_null(),
37            "[Fatal FFI Error] NDIRawVideoFrame data is not null, but should be."
38        );
39        assert!(
40            self.p_metadata.is_null(),
41            "[Fatal FFI Error] NDIRawVideoFrame metadata is not null, but should be."
42        );
43    }
44}
45
46unsafe impl Send for NDIRawVideoFrame {}
47unsafe impl Sync for NDIRawVideoFrame {}
48
49impl RawFrame for NDIRawVideoFrame {}
50
51/// A Video frame
52///
53/// C equivalent: `NDIlib_video_frame_v2_t`
54pub type VideoFrame = NDIFrame<NDIRawVideoFrame>;
55
56impl Default for VideoFrame {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl VideoFrame {
63    /// Constructs a new video frame (without allocating a frame buffer)
64    pub fn new() -> Self {
65        let raw = NDIRawVideoFrame {
66            xres: 0,
67            yres: 0,
68            FourCC: FourCCVideo::UYVY.to_ffi(),
69            frame_rate_N: 30_000,
70            frame_rate_D: 1001,
71            picture_aspect_ratio: 0.0,
72            frame_format_type: NDIFieldedFrameMode::Progressive.to_ffi(),
73            timecode: bindings::NDIlib_send_timecode_synthesize,
74            p_metadata: std::ptr::null_mut(),
75            p_data: std::ptr::null_mut(),
76            __bindgen_anon_1: bindings::NDIlib_video_frame_v2_t__bindgen_ty_1 {
77                line_stride_in_bytes: 0,
78            },
79            timestamp: 0,
80        };
81        Self {
82            raw,
83            alloc: FrameDataDropGuard::NullPtr,
84            custom_state: (),
85        }
86    }
87
88    /// Generates a [BufferInfo] for the current resolution/FourCC/field mode
89    pub fn buffer_info(&self) -> Result<BufferInfo, BufferInfoError> {
90        if let Some(cc) = self.four_cc() {
91            cc.buffer_info(self.resolution(), self.field_mode())
92        } else {
93            Err(BufferInfoError::UnspecifiedFourCC)
94        }
95    }
96
97    /// Tries to allocate a frame buffer for the video frame.
98    pub fn try_alloc(&mut self) -> Result<(), VideoFrameAllocationError> {
99        if self.is_allocated() {
100            Err(VideoFrameAllocationError::AlreadyAllocated)?;
101        }
102
103        let info = self
104            .buffer_info()
105            .map_err(VideoFrameAllocationError::BufferInfoError)?;
106
107        let (alloc, ptr) = FrameDataDropGuard::new_boxed(info.size);
108        self.alloc = alloc;
109        self.raw.p_data = ptr;
110        self.raw.__bindgen_anon_1.line_stride_in_bytes = info.line_stride as i32;
111
112        Ok(())
113    }
114
115    /// Allocates a frame buffer for the video frame. **Panics** if there is an error.
116    pub fn alloc(&mut self) {
117        self.try_alloc().unwrap();
118    }
119
120    /// Deallocates the frame buffer
121    pub fn dealloc(&mut self) {
122        let drops_metadata = self.alloc.is_from_sdk();
123        unsafe { self.alloc.drop_buffer(&mut self.raw) };
124        self.raw.p_data = std::ptr::null_mut();
125        self.raw.__bindgen_anon_1.line_stride_in_bytes = -1;
126        if drops_metadata {
127            self.raw.p_metadata = std::ptr::null_mut();
128        }
129    }
130
131    /// Read access to the frame data
132    pub fn video_data(&self) -> Result<(&[u8], BufferInfo), VideoFrameAccessError> {
133        if !self.is_allocated() {
134            Err(VideoFrameAccessError::NotAllocated)?;
135        }
136
137        let info = self
138            .buffer_info()
139            .map_err(VideoFrameAccessError::BufferInfoError)?;
140
141        assert_eq!(
142            info.line_stride,
143            self.lib_stride() as usize,
144            "[Fatal FFI Error] Stride mismatch"
145        );
146
147        assert!(
148            !self.raw.p_data.is_null(),
149            "[Invariant Error] data pointer does not match allocation"
150        );
151        Ok((
152            unsafe { std::slice::from_raw_parts(self.raw.p_data, info.size) },
153            info,
154        ))
155    }
156
157    /// Mutable access to the frame data
158    pub fn video_data_mut(&mut self) -> Result<(&mut [u8], BufferInfo), VideoFrameAccessError> {
159        if !self.is_allocated() {
160            Err(VideoFrameAccessError::NotAllocated)?;
161        }
162
163        if !self.alloc.is_mut() {
164            Err(VideoFrameAccessError::Readonly)?;
165        }
166
167        let info = self
168            .buffer_info()
169            .map_err(VideoFrameAccessError::BufferInfoError)?;
170
171        assert_eq!(
172            info.line_stride,
173            self.lib_stride() as usize,
174            "[Fatal FFI Error] Stride mismatch"
175        );
176
177        assert!(
178            !self.raw.p_data.is_null(),
179            "[Invariant Error] data pointer does not match allocation"
180        );
181
182        Ok((
183            unsafe { std::slice::from_raw_parts_mut(self.raw.p_data, info.size) },
184            info,
185        ))
186    }
187}
188
189#[non_exhaustive]
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum VideoFrameAllocationError {
192    /// The frame is already allocated
193    /// You have to deallocate it first
194    AlreadyAllocated,
195    /// An error occurred while trying to compute the buffer info
196    BufferInfoError(BufferInfoError),
197}
198
199#[non_exhaustive]
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum VideoFrameAccessError {
202    /// It is impossible to get a reference to a frame buffer that does not exist
203    NotAllocated,
204    /// Only possible for {VideoFrame::video_data_mut} if the buffer is not
205    /// intended to be modified (like a received frame)
206    Readonly,
207    /// An error occurred while trying to compute the buffer info
208    BufferInfoError(BufferInfoError),
209}
210
211// Property accessors
212impl VideoFrame {
213    /// Gets the resolution of the frame.
214    pub fn resolution(&self) -> Resolution {
215        Resolution::from_i32(self.raw.xres, self.raw.yres)
216    }
217
218    /// Sets the resolution of the frame.
219    /// This will fail if the frame is already allocated.
220    pub fn set_resolution(&mut self, resolution: Resolution) -> Result<(), AlreadyAllocatedError> {
221        if self.is_allocated() {
222            Err(AlreadyAllocatedError {})
223        } else {
224            (self.raw.xres, self.raw.yres) = resolution.to_i32();
225
226            // self.raw.picture_aspect_ratio = resolution.aspect_ratio() as f32;
227            // Let NDI do this for us
228            // This assignment is necessary in case a frame is received (which sets the aspect ratio),
229            // then deallocated, and then reallocated with a different resolution.
230            self.raw.picture_aspect_ratio = 0.0;
231            Ok(())
232        }
233    }
234
235    /// Gets the FourCC format of the frame
236    pub fn four_cc(&self) -> Option<FourCCVideo> {
237        FourCCVideo::from_ffi(self.raw.FourCC)
238    }
239
240    pub fn raw_four_cc(&self) -> FourCC {
241        FourCC::from_ffi(self.raw.FourCC)
242    }
243
244    /// Sets the FourCC format of the frame.
245    /// This will fail if the frame is already allocated.
246    pub fn set_four_cc(&mut self, four_cc: FourCCVideo) -> Result<(), AlreadyAllocatedError> {
247        if self.is_allocated() {
248            Err(AlreadyAllocatedError {})
249        } else {
250            self.raw.FourCC = four_cc.to_ffi();
251            Ok(())
252        }
253    }
254
255    pub fn frame_rate(&self) -> Rational32 {
256        Rational32::new_raw(self.raw.frame_rate_N, self.raw.frame_rate_D)
257    }
258    pub fn set_frame_rate(&mut self, frame_rate: Rational32) {
259        self.raw.frame_rate_N = *frame_rate.numer();
260        self.raw.frame_rate_D = *frame_rate.denom();
261    }
262
263    /// Access the metadata associated with the frame if any
264    pub fn metadata(&self) -> Option<&CStr> {
265        if self.raw.p_metadata.is_null() {
266            None
267        } else {
268            Some(unsafe { CStr::from_ptr(self.raw.p_metadata) })
269        }
270    }
271    // TODO: pub fn set_metadata
272
273    /// gets the current field mode of the frame
274    pub fn field_mode(&self) -> NDIFieldedFrameMode {
275        NDIFieldedFrameMode::from_ffi(self.raw.frame_format_type)
276            .expect("[Fatal FFI Error] Invalid frame format type")
277    }
278    /// sets the field mode of the frame.
279    /// This will fail if the frame is already allocated.
280    pub fn set_frame_format(
281        &mut self,
282        frame_format: NDIFieldedFrameMode,
283    ) -> Result<(), AlreadyAllocatedError> {
284        if self.is_allocated() {
285            Err(AlreadyAllocatedError {})
286        } else {
287            self.raw.frame_format_type = frame_format.to_ffi();
288            Ok(())
289        }
290    }
291
292    pub fn send_time(&self) -> NDITime {
293        NDITime::from_ffi(self.raw.timecode)
294    }
295    pub fn set_send_time(&mut self, time: NDITime) {
296        self.raw.timecode = time.to_ffi();
297    }
298
299    pub fn recv_time(&self) -> NDITime {
300        NDITime::from_ffi(self.raw.timestamp)
301    }
302    pub fn set_recv_time(&mut self, time: NDITime) {
303        self.raw.timestamp = time.to_ffi();
304    }
305
306    /// This is not relevant until you do stuff with the allocation
307    fn lib_stride(&self) -> i32 {
308        unsafe { self.raw.__bindgen_anon_1.line_stride_in_bytes }
309    }
310}
311
312#[derive(Debug, Clone)]
313pub struct AlreadyAllocatedError {}
314
315// Dangerous APIs
316#[cfg(feature = "dangerous_apis")]
317impl VideoFrame {
318    /// Forcefully sets the resolution even if the frame is allocated.
319    ///
320    /// <div class="warning">
321    /// This API is extremely dangerous and should only be used with extreme caution.
322    ///
323    /// If used incorrectly, it can lead to memory corruption by out-of-bounds access.
324    /// </div>
325    pub unsafe fn force_set_resolution(&mut self, resolution: Resolution) {
326        (self.raw.xres, self.raw.yres) = resolution.to_i32();
327        self.raw.picture_aspect_ratio = resolution.aspect_ratio() as f32;
328    }
329
330    /// Forcefully sets the FourCC even if the frame is allocated.
331    ///
332    /// <div class="warning">
333    /// This API is extremely dangerous and should only be used with extreme caution.
334    ///
335    /// If used incorrectly, it can lead to memory corruption by out-of-bounds access.
336    /// </div>
337    pub unsafe fn force_set_four_cc(&mut self, four_cc: FourCCVideo) {
338        self.raw.FourCC = four_cc.to_ffi();
339    }
340
341    /// Forcefully sets the FourCC even if the frame is allocated.
342    ///
343    /// <div class="warning">
344    /// This API is extremely dangerous and should only be used with extreme caution.
345    ///
346    /// If used incorrectly, it can lead to memory corruption by out-of-bounds access.
347    /// </div>
348    pub unsafe fn force_set_raw_four_cc(&mut self, four_cc: FourCC) {
349        self.raw.FourCC = four_cc.to_ffi();
350    }
351
352    /// Forcefully sets the field mode even if the frame is allocated.
353    ///
354    /// <div class="warning">
355    /// This API is extremely dangerous and should only be used with extreme caution.
356    ///
357    /// If used incorrectly, it can lead to memory corruption by out-of-bounds access.
358    /// </div>
359    pub unsafe fn force_set_frame_format(&mut self, frame_format: NDIFieldedFrameMode) {
360        self.raw.frame_format_type = frame_format.to_ffi();
361    }
362
363    ///
364    /// <div class="warning">
365    /// This API is extremely dangerous and should only be used with extreme caution.
366    ///
367    /// If used incorrectly, it can lead to memory corruption by out-of-bounds access.
368    /// </div>
369    pub unsafe fn set_lib_stride(&mut self, stride: i32) {
370        self.raw.__bindgen_anon_1.line_stride_in_bytes = stride;
371    }
372}
373
374impl Debug for VideoFrame {
375    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376        write!(f, "VideoFrame {{ ")?;
377
378        write!(f, "resolution: {}x{}, ", self.raw.xres, self.raw.yres)?;
379
380        write!(
381            f,
382            "frame rate: {:.2}fps, ",
383            self.frame_rate().to_f64().unwrap_or(-1.)
384        )?;
385
386        if let Some(cc) = self.four_cc() {
387            write!(f, "FourCC: {:?}, ", cc)?;
388        } else {
389            write!(f, "FourCC: {:#x}, ", self.raw.FourCC)?;
390        }
391
392        write!(f, "format: {:?}, ", self.field_mode())?;
393
394        write!(f, "stride: {}, ", self.lib_stride())?;
395
396        write!(f, "metadata: {:?}, ", self.metadata())?;
397
398        write!(
399            f,
400            "timing: send={:?} recv={:?}, ",
401            self.send_time(),
402            self.recv_time()
403        )?;
404
405        write!(f, "alloc: {:?} @ {:?} }}", self.raw.p_data, self.alloc)
406    }
407}