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
172#[derive(Debug, Clone)]
174pub struct VideoFrame {
175 pub inner: MediaFrame,
177 pub width: u32,
179 pub height: u32,
181 pub fps_num: u32,
183 pub fps_den: u32,
185}
186
187#[derive(Debug, Clone)]
189pub struct AudioFrame {
190 pub inner: MediaFrame,
192 pub sample_rate: u32,
194 pub channels: u8,
196 pub bits_per_sample: u8,
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn video_keyframe_classification() {
206 let key = MediaFrame::new_video(0, 0, Bytes::from_static(b"x"), CodecId::H264, true);
207 assert!(key.is_keyframe());
208 assert!(key.is_video());
209 assert!(!key.is_audio());
210 assert_eq!(key.frame_type, FrameType::Key);
211
212 let delta = MediaFrame::new_video(1, 1, Bytes::new(), CodecId::H264, false);
213 assert!(!delta.is_keyframe());
214 assert!(delta.is_video());
215 }
216
217 #[test]
218 fn audio_frame_defaults_track_and_type() {
219 let a = MediaFrame::new_audio(10, Bytes::new(), CodecId::AAC);
220 assert!(a.is_audio());
221 assert!(!a.is_video());
222 assert_eq!(a.track_id, 1);
223 assert_eq!(a.dts, a.pts); }
225
226 #[test]
227 fn codec_id_roundtrips_through_u8() {
228 for id in [CodecId::H264, CodecId::Opus, CodecId::Raw, CodecId::Unknown] {
229 assert_eq!(CodecId::try_from(id as u8), Ok(id));
230 }
231 assert_eq!(CodecId::try_from(200u8), Err(200));
232 }
233
234 #[test]
235 fn frame_flags_compose() {
236 let flags = FrameFlags::CONFIG | FrameFlags::FIRST;
237 assert!(flags.contains(FrameFlags::CONFIG));
238 assert!(!flags.contains(FrameFlags::GOP_END));
239 }
240}