Skip to main content

rsmpeg/avformat/
avformat.rs

1use std::{
2    ffi::{c_void, CStr},
3    os::raw::c_int,
4    ptr::{self, NonNull},
5};
6
7use crate::{
8    avcodec::{
9        AVCodecParameters, AVCodecParametersMut, AVCodecParametersRef, AVCodecRef, AVPacket,
10    },
11    avformat::{AVIOContext, AVIOContextCustom, AVIOContextURL},
12    avutil::{AVDictionary, AVDictionaryMut, AVDictionaryRef, AVRational},
13    error::{Result, RsmpegError},
14    ffi,
15    shared::*,
16};
17
18/// Container of all kinds of AVIOContexts.
19pub enum AVIOContextContainer {
20    Url(AVIOContextURL),
21    Custom(AVIOContextCustom),
22}
23
24impl AVIOContextContainer {
25    /// Get the raw pointer of the AVIOContext.
26    pub(crate) fn as_mut_ptr(&mut self) -> *mut ffi::AVIOContext {
27        match self {
28            Self::Url(ctx) => ctx.as_mut_ptr(),
29            Self::Custom(ctx) => ctx.as_mut_ptr(),
30        }
31    }
32}
33
34wrap! {
35    AVFormatContextInput: ffi::AVFormatContext,
36    io_context: Option<AVIOContextContainer> = None,
37}
38
39#[bon::bon]
40impl AVFormatContextInput {
41    /// Create a builder that allows for advanced configuration.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use rsmpeg::avformat::{AVInputFormat, AVFormatContextInput};
47    /// use rsmpeg::avutil::AVDictionary;
48    ///
49    /// let mjpeg_options = AVDictionary::new(c"framerate", c"10", 0);
50    /// let mut mjpeg_options = Some(mjpeg_options);
51    ///
52    /// let input_context = AVFormatContextInput::builder()
53    ///     .url(c"assets/mountain.jpg")
54    ///     .format(&AVInputFormat::find(c"mjpeg").unwrap())
55    ///     .options(&mut mjpeg_options)
56    ///     .open().unwrap();
57    ///
58    /// assert!(mjpeg_options.is_none(), "unconsumed format options: {mjpeg_options:?}");
59    /// ```
60    #[builder(finish_fn = open)]
61    pub fn builder(
62        /// Url of the stream to open. Can sometimes be omitted when using custom io.
63        url: Option<&CStr>,
64        /// Use provided input format instead of implicit inferrence from url during `avformat_open_input`.
65        format: Option<&AVInputFormat>,
66        /// Format-specific options provided to `avformat_open_input`. After opening, the dictionary will contain any unused values (if any).
67        options: Option<&mut Option<AVDictionary>>,
68        /// Use provided IO context instead of implicit creation during `avformat_open_input`.
69        mut io_context: Option<AVIOContextContainer>,
70    ) -> Result<Self> {
71        let mut input_format_context = {
72            let input_format_context = unsafe { ffi::avformat_alloc_context() };
73
74            if let Some(io_context) = io_context.as_mut() {
75                unsafe {
76                    (*input_format_context).pb = match io_context {
77                        AVIOContextContainer::Url(ctx) => ctx.as_mut_ptr(),
78                        AVIOContextContainer::Custom(ctx) => ctx.as_mut_ptr(),
79                    };
80                }
81            };
82
83            input_format_context
84        };
85
86        let url_ptr = url.map(|x| x.as_ptr()).unwrap_or(std::ptr::null());
87        let fmt_ptr = format.map(|x| x.as_ptr()).unwrap_or_else(std::ptr::null);
88
89        let mut dummy_options = None;
90        let options = options.unwrap_or(&mut dummy_options);
91        let mut options_ptr = options
92            .as_mut()
93            .map(|x| x.as_mut_ptr())
94            .unwrap_or_else(std::ptr::null_mut);
95
96        unsafe {
97            ffi::avformat_open_input(
98                &mut input_format_context,
99                url_ptr,
100                fmt_ptr,
101                &mut options_ptr,
102            )
103        }
104        .upgrade()
105        .map_err(RsmpegError::OpenInputError)?;
106
107        // Forget the old options since it's ownership is transferred.
108        let mut new_options = options_ptr
109            .upgrade()
110            .map(|x| unsafe { AVDictionary::from_raw(x) });
111        std::mem::swap(options, &mut new_options);
112        std::mem::forget(new_options);
113
114        // Here we can be sure that context is non null, constructing here for
115        // dropping when `avformat_find_stream_info` fails.
116        let mut context = unsafe { Self::from_raw(NonNull::new(input_format_context).unwrap()) };
117        context.io_context = io_context;
118
119        unsafe { ffi::avformat_find_stream_info(context.as_mut_ptr(), ptr::null_mut()) }
120            .upgrade()
121            .map_err(RsmpegError::FindStreamInfoError)?;
122
123        Ok(context)
124    }
125
126    /// Create a [`AVFormatContextInput`] instance of a file, and find info of
127    /// all streams.
128    ///
129    /// - `url`: url of the stream to open.
130    ///
131    /// For more advanced configuration, see [`Self::builder`].
132    pub fn open(url: &CStr) -> Result<Self> {
133        Self::builder().url(url).open()
134    }
135
136    /// Create a [`AVFormatContextInput`] instance from an [`AVIOContext`], and find info of
137    /// all streams.
138    ///
139    /// For more advanced configuration, see [`Self::builder`].
140    pub fn from_io_context(io_context: AVIOContextContainer) -> Result<Self> {
141        Self::builder().io_context(io_context).open()
142    }
143
144    /// Dump [`ffi::AVFormatContext`]'s info in the "FFmpeg" way.
145    ///
146    /// The index and filename here is just for info printing, it really doesn't matter.
147    pub fn dump(&mut self, index: usize, filename: &CStr) -> Result<()> {
148        unsafe {
149            // This input context, so the last parameter is 0
150            ffi::av_dump_format(self.as_mut_ptr(), index as i32, filename.as_ptr(), 0);
151        }
152        Ok(())
153    }
154
155    /// Return the next packet of a stream. This function returns what is stored
156    /// in the file, and does not validate that what is there are valid packets
157    /// for the decoder. It will split what is stored in the file into packets
158    /// and return one for each call. It will not omit invalid data between
159    /// valid packets so as to give the decoder the maximum information possible
160    /// for decoding.
161    ///
162    /// Return `Err(_)` on error, Return `Ok(None)` on EOF.
163    pub fn read_packet(&mut self) -> Result<Option<AVPacket>> {
164        let mut packet = AVPacket::new();
165        match unsafe { ffi::av_read_frame(self.as_mut_ptr(), packet.as_mut_ptr()) }.upgrade() {
166            Ok(_) => Ok(Some(packet)),
167            Err(ffi::AVERROR_EOF) => Ok(None),
168            Err(x) => Err(x)?,
169        }
170    }
171
172    /// Seek to the keyframe at timestamp, 'timestamp' in 'stream_index'.
173    pub fn seek(&mut self, stream_index: i32, timestamp: i64, flags: i32) -> Result<()> {
174        unsafe { ffi::av_seek_frame(self.as_mut_ptr(), stream_index, timestamp, flags) }
175            .upgrade()?;
176        Ok(())
177    }
178
179    /// Return the stream index and stream decoder if there is any "best" stream.
180    /// "best" means the most likely what the user wants.
181    pub fn find_best_stream(
182        &self,
183        media_type: ffi::AVMediaType,
184    ) -> Result<Option<(usize, AVCodecRef<'static>)>> {
185        // After FFmpeg 4.4 this should be changed to *const AVCodec, here we
186        // preserve the backward compatibility.
187        let dec = ptr::null_mut();
188        let mut dec = dec as _;
189
190        // ATTENTION: usage different from FFmpeg documentation.
191        //
192        // According to ffmpeg's source code, here we legally assume that
193        // `av_find_best_stream` doesn't change given `*mut AVFormatContext`.
194        match unsafe {
195            ffi::av_find_best_stream(self.as_ptr() as *mut _, media_type, -1, -1, &mut dec, 0)
196        }
197        .upgrade()
198        {
199            Ok(index) => Ok(Some((index as usize, unsafe {
200                AVCodecRef::from_raw(NonNull::new(dec as *mut _).unwrap())
201            }))),
202            Err(ffi::AVERROR_STREAM_NOT_FOUND) => Ok(None),
203            Err(e) => Err(RsmpegError::AVError(e)),
204        }
205    }
206}
207
208impl<'stream> AVFormatContextInput {
209    /// Return slice of [`AVStreamRef`].
210    pub fn streams(&'stream self) -> &'stream [AVStreamRef<'stream>] {
211        // #define `<->` as "has the same layout due to repr(transparent)"
212        // ```
213        // NonNull<ffi::AVStream> <-> *const ffi::AVStream
214        // AVStream <-> NonNull<ffi::AVStream>
215        // AVStreamRef <-> AVStream
216        // ```
217        // indicates: AVStreamRef <-> *const ffi::AVStream
218        let streams = self.streams as *const *const ffi::AVStream as *const AVStreamRef<'stream>;
219        // u32 to usize, safe
220        let len = self.nb_streams as usize;
221
222        // I trust that FFmpeg won't give me null pointers :-(
223        #[cfg(debug_assertions)]
224        {
225            let arr = unsafe {
226                std::slice::from_raw_parts(self.streams as *const *const ffi::AVStream, len)
227            };
228            for ptr in arr {
229                assert!(!ptr.is_null());
230            }
231        }
232
233        unsafe { std::slice::from_raw_parts(streams, len) }
234    }
235
236    /// Return slice of [`AVStreamMut`].
237    pub fn streams_mut(&'stream mut self) -> &'stream mut [AVStreamMut<'stream>] {
238        // #define `<->` as "has the same layout due to repr(transparent)"
239        // ```
240        // NonNull<ffi::AVStream> <-> *const ffi::AVStream
241        // AVStream <-> NonNull<ffi::AVStream>
242        // AVStreamMut <-> AVStream
243        // ```
244        // indicates: AVStreamMut <-> *const ffi::AVStream
245        let streams = self.streams as *mut AVStreamMut<'stream>;
246        // u32 to usize, safe
247        let len = self.nb_streams as usize;
248
249        // I trust that FFmpeg won't give me null pointers :-(
250        #[cfg(debug_assertions)]
251        {
252            let arr = unsafe {
253                std::slice::from_raw_parts(self.streams as *const *const ffi::AVStream, len)
254            };
255            for ptr in arr {
256                assert!(!ptr.is_null());
257            }
258        }
259
260        unsafe { std::slice::from_raw_parts_mut(streams, len) }
261    }
262
263    /// Get [`AVInputFormatRef`] in the [`AVFormatContextInput`].
264    pub fn iformat(&'stream self) -> AVInputFormatRef<'stream> {
265        // From the implementation of FFmpeg's `avformat_open_input`, we can be
266        // sure that iformat won't be null when demuxing.
267        unsafe { AVInputFormatRef::from_raw(NonNull::new(self.iformat as *mut _).unwrap()) }
268    }
269
270    /// Get metadata of the [`ffi::AVFormatContext`] in [`crate::avutil::AVDictionary`].
271    /// demuxing: set by libavformat in `avformat_open_input()`
272    /// muxing: may be set by the caller before `avformat_write_header()`
273    pub fn metadata(&'stream self) -> Option<AVDictionaryRef<'stream>> {
274        // From implementation:
275        // `avformat_find_stream_info()->()read_frame_internal()`, we know
276        // `metadata` can be null.
277        NonNull::new(self.metadata).map(|x| unsafe { AVDictionaryRef::from_raw(x) })
278    }
279}
280
281impl Drop for AVFormatContextInput {
282    fn drop(&mut self) {
283        let mut context = self.as_mut_ptr();
284        unsafe { ffi::avformat_close_input(&mut context) }
285    }
286}
287
288wrap! {
289    AVFormatContextOutput: ffi::AVFormatContext,
290    io_context: Option<AVIOContextContainer> = None,
291}
292
293impl AVFormatContextOutput {
294    /// Open a file and create a [`AVFormatContextOutput`] instance of that
295    /// file. Give it an [`AVIOContext`] if you want custom IO.
296    pub fn create(filename: &CStr) -> Result<Self> {
297        Self::builder().filename(filename).build()
298    }
299}
300
301#[bon::bon]
302impl AVFormatContextOutput {
303    /// Builder for [`AVFormatContextOutput`].
304    #[builder]
305    pub fn builder(
306        oformat: Option<&AVOutputFormat>,
307        format_name: Option<&CStr>,
308        filename: Option<&CStr>,
309        options: Option<&mut Option<AVDictionary>>,
310        io_context: Option<AVIOContextContainer>,
311    ) -> Result<Self> {
312        let mut output_format_context = ptr::null_mut();
313        let oformat = oformat
314            .map(AVOutputFormat::as_ptr)
315            .unwrap_or_else(|| ptr::null_mut());
316        let format_name = format_name
317            .map(CStr::as_ptr)
318            .unwrap_or_else(|| ptr::null_mut());
319        let filename_ptr = filename
320            .map(CStr::as_ptr)
321            .unwrap_or_else(|| ptr::null_mut());
322
323        // Alloc the context
324        unsafe {
325            ffi::avformat_alloc_output_context2(
326                &mut output_format_context,
327                oformat,
328                format_name,
329                filename_ptr,
330            )
331        }
332        .upgrade()?;
333
334        let mut output_format_context =
335            unsafe { Self::from_raw(NonNull::new(output_format_context).unwrap()) };
336
337        // Documentation of [`ffi::AVFormatContext::pb`] states:
338        //
339        // Do NOT set this field if AVFMT_NOFILE flag is set in
340        // iformat/oformat.flags. In such a case, the (de)muxer will handle I/O
341        // in some other way and this field will be NULL.
342        //
343        // Therefore, we won't use the user-given AVIOContext even if the caller provides one.
344        if output_format_context.oformat().flags & ffi::AVFMT_NOFILE as i32 == 0 {
345            // If user provides us an `AVIOCustomContext`, use it, or we create a default one.
346            let io_context = if let Some(io_context) = io_context {
347                Some(io_context)
348            } else if let Some(filename) = filename {
349                Some(AVIOContextContainer::Url(AVIOContextURL::open(
350                    filename,
351                    ffi::AVIO_FLAG_WRITE,
352                    options,
353                )?))
354            } else {
355                None
356            };
357            if let Some(mut io_context) = io_context {
358                unsafe {
359                    output_format_context.deref_mut().pb = io_context.as_mut_ptr();
360                }
361                output_format_context.io_context = Some(io_context);
362            }
363        }
364
365        Ok(output_format_context)
366    }
367
368    /// Allocate the stream private data and write the stream header to an
369    /// output media file.
370    ///
371    /// - `options`: An [`AVDictionary`] filled with [`AVFormatContextInput`]
372    ///     and muxer-private options. On return this parameter will be replaced
373    ///     with a dict containing options that were not found. Set this to `None`
374    ///     if it's not needed.
375    pub fn write_header(&mut self, dict: &mut Option<AVDictionary>) -> Result<()> {
376        let mut dict_ptr = dict
377            .take()
378            .map(|x| x.into_raw().as_ptr())
379            .unwrap_or_else(ptr::null_mut);
380
381        let result = unsafe { ffi::avformat_write_header(self.as_mut_ptr(), &mut dict_ptr as _) };
382
383        // Move back the ownership if not consumed.
384        *dict = dict_ptr
385            .upgrade()
386            .map(|x| unsafe { AVDictionary::from_raw(x) });
387
388        result.upgrade()?;
389
390        Ok(())
391    }
392
393    /// Write the stream trailer to an output media file and free the file
394    /// private data.
395    pub fn write_trailer(&mut self) -> Result<()> {
396        unsafe { ffi::av_write_trailer(self.as_mut_ptr()) }.upgrade()?;
397        Ok(())
398    }
399
400    /// Dump [`ffi::AVFormatContext`] info in the "FFmpeg" way.
401    ///
402    /// The filename here is just for info printing, it's really doesn't matter.
403    pub fn dump(&mut self, index: i32, filename: &CStr) -> Result<()> {
404        unsafe {
405            // This is output context, so the last parameter is 1
406            ffi::av_dump_format(self.as_mut_ptr(), index, filename.as_ptr(), 1);
407        }
408        Ok(())
409    }
410
411    /// Write a packet to an output media file.
412    ///
413    /// This function passes the packet directly to the muxer, without any
414    /// buffering or reordering. The caller is responsible for correctly
415    /// interleaving the packets if the format requires it. Callers that want
416    /// libavformat to handle the interleaving should call
417    /// [`Self::interleaved_write_frame()`] instead of this function.
418    pub fn write_frame(&mut self, packet: &mut AVPacket) -> Result<()> {
419        unsafe { ffi::av_write_frame(self.as_mut_ptr(), packet.as_mut_ptr()) }.upgrade()?;
420        Ok(())
421    }
422
423    /// Write a packet to an output media file ensuring correct interleaving.
424    ///
425    /// This function will buffer the packets internally as needed to make sure
426    /// the packets in the output file are properly interleaved in the order of
427    /// increasing dts. Callers doing their own interleaving should call
428    /// [`Self::write_frame()`] instead of this function.
429    pub fn interleaved_write_frame(&mut self, packet: &mut AVPacket) -> Result<()> {
430        unsafe { ffi::av_interleaved_write_frame(self.as_mut_ptr(), packet.as_mut_ptr()) }
431            .upgrade()?;
432        Ok(())
433    }
434}
435
436impl<'stream> AVFormatContextOutput {
437    /// Return slice of [`AVStreamRef`].
438    pub fn streams(&'stream self) -> &'stream [AVStreamRef<'stream>] {
439        // #define `<->` as "has the same layout due to repr(transparent)"
440        // ```
441        // NonNull<ffi::AVStream> <-> *const ffi::AVStream
442        // AVStream <-> NonNull<ffi::AVStream>
443        // AVStreamRef <-> AVStream
444        // ```
445        // indicates: AVStreamRef <-> *const ffi::AVStream
446        let streams = self.streams as *const *const ffi::AVStream as *const AVStreamRef<'stream>;
447        // u32 to usize, safe
448        let len = self.nb_streams as usize;
449
450        // I trust that FFmpeg won't give me null pointers :-(
451        #[cfg(debug_assertions)]
452        {
453            let arr = unsafe {
454                std::slice::from_raw_parts(self.streams as *const *const ffi::AVStream, len)
455            };
456            for ptr in arr {
457                assert!(!ptr.is_null());
458            }
459        }
460
461        unsafe { std::slice::from_raw_parts(streams, len) }
462    }
463
464    /// Get [`AVOutputFormat`] from the [`AVFormatContextOutput`].
465    pub fn oformat(&self) -> AVOutputFormatRef<'static> {
466        // From the implementation of FFmpeg's `avformat_alloc_output_context2`,
467        // we can be sure that `oformat` won't be null when muxing.
468        unsafe { AVOutputFormatRef::from_raw(NonNull::new(self.oformat as *mut _).unwrap()) }
469    }
470
471    /// Set [`AVOutputFormat`] in the [`AVFormatContextOutput`].
472    pub fn set_oformat(&mut self, format: AVOutputFormatRef<'static>) {
473        // `as _` is for compatibility with older FFmpeg versions(< 5.0)
474        unsafe {
475            self.deref_mut().oformat = format.as_ptr() as _;
476        }
477    }
478
479    /// Add a new stream to a media file, should be called by the user before
480    /// [`Self::write_header()`];
481    pub fn new_stream(&'stream mut self) -> AVStreamMut<'stream> {
482        // According to the FFmpeg documention and inner implementation, the
483        // second parameter of avformat_new_stream is unused. So ignore it.
484        let new_stream = unsafe { ffi::avformat_new_stream(self.as_mut_ptr(), ptr::null()) }
485            .upgrade()
486            .unwrap();
487
488        unsafe { AVStreamMut::from_raw(new_stream) }
489    }
490}
491
492impl Drop for AVFormatContextOutput {
493    fn drop(&mut self) {
494        // Here we drop the io context, which won't be touched by
495        // avformat_free_context, so let it dangling is safe.
496        if unsafe { *self.oformat }.flags & ffi::AVFMT_NOFILE as i32 == 0 {
497            if let Some(pb) = NonNull::new(self.pb) {
498                let _ = unsafe { AVIOContext::from_raw(pb) };
499            }
500        }
501
502        unsafe {
503            ffi::avformat_free_context(self.as_mut_ptr());
504        }
505    }
506}
507
508wrap_ref!(AVInputFormat: ffi::AVInputFormat);
509
510impl AVInputFormat {
511    /// Find [`AVInputFormat`] based on the short name of the input format.
512    pub fn find(short_name: &CStr) -> Option<AVInputFormatRef<'static>> {
513        unsafe { ffi::av_find_input_format(short_name.as_ptr()) }
514            .upgrade()
515            .map(|x| unsafe { AVInputFormatRef::from_raw(x) })
516    }
517
518    /// Iterate over all registered demuxers.
519    pub fn iterate() -> AVInputFormatIter {
520        AVInputFormatIter {
521            opaque: ptr::null_mut(),
522        }
523    }
524
525    /// A comma separated list of short names for the format. New names
526    /// may be appended with a minor bump.
527    pub fn name(&self) -> &CStr {
528        unsafe { CStr::from_ptr(self.name) }
529    }
530
531    /// Descriptive name for the format, meant to be more human-readable
532    /// than name. You should use the NULL_IF_CONFIG_SMALL() macro
533    /// to define it.
534    pub fn long_name(&self) -> &CStr {
535        unsafe { CStr::from_ptr(self.long_name) }
536    }
537}
538
539pub struct AVInputFormatIter {
540    opaque: *mut c_void,
541}
542
543impl Iterator for AVInputFormatIter {
544    type Item = AVInputFormatRef<'static>;
545
546    fn next(&mut self) -> Option<Self::Item> {
547        unsafe { ffi::av_demuxer_iterate(&mut self.opaque) }
548            .upgrade()
549            .map(|x| unsafe { AVInputFormatRef::from_raw(x) })
550    }
551}
552
553wrap_ref!(AVOutputFormat: ffi::AVOutputFormat);
554
555impl AVOutputFormat {
556    /// Return the output format in the list of registered output formats which
557    /// best matches the provided parameters, or return NULL if there is no
558    /// match.
559    pub fn guess_format(
560        short_name: Option<&CStr>,
561        filename: Option<&CStr>,
562        mime_type: Option<&CStr>,
563    ) -> Option<AVOutputFormatRef<'static>> {
564        let short_name = short_name.map(|x| x.as_ptr()).unwrap_or_else(ptr::null);
565        let filename = filename.map(|x| x.as_ptr()).unwrap_or_else(ptr::null);
566        let mime_type = mime_type.map(|x| x.as_ptr()).unwrap_or_else(ptr::null);
567
568        unsafe { ffi::av_guess_format(short_name, filename, mime_type) }
569            .upgrade()
570            .map(|x| unsafe { AVOutputFormatRef::from_raw(x) })
571    }
572
573    /// Iterate over all registered muxers.
574    pub fn iterate() -> AVOutputFormatIter {
575        AVOutputFormatIter {
576            opaque: ptr::null_mut(),
577        }
578    }
579
580    /// A comma separated list of short names for the format. New names
581    /// may be appended with a minor bump.
582    pub fn name(&self) -> &CStr {
583        unsafe { CStr::from_ptr(self.name) }
584    }
585
586    /// Descriptive name for the format, meant to be more human-readable
587    /// than name. You should use the NULL_IF_CONFIG_SMALL() macro
588    /// to define it.
589    pub fn long_name(&self) -> &CStr {
590        unsafe { CStr::from_ptr(self.long_name) }
591    }
592}
593
594pub struct AVOutputFormatIter {
595    opaque: *mut c_void,
596}
597
598impl Iterator for AVOutputFormatIter {
599    type Item = AVOutputFormatRef<'static>;
600
601    fn next(&mut self) -> Option<Self::Item> {
602        unsafe { ffi::av_muxer_iterate(&mut self.opaque) }
603            .upgrade()
604            .map(|x| unsafe { AVOutputFormatRef::from_raw(x) })
605    }
606}
607
608wrap_ref_mut!(#[repr(transparent)] AVStream: ffi::AVStream);
609settable!(AVStream {
610    avg_frame_rate: AVRational,
611    discard: i32,
612    disposition: c_int,
613    duration: i64,
614    event_flags: c_int,
615    sample_aspect_ratio: AVRational,
616    time_base: AVRational,
617});
618
619impl AVStream {
620    /// Guess the frame rate, based on both the container and codec information.
621    ///
622    /// Return None when index is not valid. Some(0/1) if no idea.
623    pub fn guess_framerate(&self) -> Option<AVRational> {
624        Some(unsafe {
625            // ATTENTION: Usage diff from documentation, but according to
626            // FFmpeg's implementation, we can use nullptr in first parameter
627            // and use const pointer in second parameter.
628            ffi::av_guess_frame_rate(ptr::null_mut(), self.as_ptr() as *mut _, ptr::null_mut())
629        })
630    }
631}
632
633impl<'stream> AVStream {
634    /// Get codec parameters of current stream.
635    pub fn codecpar(&'stream self) -> AVCodecParametersRef<'stream> {
636        // Implementation of `avformat_new_stream` tells us this cannot be Null.
637        unsafe { AVCodecParametersRef::from_raw(NonNull::new(self.codecpar).unwrap()) }
638    }
639
640    /// Get mutable reference of codec parameters in current stream.
641    pub fn codecpar_mut(&'stream mut self) -> AVCodecParametersMut<'stream> {
642        unsafe { AVCodecParametersMut::from_raw(NonNull::new(self.codecpar).unwrap()) }
643    }
644
645    /// Set codecpar of current stream with given `parameters`.
646    pub fn set_codecpar(&mut self, parameters: AVCodecParameters) {
647        // Since the codecpar in AVStram is always NonNull, this function accepts
648        // a Parameters rather than Option<Parameters>
649
650        // ATTENTION: this workflow differs from c version.
651        if let Some(codecpar) = self.codecpar.upgrade() {
652            let _ = unsafe { AVCodecParameters::from_raw(codecpar) };
653        }
654        unsafe {
655            self.deref_mut().codecpar = parameters.into_raw().as_ptr();
656        }
657    }
658
659    /// Get metadata of current stream.
660    pub fn metadata(&'stream self) -> Option<AVDictionaryRef<'stream>> {
661        NonNull::new(self.metadata).map(|x| unsafe { AVDictionaryRef::from_raw(x) })
662    }
663
664    /// Get mutable reference of metadata in current stream.
665    pub fn metadata_mut(&'stream mut self) -> Option<AVDictionaryMut<'stream>> {
666        NonNull::new(self.metadata).map(|x| unsafe { AVDictionaryMut::from_raw(x) })
667    }
668
669    /// Set metadata of current [`AVStream`].
670    pub fn set_metadata(&mut self, dict: Option<AVDictionary>) {
671        // Drop the old_dict
672        let _ = NonNull::new(self.metadata).map(|x| unsafe { AVDictionary::from_raw(x) });
673
674        // Move in the new dict.
675        unsafe {
676            self.deref_mut().metadata = dict
677                .map(|x| x.into_raw().as_ptr())
678                .unwrap_or(ptr::null_mut());
679        }
680    }
681}
682
683#[cfg(test)]
684mod test {
685    use super::*;
686
687    #[test]
688    fn test_find_input_format() {
689        let name = c"mpeg";
690        let filter_ref = AVInputFormat::find(name).unwrap();
691        assert_eq!(filter_ref.long_name(), c"MPEG-PS (MPEG-2 Program Stream)");
692
693        let name = c"asf";
694        let filter_ref = AVInputFormat::find(name).unwrap();
695        assert_eq!(
696            filter_ref.long_name(),
697            c"ASF (Advanced / Active Streaming Format)"
698        );
699
700        let name = c"__random__";
701        assert!(AVInputFormat::find(name).is_none());
702    }
703
704    #[test]
705    fn test_iterate_input_formats() {
706        let inputs = AVInputFormat::iterate()
707            .map(|x| x.name().to_string_lossy().into_owned())
708            .collect::<Vec<_>>();
709        dbg!(&inputs);
710        assert!(!inputs.is_empty());
711        assert!(inputs.contains(&"mpeg".to_string()));
712        assert!(inputs.contains(&"asf".to_string()));
713    }
714
715    #[test]
716    fn test_iterate_output_formats() {
717        let outputs = AVOutputFormat::iterate()
718            .map(|x| x.name().to_string_lossy().into_owned())
719            .collect::<Vec<_>>();
720        dbg!(&outputs);
721        assert!(!outputs.is_empty());
722        assert!(outputs.contains(&"mpeg".to_string()));
723        assert!(outputs.contains(&"asf".to_string()));
724    }
725}