ffmpeg/
swscale.rs

1// Copyright 2022 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
9use thiserror::Error as ThisError;
10
11use crate::avcodec::AvError;
12use crate::avcodec::AvFrame;
13use crate::avcodec::Dimensions;
14use crate::ffi;
15
16/// A struct able to copy a decoded `AvFrame` into an `OutputBuffer`'s memory, converting the pixel
17/// format if needed.
18pub 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)]
25pub 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
39impl Drop for SwConverter {
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
47impl SwConverter {
48    /// Create a new format converter that will convert frames from `src_format` to `dst_format`.
49    ///
50    /// `width` and `height` are the coded size of the frames to be converted. The source and target
51    /// must have the same size in pixels.
52    pub fn new(
53        width: usize,
54        height: usize,
55        src_pix_format: ffi::AVPixelFormat,
56        dst_pix_format: ffi::AVPixelFormat,
57    ) -> anyhow::Result<Self> {
58        // SAFETY:
59        // Safe because we don't pass any non-null pointer to this function.
60        let sws_context = unsafe {
61            ffi::sws_getContext(
62                width as i32,
63                height as i32,
64                src_pix_format,
65                width as i32,
66                height as i32,
67                dst_pix_format,
68                0,
69                std::ptr::null_mut(),
70                std::ptr::null_mut(),
71                std::ptr::null_mut(),
72            )
73        };
74
75        if sws_context.is_null() {
76            anyhow::bail!("error while creating the SWS context")
77        }
78
79        Ok(Self {
80            sws_context,
81            src_pix_format,
82            dst_pix_format,
83        })
84    }
85
86    /// Copy `src` into `dst` while converting its pixel format according to the parameters the
87    /// frame converter was created with.
88    ///
89    /// `dst` must be a [writable] frame with the same dimensions as `src` and the same format as
90    /// `dst_pix_format` passed to the constructor.
91    ///
92    /// Note that empty `dst` is not currently allowed as this function does not handle allocation.
93    ///
94    /// [writable]: AvFrame::is_writable
95    pub fn convert(&mut self, src: &AvFrame, dst: &mut AvFrame) -> Result<(), ConversionError> {
96        if src.format != self.src_pix_format {
97            return Err(ConversionError::FormatMismatch {
98                frame: src.format,
99                converter: self.src_pix_format,
100            });
101        }
102
103        if dst.format != self.dst_pix_format {
104            return Err(ConversionError::FormatMismatch {
105                frame: dst.format,
106                converter: self.dst_pix_format,
107            });
108        }
109
110        if src.dimensions() != dst.dimensions() {
111            return Err(ConversionError::DimensionMismatch(
112                src.dimensions(),
113                dst.dimensions(),
114            ));
115        }
116
117        if !dst.is_writable() {
118            return Err(ConversionError::NotWritable);
119        }
120
121        // SAFETY:
122        // Safe because `sws_context`, `src_ref.data` and `dst_data` are all valid pointers, and
123        // we made sure the sizes provided are within the bounds of the buffers.
124        AvError::result(unsafe {
125            ffi::sws_scale(
126                self.sws_context,
127                src.data.as_ptr() as *const *const u8,
128                src.linesize.as_ptr(),
129                0,
130                src.height,
131                dst.data.as_ptr(),
132                dst.linesize.as_ptr(),
133            )
134        })
135        .map_err(Into::into)
136    }
137}