1use bytes::Bytes;
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[repr(u8)]
10#[non_exhaustive]
11pub enum CodecId {
12 H264 = 0,
14 H265 = 1,
16 VP8 = 2,
18 VP9 = 3,
20 AV1 = 4,
22 VVC = 12,
24 AAC = 5,
26 Opus = 6,
28 G711Alaw = 7,
30 G711Ulaw = 8,
32 G722 = 9,
34 MP3 = 10,
36 Raw = 11,
38 Unknown = 255,
40}
41
42impl TryFrom<u8> for CodecId {
43 type Error = u8;
44
45 fn try_from(v: u8) -> std::result::Result<Self, u8> {
46 match v {
47 0 => Ok(CodecId::H264),
48 1 => Ok(CodecId::H265),
49 2 => Ok(CodecId::VP8),
50 3 => Ok(CodecId::VP9),
51 4 => Ok(CodecId::AV1),
52 5 => Ok(CodecId::AAC),
53 6 => Ok(CodecId::Opus),
54 7 => Ok(CodecId::G711Alaw),
55 8 => Ok(CodecId::G711Ulaw),
56 9 => Ok(CodecId::G722),
57 10 => Ok(CodecId::MP3),
58 11 => Ok(CodecId::Raw),
59 12 => Ok(CodecId::VVC),
60 255 => Ok(CodecId::Unknown),
61 n => Err(n),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68pub enum FrameType {
69 Key,
71 Delta,
73 Audio,
75}
76
77bitflags::bitflags! {
78 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80 pub struct FrameFlags: u16 {
81 const FIRST = 0b0000_0001;
83 const GOP_END = 0b0000_0010;
85 const CONFIG = 0b0000_0100;
87 const DISCONTINUITY = 0b0000_1000;
89 }
90}
91
92#[derive(Debug, Clone)]
98pub struct MediaFrame {
99 pub pts: i64,
101 pub dts: i64,
103 pub duration: Option<u64>,
105 pub data: Bytes,
107 pub codec: CodecId,
109 pub frame_type: FrameType,
111 pub flags: FrameFlags,
113 pub track_id: u32,
115}
116
117impl MediaFrame {
118 pub fn new_video(pts: i64, dts: i64, data: Bytes, codec: CodecId, is_key: bool) -> Self {
121 Self {
122 pts,
123 dts,
124 duration: None,
125 data,
126 codec,
127 frame_type: if is_key {
128 FrameType::Key
129 } else {
130 FrameType::Delta
131 },
132 flags: FrameFlags::empty(),
133 track_id: 0,
134 }
135 }
136
137 pub fn new_audio(pts: i64, data: Bytes, codec: CodecId) -> Self {
139 Self {
140 pts,
141 dts: pts,
142 duration: None,
143 data,
144 codec,
145 frame_type: FrameType::Audio,
146 flags: FrameFlags::empty(),
147 track_id: 1,
148 }
149 }
150
151 pub fn is_keyframe(&self) -> bool {
153 self.frame_type == FrameType::Key
154 }
155
156 pub fn is_audio(&self) -> bool {
158 self.frame_type == FrameType::Audio
159 }
160
161 pub fn is_video(&self) -> bool {
163 matches!(self.frame_type, FrameType::Key | FrameType::Delta)
164 }
165
166 pub fn pts_duration(&self) -> Duration {
168 Duration::from_millis(self.pts.unsigned_abs())
169 }
170
171 pub fn pts_in(&self, timescale: u32) -> i64 {
189 rescale_ms(self.pts, timescale)
190 }
191
192 pub fn dts_in(&self, timescale: u32) -> i64 {
195 rescale_ms(self.dts, timescale)
196 }
197
198 pub fn duration_in(&self, timescale: u32) -> Option<i64> {
201 self.duration.map(|d| rescale_ms(d as i64, timescale))
202 }
203}
204
205fn rescale_ms(value_ms: i64, timescale: u32) -> i64 {
209 (value_ms as i128 * timescale as i128 / 1000) as i64
210}
211
212#[derive(Debug, Clone)]
214pub struct VideoFrame {
215 pub inner: MediaFrame,
217 pub width: u32,
219 pub height: u32,
221 pub fps_num: u32,
223 pub fps_den: u32,
225}
226
227#[derive(Debug, Clone)]
229pub struct AudioFrame {
230 pub inner: MediaFrame,
232 pub sample_rate: u32,
234 pub channels: u8,
236 pub bits_per_sample: u8,
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn video_keyframe_classification() {
246 let key = MediaFrame::new_video(0, 0, Bytes::from_static(b"x"), CodecId::H264, true);
247 assert!(key.is_keyframe());
248 assert!(key.is_video());
249 assert!(!key.is_audio());
250 assert_eq!(key.frame_type, FrameType::Key);
251
252 let delta = MediaFrame::new_video(1, 1, Bytes::new(), CodecId::H264, false);
253 assert!(!delta.is_keyframe());
254 assert!(delta.is_video());
255 }
256
257 #[test]
258 fn audio_frame_defaults_track_and_type() {
259 let a = MediaFrame::new_audio(10, Bytes::new(), CodecId::AAC);
260 assert!(a.is_audio());
261 assert!(!a.is_video());
262 assert_eq!(a.track_id, 1);
263 assert_eq!(a.dts, a.pts); }
265
266 #[test]
267 fn codec_id_roundtrips_through_u8() {
268 for id in [CodecId::H264, CodecId::Opus, CodecId::Raw, CodecId::Unknown] {
269 assert_eq!(CodecId::try_from(id as u8), Ok(id));
270 }
271 assert_eq!(CodecId::try_from(200u8), Err(200));
272 }
273
274 #[test]
275 fn rescales_timestamps_into_other_timebases() {
276 let mut f = MediaFrame::new_video(1000, 990, Bytes::new(), CodecId::H264, true);
277 f.duration = Some(40); assert_eq!(f.pts_in(90_000), 90_000);
280 assert_eq!(f.dts_in(90_000), 89_100);
281 assert_eq!(f.duration_in(90_000), Some(3_600));
282 assert_eq!(f.pts_in(48_000), 48_000);
284 let g = MediaFrame::new_audio(10, Bytes::new(), CodecId::AAC);
286 assert_eq!(g.duration_in(90_000), None);
287 }
288
289 #[test]
290 fn rescale_does_not_overflow_for_long_streams() {
291 let f = MediaFrame::new_video(86_400_000, 86_400_000, Bytes::new(), CodecId::H264, true);
293 assert_eq!(f.pts_in(90_000), 86_400_000i64 * 90);
294 }
295
296 #[test]
297 fn frame_flags_compose() {
298 let flags = FrameFlags::CONFIG | FrameFlags::FIRST;
299 assert!(flags.contains(FrameFlags::CONFIG));
300 assert!(!flags.contains(FrameFlags::GOP_END));
301 }
302}