scuffle_ffmpeg/
scaler.rs

1use crate::error::{FfmpegError, FfmpegErrorCode};
2use crate::ffi::*;
3use crate::frame::VideoFrame;
4use crate::smart_object::SmartPtr;
5use crate::AVPixelFormat;
6
7/// A scaler is a wrapper around an [`SwsContext`]. Which is used to scale or transform video frames.
8pub struct VideoScaler {
9    ptr: SmartPtr<SwsContext>,
10    frame: VideoFrame,
11    pixel_format: AVPixelFormat,
12    width: i32,
13    height: i32,
14}
15
16/// Safety: `Scaler` is safe to send between threads.
17unsafe impl Send for VideoScaler {}
18
19impl VideoScaler {
20    /// Creates a new `Scaler` instance.
21    pub fn new(
22        input_width: i32,
23        input_height: i32,
24        incoming_pixel_fmt: AVPixelFormat,
25        width: i32,
26        height: i32,
27        pixel_format: AVPixelFormat,
28    ) -> Result<Self, FfmpegError> {
29        // Safety: `sws_getContext` is safe to call, and the pointer returned is valid.
30        let ptr = unsafe {
31            sws_getContext(
32                input_width,
33                input_height,
34                incoming_pixel_fmt.into(),
35                width,
36                height,
37                pixel_format.into(),
38                SWS_BILINEAR as i32,
39                std::ptr::null_mut(),
40                std::ptr::null_mut(),
41                std::ptr::null(),
42            )
43        };
44
45        let destructor = |ptr: &mut *mut SwsContext| {
46            // Safety: `sws_freeContext` is safe to call.
47            unsafe {
48                sws_freeContext(*ptr);
49            }
50
51            *ptr = std::ptr::null_mut();
52        };
53
54        // Safety: `ptr` is a valid pointer & `destructor` has been setup to free the context.
55        let ptr = unsafe { SmartPtr::wrap_non_null(ptr, destructor) }.ok_or(FfmpegError::Alloc)?;
56
57        let mut frame = VideoFrame::new()?;
58
59        frame.set_width(width as usize);
60        frame.set_height(height as usize);
61        frame.set_format(pixel_format.into());
62
63        // Safety: `av_frame_get_buffer` is safe to call.
64        unsafe { frame.alloc_frame_buffer(Some(32)) }.expect("Failed to allocate frame buffer");
65
66        Ok(Self {
67            ptr,
68            frame,
69            pixel_format,
70            width,
71            height,
72        })
73    }
74
75    /// Returns the pixel format of the scalar.
76    pub const fn pixel_format(&self) -> AVPixelFormat {
77        self.pixel_format
78    }
79
80    /// Returns the width of the scalar.
81    pub const fn width(&self) -> i32 {
82        self.width
83    }
84
85    /// Returns the height of the scalar.
86    pub const fn height(&self) -> i32 {
87        self.height
88    }
89
90    /// Processes a frame through the scalar.
91    pub fn process<'a>(&'a mut self, frame: &VideoFrame) -> Result<&'a VideoFrame, FfmpegError> {
92        // Safety: `frame` is a valid pointer, and `self.ptr` is a valid pointer.
93        let frame_ptr = unsafe { frame.as_ptr().as_ref().unwrap() };
94        // Safety: `self.frame` is a valid pointer.
95        let self_frame_ptr = unsafe { self.frame.as_ptr().as_ref().unwrap() };
96
97        // Safety: `sws_scale` is safe to call.
98        FfmpegErrorCode(unsafe {
99            sws_scale(
100                self.ptr.as_mut_ptr(),
101                frame_ptr.data.as_ptr() as *const *const u8,
102                frame_ptr.linesize.as_ptr(),
103                0,
104                frame_ptr.height,
105                self_frame_ptr.data.as_ptr(),
106                self_frame_ptr.linesize.as_ptr(),
107            )
108        })
109        .result()?;
110
111        // Copy the other fields from the input frame to the output frame.
112        self.frame.set_dts(frame.dts());
113        self.frame.set_pts(frame.pts());
114        self.frame.set_duration(frame.duration());
115        self.frame.set_time_base(frame.time_base());
116
117        Ok(&self.frame)
118    }
119}
120
121#[cfg(test)]
122#[cfg_attr(all(test, coverage_nightly), coverage(off))]
123mod tests {
124    use insta::assert_debug_snapshot;
125    use rand::Rng;
126
127    use crate::frame::VideoFrame;
128    use crate::scaler::{AVPixelFormat, VideoScaler};
129
130    #[test]
131    fn test_scalar_new() {
132        let input_width = 1920;
133        let input_height = 1080;
134        let incoming_pixel_fmt = AVPixelFormat::Yuv420p;
135        let output_width = 1280;
136        let output_height = 720;
137        let output_pixel_fmt = AVPixelFormat::Rgb24;
138        let scalar = VideoScaler::new(
139            input_width,
140            input_height,
141            incoming_pixel_fmt,
142            output_width,
143            output_height,
144            output_pixel_fmt,
145        );
146
147        assert!(scalar.is_ok(), "Expected Scalar::new to succeed");
148        let scalar = scalar.unwrap();
149
150        assert_eq!(
151            scalar.width(),
152            output_width,
153            "Expected Scalar width to match the output width"
154        );
155        assert_eq!(
156            scalar.height(),
157            output_height,
158            "Expected Scalar height to match the output height"
159        );
160        assert_eq!(
161            scalar.pixel_format(),
162            output_pixel_fmt,
163            "Expected Scalar pixel format to match the output pixel format"
164        );
165    }
166
167    #[test]
168    fn test_scalar_process() {
169        let input_width = 1920;
170        let input_height = 1080;
171        let incoming_pixel_fmt = AVPixelFormat::Yuv420p;
172        let output_width = 1280;
173        let output_height = 720;
174        let output_pixel_fmt = AVPixelFormat::Rgb24;
175
176        let mut scalar = VideoScaler::new(
177            input_width,
178            input_height,
179            incoming_pixel_fmt,
180            output_width,
181            output_height,
182            output_pixel_fmt,
183        )
184        .expect("Failed to create Scalar");
185
186        let mut input_frame: VideoFrame = VideoFrame::new().expect("Failed to create Frame");
187        // Safety: `input_frame` is a valid pointer
188        input_frame.set_width(input_width as usize);
189        input_frame.set_height(input_height as usize);
190        input_frame.set_format(incoming_pixel_fmt.into());
191
192        // Safety: `av_frame_get_buffer` is safe to call.
193        unsafe { input_frame.alloc_frame_buffer(Some(32)) }.expect("Failed to allocate frame buffer");
194
195        // We need to fill the buffer with random data otherwise the result will be based off uninitialized data.
196        let mut rng = rand::rng();
197
198        for y in 0..input_height {
199            // Safety: `frame_mut.data[0]` is a valid pointer
200            let y = (y * input_frame.linesize(0).unwrap()) as usize;
201            let row = &mut input_frame.data_mut(0).unwrap()[y..y + input_width as usize];
202            rng.fill(row);
203        }
204
205        let half_height = (input_height + 1) / 2;
206        let half_width = (input_width + 1) / 2;
207
208        for y in 0..half_height {
209            let y = (y * input_frame.linesize(1).unwrap()) as usize;
210            let row = &mut input_frame.data_mut(1).unwrap()[y..y + half_width as usize];
211            rng.fill(row);
212        }
213
214        for y in 0..half_height {
215            let y = (y * input_frame.linesize(2).unwrap()) as usize;
216            let row = &mut input_frame.data_mut(2).unwrap()[y..y + half_width as usize];
217            rng.fill(row);
218        }
219
220        let result = scalar.process(&input_frame);
221
222        assert!(
223            result.is_ok(),
224            "Expected Scalar::process to succeed, but got error: {:?}",
225            result
226        );
227
228        let output_frame = result.unwrap();
229        assert_debug_snapshot!(output_frame, @r"
230        VideoFrame {
231            width: 1280,
232            height: 720,
233            sample_aspect_ratio: Rational {
234                numerator: 0,
235                denominator: 1,
236            },
237            pts: None,
238            dts: None,
239            duration: Some(
240                0,
241            ),
242            best_effort_timestamp: None,
243            time_base: Rational {
244                numerator: 0,
245                denominator: 1,
246            },
247            format: AVPixelFormat::Rgb24,
248            is_audio: false,
249            is_video: true,
250            is_keyframe: false,
251        }
252        ");
253    }
254}