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}