scuffle_ffmpeg/
codec.rs

1use rusty_ffmpeg::ffi::*;
2
3use crate::AVCodecID;
4
5/// A wrapper around an [`AVCodec`] pointer.
6///
7/// This is specifically used for decoders. The most typical way to use this is to create it from a [`AVCodecID`] or to search for it by name.
8#[derive(Clone, Copy, PartialEq, Eq)]
9pub struct DecoderCodec(*const AVCodec);
10
11impl std::fmt::Debug for DecoderCodec {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        // Safety: The pointer here is valid.
14        if let Some(codec) = unsafe { self.0.as_ref() } {
15            // Safety: The pointer here is valid.
16            let name = unsafe { std::ffi::CStr::from_ptr(codec.name) };
17
18            f.debug_struct("DecoderCodec")
19                .field("name", &name)
20                .field("id", &codec.id)
21                .finish()
22        } else {
23            f.debug_struct("DecoderCodec")
24                .field("name", &"null")
25                .field("id", &AVCodecID::None)
26                .finish()
27        }
28    }
29}
30
31impl DecoderCodec {
32    /// Creates an empty [`DecoderCodec`].
33    pub const fn empty() -> Self {
34        Self(std::ptr::null())
35    }
36
37    /// Returns true if the [`DecoderCodec`] is empty.
38    pub const fn is_empty(&self) -> bool {
39        self.0.is_null()
40    }
41
42    /// Creates a [`DecoderCodec`] from a [`AVCodecID`].
43    pub fn new(codec_id: AVCodecID) -> Option<Self> {
44        // Safety: `avcodec_find_decoder` is safe to call.
45        let codec = unsafe { avcodec_find_decoder(codec_id.0 as crate::ffi::AVCodecID) };
46        if codec.is_null() { None } else { Some(Self(codec)) }
47    }
48
49    /// Creates a [`DecoderCodec`] from a codec name.
50    pub fn by_name(name: &str) -> Option<Self> {
51        let c_name = std::ffi::CString::new(name).ok()?;
52
53        // Safety: `avcodec_find_decoder_by_name` is safe to call with a valid c-string.
54        let codec = unsafe { avcodec_find_decoder_by_name(c_name.as_ptr()) };
55        if codec.is_null() { None } else { Some(Self(codec)) }
56    }
57
58    /// Returns the raw pointer to the [`AVCodec`].
59    pub const fn as_ptr(&self) -> *const AVCodec {
60        self.0
61    }
62
63    /// Creates a [`DecoderCodec`] from a raw pointer.
64    ///
65    /// # Safety
66    /// The provided pointer must either be null or point to a valid [`AVCodec`].
67    pub const unsafe fn from_ptr(ptr: *const AVCodec) -> Self {
68        Self(ptr)
69    }
70}
71
72/// A wrapper around an [`AVCodec`] pointer.
73///
74/// This is specifically used for encoders. The most typical way to use this is to create it from a [`AVCodecID`] or to search for it by name.
75#[derive(Clone, Copy, PartialEq, Eq)]
76pub struct EncoderCodec(*const AVCodec);
77
78impl std::fmt::Debug for EncoderCodec {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        // Safety: The pointer here is valid.
81        if let Some(codec) = unsafe { self.0.as_ref() } {
82            // Safety: The pointer here is valid.
83            let name = unsafe { std::ffi::CStr::from_ptr(codec.name) };
84
85            f.debug_struct("EncoderCodec")
86                .field("name", &name)
87                .field("id", &codec.id)
88                .finish()
89        } else {
90            f.debug_struct("EncoderCodec")
91                .field("name", &"null")
92                .field("id", &AVCodecID::None)
93                .finish()
94        }
95    }
96}
97
98impl EncoderCodec {
99    /// Creates an empty [`EncoderCodec`].
100    pub const fn empty() -> Self {
101        Self(std::ptr::null())
102    }
103
104    /// Returns true if the [`EncoderCodec`] is empty.
105    pub const fn is_empty(&self) -> bool {
106        self.0.is_null()
107    }
108
109    /// Creates an [`EncoderCodec`] from a [`AVCodecID`].
110    pub fn new(codec_id: AVCodecID) -> Option<Self> {
111        // Safety: `avcodec_find_encoder` is safe to call.
112        let codec = unsafe { avcodec_find_encoder(codec_id.0 as crate::ffi::AVCodecID) };
113        if codec.is_null() { None } else { Some(Self(codec)) }
114    }
115
116    /// Creates an [`EncoderCodec`] from a codec name.
117    pub fn by_name(name: &str) -> Option<Self> {
118        let c_name = std::ffi::CString::new(name).ok()?;
119        // Safety: `avcodec_find_encoder_by_name` is safe to call with a valid c-string.
120        let codec = unsafe { avcodec_find_encoder_by_name(c_name.as_ptr()) };
121        if codec.is_null() { None } else { Some(Self(codec)) }
122    }
123
124    /// Returns the raw pointer to the [`AVCodec`].
125    pub const fn as_ptr(&self) -> *const AVCodec {
126        self.0
127    }
128
129    /// Creates an [`EncoderCodec`] from a raw pointer.
130    ///
131    /// # Safety
132    /// The provided pointer must either be null or point to a valid [`AVCodec`].
133    pub const unsafe fn from_ptr(ptr: *const AVCodec) -> Self {
134        Self(ptr)
135    }
136}
137
138impl From<EncoderCodec> for *const AVCodec {
139    fn from(codec: EncoderCodec) -> Self {
140        codec.0
141    }
142}
143
144impl From<DecoderCodec> for *const AVCodec {
145    fn from(codec: DecoderCodec) -> Self {
146        codec.0
147    }
148}
149
150#[cfg(test)]
151#[cfg_attr(all(test, coverage_nightly), coverage(off))]
152mod tests {
153    use crate::codec::{AVCodecID, DecoderCodec, EncoderCodec};
154    use crate::ffi::{AVCodec, avcodec_find_decoder, avcodec_find_encoder};
155
156    #[test]
157    fn test_decoder_codec_debug_null() {
158        let decoder_codec = DecoderCodec::empty();
159        let debug_output = format!("{decoder_codec:?}");
160
161        insta::assert_snapshot!(debug_output, @r#"DecoderCodec { name: "null", id: AVCodecID::None }"#);
162    }
163
164    #[test]
165    fn test_decoder_codec_debug_non_null() {
166        let decoder_codec = DecoderCodec::new(AVCodecID::H264).expect("H264 codec should be available");
167        let debug_output = format!("{decoder_codec:?}");
168
169        insta::assert_snapshot!(debug_output, @r#"DecoderCodec { name: "h264", id: 27 }"#);
170    }
171
172    #[test]
173    fn test_decoder_codec_new_invalid_codec_id() {
174        let invalid_codec_id = AVCodecID::None;
175        let result = DecoderCodec::new(invalid_codec_id);
176
177        assert!(
178            result.is_none(),
179            "Expected `DecoderCodec::new` to return None for an invalid codec ID"
180        );
181    }
182
183    #[test]
184    fn test_decoder_codec_by_name_valid() {
185        let result = DecoderCodec::by_name("h264");
186
187        assert!(
188            result.is_some(),
189            "Expected `DecoderCodec::by_name` to return Some for a valid codec name"
190        );
191
192        let codec = result.unwrap();
193        assert!(!codec.as_ptr().is_null(), "Expected a non-null codec pointer");
194    }
195
196    #[test]
197    fn test_decoder_codec_by_name_invalid() {
198        let invalid_codec_name = "nonexistent_codec";
199        let result = DecoderCodec::by_name(invalid_codec_name);
200
201        assert!(
202            result.is_none(),
203            "Expected `DecoderCodec::by_name` to return None for an invalid codec name"
204        );
205    }
206
207    #[test]
208    fn test_decoder_codec_from_ptr_valid() {
209        // Safety: `avcodec_find_decoder` is safe to call.
210        let codec_ptr = unsafe { avcodec_find_decoder(AVCodecID::H264.into()) };
211        assert!(!codec_ptr.is_null(), "Expected a valid codec pointer for H264");
212
213        // Safety: The pointer was allocated by `avcodec_find_decoder` and is valid.
214        let decoder_codec = unsafe { DecoderCodec::from_ptr(codec_ptr) };
215        assert_eq!(
216            decoder_codec.as_ptr(),
217            codec_ptr,
218            "Expected the codec pointer in DecoderCodec to match the original pointer"
219        );
220    }
221
222    #[test]
223    fn test_encoder_codec_debug_valid() {
224        // Safety: `avcodec_find_encoder` is safe to call.
225        let codec_ptr = unsafe { avcodec_find_encoder(AVCodecID::Mpeg4.into()) };
226
227        assert!(!codec_ptr.is_null(), "Expected a valid codec pointer for MPEG4");
228
229        let encoder_codec = EncoderCodec(codec_ptr);
230        insta::assert_debug_snapshot!(encoder_codec, @r#"
231        EncoderCodec {
232            name: "mpeg4",
233            id: 12,
234        }
235        "#);
236    }
237
238    #[test]
239    fn test_encoder_codec_debug_null() {
240        let encoder_codec = EncoderCodec(std::ptr::null());
241        insta::assert_debug_snapshot!(encoder_codec, @r#"
242        EncoderCodec {
243            name: "null",
244            id: AVCodecID::None,
245        }
246        "#);
247    }
248
249    #[test]
250    fn test_encoder_codec_empty() {
251        let encoder_codec = EncoderCodec::empty();
252        assert!(
253            encoder_codec.as_ptr().is_null(),
254            "Expected the encoder codec pointer to be null"
255        );
256
257        insta::assert_debug_snapshot!(encoder_codec, @r#"
258        EncoderCodec {
259            name: "null",
260            id: AVCodecID::None,
261        }
262        "#);
263    }
264
265    #[test]
266    fn test_encoder_codec_new_invalid_codec() {
267        let invalid_codec_id = AVCodecID::None;
268        let result = EncoderCodec::new(invalid_codec_id);
269
270        assert!(result.is_none(), "Expected None for an invalid codec ID");
271    }
272
273    #[test]
274    fn test_encoder_codec_by_name_valid() {
275        let result = EncoderCodec::by_name("mpeg4");
276        assert!(result.is_some(), "Expected a valid encoder codec for the name {}", "mpeg4");
277
278        let encoder_codec = result.unwrap();
279        assert!(!encoder_codec.as_ptr().is_null(), "Expected a non-null encoder codec pointer");
280    }
281
282    #[test]
283    fn test_encoder_codec_by_name_invalid() {
284        let invalid_encoder_name = "invalid_encoder_name";
285        let result = EncoderCodec::by_name(invalid_encoder_name);
286
287        assert!(
288            result.is_none(),
289            "Expected None for an invalid encoder name {invalid_encoder_name}"
290        );
291    }
292
293    #[test]
294    fn test_encoder_codec_into_raw_ptr() {
295        let valid_codec_id = AVCodecID::Aac;
296        let encoder_codec = EncoderCodec::new(valid_codec_id).expect("Expected a valid encoder codec for AAC");
297        let raw_ptr: *const AVCodec = encoder_codec.into();
298
299        assert_eq!(
300            raw_ptr,
301            encoder_codec.as_ptr(),
302            "The raw pointer should match the encoder codec's internal pointer"
303        );
304    }
305
306    #[test]
307    fn test_decoder_codec_into_raw_ptr() {
308        let valid_codec_id = AVCodecID::Aac;
309        let decoder_codec = DecoderCodec::new(valid_codec_id).expect("Expected a valid decoder codec for AAC");
310        let raw_ptr: *const AVCodec = decoder_codec.into();
311
312        assert_eq!(
313            raw_ptr,
314            decoder_codec.as_ptr(),
315            "The raw pointer should match the decoder codec's internal pointer"
316        );
317    }
318
319    #[test]
320    fn test_codec_into_raw_ptr_empty() {
321        let empty_encoder_codec = EncoderCodec::empty();
322        let raw_ptr: *const AVCodec = empty_encoder_codec.into();
323        assert!(raw_ptr.is_null(), "The raw pointer should be null for an empty EncoderCodec");
324
325        let empty_decoder_codec = DecoderCodec::empty();
326        let raw_ptr: *const AVCodec = empty_decoder_codec.into();
327        assert!(raw_ptr.is_null(), "The raw pointer should be null for an empty DecoderCodec");
328    }
329}