use std::collections::HashMap;
use crate::packet::extension::RtpHeaderExtensions;
pub use crate::packet::extension::ExtensionFormat;
pub const AUDIO_LEVEL_URI: &str = "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
pub const VIDEO_ORIENTATION_URI: &str = "urn:3gpp:video-orientation";
pub const TRANSPORT_CC_URI: &str =
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
pub const ABS_SEND_TIME_URI: &str = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time";
pub const SDES_MID_URI: &str = "urn:ietf:params:rtp-hdrext:sdes:mid";
pub const SDES_RTP_STREAM_ID_URI: &str = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
pub fn create_header_extensions(format: ExtensionFormat) -> RtpHeaderExtensions {
match format {
ExtensionFormat::OneByte => RtpHeaderExtensions::new_one_byte(),
ExtensionFormat::TwoByte => RtpHeaderExtensions::new_two_byte(),
ExtensionFormat::Legacy => {
RtpHeaderExtensions::new_legacy(0x1000)
}
}
}
pub fn create_audio_level_data(voice_activity: bool, level: u8) -> Vec<u8> {
let level = level.min(127);
let data = if voice_activity {
vec![level]
} else {
vec![level | 0x80]
};
data
}
pub fn parse_audio_level_data(data: &[u8]) -> Option<(bool, u8)> {
if data.is_empty() {
return None;
}
let byte = data[0];
let voice_activity = (byte & 0x80) == 0;
let level = byte & 0x7F;
Some((voice_activity, level))
}
pub fn create_video_orientation_data(
camera_front_facing: bool,
camera_flipped: bool,
rotation: u16,
) -> Vec<u8> {
let normalized_degrees = match rotation {
0..=44 => 0,
45..=134 => 90,
135..=224 => 180,
_ => 270,
};
let mut orientation_byte = 0;
if camera_front_facing {
orientation_byte |= 0x01;
}
if camera_flipped {
orientation_byte |= 0x02;
}
match normalized_degrees {
90 => orientation_byte |= 0x04, 180 => orientation_byte |= 0x08, 270 => orientation_byte |= 0x0C, _ => {} }
vec![orientation_byte]
}
pub fn parse_video_orientation_data(data: &[u8]) -> Option<(bool, bool, u16)> {
if data.is_empty() {
return None;
}
let byte = data[0];
let camera_front_facing = (byte & 0x01) != 0;
let camera_flipped = (byte & 0x02) != 0;
let rotation_bits = (byte >> 2) & 0x03;
let rotation_degrees = match rotation_bits {
0 => 0,
1 => 90,
2 => 180,
3 => 270,
_ => unreachable!(),
};
Some((camera_front_facing, camera_flipped, rotation_degrees))
}
pub fn create_transport_cc_data(sequence_number: u16) -> Vec<u8> {
vec![(sequence_number >> 8) as u8, (sequence_number & 0xFF) as u8]
}
pub fn parse_transport_cc_data(data: &[u8]) -> Option<u16> {
if data.len() < 2 {
return None;
}
let sequence_number = ((data[0] as u16) << 8) | (data[1] as u16);
Some(sequence_number)
}
pub fn create_webrtc_extension_mappings() -> HashMap<u8, String> {
let mut mappings = HashMap::new();
mappings.insert(1, AUDIO_LEVEL_URI.to_string());
mappings.insert(3, VIDEO_ORIENTATION_URI.to_string());
mappings.insert(5, TRANSPORT_CC_URI.to_string());
mappings.insert(10, ABS_SEND_TIME_URI.to_string());
mappings.insert(13, SDES_MID_URI.to_string());
mappings.insert(14, SDES_RTP_STREAM_ID_URI.to_string());
mappings
}
pub fn find_extension_id(mappings: &HashMap<u8, String>, uri: &str) -> Option<u8> {
for (id, mapped_uri) in mappings {
if mapped_uri == uri {
return Some(*id);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audio_level_data() {
let active_data = create_audio_level_data(true, 30);
assert_eq!(active_data.len(), 1);
assert_eq!(active_data[0], 30);
let inactive_data = create_audio_level_data(false, 60);
assert_eq!(inactive_data.len(), 1);
assert_eq!(inactive_data[0], 0x80 | 60);
let (active, level) = parse_audio_level_data(&active_data).unwrap();
assert_eq!(active, true);
assert_eq!(level, 30);
let (active, level) = parse_audio_level_data(&inactive_data).unwrap();
assert_eq!(active, false);
assert_eq!(level, 60);
}
#[test]
fn test_video_orientation_data() {
let data = create_video_orientation_data(true, true, 90);
assert_eq!(data.len(), 1);
assert_eq!(data[0], 0x07);
let data = create_video_orientation_data(false, false, 180);
assert_eq!(data.len(), 1);
assert_eq!(data[0], 0x08);
let (front, flipped, rotation) = parse_video_orientation_data(&data).unwrap();
assert_eq!(front, false);
assert_eq!(flipped, false);
assert_eq!(rotation, 180);
}
#[test]
fn test_transport_cc_data() {
let data = create_transport_cc_data(1000);
assert_eq!(data.len(), 2);
assert_eq!(data[0], 3); assert_eq!(data[1], 232);
let sequence_number = parse_transport_cc_data(&data).unwrap();
assert_eq!(sequence_number, 1000);
}
#[test]
fn test_webrtc_extension_mappings() {
let mappings = create_webrtc_extension_mappings();
assert_eq!(mappings.get(&1).unwrap(), AUDIO_LEVEL_URI);
assert_eq!(mappings.get(&3).unwrap(), VIDEO_ORIENTATION_URI);
assert_eq!(mappings.get(&5).unwrap(), TRANSPORT_CC_URI);
assert_eq!(find_extension_id(&mappings, AUDIO_LEVEL_URI), Some(1));
assert_eq!(find_extension_id(&mappings, VIDEO_ORIENTATION_URI), Some(3));
assert_eq!(find_extension_id(&mappings, "non-existent-uri"), None);
}
}