1#[cfg(all(
8 feature = "amf-direct",
9 any(target_os = "linux", target_os = "windows")
10))]
11pub mod amf;
12#[cfg(all(
13 feature = "amf-direct",
14 any(target_os = "linux", target_os = "windows")
15))]
16mod hevc;
17
18#[cfg(all(
19 feature = "nvenc-direct",
20 any(target_os = "linux", target_os = "windows")
21))]
22pub mod nvenc;
23#[cfg(target_os = "macos")]
24pub mod videotoolbox;
25
26use ustreamer_capture::CapturedFrame;
27use ustreamer_proto::{
28 control::{ControlMessage, DecoderConfigMessage},
29 quality::EncodeParams,
30};
31
32#[derive(Debug)]
34pub struct EncodedFrame {
35 pub data: Vec<u8>,
37 pub is_keyframe: bool,
39 pub is_refine: bool,
41 pub is_lossless: bool,
43 pub encode_time_us: u64,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct DecoderConfig {
50 pub codec: String,
52 pub description: Option<Vec<u8>>,
54 pub coded_width: u32,
56 pub coded_height: u32,
58}
59
60impl DecoderConfig {
61 pub fn to_control_message(&self) -> ControlMessage {
63 let mut message = DecoderConfigMessage::low_latency(self.codec.clone())
64 .with_dimensions(self.coded_width, self.coded_height);
65 if let Some(description) = &self.description {
66 message = message.with_description(description.clone());
67 }
68
69 ControlMessage::DecoderConfig(message)
70 }
71
72 pub fn to_control_message_bytes(&self) -> Vec<u8> {
74 self.to_control_message()
75 .to_bytes()
76 .expect("decoder control message should serialize")
77 }
78}
79
80pub trait FrameEncoder: Send {
82 fn encode(
84 &mut self,
85 frame: &CapturedFrame,
86 params: &EncodeParams,
87 ) -> Result<EncodedFrame, EncodeError>;
88
89 fn flush(&mut self) -> Result<Vec<EncodedFrame>, EncodeError>;
91
92 fn decoder_config(&self) -> Option<DecoderConfig> {
94 None
95 }
96}
97
98#[derive(Debug, thiserror::Error)]
99pub enum EncodeError {
100 #[error("encoder initialization failed: {0}")]
101 InitFailed(String),
102 #[error("encoding failed: {0}")]
103 EncodeFailed(String),
104 #[error("unsupported configuration: {0}")]
105 UnsupportedConfig(String),
106 #[error("unsupported frame input: {0}")]
107 UnsupportedFrame(String),
108}
109
110#[cfg(test)]
111mod tests {
112 use super::DecoderConfig;
113 use ustreamer_proto::control::ControlMessage;
114
115 #[test]
116 fn decoder_config_control_message_includes_base64_description() {
117 let config = DecoderConfig {
118 codec: "hvc1.1.6.L153.B0".into(),
119 description: Some(vec![0x01, 0x02, 0x03]),
120 coded_width: 1920,
121 coded_height: 1080,
122 };
123
124 let message = config.to_control_message();
125 let bytes = config.to_control_message_bytes();
126 assert_eq!(ControlMessage::from_slice(&bytes).unwrap(), message);
127
128 let json = String::from_utf8(bytes).unwrap();
129 assert!(json.contains("\"type\":\"decoder-config\""));
130 assert!(json.contains("\"codec\":\"hvc1.1.6.L153.B0\""));
131 assert!(json.contains("\"codedWidth\":1920"));
132 assert!(json.contains("\"codedHeight\":1080"));
133 assert!(json.contains("\"descriptionBase64\":\"AQID\""));
134 }
135
136 #[test]
137 fn decoder_config_control_message_omits_description_when_absent() {
138 let config = DecoderConfig {
139 codec: "av01.0.08M.08".into(),
140 description: None,
141 coded_width: 1280,
142 coded_height: 720,
143 };
144
145 let bytes = config.to_control_message_bytes();
146 let message = String::from_utf8(bytes.clone()).unwrap();
147 assert!(message.contains("\"codec\":\"av01.0.08M.08\""));
148 assert!(!message.contains("descriptionBase64"));
149 assert_eq!(
150 ControlMessage::from_slice(&bytes).unwrap(),
151 config.to_control_message()
152 );
153 }
154}