1use crate::AVPixelFormat;
2use crate::error::{FfmpegError, FfmpegErrorCode};
3use crate::ffi::*;
4use crate::frame::VideoFrame;
5use crate::smart_object::SmartPtr;
6
7pub struct VideoScaler {
9 ptr: SmartPtr<SwsContext>,
10 frame: VideoFrame,
11 pixel_format: AVPixelFormat,
12 width: i32,
13 height: i32,
14}
15
16unsafe impl Send for VideoScaler {}
18
19impl VideoScaler {
20 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 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 unsafe {
48 sws_freeContext(*ptr);
49 }
50
51 *ptr = std::ptr::null_mut();
52 };
53
54 let ptr = unsafe { SmartPtr::wrap_non_null(ptr, destructor) }.ok_or(FfmpegError::Alloc)?;
56
57 let frame = VideoFrame::builder()
58 .width(width)
59 .height(height)
60 .pix_fmt(pixel_format)
61 .build()?;
62
63 Ok(Self {
64 ptr,
65 frame,
66 pixel_format,
67 width,
68 height,
69 })
70 }
71
72 pub const fn pixel_format(&self) -> AVPixelFormat {
74 self.pixel_format
75 }
76
77 pub const fn width(&self) -> i32 {
79 self.width
80 }
81
82 pub const fn height(&self) -> i32 {
84 self.height
85 }
86
87 pub fn process<'a>(&'a mut self, frame: &VideoFrame) -> Result<&'a VideoFrame, FfmpegError> {
89 let frame_ptr = unsafe { frame.as_ptr().as_ref().unwrap() };
91 let self_frame_ptr = unsafe { self.frame.as_ptr().as_ref().unwrap() };
93
94 FfmpegErrorCode(unsafe {
96 sws_scale(
97 self.ptr.as_mut_ptr(),
98 frame_ptr.data.as_ptr() as *const *const u8,
99 frame_ptr.linesize.as_ptr(),
100 0,
101 frame_ptr.height,
102 self_frame_ptr.data.as_ptr(),
103 self_frame_ptr.linesize.as_ptr(),
104 )
105 })
106 .result()?;
107
108 self.frame.set_dts(frame.dts());
110 self.frame.set_pts(frame.pts());
111 self.frame.set_duration(frame.duration());
112 self.frame.set_time_base(frame.time_base());
113
114 Ok(&self.frame)
115 }
116}
117
118#[cfg(test)]
119#[cfg_attr(all(test, coverage_nightly), coverage(off))]
120mod tests {
121 use insta::assert_debug_snapshot;
122 use rand::Rng;
123
124 use crate::frame::VideoFrame;
125 use crate::scaler::{AVPixelFormat, VideoScaler};
126
127 #[test]
128 fn test_scalar_new() {
129 let input_width = 1920;
130 let input_height = 1080;
131 let incoming_pixel_fmt = AVPixelFormat::Yuv420p;
132 let output_width = 1280;
133 let output_height = 720;
134 let output_pixel_fmt = AVPixelFormat::Rgb24;
135 let scalar = VideoScaler::new(
136 input_width,
137 input_height,
138 incoming_pixel_fmt,
139 output_width,
140 output_height,
141 output_pixel_fmt,
142 );
143
144 assert!(scalar.is_ok(), "Expected Scalar::new to succeed");
145 let scalar = scalar.unwrap();
146
147 assert_eq!(
148 scalar.width(),
149 output_width,
150 "Expected Scalar width to match the output width"
151 );
152 assert_eq!(
153 scalar.height(),
154 output_height,
155 "Expected Scalar height to match the output height"
156 );
157 assert_eq!(
158 scalar.pixel_format(),
159 output_pixel_fmt,
160 "Expected Scalar pixel format to match the output pixel format"
161 );
162 }
163
164 #[test]
165 fn test_scalar_process() {
166 let input_width = 1920;
167 let input_height = 1080;
168 let incoming_pixel_fmt = AVPixelFormat::Yuv420p;
169 let output_width = 1280;
170 let output_height = 720;
171 let output_pixel_fmt = AVPixelFormat::Rgb24;
172
173 let mut scalar = VideoScaler::new(
174 input_width,
175 input_height,
176 incoming_pixel_fmt,
177 output_width,
178 output_height,
179 output_pixel_fmt,
180 )
181 .expect("Failed to create Scalar");
182
183 let mut input_frame = VideoFrame::builder()
184 .width(input_width)
185 .height(input_height)
186 .pix_fmt(incoming_pixel_fmt)
187 .build()
188 .expect("Failed to create VideoFrame");
189
190 let mut rng = rand::rng();
192
193 for data_idx in 0..rusty_ffmpeg::ffi::AV_NUM_DATA_POINTERS {
194 if let Some(mut data_buf) = input_frame.data_mut(data_idx as usize) {
195 for row_idx in 0..data_buf.height() {
196 let row = data_buf.get_row_mut(row_idx as usize).unwrap();
197 rng.fill(row);
198 }
199 }
200 }
201
202 let result = scalar.process(&input_frame);
203
204 assert!(
205 result.is_ok(),
206 "Expected Scalar::process to succeed, but got error: {result:?}"
207 );
208
209 let output_frame = result.unwrap();
210 assert_debug_snapshot!(output_frame, @r"
211 VideoFrame {
212 width: 1280,
213 height: 720,
214 sample_aspect_ratio: Rational {
215 numerator: 1,
216 denominator: 1,
217 },
218 pts: None,
219 dts: None,
220 duration: Some(
221 0,
222 ),
223 best_effort_timestamp: None,
224 time_base: Rational {
225 numerator: 0,
226 denominator: 1,
227 },
228 format: AVPixelFormat::Rgb24,
229 is_audio: false,
230 is_video: true,
231 is_keyframe: false,
232 }
233 ");
234 }
235}