#[cfg(all(
feature = "amf-direct",
any(target_os = "linux", target_os = "windows")
))]
pub mod amf;
#[cfg(all(
feature = "amf-direct",
any(target_os = "linux", target_os = "windows")
))]
mod hevc;
#[cfg(all(
feature = "nvenc-direct",
any(target_os = "linux", target_os = "windows")
))]
pub mod nvenc;
#[cfg(target_os = "macos")]
pub mod videotoolbox;
use ustreamer_capture::CapturedFrame;
use ustreamer_proto::{
control::{ControlMessage, DecoderConfigMessage},
quality::EncodeParams,
};
#[derive(Debug)]
pub struct EncodedFrame {
pub data: Vec<u8>,
pub is_keyframe: bool,
pub is_refine: bool,
pub is_lossless: bool,
pub encode_time_us: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecoderConfig {
pub codec: String,
pub description: Option<Vec<u8>>,
pub coded_width: u32,
pub coded_height: u32,
}
impl DecoderConfig {
pub fn to_control_message(&self) -> ControlMessage {
let mut message = DecoderConfigMessage::low_latency(self.codec.clone())
.with_dimensions(self.coded_width, self.coded_height);
if let Some(description) = &self.description {
message = message.with_description(description.clone());
}
ControlMessage::DecoderConfig(message)
}
pub fn to_control_message_bytes(&self) -> Vec<u8> {
self.to_control_message()
.to_bytes()
.expect("decoder control message should serialize")
}
}
pub trait FrameEncoder: Send {
fn encode(
&mut self,
frame: &CapturedFrame,
params: &EncodeParams,
) -> Result<EncodedFrame, EncodeError>;
fn flush(&mut self) -> Result<Vec<EncodedFrame>, EncodeError>;
fn decoder_config(&self) -> Option<DecoderConfig> {
None
}
}
#[derive(Debug, thiserror::Error)]
pub enum EncodeError {
#[error("encoder initialization failed: {0}")]
InitFailed(String),
#[error("encoding failed: {0}")]
EncodeFailed(String),
#[error("unsupported configuration: {0}")]
UnsupportedConfig(String),
#[error("unsupported frame input: {0}")]
UnsupportedFrame(String),
}
#[cfg(test)]
mod tests {
use super::DecoderConfig;
use ustreamer_proto::control::ControlMessage;
#[test]
fn decoder_config_control_message_includes_base64_description() {
let config = DecoderConfig {
codec: "hvc1.1.6.L153.B0".into(),
description: Some(vec![0x01, 0x02, 0x03]),
coded_width: 1920,
coded_height: 1080,
};
let message = config.to_control_message();
let bytes = config.to_control_message_bytes();
assert_eq!(ControlMessage::from_slice(&bytes).unwrap(), message);
let json = String::from_utf8(bytes).unwrap();
assert!(json.contains("\"type\":\"decoder-config\""));
assert!(json.contains("\"codec\":\"hvc1.1.6.L153.B0\""));
assert!(json.contains("\"codedWidth\":1920"));
assert!(json.contains("\"codedHeight\":1080"));
assert!(json.contains("\"descriptionBase64\":\"AQID\""));
}
#[test]
fn decoder_config_control_message_omits_description_when_absent() {
let config = DecoderConfig {
codec: "av01.0.08M.08".into(),
description: None,
coded_width: 1280,
coded_height: 720,
};
let bytes = config.to_control_message_bytes();
let message = String::from_utf8(bytes.clone()).unwrap();
assert!(message.contains("\"codec\":\"av01.0.08M.08\""));
assert!(!message.contains("descriptionBase64"));
assert_eq!(
ControlMessage::from_slice(&bytes).unwrap(),
config.to_control_message()
);
}
}