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)
    }
}