scuffle_ffmpeg/io/
input.rs

1use std::ffi::CStr;
2
3use super::internal::{Inner, InnerOptions, read_packet, seek};
4use crate::consts::{Const, DEFAULT_BUFFER_SIZE};
5use crate::dict::Dictionary;
6use crate::error::{FfmpegError, FfmpegErrorCode};
7use crate::ffi::*;
8use crate::packet::{Packet, Packets};
9use crate::smart_object::SmartObject;
10use crate::stream::Streams;
11
12/// Represents an input stream.
13pub struct Input<T: Send + Sync> {
14    inner: SmartObject<Inner<T>>,
15}
16
17/// Safety: `Input` is safe to send between threads.
18unsafe impl<T: Send + Sync> Send for Input<T> {}
19
20/// Represents the options for an input stream.
21#[derive(Debug, Clone)]
22pub struct InputOptions<I: FnMut() -> bool> {
23    /// The buffer size for the input stream.
24    pub buffer_size: usize,
25    /// The dictionary for the input stream.
26    pub dictionary: Dictionary,
27    /// The interrupt callback for the input stream.
28    pub interrupt_callback: Option<I>,
29}
30
31/// Default implementation for `InputOptions`.
32impl Default for InputOptions<fn() -> bool> {
33    fn default() -> Self {
34        Self {
35            buffer_size: DEFAULT_BUFFER_SIZE,
36            dictionary: Dictionary::new(),
37            interrupt_callback: None,
38        }
39    }
40}
41
42impl<T: std::io::Read + Send + Sync> Input<T> {
43    /// Creates a new `Input` instance with default options.
44    pub fn new(input: T) -> Result<Self, FfmpegError> {
45        Self::with_options(input, &mut InputOptions::default())
46    }
47
48    /// Creates a new `Input` instance with custom options.
49    pub fn with_options(input: T, options: &mut InputOptions<impl FnMut() -> bool>) -> Result<Self, FfmpegError> {
50        Self::create_input(
51            Inner::new(
52                input,
53                InnerOptions {
54                    buffer_size: options.buffer_size,
55                    read_fn: Some(read_packet::<T>),
56                    ..Default::default()
57                },
58            )?,
59            None,
60            &mut options.dictionary,
61        )
62    }
63
64    /// Creates a new `Input` instance with seekable options.
65    pub fn seekable(input: T) -> Result<Self, FfmpegError>
66    where
67        T: std::io::Seek,
68    {
69        Self::seekable_with_options(input, InputOptions::default())
70    }
71
72    /// Creates a new `Input` instance with seekable options.
73    pub fn seekable_with_options(input: T, mut options: InputOptions<impl FnMut() -> bool>) -> Result<Self, FfmpegError>
74    where
75        T: std::io::Seek,
76    {
77        Self::create_input(
78            Inner::new(
79                input,
80                InnerOptions {
81                    buffer_size: options.buffer_size,
82                    read_fn: Some(read_packet::<T>),
83                    seek_fn: Some(seek::<T>),
84                    ..Default::default()
85                },
86            )?,
87            None,
88            &mut options.dictionary,
89        )
90    }
91}
92
93impl<T: Send + Sync> Input<T> {
94    /// Returns a constant pointer to the input stream.
95    pub const fn as_ptr(&self) -> *const AVFormatContext {
96        self.inner.inner_ref().context.as_ptr()
97    }
98
99    /// Returns a mutable pointer to the input stream.
100    pub const fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
101        self.inner.inner_mut().context.as_mut_ptr()
102    }
103
104    /// Returns the streams of the input stream.
105    pub const fn streams(&self) -> Const<'_, Streams<'_>> {
106        // Safety: See the documentation of `Streams::new`.
107        // We upcast the pointer to be mut because the function signature requires it.
108        // However we do not mutate the pointer as its returned as a `Const<Streams>` which
109        // restricts the mutability of the streams to be const.
110        unsafe { Const::new(Streams::new(self.inner.inner_ref().context.as_ptr() as *mut _)) }
111    }
112
113    /// Returns a mutable reference to the streams of the input stream.
114    pub const fn streams_mut(&mut self) -> Streams<'_> {
115        // Safety: See the documentation of `Streams::new`.
116        unsafe { Streams::new(self.inner.inner_mut().context.as_mut_ptr()) }
117    }
118
119    /// Returns the packets of the input stream.
120    pub const fn packets(&mut self) -> Packets<'_> {
121        // Safety: See the documentation of `Packets::new`.
122        unsafe { Packets::new(self.inner.inner_mut().context.as_mut_ptr()) }
123    }
124
125    /// Receives a packet from the input stream.
126    pub fn receive_packet(&mut self) -> Result<Option<Packet>, FfmpegError> {
127        self.packets().receive()
128    }
129
130    fn create_input(mut inner: Inner<T>, path: Option<&CStr>, dictionary: &mut Dictionary) -> Result<Self, FfmpegError> {
131        // Safety: avformat_open_input is safe to call
132        FfmpegErrorCode(unsafe {
133            avformat_open_input(
134                inner.context.as_mut(),
135                path.map(|p| p.as_ptr()).unwrap_or(std::ptr::null()),
136                std::ptr::null(),
137                dictionary.as_mut_ptr_ref(),
138            )
139        })
140        .result()?;
141
142        if inner.context.as_ptr().is_null() {
143            return Err(FfmpegError::Alloc);
144        }
145
146        let mut inner = SmartObject::new(inner, |inner| {
147            // Safety: The pointer is valid. We own this resource so we need to free it
148            unsafe { avformat_close_input(inner.context.as_mut()) };
149        });
150
151        // We now own the context and this is freed when the object is dropped
152        inner.context.set_destructor(|_| {});
153
154        // Safety: avformat_find_stream_info is safe to call
155        FfmpegErrorCode(unsafe { avformat_find_stream_info(inner.context.as_mut_ptr(), std::ptr::null_mut()) }).result()?;
156
157        Ok(Self { inner })
158    }
159}
160
161impl Input<()> {
162    /// Opens an input stream from a file path.
163    pub fn open(path: &str) -> Result<Self, FfmpegError> {
164        // We immediately create an input and setup the inner, before using it.
165        // Safety: When we pass this inner to `create_input` with a valid path, the inner will be initialized by ffmpeg using the path.
166        let inner = unsafe { Inner::empty() };
167
168        Self::create_input(inner, Some(&std::ffi::CString::new(path).unwrap()), &mut Dictionary::new())
169    }
170}
171
172#[cfg(test)]
173#[cfg_attr(all(test, coverage_nightly), coverage(off))]
174mod tests {
175    use std::io::Cursor;
176
177    use insta::Settings;
178
179    use super::{DEFAULT_BUFFER_SIZE, FfmpegError, Input, InputOptions};
180
181    fn configure_insta_filters(settings: &mut Settings) {
182        settings.add_filter(r"0x0000000000000000", "[NULL_POINTER]");
183        settings.add_filter(r"0x[0-9a-f]{16}", "[NON_NULL_POINTER]");
184    }
185
186    #[test]
187    fn test_input_options_default() {
188        let default_options = InputOptions::default();
189
190        assert_eq!(default_options.buffer_size, DEFAULT_BUFFER_SIZE);
191        assert!(default_options.dictionary.is_empty());
192        assert!(default_options.interrupt_callback.is_none());
193    }
194
195    #[test]
196    fn test_open_valid_file() {
197        let valid_file_path = "../../assets/avc_aac_large.mp4";
198        assert!(std::path::Path::new(valid_file_path).exists(), "Test file does not exist");
199
200        let result = Input::open(valid_file_path);
201        assert!(result.is_ok(), "Expected success but got error");
202    }
203
204    #[test]
205    fn test_open_invalid_path() {
206        let invalid_path = "invalid_file.mp4";
207        let result = Input::open(invalid_path);
208        assert!(result.is_err(), "Expected an error for invalid path");
209        if let Err(err) = result {
210            match err {
211                FfmpegError::Code(_) => (),
212                _ => panic!("Unexpected error type: {err:?}"),
213            }
214        }
215    }
216
217    #[test]
218    fn test_new_with_default_options() {
219        let valid_media_data: Vec<u8> = include_bytes!("../../../../assets/avc_aac_large.mp4").to_vec();
220        let data = Cursor::new(valid_media_data);
221        let result = Input::new(data);
222
223        if let Err(e) = &result {
224            eprintln!("Error encountered: {e:?}");
225        }
226
227        assert!(result.is_ok(), "Expected success but got error");
228    }
229
230    #[test]
231    fn test_seekable_with_valid_input() {
232        let valid_media_data: Vec<u8> = include_bytes!("../../../../assets/avc_aac_large.mp4").to_vec();
233        let data = Cursor::new(valid_media_data);
234        let result = Input::seekable(data);
235
236        if let Err(e) = &result {
237            eprintln!("Error encountered: {e:?}");
238        }
239
240        assert!(result.is_ok(), "Expected success but got error");
241    }
242
243    #[test]
244    fn test_as_ptr() {
245        let valid_file_path = "../../assets/avc_aac_large.mp4";
246        let input = Input::open(valid_file_path).expect("Failed to open valid file");
247
248        let ptr = input.as_ptr();
249        assert!(!ptr.is_null(), "Expected non-null pointer");
250    }
251
252    #[test]
253    fn test_as_mut_ptr() {
254        let valid_file_path = "../../assets/avc_aac_large.mp4";
255        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
256
257        let ptr = input.as_mut_ptr();
258        assert!(!ptr.is_null(), "Expected non-null mutable pointer");
259    }
260
261    #[test]
262    fn test_streams() {
263        let valid_file_path = "../../assets/avc_aac_large.mp4";
264        let input = Input::open(valid_file_path).expect("Failed to open valid file");
265        let streams = input.streams();
266
267        assert!(!streams.is_empty(), "Expected at least one stream");
268
269        let mut settings = Settings::new();
270        configure_insta_filters(&mut settings);
271
272        settings.bind(|| {
273            insta::assert_debug_snapshot!(streams, @r#"
274            Streams {
275                input: [NON_NULL_POINTER],
276                streams: [
277                    Stream {
278                        index: 0,
279                        id: 1,
280                        time_base: Rational {
281                            numerator: 1,
282                            denominator: 15360,
283                        },
284                        start_time: Some(
285                            0,
286                        ),
287                        duration: Some(
288                            16384,
289                        ),
290                        nb_frames: Some(
291                            64,
292                        ),
293                        disposition: 1,
294                        discard: AVDiscard::Default,
295                        sample_aspect_ratio: Rational {
296                            numerator: 1,
297                            denominator: 1,
298                        },
299                        metadata: {
300                            "language": "und",
301                            "handler_name": "GPAC ISO Video Handler",
302                            "vendor_id": "[0][0][0][0]",
303                            "encoder": "Lavc60.9.100 libx264",
304                        },
305                        avg_frame_rate: Rational {
306                            numerator: 60,
307                            denominator: 1,
308                        },
309                        r_frame_rate: Rational {
310                            numerator: 60,
311                            denominator: 1,
312                        },
313                    },
314                    Stream {
315                        index: 1,
316                        id: 2,
317                        time_base: Rational {
318                            numerator: 1,
319                            denominator: 48000,
320                        },
321                        start_time: Some(
322                            0,
323                        ),
324                        duration: Some(
325                            48096,
326                        ),
327                        nb_frames: Some(
328                            48,
329                        ),
330                        disposition: 1,
331                        discard: AVDiscard::Default,
332                        sample_aspect_ratio: Rational {
333                            numerator: 0,
334                            denominator: 1,
335                        },
336                        metadata: {
337                            "language": "und",
338                            "handler_name": "GPAC ISO Audio Handler",
339                            "vendor_id": "[0][0][0][0]",
340                        },
341                        avg_frame_rate: Rational {
342                            numerator: 0,
343                            denominator: 1,
344                        },
345                        r_frame_rate: Rational {
346                            numerator: 0,
347                            denominator: 1,
348                        },
349                    },
350                ],
351            }
352            "#);
353        });
354    }
355
356    #[test]
357    fn test_packets() {
358        let valid_file_path = "../../assets/avc_aac_large.mp4";
359        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
360        let mut packets = input.packets();
361
362        for _ in 0..5 {
363            match packets.next() {
364                Some(Ok(_)) => (),
365                Some(Err(e)) => panic!("Error encountered while reading packets: {e:?}"),
366                None => break,
367            }
368        }
369
370        let mut settings = insta::Settings::new();
371        configure_insta_filters(&mut settings);
372
373        settings.bind(|| {
374            insta::assert_debug_snapshot!(packets, @r"
375            Packets {
376                context: [NON_NULL_POINTER],
377            }
378            ");
379        });
380    }
381
382    #[test]
383    fn test_receive_packet() {
384        let valid_file_path = "../../assets/avc_aac_large.mp4";
385        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
386
387        let mut packets = Vec::new();
388        while let Ok(Some(packet)) = input.receive_packet() {
389            assert!(!packet.data().is_empty(), "Expected a non-empty packet");
390            assert!(packet.stream_index() >= 0, "Expected a valid stream index");
391            packets.push(packet);
392        }
393
394        if packets.is_empty() {
395            panic!("Expected at least one packet but received none");
396        }
397
398        insta::assert_debug_snapshot!(packets);
399    }
400}