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