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,
15 VP8 = 2,
16 VP9 = 3,
17 AV1 = 4,
18 VVC = 12,
20 AAC = 5,
22 Opus = 6,
23 G711Alaw = 7,
24 G711Ulaw = 8,
25 G722 = 9,
26 MP3 = 10,
27 Raw = 11,
29 Unknown = 255,
30}
31
32impl TryFrom<u8> for CodecId {
33 type Error = u8;
34
35 fn try_from(v: u8) -> std::result::Result<Self, u8> {
36 match v {
37 0 => Ok(CodecId::H264),
38 1 => Ok(CodecId::H265),
39 2 => Ok(CodecId::VP8),
40 3 => Ok(CodecId::VP9),
41 4 => Ok(CodecId::AV1),
42 5 => Ok(CodecId::AAC),
43 6 => Ok(CodecId::Opus),
44 7 => Ok(CodecId::G711Alaw),
45 8 => Ok(CodecId::G711Ulaw),
46 9 => Ok(CodecId::G722),
47 10 => Ok(CodecId::MP3),
48 11 => Ok(CodecId::Raw),
49 12 => Ok(CodecId::VVC),
50 255 => Ok(CodecId::Unknown),
51 n => Err(n),
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
58pub enum FrameType {
59 Key,
61 Delta,
63 Audio,
65}
66
67bitflags::bitflags! {
68 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70 pub struct FrameFlags: u16 {
71 const FIRST = 0b0000_0001;
73 const GOP_END = 0b0000_0010;
75 const CONFIG = 0b0000_0100;
77 const DISCONTINUITY = 0b0000_1000;
79 }
80}
81
82#[derive(Debug, Clone)]
88pub struct MediaFrame {
89 pub pts: i64,
91 pub dts: i64,
93 pub duration: Option<u64>,
95 pub data: Bytes,
97 pub codec: CodecId,
99 pub frame_type: FrameType,
101 pub flags: FrameFlags,
103 pub track_id: u32,
105}
106
107impl MediaFrame {
108 pub fn new_video(pts: i64, dts: i64, data: Bytes, codec: CodecId, is_key: bool) -> Self {
109 Self {
110 pts,
111 dts,
112 duration: None,
113 data,
114 codec,
115 frame_type: if is_key {
116 FrameType::Key
117 } else {
118 FrameType::Delta
119 },
120 flags: FrameFlags::empty(),
121 track_id: 0,
122 }
123 }
124
125 pub fn new_audio(pts: i64, data: Bytes, codec: CodecId) -> Self {
126 Self {
127 pts,
128 dts: pts,
129 duration: None,
130 data,
131 codec,
132 frame_type: FrameType::Audio,
133 flags: FrameFlags::empty(),
134 track_id: 1,
135 }
136 }
137
138 pub fn is_keyframe(&self) -> bool {
139 self.frame_type == FrameType::Key
140 }
141
142 pub fn is_audio(&self) -> bool {
143 self.frame_type == FrameType::Audio
144 }
145
146 pub fn is_video(&self) -> bool {
147 matches!(self.frame_type, FrameType::Key | FrameType::Delta)
148 }
149
150 pub fn pts_duration(&self) -> Duration {
151 Duration::from_millis(self.pts.unsigned_abs())
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct VideoFrame {
158 pub inner: MediaFrame,
159 pub width: u32,
160 pub height: u32,
161 pub fps_num: u32,
162 pub fps_den: u32,
163}
164
165#[derive(Debug, Clone)]
167pub struct AudioFrame {
168 pub inner: MediaFrame,
169 pub sample_rate: u32,
170 pub channels: u8,
171 pub bits_per_sample: u8,
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn video_keyframe_classification() {
180 let key = MediaFrame::new_video(0, 0, Bytes::from_static(b"x"), CodecId::H264, true);
181 assert!(key.is_keyframe());
182 assert!(key.is_video());
183 assert!(!key.is_audio());
184 assert_eq!(key.frame_type, FrameType::Key);
185
186 let delta = MediaFrame::new_video(1, 1, Bytes::new(), CodecId::H264, false);
187 assert!(!delta.is_keyframe());
188 assert!(delta.is_video());
189 }
190
191 #[test]
192 fn audio_frame_defaults_track_and_type() {
193 let a = MediaFrame::new_audio(10, Bytes::new(), CodecId::AAC);
194 assert!(a.is_audio());
195 assert!(!a.is_video());
196 assert_eq!(a.track_id, 1);
197 assert_eq!(a.dts, a.pts); }
199
200 #[test]
201 fn codec_id_roundtrips_through_u8() {
202 for id in [CodecId::H264, CodecId::Opus, CodecId::Raw, CodecId::Unknown] {
203 assert_eq!(CodecId::try_from(id as u8), Ok(id));
204 }
205 assert_eq!(CodecId::try_from(200u8), Err(200));
206 }
207
208 #[test]
209 fn frame_flags_compose() {
210 let flags = FrameFlags::CONFIG | FrameFlags::FIRST;
211 assert!(flags.contains(FrameFlags::CONFIG));
212 assert!(!flags.contains(FrameFlags::GOP_END));
213 }
214}