scuffle_ffmpeg/
stream.rs

1use std::marker::PhantomData;
2
3use crate::consts::{Const, Mut};
4use crate::dict::Dictionary;
5use crate::ffi::*;
6use crate::rational::Rational;
7use crate::utils::check_i64;
8use crate::{AVDiscard, AVMediaType};
9
10/// A collection of streams. Streams implements [`IntoIterator`] to iterate over the streams.
11pub struct Streams<'a> {
12    input: *mut AVFormatContext,
13    _marker: PhantomData<&'a mut AVFormatContext>,
14}
15
16impl std::fmt::Debug for Streams<'_> {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        let mut streams = Vec::new();
19        for stream in self.iter() {
20            streams.push(stream);
21        }
22
23        f.debug_struct("Streams")
24            .field("input", &self.input)
25            .field("streams", &streams)
26            .finish()
27    }
28}
29
30/// Safety: `Streams` is safe to send between threads.
31unsafe impl Send for Streams<'_> {}
32
33impl<'a> Streams<'a> {
34    /// Creates a new `Streams` instance.
35    ///
36    /// # Safety
37    /// This function is unsafe because the caller must ensure that the lifetime & the mutablity
38    /// of the `AVFormatContext` matches the lifetime & mutability of the `Streams`.
39    pub const unsafe fn new(input: *mut AVFormatContext) -> Self {
40        Self {
41            input,
42            _marker: PhantomData,
43        }
44    }
45
46    /// Returns the index of the best stream of the given media type.
47    pub fn best_index(&self, media_type: AVMediaType) -> Option<usize> {
48        // Safety: av_find_best_stream is safe to call, 'input' is a valid pointer
49        // We upcast the pointer to a mutable pointer because the function signature
50        // requires it, but it does not mutate the pointer.
51        let stream = unsafe { av_find_best_stream(self.input, media_type.into(), -1, -1, std::ptr::null_mut(), 0) };
52        if stream < 0 {
53            return None;
54        }
55
56        Some(stream as usize)
57    }
58
59    /// Returns the best stream of the given media type.
60    pub fn best(&'a self, media_type: AVMediaType) -> Option<Const<'a, Stream<'a>>> {
61        let stream = self.best_index(media_type)?;
62
63        // Safety: This function is safe because we return a Const<Stream> which restricts
64        // the mutability of the stream.
65        let stream = unsafe { self.get_unchecked(stream)? };
66
67        Some(Const::new(stream))
68    }
69
70    /// Returns the best mutable stream of the given media type.
71    pub fn best_mut(&'a mut self, media_type: AVMediaType) -> Option<Stream<'a>> {
72        self.best(media_type).map(|s| s.0)
73    }
74
75    /// Returns an iterator over the streams.
76    pub const fn iter(&'a self) -> StreamIter<'a> {
77        StreamIter {
78            input: Self {
79                input: self.input,
80                _marker: PhantomData,
81            },
82            index: 0,
83        }
84    }
85
86    /// Returns the length of the streams.
87    pub const fn len(&self) -> usize {
88        // Safety: The lifetime makes sure we have a valid pointer for reading and nobody has
89        // access to the pointer for writing.
90        let input = unsafe { &*self.input };
91        input.nb_streams as usize
92    }
93
94    /// Returns whether the streams are empty.
95    pub const fn is_empty(&self) -> bool {
96        self.len() == 0
97    }
98
99    /// Returns the stream at the given index.
100    pub const fn get(&'a mut self, index: usize) -> Option<Stream<'a>> {
101        // Safety: this function requires mutability, therefore its safe to call the unchecked
102        // version.
103        unsafe { self.get_unchecked(index) }
104    }
105
106    /// Returns the stream at the given index.
107    ///
108    /// # Safety
109    /// This function is unsafe because it does not require mutability. The caller must
110    /// guarantee that the stream is not mutated and that multiple streams of the same index exist.
111    pub const unsafe fn get_unchecked(&self, index: usize) -> Option<Stream<'a>> {
112        if index >= self.len() {
113            return None;
114        }
115
116        // Safety: The lifetime makes sure we have a valid pointer for reading and nobody has
117        // access to the pointer for writing.
118        let input = unsafe { &*self.input };
119        // Safety: we make sure that there are enough streams to access the index.
120        let stream = unsafe { input.streams.add(index) };
121        // Safety: The pointer is valid.
122        let stream = unsafe { *stream };
123        // Safety: The pointer is valid.
124        let stream = unsafe { &mut *stream };
125
126        Some(Stream::new(stream, self.input))
127    }
128}
129
130impl<'a> IntoIterator for Streams<'a> {
131    type IntoIter = StreamIter<'a>;
132    type Item = Const<'a, Stream<'a>>;
133
134    fn into_iter(self) -> Self::IntoIter {
135        StreamIter { input: self, index: 0 }
136    }
137}
138
139/// An iterator over the streams.
140pub struct StreamIter<'a> {
141    input: Streams<'a>,
142    index: usize,
143}
144
145impl<'a> Iterator for StreamIter<'a> {
146    type Item = Const<'a, Stream<'a>>;
147
148    fn next(&mut self) -> Option<Self::Item> {
149        // Safety: we return a Const version of the stream, so there cannot exist multiple mutable
150        // streams of the same index.
151        let stream = unsafe { self.input.get_unchecked(self.index)? };
152        self.index += 1;
153        Some(Const::new(stream))
154    }
155
156    fn size_hint(&self) -> (usize, Option<usize>) {
157        let remaining = self.input.len() - self.index;
158        (remaining, Some(remaining))
159    }
160}
161
162impl std::iter::ExactSizeIterator for StreamIter<'_> {}
163
164/// A Stream is a wrapper around an [`AVStream`].
165pub struct Stream<'a>(&'a mut AVStream, *mut AVFormatContext);
166
167impl<'a> Stream<'a> {
168    /// Creates a new `Stream` instance.
169    pub(crate) const fn new(stream: &'a mut AVStream, input: *mut AVFormatContext) -> Self {
170        Self(stream, input)
171    }
172
173    /// Returns a constant pointer to the stream.
174    pub const fn as_ptr(&self) -> *const AVStream {
175        self.0
176    }
177
178    /// Returns a mutable pointer to the stream.
179    pub const fn as_mut_ptr(&mut self) -> *mut AVStream {
180        self.0
181    }
182}
183
184impl<'a> Stream<'a> {
185    /// Returns the index of the stream.
186    pub const fn index(&self) -> i32 {
187        self.0.index
188    }
189
190    /// Returns the ID of the stream.
191    pub const fn id(&self) -> i32 {
192        self.0.id
193    }
194
195    /// Returns the codec parameters of the stream.
196    pub const fn codec_parameters(&self) -> Option<&'a AVCodecParameters> {
197        // Safety: the pointer is valid
198        unsafe { self.0.codecpar.as_ref() }
199    }
200
201    /// Returns the time base of the stream.
202    pub fn time_base(&self) -> Rational {
203        self.0.time_base.into()
204    }
205
206    /// Sets the time base of the stream.
207    pub fn set_time_base(&mut self, time_base: impl Into<Rational>) {
208        self.0.time_base = time_base.into().into();
209    }
210
211    /// Returns the start time of the stream.
212    pub const fn start_time(&self) -> Option<i64> {
213        check_i64(self.0.start_time)
214    }
215
216    /// Sets the start time of the stream.
217    pub const fn set_start_time(&mut self, start_time: Option<i64>) {
218        self.0.start_time = match start_time {
219            Some(start_time) => start_time,
220            None => AV_NOPTS_VALUE,
221        }
222    }
223
224    /// Returns the duration of the stream.
225    pub const fn duration(&self) -> Option<i64> {
226        check_i64(self.0.duration)
227    }
228
229    /// Sets the duration of the stream.
230    pub const fn set_duration(&mut self, duration: Option<i64>) {
231        self.0.duration = match duration {
232            Some(duration) => duration,
233            None => AV_NOPTS_VALUE,
234        }
235    }
236
237    /// Returns the number of frames in the stream.
238    pub const fn nb_frames(&self) -> Option<i64> {
239        check_i64(self.0.nb_frames)
240    }
241
242    /// Sets the number of frames in the stream.
243    pub const fn set_nb_frames(&mut self, nb_frames: i64) {
244        self.0.nb_frames = nb_frames;
245    }
246
247    /// Returns the disposition of the stream.
248    pub const fn disposition(&self) -> i32 {
249        self.0.disposition
250    }
251
252    /// Sets the disposition of the stream.
253    pub const fn set_disposition(&mut self, disposition: i32) {
254        self.0.disposition = disposition;
255    }
256
257    /// Returns the discard flag of the stream.
258    pub const fn discard(&self) -> AVDiscard {
259        AVDiscard(self.0.discard)
260    }
261
262    /// Sets the discard flag of the stream.
263    pub fn set_discard(&mut self, discard: AVDiscard) {
264        self.0.discard = discard.into();
265    }
266
267    /// Returns the sample aspect ratio of the stream.
268    pub fn sample_aspect_ratio(&self) -> Rational {
269        self.0.sample_aspect_ratio.into()
270    }
271
272    /// Sets the sample aspect ratio of the stream.
273    pub fn set_sample_aspect_ratio(&mut self, sample_aspect_ratio: impl Into<Rational>) {
274        self.0.sample_aspect_ratio = sample_aspect_ratio.into().into();
275    }
276
277    /// Returns the metadata of the stream.
278    pub const fn metadata(&self) -> Const<'_, Dictionary> {
279        // Safety: the pointer metadata pointer does not live longer than this object,
280        // see `Const::new`
281        Const::new(unsafe { Dictionary::from_ptr_ref(self.0.metadata) })
282    }
283
284    /// Returns a mutable reference to the metadata of the stream.
285    pub const fn metadata_mut(&mut self) -> Mut<'_, Dictionary> {
286        // Safety: the pointer metadata pointer does not live longer than this object,
287        // see `Mut::new`
288        Mut::new(unsafe { Dictionary::from_ptr_ref(self.0.metadata) })
289    }
290
291    /// Returns the average frame rate of the stream.
292    pub fn avg_frame_rate(&self) -> Rational {
293        self.0.avg_frame_rate.into()
294    }
295
296    /// Returns the real frame rate of the stream.
297    pub fn r_frame_rate(&self) -> Rational {
298        self.0.r_frame_rate.into()
299    }
300
301    /// Returns the format context of the stream.
302    ///
303    /// # Safety
304    /// This function is unsafe because it returns a mutable pointer to the format context.
305    /// The caller must ensure that they have exclusive access to the format context.
306    pub const unsafe fn format_context(&self) -> *mut AVFormatContext {
307        self.1
308    }
309}
310
311impl std::fmt::Debug for Stream<'_> {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        f.debug_struct("Stream")
314            .field("index", &self.index())
315            .field("id", &self.id())
316            .field("time_base", &self.time_base())
317            .field("start_time", &self.start_time())
318            .field("duration", &self.duration())
319            .field("nb_frames", &self.nb_frames())
320            .field("disposition", &self.disposition())
321            .field("discard", &self.discard())
322            .field("sample_aspect_ratio", &self.sample_aspect_ratio())
323            .field("metadata", &self.metadata())
324            .field("avg_frame_rate", &self.avg_frame_rate())
325            .field("r_frame_rate", &self.r_frame_rate())
326            .finish()
327    }
328}
329
330#[cfg(test)]
331#[cfg_attr(all(test, coverage_nightly), coverage(off))]
332mod tests {
333    use std::collections::BTreeMap;
334    use std::num::NonZero;
335
336    use insta::{Settings, assert_debug_snapshot};
337
338    use crate::AVDiscard;
339    use crate::ffi::AVStream;
340    use crate::io::Input;
341    use crate::rational::Rational;
342    use crate::stream::AVMediaType;
343
344    #[test]
345    fn test_best_stream() {
346        let valid_file_path = "../../assets/avc_aac_large.mp4";
347        let input = Input::open(valid_file_path).expect("Failed to open valid file");
348        let streams = input.streams();
349
350        let media_type = AVMediaType::Video;
351        let best_stream = streams.best(media_type);
352
353        assert!(best_stream.is_some(), "Expected best stream to be found");
354        let best_stream = best_stream.unwrap();
355        assert!(best_stream.index() >= 0, "Expected a valid stream index");
356    }
357
358    #[test]
359    fn test_best_none_stream() {
360        let valid_file_path = "../../assets/avc_aac_large.mp4";
361        let input = Input::open(valid_file_path).expect("Failed to open valid file");
362        let streams = input.streams();
363        let invalid_media_type = AVMediaType::Subtitle;
364        let best_stream = streams.best(invalid_media_type);
365
366        assert!(
367            best_stream.is_none(),
368            "Expected `best` to return None for unsupported media type"
369        );
370    }
371
372    #[test]
373    fn test_best_mut_stream() {
374        let valid_file_path = "../../assets/avc_aac_large.mp4";
375        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
376        let mut streams = input.streams_mut();
377
378        let media_type = AVMediaType::Video;
379        let best_mut_stream = streams.best_mut(media_type);
380
381        assert!(best_mut_stream.is_some(), "Expected best mutable stream to be found");
382        let best_mut_stream = best_mut_stream.unwrap();
383        assert!(best_mut_stream.index() >= 0, "Expected a valid stream index");
384    }
385
386    #[test]
387    fn test_streams_into_iter() {
388        let valid_file_path = "../../assets/avc_aac_large.mp4";
389        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
390        let streams = input.streams_mut();
391        let streams_len = streams.len();
392        let iter = streams.into_iter();
393        let collected_streams: Vec<_> = iter.collect();
394
395        assert_eq!(
396            collected_streams.len(),
397            streams_len,
398            "Expected the iterator to yield the same number of streams as `streams.len()`"
399        );
400
401        for stream in collected_streams {
402            assert!(stream.index() >= 0, "Expected a valid stream index");
403        }
404    }
405
406    #[test]
407    fn test_streams_iter() {
408        let valid_file_path = "../../assets/avc_aac_large.mp4";
409        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
410        let streams = input.streams_mut();
411        let iter = streams.iter();
412        let collected_streams: Vec<_> = iter.collect();
413
414        assert_eq!(
415            collected_streams.len(),
416            streams.len(),
417            "Expected iterator to yield the same number of streams as `streams.len()`"
418        );
419
420        for stream in collected_streams {
421            assert!(stream.index() >= 0, "Expected a valid stream index");
422        }
423    }
424
425    #[test]
426    fn test_streams_get_valid_index() {
427        let valid_file_path = "../../assets/avc_aac_large.mp4";
428        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
429        let mut streams = input.streams_mut();
430        let stream_index = 0;
431        let stream = streams.get(stream_index);
432
433        assert!(stream.is_some(), "Expected `get` to return Some for a valid index");
434        let stream = stream.unwrap();
435
436        assert_eq!(stream.index(), stream_index as i32, "Stream index should match");
437        assert!(stream.id() >= 0, "Stream ID should be valid");
438    }
439
440    #[test]
441    fn test_streams_get_invalid_index() {
442        let valid_file_path = "../../assets/avc_aac_large.mp4";
443        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
444        let mut streams = input.streams_mut();
445        let invalid_index = streams.len();
446        let stream = streams.get(invalid_index);
447
448        assert!(stream.is_none(), "Expected `get` to return None for an invalid index");
449    }
450
451    #[test]
452    fn test_stream_as_mut_ptr() {
453        let valid_file_path = "../../assets/avc_aac_large.mp4";
454        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
455        let mut streams = input.streams_mut();
456        let stream_index = 0;
457        let mut stream = streams.get(stream_index).expect("Expected a valid stream");
458        let stream_mut_ptr = stream.as_mut_ptr();
459
460        assert!(!stream_mut_ptr.is_null(), "Expected a non-null mutable pointer");
461        assert_eq!(
462            stream_mut_ptr,
463            stream.as_ptr() as *mut AVStream,
464            "Mutable pointer should match the constant pointer cast to mutable"
465        );
466    }
467
468    #[test]
469    fn test_stream_nb_frames() {
470        let valid_file_path = "../../assets/avc_aac_large.mp4";
471        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
472        let mut streams = input.streams_mut();
473        let mut stream = streams.get(0).expect("Expected a valid stream");
474
475        let test_nb_frames = 100;
476        stream.set_nb_frames(test_nb_frames);
477        assert_eq!(
478            stream.nb_frames(),
479            Some(test_nb_frames),
480            "Expected `nb_frames` to match the set value"
481        );
482    }
483
484    #[test]
485    fn test_stream_disposition() {
486        let valid_file_path = "../../assets/avc_aac_large.mp4";
487        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
488        let mut streams = input.streams_mut();
489        let mut stream = streams.get(0).expect("Expected a valid stream");
490
491        let test_disposition = 0x01;
492        stream.set_disposition(test_disposition);
493        assert_eq!(
494            stream.disposition(),
495            test_disposition,
496            "Expected `disposition` to match the set value"
497        );
498    }
499
500    #[test]
501    fn test_stream_discard() {
502        let valid_file_path = "../../assets/avc_aac_large.mp4";
503        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
504        let mut streams = input.streams_mut();
505        let mut stream = streams.get(0).expect("Expected a valid stream");
506
507        let test_discard = AVDiscard::All;
508        stream.set_discard(test_discard);
509        assert_eq!(stream.discard(), test_discard, "Expected `discard` to match the set value");
510    }
511
512    #[test]
513    fn test_stream_sample_aspect_ratio() {
514        let valid_file_path = "../../assets/avc_aac_large.mp4";
515        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
516        let mut streams = input.streams_mut();
517        let mut stream = streams.get(0).expect("Expected a valid stream");
518
519        let test_aspect_ratio = Rational::new(4, NonZero::new(3).unwrap());
520        stream.set_sample_aspect_ratio(test_aspect_ratio);
521        assert_eq!(
522            stream.sample_aspect_ratio(),
523            test_aspect_ratio,
524            "Expected `sample_aspect_ratio` to match the set value"
525        );
526    }
527
528    #[test]
529    fn test_stream_metadata_insta() {
530        let valid_file_path = "../../assets/avc_aac_large.mp4";
531        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
532        let mut streams = input.streams_mut();
533        let mut stream = streams.get(0).expect("Expected a valid stream");
534        let mut metadata = stream.metadata_mut();
535        metadata.set(c"test_key", c"test_value").expect("Failed to set test_key");
536        metadata
537            .set(c"test_key_2", c"test_value_2")
538            .expect("Failed to set test_key_2");
539        let metadata = stream.metadata();
540
541        // sorting metadata as the order is not guaranteed
542        let sorted_metadata: BTreeMap<_, _> = metadata
543            .iter()
544            .filter_map(|(key, value)| {
545                // convert `CStr` to `&str` to gracefully handle invalid UTF-8
546                Some((key.to_str().ok()?.to_string(), value.to_str().ok()?.to_string()))
547            })
548            .collect();
549
550        assert_debug_snapshot!(sorted_metadata, @r###"
551        {
552            "encoder": "Lavc60.9.100 libx264",
553            "handler_name": "GPAC ISO Video Handler",
554            "language": "und",
555            "test_key": "test_value",
556            "test_key_2": "test_value_2",
557            "vendor_id": "[0][0][0][0]",
558        }
559        "###);
560    }
561
562    #[test]
563    fn test_stream_frame_rates() {
564        let valid_file_path = "../../assets/avc_aac_large.mp4";
565        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
566        let mut streams = input.streams_mut();
567        let stream = streams.get(0).expect("Expected a valid stream");
568        let avg_frame_rate = stream.avg_frame_rate();
569        let real_frame_rate = stream.r_frame_rate();
570
571        assert!(avg_frame_rate.as_f64() > 0.0, "Expected non-zero avg_frame_rate numerator");
572        assert!(real_frame_rate.as_f64() > 0.0, "Expected non-zero r_frame_rate numerator");
573    }
574
575    #[test]
576    fn test_stream_format_context() {
577        let valid_file_path = "../../assets/avc_aac_large.mp4";
578        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
579        let mut streams = input.streams_mut();
580        let stream = streams.get(0).expect("Expected a valid stream");
581
582        // Safety: We are the only ones who have access.
583        let format_context = unsafe { stream.format_context() };
584
585        assert_eq!(
586            format_context as *const _,
587            input.as_ptr(),
588            "Expected `format_context` to match the input's context"
589        );
590    }
591
592    #[test]
593    fn test_stream_debug() {
594        let valid_file_path = "../../assets/avc_aac_large.mp4";
595        let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
596        let mut streams = input.streams_mut();
597        let stream = streams.get(0).expect("Expected a valid stream");
598
599        let metadata = stream.metadata();
600        // sorting metadata as the order is not guaranteed
601        let sorted_metadata: BTreeMap<_, _> = metadata
602            .iter()
603            .filter_map(|(key, value)| {
604                // convert `CStr` to `&str` to gracefully handle invalid UTF-8
605                Some((key.to_str().ok()?.to_string(), value.to_str().ok()?.to_string()))
606            })
607            .collect();
608
609        let serialized_metadata = sorted_metadata
610            .iter()
611            .map(|(key, value)| format!("        \"{key}\": \"{value}\","))
612            .collect::<Vec<_>>()
613            .join("\n");
614
615        let replacement_metadata = format!("metadata: {{\n{serialized_metadata}\n    }}");
616        let mut settings = Settings::new();
617        let metadata_regex = r"metadata: \{[^}]*\}";
618        settings.add_filter(metadata_regex, &replacement_metadata);
619
620        settings.bind(|| {
621            assert_debug_snapshot!(stream, @r#"
622            Stream {
623                index: 0,
624                id: 1,
625                time_base: Rational {
626                    numerator: 1,
627                    denominator: 15360,
628                },
629                start_time: Some(
630                    0,
631                ),
632                duration: Some(
633                    16384,
634                ),
635                nb_frames: Some(
636                    64,
637                ),
638                disposition: 1,
639                discard: AVDiscard::Default,
640                sample_aspect_ratio: Rational {
641                    numerator: 1,
642                    denominator: 1,
643                },
644                metadata: {
645                    "encoder": "Lavc60.9.100 libx264",
646                    "handler_name": "GPAC ISO Video Handler",
647                    "language": "und",
648                    "vendor_id": "[0][0][0][0]",
649                },
650                avg_frame_rate: Rational {
651                    numerator: 60,
652                    denominator: 1,
653                },
654                r_frame_rate: Rational {
655                    numerator: 60,
656                    denominator: 1,
657                },
658            }
659            "#);
660        });
661    }
662}