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