xref: /aosp_15_r20/external/virtio-media/extras/ffmpeg-decoder/src/ffmpeg/swscale.rs (revision 1b4853f54772485c5dd4001ae33a7a958bcc97a1)
1 // Copyright 2022-2024 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! This module implements a lightweight and safe interface over the conversion functions of
6 //! `libswscale`. It is designed to concentrate all calls to unsafe methods in one place, while
7 //! providing a higher-level interface for converting decoded frames from one format to another.
8 
9 use thiserror::Error as ThisError;
10 
11 use crate::ffmpeg::avcodec::AvError;
12 use crate::ffmpeg::avcodec::AvFrame;
13 use crate::ffmpeg::avcodec::Dimensions;
14 use crate::ffmpeg::ffi;
15 
16 /// A struct able to copy a decoded `AvFrame` into an `OutputBuffer`'s memory, converting the pixel
17 /// format if needed.
18 pub struct SwConverter {
19     sws_context: *mut ffi::SwsContext,
20     src_pix_format: ffi::AVPixelFormat,
21     dst_pix_format: ffi::AVPixelFormat,
22 }
23 
24 #[derive(Debug, ThisError)]
25 pub enum ConversionError {
26     #[error("AvFrame's format {frame} does not match converter {converter} configuration")]
27     FormatMismatch {
28         frame: ffi::AVPixelFormat,
29         converter: ffi::AVPixelFormat,
30     },
31     #[error("source AvFrame's dimension {0:?} does not match destination's {1:?}")]
32     DimensionMismatch(Dimensions, Dimensions),
33     #[error("destination AvFrame needs to be refcounted with refcount=1")]
34     NotWritable,
35     #[error("error during conversion with libswscale: {0}")]
36     AvError(#[from] AvError),
37 }
38 
39 impl Drop for SwConverter {
drop(&mut self)40     fn drop(&mut self) {
41         // SAFETY:
42         // Safe because `sws_context` is valid through the life of this object.
43         unsafe { ffi::sws_freeContext(self.sws_context) };
44     }
45 }
46 
47 #[derive(Debug, ThisError)]
48 pub enum SwConverterCreationError {
49     #[error("sws_getContext returned NULL")]
50     CreationFailure,
51 }
52 
53 impl SwConverter {
54     /// Create a new format converter that will convert frames from `src_format` to `dst_format`.
55     ///
56     /// `width` and `height` are the coded size of the frames to be converted. The source and target
57     /// must have the same size in pixels.
new( width: usize, height: usize, src_pix_format: ffi::AVPixelFormat, dst_pix_format: ffi::AVPixelFormat, ) -> Result<Self, SwConverterCreationError>58     pub fn new(
59         width: usize,
60         height: usize,
61         src_pix_format: ffi::AVPixelFormat,
62         dst_pix_format: ffi::AVPixelFormat,
63     ) -> Result<Self, SwConverterCreationError> {
64         // SAFETY:
65         // Safe because we don't pass any non-null pointer to this function.
66         let sws_context = unsafe {
67             ffi::sws_getContext(
68                 width as i32,
69                 height as i32,
70                 src_pix_format,
71                 width as i32,
72                 height as i32,
73                 dst_pix_format,
74                 0,
75                 std::ptr::null_mut(),
76                 std::ptr::null_mut(),
77                 std::ptr::null_mut(),
78             )
79         };
80 
81         if sws_context.is_null() {
82             Err(SwConverterCreationError::CreationFailure)
83         } else {
84             Ok(Self {
85                 sws_context,
86                 src_pix_format,
87                 dst_pix_format,
88             })
89         }
90     }
91 
92     /// Copy `src` into `dst` while converting its pixel format according to the parameters the
93     /// frame converter was created with.
94     ///
95     /// `dst` must be a [writable] frame with the same dimensions as `src` and the same format as
96     /// `dst_pix_format` passed to the constructor.
97     ///
98     /// Note that empty `dst` is not currently allowed as this function does not handle allocation.
99     ///
100     /// [writable]: AvFrame::is_writable
convert(&mut self, src: &AvFrame, dst: &mut AvFrame) -> Result<(), ConversionError>101     pub fn convert(&mut self, src: &AvFrame, dst: &mut AvFrame) -> Result<(), ConversionError> {
102         if src.format != self.src_pix_format {
103             return Err(ConversionError::FormatMismatch {
104                 frame: src.format,
105                 converter: self.src_pix_format,
106             });
107         }
108 
109         if dst.format != self.dst_pix_format {
110             return Err(ConversionError::FormatMismatch {
111                 frame: dst.format,
112                 converter: self.dst_pix_format,
113             });
114         }
115 
116         if src.dimensions() != dst.dimensions() {
117             return Err(ConversionError::DimensionMismatch(
118                 src.dimensions(),
119                 dst.dimensions(),
120             ));
121         }
122 
123         if !dst.is_writable() {
124             return Err(ConversionError::NotWritable);
125         }
126 
127         // SAFETY:
128         // Safe because `sws_context`, `src_ref.data` and `dst_data` are all valid pointers, and
129         // we made sure the sizes provided are within the bounds of the buffers.
130         AvError::result(unsafe {
131             ffi::sws_scale(
132                 self.sws_context,
133                 src.data.as_ptr() as *const *const u8,
134                 src.linesize.as_ptr(),
135                 0,
136                 src.height,
137                 dst.data.as_ptr(),
138                 dst.linesize.as_ptr(),
139             )
140         })
141         .map_err(Into::into)
142     }
143 }
144