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