use std::any::Any;
use std::any::TypeId;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::hash::BuildHasherDefault;
use std::hash::Hasher;
use std::panic::UnwindSafe;
use std::str::from_utf8;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use crate::util::InstantExt;
use crate::util::SystemTimeExt;
use crate::util::already_happened;
use crate::util::epoch_to_beginning;
use crate::rtp_::Frequency;
use super::mtime::MediaTime;
use super::{Mid, Rid};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Extension {
AbsoluteSendTime,
AbsoluteCaptureTime,
AudioLevel,
TransmissionTimeOffset,
VideoOrientation,
TransportSequenceNumber,
PlayoutDelay,
VideoContentType,
VideoTiming,
RtpStreamId,
RepairedRtpStreamId,
RtpMid,
FrameMarking,
ColorSpace,
#[doc(hidden)]
UnknownUri(String, Arc<dyn ExtensionSerializer>),
}
#[repr(u16)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum ExtensionsForm {
OneByte = 0xBEDE,
TwoByte = 0x1000,
}
pub const MAX_ID_ONE_BYTE_FORM: u8 = 14;
pub const MAX_ID: u8 = 16;
impl ExtensionsForm {
pub(crate) fn as_u16(self) -> u16 {
self as u16
}
pub(crate) fn serialize(self) -> [u8; 2] {
self.as_u16().to_be_bytes()
}
pub(crate) fn parse(bytes: [u8; 2]) -> Option<Self> {
let serialized = u16::from_be_bytes(bytes);
if serialized == ExtensionsForm::OneByte.as_u16() {
Some(ExtensionsForm::OneByte)
} else if (serialized & 0xFFF0) == ExtensionsForm::TwoByte.as_u16() {
Some(ExtensionsForm::TwoByte)
} else {
None
}
}
}
impl UnwindSafe for Extension {}
pub trait ExtensionSerializer: Debug + Send + Sync + 'static {
fn write_to(&self, buf: &mut [u8], ev: &ExtensionValues) -> usize;
fn parse_value(&self, buf: &[u8], ev: &mut ExtensionValues) -> bool;
fn is_video(&self) -> bool;
fn is_audio(&self) -> bool;
fn requires_two_byte_form(&self, _ev: &ExtensionValues) -> bool {
false
}
}
impl Extension {
fn requires_two_byte_form(&self, ev: &ExtensionValues) -> bool {
match self {
Extension::UnknownUri(_, serializer) => serializer.requires_two_byte_form(ev),
_ => false,
}
}
}
#[derive(Debug)]
struct SdpUnknownUri;
impl ExtensionSerializer for SdpUnknownUri {
fn write_to(&self, _buf: &mut [u8], _ev: &ExtensionValues) -> usize {
unreachable!("Incorrect ExtensionSerializer::write_to")
}
fn parse_value(&self, _buf: &[u8], _ev: &mut ExtensionValues) -> bool {
unreachable!("Incorrect ExtensionSerializer::parse_value")
}
fn is_video(&self) -> bool {
unreachable!("Incorrect ExtensionSerializer::is_video")
}
fn is_audio(&self) -> bool {
unreachable!("Incorrect ExtensionSerializer::is_audio")
}
}
const EXT_URI: &[(Extension, &str)] = &[
(
Extension::AbsoluteSendTime,
"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",
),
(
Extension::AbsoluteCaptureTime,
"http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time",
),
(
Extension::AudioLevel,
"urn:ietf:params:rtp-hdrext:ssrc-audio-level",
),
(
Extension::TransmissionTimeOffset,
"urn:ietf:params:rtp-hdrext:toffset",
),
(
Extension::VideoOrientation, "urn:3gpp:video-orientation",
),
(
Extension::TransportSequenceNumber,
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01",
),
(
Extension::PlayoutDelay,
"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay",
),
(
Extension::VideoContentType,
"http://www.webrtc.org/experiments/rtp-hdrext/video-content-type",
),
(
Extension::VideoTiming,
"http://www.webrtc.org/experiments/rtp-hdrext/video-timing",
),
(
Extension::RtpStreamId,
"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
),
(
Extension::RepairedRtpStreamId,
"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
),
(
Extension::RtpMid, "urn:ietf:params:rtp-hdrext:sdes:mid",
),
(
Extension::FrameMarking,
"http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07",
),
(
Extension::ColorSpace,
"http://www.webrtc.org/experiments/rtp-hdrext/color-space",
),
];
impl Extension {
pub(crate) fn from_sdp_uri(uri: &str) -> Self {
for (t, spec) in EXT_URI.iter() {
if *spec == uri {
return t.clone();
}
}
Extension::UnknownUri(uri.to_string(), Arc::new(SdpUnknownUri))
}
pub fn with_serializer(uri: &str, s: impl ExtensionSerializer) -> Self {
Extension::UnknownUri(uri.to_string(), Arc::new(s))
}
pub fn as_uri(&self) -> &str {
for (t, spec) in EXT_URI.iter() {
if t == self {
return spec;
}
}
if let Extension::UnknownUri(uri, _) = self {
return uri;
}
"unknown"
}
pub(crate) fn is_serialized(&self) -> bool {
if let Self::UnknownUri(_, s) = self {
let is_sdp = (s as &(dyn Any + 'static))
.downcast_ref::<SdpUnknownUri>()
.is_some();
if is_sdp {
panic!("is_serialized on SdpUnkownUri, this is a bug");
}
}
true
}
fn is_audio(&self) -> bool {
use Extension::*;
if let UnknownUri(_, serializer) = self {
return serializer.is_audio();
}
matches!(
self,
RtpStreamId
| RepairedRtpStreamId
| RtpMid
| AbsoluteSendTime
| AbsoluteCaptureTime
| AudioLevel
| TransportSequenceNumber
| TransmissionTimeOffset
| PlayoutDelay
)
}
fn is_video(&self) -> bool {
use Extension::*;
if let UnknownUri(_, serializer) = self {
return serializer.is_video();
}
matches!(
self,
RtpStreamId
| RepairedRtpStreamId
| RtpMid
| AbsoluteSendTime
| AbsoluteCaptureTime
| VideoOrientation
| TransportSequenceNumber
| TransmissionTimeOffset
| PlayoutDelay
| VideoContentType
| VideoTiming
| FrameMarking
| ColorSpace
)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct ExtensionMap([Option<MapEntry>; MAX_ID as usize]);
#[derive(Debug, Clone, PartialEq, Eq)]
struct MapEntry {
ext: Extension,
locked: bool,
}
impl ExtensionMap {
pub fn empty() -> Self {
ExtensionMap(std::array::from_fn(|_| None))
}
pub fn standard() -> Self {
let mut exts = Self::empty();
exts.set(1, Extension::AudioLevel);
exts.set(2, Extension::AbsoluteSendTime);
exts.set(3, Extension::TransportSequenceNumber);
exts.set(4, Extension::RtpMid);
exts.set(10, Extension::RtpStreamId);
exts.set(11, Extension::RepairedRtpStreamId);
exts.set(13, Extension::VideoOrientation);
exts
}
pub(crate) fn clear(&mut self) {
for i in &mut self.0 {
*i = None;
}
}
pub fn set(&mut self, id: u8, ext: Extension) {
if id < 1 || id > MAX_ID {
debug!("Set RTP extension out of range 1-{}: {}", MAX_ID, id);
return;
}
let idx = id as usize - 1;
let m = MapEntry { ext, locked: false };
self.0[idx] = Some(m);
}
pub fn lookup(&self, id: u8) -> Option<&Extension> {
if id >= 1 && id <= MAX_ID {
self.0[id as usize - 1].as_ref().map(|m| &m.ext)
} else {
debug!("Lookup RTP extension out of range 1-{}: {}", MAX_ID, id);
None
}
}
pub fn id_of(&self, e: Extension) -> Option<u8> {
self.0
.iter()
.position(|x| x.as_ref().map(|e| &e.ext) == Some(&e))
.map(|p| p as u8 + 1)
}
pub fn iter(&self) -> impl Iterator<Item = (u8, &Extension)> + '_ {
self.0
.iter()
.enumerate()
.filter_map(|(i, e)| e.as_ref().map(|e| (i, e)))
.map(|(i, e)| ((i + 1) as u8, &e.ext))
}
pub fn iter_by_media_type(&self, audio: bool) -> impl Iterator<Item = (u8, &Extension)> + '_ {
self.iter().filter(move |(_id, ext)| {
if audio {
ext.is_audio()
} else {
ext.is_video()
}
})
}
#[allow(unused)]
pub fn iter_audio(&self) -> impl Iterator<Item = (u8, &Extension)> + '_ {
self.iter_by_media_type(true)
}
#[allow(unused)]
pub fn iter_video(&self) -> impl Iterator<Item = (u8, &Extension)> + '_ {
self.iter_by_media_type(false)
}
pub(crate) fn cloned_with_type(&self, audio: bool) -> Self {
let mut x = ExtensionMap::empty();
for (id, ext) in self.iter_by_media_type(audio) {
x.set(id, ext.clone());
}
x
}
pub(crate) fn parse(
&self,
mut buf: &[u8],
form: ExtensionsForm,
ext_vals: &mut ExtensionValues,
) {
loop {
if buf.is_empty() {
return;
}
if buf[0] == 0 {
buf = &buf[1..];
continue;
}
let (id, len) = match form {
ExtensionsForm::OneByte => {
let id = buf[0] >> 4;
let len = (buf[0] & 0xf) as usize + 1;
buf = &buf[1..];
if id == 15 {
return;
}
(id, len)
}
ExtensionsForm::TwoByte => {
if buf.len() < 2 {
trace!("Not enough ext header len: {} < {}", buf.len(), 2);
return;
}
let id = buf[0];
let len = buf[1] as usize;
buf = &buf[2..];
(id, len)
}
};
if buf.len() < len {
trace!("Not enough type ext len: {} < {}", buf.len(), len);
return;
}
let ext_buf = &buf[..len];
if let Some(ext) = self.lookup(id) {
ext.parse_value(ext_buf, ext_vals);
}
buf = &buf[len..];
}
}
pub(crate) fn form(&self, ev: &ExtensionValues) -> ExtensionsForm {
if self
.iter()
.any(|(id, ext)| id > MAX_ID_ONE_BYTE_FORM || ext.requires_two_byte_form(ev))
{
ExtensionsForm::TwoByte
} else {
ExtensionsForm::OneByte
}
}
pub(crate) fn write_to(
&self,
ext_buf: &mut [u8],
ev: &ExtensionValues,
form: ExtensionsForm,
) -> usize {
let orig_len = ext_buf.len();
let mut b = ext_buf;
for (idx, x) in self.0.iter().enumerate() {
if let Some(v) = x {
match form {
ExtensionsForm::OneByte => {
if let Some(n) = v.ext.write_to(&mut b[1..], ev) {
assert!(n <= 16);
assert!(n > 0);
b[0] = (idx as u8 + 1) << 4 | (n as u8 - 1);
b = &mut b[1 + n..];
}
}
ExtensionsForm::TwoByte => {
if let Some(n) = v.ext.write_to(&mut b[2..], ev) {
b[0] = (idx + 1) as u8;
b[1] = n as u8;
b = &mut b[2 + n..];
}
}
};
}
}
orig_len - b.len()
}
pub(crate) fn remap(&mut self, remote_exts: &[(u8, &Extension)]) {
for (id, ext) in remote_exts {
self.swap(*id, ext);
}
}
fn swap(&mut self, id: u8, ext: &Extension) {
if id < 1 || id > MAX_ID {
return;
}
let new_index = id as usize - 1;
let Some(old_index) = self
.0
.iter()
.enumerate()
.find(|(_, m)| m.as_ref().map(|m| &m.ext) == Some(ext))
.map(|(i, _)| i)
else {
return;
};
let old = self.0[old_index].as_mut().unwrap();
let is_change = new_index != old_index;
if is_change && old.locked {
warn!(
"Extmap locked by previous negotiation. Ignore change: {} -> {}",
old_index, new_index
);
return;
}
old.locked = true;
if !is_change {
return;
}
self.0.swap(old_index, new_index);
}
}
impl Extension {
pub(crate) fn write_to(&self, buf: &mut [u8], ev: &ExtensionValues) -> Option<usize> {
use Extension::*;
match self {
AbsoluteSendTime => {
let time_abs = ev.abs_send_time?;
let dur = time_abs.to_unix_duration();
let time_24 = MediaTime::from(dur)
.rebase(Frequency::FIXED_POINT_6_18)
.numer() as u32;
buf[..3].copy_from_slice(&time_24.to_be_bytes()[1..]);
Some(3)
}
AbsoluteCaptureTime => {
let act = ev.abs_capture_time.as_ref()?;
let ntp_64 = act.capture_time.as_ntp_64();
if let Some(offset) = act.clock_offset {
if buf.len() < 16 {
return None;
}
buf[..8].copy_from_slice(&ntp_64.to_be_bytes());
buf[8..16].copy_from_slice(&offset.to_be_bytes());
Some(16)
} else {
if buf.len() < 8 {
return None;
}
buf[..8].copy_from_slice(&ntp_64.to_be_bytes());
Some(8)
}
}
AudioLevel => {
let v1 = ev.audio_level?;
let v2 = ev.voice_activity?;
buf[0] = if v2 { 0x80 } else { 0 } | (-(0x7f & v1) as u8);
Some(1)
}
TransmissionTimeOffset => {
let v = ev.tx_time_offs?;
buf[..4].copy_from_slice(&v.to_be_bytes());
Some(4)
}
VideoOrientation => {
let v = ev.video_orientation?;
buf[0] = v as u8;
Some(1)
}
TransportSequenceNumber => {
let v = ev.transport_cc?;
buf[..2].copy_from_slice(&v.to_be_bytes());
Some(2)
}
PlayoutDelay => {
let v1 = ev.play_delay_min?.rebase(Frequency::HUNDREDTHS);
let v2 = ev.play_delay_max?.rebase(Frequency::HUNDREDTHS);
let min = (v1.numer() & 0xfff) as u32;
let max = (v2.numer() & 0xfff) as u32;
buf[0] = (min >> 4) as u8;
buf[1] = (min << 4) as u8 | (max >> 8) as u8;
buf[2] = max as u8;
Some(3)
}
VideoContentType => {
let v = ev.video_content_type?;
buf[0] = v;
Some(1)
}
VideoTiming => {
let v = ev.video_timing?;
buf[0] = v.flags;
buf[1..3].copy_from_slice(&v.encode_start.to_be_bytes());
buf[3..5].copy_from_slice(&v.encode_finish.to_be_bytes());
buf[5..7].copy_from_slice(&v.packetize_complete.to_be_bytes());
buf[7..9].copy_from_slice(&v.last_left_pacer.to_be_bytes());
buf[9..11].copy_from_slice(&0_u16.to_be_bytes());
buf[11..13].copy_from_slice(&0_u16.to_be_bytes());
Some(13)
}
RtpStreamId => {
let v = ev.rid?;
let l = v.len();
buf[..l].copy_from_slice(v.as_bytes());
Some(l)
}
RepairedRtpStreamId => {
let v = ev.rid_repair?;
let l = v.len();
buf[..l].copy_from_slice(v.as_bytes());
Some(l)
}
RtpMid => {
let v = ev.mid?;
let l = v.len();
buf[..l].copy_from_slice(v.as_bytes());
Some(l)
}
FrameMarking => {
let v = ev.frame_mark?;
buf[..4].copy_from_slice(&v.to_be_bytes());
Some(4)
}
ColorSpace => {
None
}
UnknownUri(_, serializer) => {
let n = serializer.write_to(buf, ev);
if n == 0 { None } else { Some(n) }
}
}
}
pub(crate) fn parse_value(&self, buf: &[u8], ev: &mut ExtensionValues) -> Option<()> {
use Extension::*;
match self {
AbsoluteSendTime => {
if buf.len() < 3 {
return None;
}
let time_24 = u32::from_be_bytes([0, buf[0], buf[1], buf[2]]);
let time_micros = MediaTime::from_fixed_point_6_18(time_24 as u64)
.rebase(Frequency::MICROS)
.numer();
let time_dur = Duration::from_micros(time_micros);
let time_tmp = already_happened() + time_dur;
ev.abs_send_time = Some(time_tmp);
}
AbsoluteCaptureTime => {
if buf.len() < 8 {
return None;
}
let ntp_64 = u64::from_be_bytes(buf[..8].try_into().unwrap());
let clock_offset =
(buf.len() >= 16).then(|| i64::from_be_bytes(buf[8..16].try_into().unwrap()));
ev.abs_capture_time = Some(AbsCaptureTime {
capture_time: SystemTime::from_ntp_64(ntp_64),
clock_offset,
});
}
AudioLevel => {
if buf.is_empty() {
return None;
}
ev.audio_level = Some(-(0x7f & buf[0] as i8));
ev.voice_activity = Some(buf[0] & 0x80 > 0);
}
TransmissionTimeOffset => {
if buf.len() < 4 {
return None;
}
ev.tx_time_offs = Some(u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]));
}
VideoOrientation => {
if buf.is_empty() {
return None;
}
ev.video_orientation = Some(super::ext::VideoOrientation::from(buf[0] & 3));
}
TransportSequenceNumber => {
if buf.len() < 2 {
return None;
}
ev.transport_cc = Some(u16::from_be_bytes([buf[0], buf[1]]));
}
PlayoutDelay => {
if buf.len() < 3 {
return None;
}
let min = (buf[0] as u32) << 4 | (buf[1] as u32) >> 4;
let max = ((buf[1] & 0xf) as u32) << 8 | buf[2] as u32;
ev.play_delay_min = Some(MediaTime::from_hundredths(min as u64));
ev.play_delay_max = Some(MediaTime::from_hundredths(max as u64));
}
VideoContentType => {
if buf.is_empty() {
return None;
}
ev.video_content_type = Some(buf[0]);
}
VideoTiming => {
if buf.len() < 9 {
return None;
}
ev.video_timing = Some(self::VideoTiming {
flags: buf[0],
encode_start: u16::from_be_bytes([buf[1], buf[2]]),
encode_finish: u16::from_be_bytes([buf[3], buf[4]]),
packetize_complete: u16::from_be_bytes([buf[5], buf[6]]),
last_left_pacer: u16::from_be_bytes([buf[7], buf[8]]),
});
}
RtpStreamId => {
let s = from_utf8(buf).ok()?;
ev.rid = Some(s.into());
}
RepairedRtpStreamId => {
let s = from_utf8(buf).ok()?;
ev.rid_repair = Some(s.into());
}
RtpMid => {
let s = from_utf8(buf).ok()?;
ev.mid = Some(s.into());
}
FrameMarking => {
if buf.len() < 4 {
return None;
}
ev.frame_mark = Some(u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]));
}
ColorSpace => {
}
UnknownUri(_, serializer) => {
let success = serializer.parse_value(buf, ev);
if !success {
return None;
}
}
}
Some(())
}
}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct ExtensionValues {
pub audio_level: Option<i8>,
pub voice_activity: Option<bool>,
pub video_orientation: Option<VideoOrientation>,
#[doc(hidden)]
pub video_content_type: Option<u8>, #[doc(hidden)]
pub tx_time_offs: Option<u32>,
#[doc(hidden)]
pub abs_send_time: Option<Instant>,
#[doc(hidden)]
pub abs_capture_time: Option<AbsCaptureTime>,
#[doc(hidden)]
pub transport_cc: Option<u16>, #[doc(hidden)]
pub play_delay_min: Option<MediaTime>,
#[doc(hidden)]
pub play_delay_max: Option<MediaTime>,
#[doc(hidden)]
pub video_timing: Option<VideoTiming>,
#[doc(hidden)]
pub rid: Option<Rid>,
#[doc(hidden)]
pub rid_repair: Option<Rid>,
#[doc(hidden)]
pub mid: Option<Mid>,
#[doc(hidden)]
pub frame_mark: Option<u32>,
pub user_values: UserExtensionValues,
}
impl ExtensionValues {
pub(crate) fn update_absolute_send_time(&mut self, now: Instant) {
let Some(v) = self.abs_send_time else {
return;
};
let relative_64_secs = v - already_happened();
assert!(relative_64_secs <= Duration::from_secs(64));
let now_since_epoch = now.to_unix_duration();
let closest_64 = now_since_epoch.saturating_sub(Duration::from_micros(
now_since_epoch.as_micros() as u64 % 64_000_000,
));
let since_beginning = closest_64.saturating_sub(epoch_to_beginning());
let mut offset = already_happened() + since_beginning;
if offset + relative_64_secs > now {
offset -= Duration::from_secs(64);
}
self.abs_send_time = Some(offset + relative_64_secs);
}
}
#[derive(Clone, Default)]
pub struct UserExtensionValues {
map: Option<AnyMap>,
}
type AnyMap = HashMap<TypeId, Arc<dyn Any + Send + Sync>, BuildHasherDefault<IdHasher>>;
#[derive(Default)]
struct IdHasher(u64);
impl Hasher for IdHasher {
fn write(&mut self, _: &[u8]) {
unreachable!("TypeId calls write_u64");
}
#[inline]
fn write_u64(&mut self, id: u64) {
self.0 = id;
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
}
impl PartialEq for UserExtensionValues {
fn eq(&self, other: &Self) -> bool {
let (Some(m1), Some(m2)) = (&self.map, &other.map) else {
return self.map.is_none() == other.map.is_none();
};
for k1 in m1.keys() {
if !m2.contains_key(k1) {
return false;
}
}
for k2 in m2.keys() {
if !m1.contains_key(k2) {
return false;
}
}
true
}
}
impl Eq for UserExtensionValues {}
impl UserExtensionValues {
pub fn set<T: Send + Sync + 'static>(&mut self, val: T) {
self.map
.get_or_insert_with(HashMap::default)
.insert(TypeId::of::<T>(), Arc::new(val));
}
pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.map
.as_ref()
.and_then(|map| map.get(&TypeId::of::<T>()))
.map(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref().unwrap())
}
pub fn set_arc<T: Send + Sync + 'static>(&mut self, val: Arc<T>) {
self.map
.get_or_insert_with(HashMap::default)
.insert(TypeId::of::<T>(), val);
}
pub fn get_arc<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
self.map
.as_ref()?
.get(&TypeId::of::<T>())?
.clone()
.downcast()
.ok()
}
pub fn remove<T: Send + Sync + 'static>(&mut self) {
if let Some(map) = &mut self.map {
map.remove(&TypeId::of::<T>());
}
}
}
impl UnwindSafe for UserExtensionValues {}
impl fmt::Debug for ExtensionValues {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ExtensionValues {{")?;
if let Some(t) = self.mid {
write!(f, " mid: {t}")?;
}
if let Some(t) = self.rid {
write!(f, " rid: {t}")?;
}
if let Some(t) = self.rid_repair {
write!(f, " rid_repair: {t}")?;
}
if let Some(t) = self.abs_send_time {
write!(f, " abs_send_time: {:?}", t)?;
}
if let Some(t) = &self.abs_capture_time {
write!(f, " abs_capture_time: {:?}", t)?;
}
if let Some(t) = self.voice_activity {
write!(f, " voice_activity: {t}")?;
}
if let Some(t) = self.audio_level {
write!(f, " audio_level: {t}")?;
}
if let Some(t) = self.tx_time_offs {
write!(f, " tx_time_offs: {t}")?;
}
if let Some(t) = self.video_orientation {
write!(f, " video_orientation: {t:?}")?;
}
if let Some(t) = self.transport_cc {
write!(f, " transport_cc: {t}")?;
}
if let Some(t) = self.play_delay_min {
write!(f, " play_delay_min: {}", t.as_seconds())?;
}
if let Some(t) = self.play_delay_max {
write!(f, " play_delay_max: {}", t.as_seconds())?;
}
if let Some(t) = self.video_content_type {
write!(f, " video_content_type: {t}")?;
}
if let Some(t) = &self.video_timing {
write!(f, " video_timing: {t:?}")?;
}
if let Some(t) = &self.frame_mark {
write!(f, " frame_mark: {t}")?;
}
write!(f, " }}")?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AbsCaptureTime {
pub capture_time: SystemTime,
pub clock_offset: Option<i64>,
}
const NTP_F32: f64 = 4_294_967_296.0;
impl AbsCaptureTime {
pub fn clock_offset_secs(&self) -> Option<f64> {
self.clock_offset.map(|v| v as f64 / NTP_F32)
}
pub fn set_clock_offset(&mut self, secs: f64) {
self.clock_offset = Some((secs * NTP_F32) as i64);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VideoTiming {
pub flags: u8,
pub encode_start: u16,
pub encode_finish: u16,
pub packetize_complete: u16,
pub last_left_pacer: u16,
}
impl fmt::Display for Extension {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Extension::*;
write!(
f,
"{}",
match self {
AbsoluteSendTime => "abs-send-time",
AbsoluteCaptureTime => "abs-capture-time",
AudioLevel => "ssrc-audio-level",
TransmissionTimeOffset => "toffset",
VideoOrientation => "video-orientation",
TransportSequenceNumber => "transport-wide-cc",
PlayoutDelay => "playout-delay",
VideoContentType => "video-content-type",
VideoTiming => "video-timing",
RtpStreamId => "rtp-stream-id",
RepairedRtpStreamId => "repaired-rtp-stream-id",
RtpMid => "mid",
FrameMarking => "frame-marking07",
ColorSpace => "color-space",
UnknownUri(uri, _) => uri,
}
)
}
}
impl fmt::Debug for ExtensionMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Extensions(")?;
let joined = self
.0
.iter()
.enumerate()
.filter_map(|(i, v)| v.as_ref().map(|v| (i + 1, v)))
.map(|(i, v)| format!("{}={}", i, v.ext))
.collect::<Vec<_>>()
.join(", ");
write!(f, "{joined}")?;
write!(f, ")")?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VideoOrientation {
Deg0 = 0,
Deg90 = 3,
Deg180 = 2,
Deg270 = 1,
}
impl From<u8> for VideoOrientation {
fn from(value: u8) -> Self {
match value {
1 => Self::Deg270,
2 => Self::Deg180,
3 => Self::Deg90,
_ => Self::Deg0,
}
}
}
impl PartialEq for Extension {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Extension::AbsoluteSendTime, Extension::AbsoluteSendTime) => true,
(Extension::AbsoluteCaptureTime, Extension::AbsoluteCaptureTime) => true,
(Extension::AudioLevel, Extension::AudioLevel) => true,
(Extension::TransmissionTimeOffset, Extension::TransmissionTimeOffset) => true,
(Extension::VideoOrientation, Extension::VideoOrientation) => true,
(Extension::TransportSequenceNumber, Extension::TransportSequenceNumber) => true,
(Extension::PlayoutDelay, Extension::PlayoutDelay) => true,
(Extension::VideoContentType, Extension::VideoContentType) => true,
(Extension::VideoTiming, Extension::VideoTiming) => true,
(Extension::RtpStreamId, Extension::RtpStreamId) => true,
(Extension::RepairedRtpStreamId, Extension::RepairedRtpStreamId) => true,
(Extension::RtpMid, Extension::RtpMid) => true,
(Extension::FrameMarking, Extension::FrameMarking) => true,
(Extension::ColorSpace, Extension::ColorSpace) => true,
(Extension::UnknownUri(uri1, _), Extension::UnknownUri(uri2, _)) => uri1 == uri2,
_ => false,
}
}
}
impl Eq for Extension {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn abs_send_time() {
let now = Instant::now() + Duration::from_secs(1000);
let mut exts = ExtensionMap::empty();
exts.set(4, Extension::AbsoluteSendTime);
let ev = ExtensionValues {
abs_send_time: Some(now),
..Default::default()
};
let mut buf = vec![0_u8; 8];
exts.write_to(&mut buf[..], &ev, ExtensionsForm::OneByte);
let mut ev2 = ExtensionValues::default();
exts.parse(&buf, ExtensionsForm::OneByte, &mut ev2);
ev2.update_absolute_send_time(now + Duration::from_millis(50));
let now2 = ev2.abs_send_time.unwrap();
let abs = if now > now2 { now - now2 } else { now2 - now };
assert!(abs < Duration::from_millis(1));
}
#[test]
fn abs_send_time_two_byte_form() {
let now = Instant::now() + Duration::from_secs(1000);
let mut exts = ExtensionMap::empty();
exts.set(16, Extension::AbsoluteSendTime);
let ev = ExtensionValues {
abs_send_time: Some(now),
..Default::default()
};
let mut buf = vec![0_u8; 8];
assert_eq!(ExtensionsForm::TwoByte, exts.form(&ev));
exts.write_to(&mut buf[..], &ev, ExtensionsForm::TwoByte);
let mut ev2 = ExtensionValues::default();
exts.parse(&buf, ExtensionsForm::TwoByte, &mut ev2);
ev2.update_absolute_send_time(now + Duration::from_millis(50));
let now2 = ev2.abs_send_time.unwrap();
let abs = if now > now2 { now - now2 } else { now2 - now };
assert!(abs < Duration::from_millis(1));
}
#[test]
fn abs_capture_time_short_form() {
let now = SystemTime::now();
let mut exts = ExtensionMap::empty();
exts.set(5, Extension::AbsoluteCaptureTime);
let ev = ExtensionValues {
abs_capture_time: Some(AbsCaptureTime {
capture_time: now,
clock_offset: None,
}),
..Default::default()
};
let mut buf = vec![0_u8; 16];
exts.write_to(&mut buf[..], &ev, ExtensionsForm::OneByte);
let mut ev2 = ExtensionValues::default();
exts.parse(&buf, ExtensionsForm::OneByte, &mut ev2);
let act = ev2.abs_capture_time.unwrap();
let abs = if now > act.capture_time {
now.duration_since(act.capture_time)
} else {
act.capture_time.duration_since(now)
}
.unwrap();
assert!(abs < Duration::from_millis(1));
assert_eq!(act.clock_offset, None);
}
#[test]
fn abs_capture_time_extended_form() {
let now = SystemTime::now();
let clock_offset: i64 = 123456789;
let mut exts = ExtensionMap::empty();
exts.set(5, Extension::AbsoluteCaptureTime);
let ev = ExtensionValues {
abs_capture_time: Some(AbsCaptureTime {
capture_time: now,
clock_offset: Some(clock_offset),
}),
..Default::default()
};
let mut buf = vec![0_u8; 24];
exts.write_to(&mut buf[..], &ev, ExtensionsForm::OneByte);
let mut ev2 = ExtensionValues::default();
exts.parse(&buf, ExtensionsForm::OneByte, &mut ev2);
let act = ev2.abs_capture_time.unwrap();
let abs = if now > act.capture_time {
now.duration_since(act.capture_time)
} else {
act.capture_time.duration_since(now)
}
.unwrap();
assert!(abs < Duration::from_millis(1));
assert_eq!(act.clock_offset, Some(clock_offset));
}
#[test]
fn abs_capture_time_two_byte_form() {
let now = SystemTime::now();
let clock_offset: i64 = -987654321;
let mut exts = ExtensionMap::empty();
exts.set(16, Extension::AbsoluteCaptureTime);
let ev = ExtensionValues {
abs_capture_time: Some(AbsCaptureTime {
capture_time: now,
clock_offset: Some(clock_offset),
}),
..Default::default()
};
assert_eq!(ExtensionsForm::TwoByte, exts.form(&ev));
let mut buf = vec![0_u8; 24];
exts.write_to(&mut buf[..], &ev, ExtensionsForm::TwoByte);
let mut ev2 = ExtensionValues::default();
exts.parse(&buf, ExtensionsForm::TwoByte, &mut ev2);
let act = ev2.abs_capture_time.unwrap();
let abs = if now > act.capture_time {
now.duration_since(act.capture_time)
} else {
act.capture_time.duration_since(now)
}
.unwrap();
assert!(abs < Duration::from_millis(1));
assert_eq!(act.clock_offset, Some(clock_offset));
}
#[test]
fn playout_delay() {
let mut exts = ExtensionMap::empty();
exts.set(2, Extension::PlayoutDelay);
let ev = ExtensionValues {
play_delay_min: Some(MediaTime::from_hundredths(100)),
play_delay_max: Some(MediaTime::from_hundredths(200)),
..Default::default()
};
let mut buf = vec![0_u8; 8];
exts.write_to(&mut buf[..], &ev, ExtensionsForm::OneByte);
let mut ev2 = ExtensionValues::default();
exts.parse(&buf, ExtensionsForm::OneByte, &mut ev2);
assert_eq!(ev.play_delay_min, ev2.play_delay_min);
assert_eq!(ev.play_delay_max, ev2.play_delay_max);
}
#[test]
fn remap_exts_audio() {
use Extension::*;
let mut e1 = ExtensionMap::standard();
let mut e2 = ExtensionMap::empty();
e2.set(14, TransportSequenceNumber);
println!("{:?}", e1.iter_video().collect::<Vec<_>>());
e1.remap(&e2.iter_audio().collect::<Vec<_>>());
assert_eq!(
e1.iter_audio().collect::<Vec<_>>(),
vec![
(1, &AudioLevel),
(2, &AbsoluteSendTime),
(4, &RtpMid),
(10, &RtpStreamId),
(11, &RepairedRtpStreamId),
(14, &TransportSequenceNumber)
]
);
assert_eq!(
e1.iter_video().collect::<Vec<_>>(),
vec![
(2, &AbsoluteSendTime),
(4, &RtpMid),
(10, &RtpStreamId),
(11, &RepairedRtpStreamId),
(13, &VideoOrientation),
(14, &TransportSequenceNumber),
]
);
}
#[test]
fn remap_exts_video() {
use Extension::*;
let mut e1 = ExtensionMap::empty();
e1.set(3, TransportSequenceNumber);
e1.set(4, VideoOrientation);
e1.set(5, VideoContentType);
let mut e2 = ExtensionMap::empty();
e2.set(14, TransportSequenceNumber);
e2.set(12, VideoOrientation);
e1.remap(&e2.iter_video().collect::<Vec<_>>());
assert_eq!(
e1.iter_video().collect::<Vec<_>>(),
vec![
(5, &VideoContentType),
(12, &VideoOrientation),
(14, &TransportSequenceNumber)
]
);
}
#[test]
fn remap_exts_swaparoo() {
use Extension::*;
let mut e1 = ExtensionMap::empty();
e1.set(12, TransportSequenceNumber);
e1.set(14, VideoOrientation);
let mut e2 = ExtensionMap::empty();
e2.set(14, TransportSequenceNumber);
e2.set(12, VideoOrientation);
e1.remap(&e2.iter_video().collect::<Vec<_>>());
assert_eq!(
e1.iter_video().collect::<Vec<_>>(),
vec![(12, &VideoOrientation), (14, &TransportSequenceNumber)]
);
}
#[test]
fn remap_exts_illegal() {
use Extension::*;
let mut e1 = ExtensionMap::empty();
e1.set(12, TransportSequenceNumber);
e1.set(14, VideoOrientation);
let mut e2 = ExtensionMap::empty();
e2.set(14, TransportSequenceNumber);
e2.set(12, VideoOrientation);
let mut e3 = ExtensionMap::empty();
e3.set(1, TransportSequenceNumber);
e3.set(12, AudioLevel);
e1.remap(&e2.iter_video().collect::<Vec<_>>());
println!("{:#?}", e1.0);
assert_eq!(
e1.iter_video().collect::<Vec<_>>(),
vec![(12, &VideoOrientation), (14, &TransportSequenceNumber)]
);
e1.remap(&e3.iter_audio().collect::<Vec<_>>());
println!("{:#?}", e1.0);
assert_eq!(
e1.iter_video().collect::<Vec<_>>(),
vec![(12, &VideoOrientation), (14, &TransportSequenceNumber)]
);
}
}