Skip to main content

lumen_ffmpeg/
format.rs

1use std::{ffi::CStr, ptr};
2
3use crate::{
4    PixelFormat, Result, VideoCodec,
5    ffi::{self, AvPacket, sys},
6};
7use sys::AVMediaType::{AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO};
8
9#[derive(Debug, Clone, Copy, Default, PartialEq)]
10pub struct Rational {
11    pub numerator: i32,
12    pub denominator: i32,
13}
14
15impl Rational {
16    pub fn as_f64(self) -> Option<f64> {
17        (self.denominator != 0).then_some(self.numerator as f64 / self.denominator as f64)
18    }
19}
20
21impl From<sys::AVRational> for Rational {
22    fn from(value: sys::AVRational) -> Self {
23        Self {
24            numerator: value.num,
25            denominator: value.den,
26        }
27    }
28}
29
30#[derive(Debug, Clone, PartialEq)]
31pub struct MediaInfo {
32    pub path: String,
33    pub duration_us: Option<i64>,
34    pub bit_rate: Option<i64>,
35    pub video_streams: Vec<VideoStreamInfo>,
36    pub audio_streams: Vec<AudioStreamInfo>,
37}
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct VideoStreamInfo {
41    pub stream_index: usize,
42    pub codec: VideoCodec,
43    pub width: u32,
44    pub height: u32,
45    pub pixel_format: PixelFormat,
46    pub time_base: Rational,
47    pub avg_frame_rate: Rational,
48    pub frame_count: Option<u64>,
49    pub duration_ts: Option<i64>,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53pub struct AudioStreamInfo {
54    pub stream_index: usize,
55    pub sample_rate: u32,
56    pub channels: u16,
57    pub time_base: Rational,
58    pub frame_count: Option<u64>,
59    pub duration_ts: Option<i64>,
60}
61
62pub struct Packet {
63    pub(crate) inner: AvPacket,
64}
65
66impl Packet {
67    pub fn stream_index(&self) -> usize {
68        self.inner.stream_index()
69    }
70
71    pub fn pts(&self) -> Option<i64> {
72        self.inner.pts()
73    }
74}
75
76pub struct InputContext {
77    path: String,
78    ptr: *mut sys::AVFormatContext,
79}
80
81unsafe impl Send for InputContext {}
82
83impl std::fmt::Debug for InputContext {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.debug_struct("InputContext")
86            .field("path", &self.path)
87            .finish_non_exhaustive()
88    }
89}
90
91impl InputContext {
92    pub fn open(path: impl Into<String>) -> Result<Self> {
93        ffi::init();
94        let path = path.into();
95        let c_path = ffi::cstring("avformat_open_input", &path)?;
96        let mut ptr = ptr::null_mut();
97        unsafe {
98            ffi::check(
99                sys::avformat_open_input(
100                    &mut ptr,
101                    c_path.as_ptr(),
102                    ptr::null_mut(),
103                    ptr::null_mut(),
104                ),
105                "avformat_open_input",
106            )
107            .map_err(|error| error.with_path(path.clone()))?;
108            ffi::check(
109                sys::avformat_find_stream_info(ptr, ptr::null_mut()),
110                "avformat_find_stream_info",
111            )
112            .map_err(|error| error.with_path(path.clone()))?;
113        }
114        Ok(Self { path, ptr })
115    }
116
117    pub fn path(&self) -> &str {
118        &self.path
119    }
120
121    pub fn media_info(&self) -> MediaInfo {
122        let mut video_streams = Vec::new();
123        let mut audio_streams = Vec::new();
124        unsafe {
125            for index in 0..(*self.ptr).nb_streams as usize {
126                let stream = *(*self.ptr).streams.add(index);
127                let params = (*stream).codecpar;
128                match (*params).codec_type {
129                    AVMEDIA_TYPE_VIDEO => {
130                        video_streams.push(VideoStreamInfo {
131                            stream_index: index,
132                            codec: VideoCodec::from_av_codec_id((*params).codec_id),
133                            width: (*params).width.max(0) as u32,
134                            height: (*params).height.max(0) as u32,
135                            pixel_format: PixelFormat::from_av_pixel_format(std::mem::transmute::<
136                                i32,
137                                sys::AVPixelFormat,
138                            >(
139                                (*params).format
140                            )),
141                            time_base: (*stream).time_base.into(),
142                            avg_frame_rate: (*stream).avg_frame_rate.into(),
143                            frame_count: ((*stream).nb_frames > 0)
144                                .then_some((*stream).nb_frames as u64),
145                            duration_ts: ((*stream).duration > 0).then_some((*stream).duration),
146                        });
147                    }
148                    AVMEDIA_TYPE_AUDIO => {
149                        audio_streams.push(AudioStreamInfo {
150                            stream_index: index,
151                            sample_rate: (*params).sample_rate.max(0) as u32,
152                            channels: (*params).ch_layout.nb_channels.max(0) as u16,
153                            time_base: (*stream).time_base.into(),
154                            frame_count: ((*stream).nb_frames > 0)
155                                .then_some((*stream).nb_frames as u64),
156                            duration_ts: ((*stream).duration > 0).then_some((*stream).duration),
157                        });
158                    }
159                    _ => {}
160                }
161            }
162            MediaInfo {
163                path: self.path.clone(),
164                duration_us: ((*self.ptr).duration > 0).then_some((*self.ptr).duration),
165                bit_rate: ((*self.ptr).bit_rate > 0).then_some((*self.ptr).bit_rate),
166                video_streams,
167                audio_streams,
168            }
169        }
170    }
171
172    pub fn best_video_stream(&self) -> Result<VideoStreamInfo> {
173        self.media_info()
174            .video_streams
175            .into_iter()
176            .next()
177            .ok_or_else(|| crate::FfmpegError::new("best_video_stream", "no video stream found"))
178    }
179
180    pub fn best_audio_stream(&self) -> Result<AudioStreamInfo> {
181        self.media_info()
182            .audio_streams
183            .into_iter()
184            .next()
185            .ok_or_else(|| crate::FfmpegError::new("best_audio_stream", "no audio stream found"))
186    }
187
188    pub fn read_packet(&mut self) -> Result<Option<Packet>> {
189        let mut packet = AvPacket::new()?;
190        let result = unsafe { sys::av_read_frame(self.ptr, packet.as_mut_ptr()) };
191        if result == sys::AVERROR_EOF {
192            return Ok(None);
193        }
194        if result < 0 {
195            return Err(ffi::error_from_code("av_read_frame", result).with_path(self.path.clone()));
196        }
197        Ok(Some(Packet { inner: packet }))
198    }
199
200    pub fn seek(&mut self, timestamp: i64) -> Result<()> {
201        unsafe {
202            ffi::check(
203                sys::av_seek_frame(self.ptr, -1, timestamp, sys::AVSEEK_FLAG_BACKWARD),
204                "av_seek_frame",
205            )
206            .map_err(|error| error.with_path(self.path.clone()))
207        }
208    }
209
210    pub fn seek_stream(&mut self, stream_index: usize, timestamp: i64) -> Result<()> {
211        unsafe {
212            if stream_index >= (*self.ptr).nb_streams as usize {
213                return Err(crate::FfmpegError::new(
214                    "av_seek_frame",
215                    "stream index out of range",
216                ));
217            }
218            ffi::check(
219                sys::av_seek_frame(
220                    self.ptr,
221                    stream_index as i32,
222                    timestamp,
223                    sys::AVSEEK_FLAG_BACKWARD,
224                ),
225                "av_seek_frame",
226            )
227            .map_err(|error| error.with_path(self.path.clone()))
228        }
229    }
230
231    pub(crate) fn stream_parameters(
232        &self,
233        stream_index: usize,
234    ) -> Result<*const sys::AVCodecParameters> {
235        unsafe {
236            if stream_index >= (*self.ptr).nb_streams as usize {
237                return Err(crate::FfmpegError::new(
238                    "stream_parameters",
239                    "stream index out of range",
240                ));
241            }
242            Ok((**(*self.ptr).streams.add(stream_index)).codecpar)
243        }
244    }
245
246    pub(crate) fn stream_time_base(&self, stream_index: usize) -> Result<sys::AVRational> {
247        unsafe {
248            if stream_index >= (*self.ptr).nb_streams as usize {
249                return Err(crate::FfmpegError::new(
250                    "stream_time_base",
251                    "stream index out of range",
252                ));
253            }
254            Ok((**(*self.ptr).streams.add(stream_index)).time_base)
255        }
256    }
257}
258
259impl Drop for InputContext {
260    fn drop(&mut self) {
261        unsafe {
262            sys::avformat_close_input(&mut self.ptr);
263        }
264    }
265}
266
267pub(crate) fn codec_name(codec_id: sys::AVCodecID) -> String {
268    unsafe {
269        let name = sys::avcodec_get_name(codec_id);
270        if name.is_null() {
271            "unknown".to_string()
272        } else {
273            CStr::from_ptr(name).to_string_lossy().into_owned()
274        }
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn rational_handles_zero_denominator() {
284        assert_eq!(
285            Rational {
286                numerator: 1,
287                denominator: 0
288            }
289            .as_f64(),
290            None
291        );
292        assert_eq!(
293            Rational {
294                numerator: 1,
295                denominator: 2
296            }
297            .as_f64(),
298            Some(0.5)
299        );
300    }
301}