#[cfg(test)]
mod tests;
use serde::de::{MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
use std::iter::FromIterator;
use std::num::{NonZeroU32, NonZeroU8};
#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
pub struct RtpCodecParametersParameters(BTreeMap<String, RtpCodecParametersParametersValue>);
impl RtpCodecParametersParameters {
pub fn insert<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<String>,
V: Into<RtpCodecParametersParametersValue>,
{
self.0.insert(key.into(), value.into());
self
}
pub fn iter(
&self,
) -> std::collections::btree_map::Iter<'_, String, RtpCodecParametersParametersValue> {
self.0.iter()
}
pub fn get(&self, key: &str) -> Option<&RtpCodecParametersParametersValue> {
self.0.get(key)
}
}
impl<K, const N: usize> From<[(K, RtpCodecParametersParametersValue); N]>
for RtpCodecParametersParameters
where
K: Into<String>,
{
fn from(array: [(K, RtpCodecParametersParametersValue); N]) -> Self {
Self::from_iter(std::array::IntoIter::new(array))
}
}
impl IntoIterator for RtpCodecParametersParameters {
type Item = (String, RtpCodecParametersParametersValue);
type IntoIter =
std::collections::btree_map::IntoIter<String, RtpCodecParametersParametersValue>;
fn into_iter(
self,
) -> std::collections::btree_map::IntoIter<String, RtpCodecParametersParametersValue> {
self.0.into_iter()
}
}
impl<K> Extend<(K, RtpCodecParametersParametersValue)> for RtpCodecParametersParameters
where
K: Into<String>,
{
fn extend<T: IntoIterator<Item = (K, RtpCodecParametersParametersValue)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(k, v)| {
self.insert(k, v);
});
}
}
impl<K> FromIterator<(K, RtpCodecParametersParametersValue)> for RtpCodecParametersParameters
where
K: Into<String>,
{
fn from_iter<T: IntoIterator<Item = (K, RtpCodecParametersParametersValue)>>(iter: T) -> Self {
Self(iter.into_iter().map(|(k, v)| (k.into(), v)).collect())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
#[non_exhaustive]
pub enum RtpCodecCapabilityFinalized {
#[serde(rename_all = "camelCase")]
Audio {
mime_type: MimeTypeAudio,
preferred_payload_type: u8,
clock_rate: NonZeroU32,
channels: NonZeroU8,
parameters: RtpCodecParametersParameters,
rtcp_feedback: Vec<RtcpFeedback>,
},
#[serde(rename_all = "camelCase")]
Video {
mime_type: MimeTypeVideo,
preferred_payload_type: u8,
clock_rate: NonZeroU32,
parameters: RtpCodecParametersParameters,
rtcp_feedback: Vec<RtcpFeedback>,
},
}
impl RtpCodecCapabilityFinalized {
pub(crate) fn is_rtx(&self) -> bool {
match self {
Self::Audio { mime_type, .. } => mime_type == &MimeTypeAudio::Rtx,
Self::Video { mime_type, .. } => mime_type == &MimeTypeVideo::Rtx,
}
}
pub(crate) fn clock_rate(&self) -> NonZeroU32 {
match self {
Self::Audio { clock_rate, .. } => *clock_rate,
Self::Video { clock_rate, .. } => *clock_rate,
}
}
pub(crate) fn parameters(&self) -> &RtpCodecParametersParameters {
match self {
Self::Audio { parameters, .. } => parameters,
Self::Video { parameters, .. } => parameters,
}
}
pub(crate) fn parameters_mut(&mut self) -> &mut RtpCodecParametersParameters {
match self {
Self::Audio { parameters, .. } => parameters,
Self::Video { parameters, .. } => parameters,
}
}
pub(crate) fn preferred_payload_type(&self) -> u8 {
match self {
Self::Audio {
preferred_payload_type,
..
} => *preferred_payload_type,
Self::Video {
preferred_payload_type,
..
} => *preferred_payload_type,
}
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct RtpCapabilitiesFinalized {
pub codecs: Vec<RtpCodecCapabilityFinalized>,
pub header_extensions: Vec<RtpHeaderExtension>,
pub fec_mechanisms: Vec<String>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum MediaKind {
Audio,
Video,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(untagged)]
pub enum MimeType {
Audio(MimeTypeAudio),
Video(MimeTypeVideo),
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
pub enum MimeTypeAudio {
#[serde(rename = "audio/opus")]
Opus,
#[serde(rename = "audio/PCMU")]
Pcmu,
#[serde(rename = "audio/PCMA")]
Pcma,
#[serde(rename = "audio/ISAC")]
Isac,
#[serde(rename = "audio/G722")]
G722,
#[serde(rename = "audio/iLBC")]
Ilbc,
#[serde(rename = "audio/SILK")]
Silk,
#[serde(rename = "audio/CN")]
Cn,
#[serde(rename = "audio/telephone-event")]
TelephoneEvent,
#[serde(rename = "audio/rtx")]
Rtx,
#[serde(rename = "audio/red")]
Red,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
pub enum MimeTypeVideo {
#[serde(rename = "video/VP8")]
Vp8,
#[serde(rename = "video/VP9")]
Vp9,
#[serde(rename = "video/H264")]
H264,
#[serde(rename = "video/H265")]
H265,
#[serde(rename = "video/rtx")]
Rtx,
#[serde(rename = "video/red")]
Red,
#[serde(rename = "video/ulpfec")]
Ulpfec,
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum RtpCodecCapability {
#[serde(rename_all = "camelCase")]
Audio {
mime_type: MimeTypeAudio,
#[serde(skip_serializing_if = "Option::is_none")]
preferred_payload_type: Option<u8>,
clock_rate: NonZeroU32,
channels: NonZeroU8,
parameters: RtpCodecParametersParameters,
rtcp_feedback: Vec<RtcpFeedback>,
},
#[serde(rename_all = "camelCase")]
Video {
mime_type: MimeTypeVideo,
#[serde(skip_serializing_if = "Option::is_none")]
preferred_payload_type: Option<u8>,
clock_rate: NonZeroU32,
parameters: RtpCodecParametersParameters,
rtcp_feedback: Vec<RtcpFeedback>,
},
}
impl RtpCodecCapability {
pub(crate) fn mime_type(&self) -> MimeType {
match self {
Self::Audio { mime_type, .. } => MimeType::Audio(*mime_type),
Self::Video { mime_type, .. } => MimeType::Video(*mime_type),
}
}
pub(crate) fn parameters(&self) -> &RtpCodecParametersParameters {
match self {
Self::Audio { parameters, .. } => parameters,
Self::Video { parameters, .. } => parameters,
}
}
pub(crate) fn parameters_mut(&mut self) -> &mut RtpCodecParametersParameters {
match self {
Self::Audio { parameters, .. } => parameters,
Self::Video { parameters, .. } => parameters,
}
}
pub(crate) fn preferred_payload_type(&self) -> Option<u8> {
match self {
Self::Audio {
preferred_payload_type,
..
} => *preferred_payload_type,
Self::Video {
preferred_payload_type,
..
} => *preferred_payload_type,
}
}
pub(crate) fn rtcp_feedback(&self) -> &Vec<RtcpFeedback> {
match self {
Self::Audio { rtcp_feedback, .. } => rtcp_feedback,
Self::Video { rtcp_feedback, .. } => rtcp_feedback,
}
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtpCapabilities {
pub codecs: Vec<RtpCodecCapability>,
pub header_extensions: Vec<RtpHeaderExtension>,
#[serde(default)]
pub fec_mechanisms: Vec<String>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum RtpHeaderExtensionDirection {
SendRecv,
SendOnly,
RecvOnly,
Inactive,
}
impl Default for RtpHeaderExtensionDirection {
fn default() -> Self {
Self::SendRecv
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
pub enum RtpHeaderExtensionUri {
#[serde(rename = "urn:ietf:params:rtp-hdrext:sdes:mid")]
Mid,
#[serde(rename = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id")]
RtpStreamId,
#[serde(rename = "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id")]
RepairRtpStreamId,
#[serde(rename = "http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07")]
FrameMarkingDraft07,
#[serde(rename = "urn:ietf:params:rtp-hdrext:framemarking")]
FrameMarking,
#[serde(rename = "urn:ietf:params:rtp-hdrext:ssrc-audio-level")]
AudioLevel,
#[serde(rename = "urn:3gpp:video-orientation")]
VideoOrientation,
#[serde(rename = "urn:ietf:params:rtp-hdrext:toffset")]
TimeOffset,
#[serde(rename = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01")]
TransportWideCcDraft01,
#[serde(rename = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time")]
AbsSendTime,
#[doc(hidden)]
#[serde(other, rename = "unsupported")]
Unsupported,
}
impl RtpHeaderExtensionUri {
pub fn as_str(&self) -> &'static str {
match self {
RtpHeaderExtensionUri::Mid => "urn:ietf:params:rtp-hdrext:sdes:mid",
RtpHeaderExtensionUri::RtpStreamId => "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
RtpHeaderExtensionUri::RepairRtpStreamId => {
"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"
}
RtpHeaderExtensionUri::FrameMarkingDraft07 => {
"http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07"
}
RtpHeaderExtensionUri::FrameMarking => "urn:ietf:params:rtp-hdrext:framemarking",
RtpHeaderExtensionUri::AudioLevel => "urn:ietf:params:rtp-hdrext:ssrc-audio-level",
RtpHeaderExtensionUri::VideoOrientation => "urn:3gpp:video-orientation",
RtpHeaderExtensionUri::TimeOffset => "urn:ietf:params:rtp-hdrext:toffset",
RtpHeaderExtensionUri::TransportWideCcDraft01 => {
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
}
RtpHeaderExtensionUri::AbsSendTime => {
"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"
}
RtpHeaderExtensionUri::Unsupported => "unsupported",
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtpHeaderExtension {
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<MediaKind>,
pub uri: RtpHeaderExtensionUri,
pub preferred_id: u16,
pub preferred_encrypt: bool,
pub direction: RtpHeaderExtensionDirection,
}
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtpParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub mid: Option<String>,
pub codecs: Vec<RtpCodecParameters>,
pub header_extensions: Vec<RtpHeaderExtensionParameters>,
pub encodings: Vec<RtpEncodingParameters>,
pub rtcp: RtcpParameters,
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(untagged)]
pub enum RtpCodecParametersParametersValue {
String(String),
Number(u32),
}
impl From<String> for RtpCodecParametersParametersValue {
fn from(s: String) -> Self {
Self::String(s)
}
}
impl From<&str> for RtpCodecParametersParametersValue {
fn from(s: &str) -> Self {
Self::String(s.to_string())
}
}
impl From<u8> for RtpCodecParametersParametersValue {
fn from(n: u8) -> Self {
Self::Number(n as u32)
}
}
impl From<u16> for RtpCodecParametersParametersValue {
fn from(n: u16) -> Self {
Self::Number(n as u32)
}
}
impl From<u32> for RtpCodecParametersParametersValue {
fn from(n: u32) -> Self {
Self::Number(n)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(untagged, rename_all = "lowercase")]
pub enum RtpCodecParameters {
#[serde(rename_all = "camelCase")]
Audio {
mime_type: MimeTypeAudio,
payload_type: u8,
clock_rate: NonZeroU32,
channels: NonZeroU8,
parameters: RtpCodecParametersParameters,
rtcp_feedback: Vec<RtcpFeedback>,
},
#[serde(rename_all = "camelCase")]
Video {
mime_type: MimeTypeVideo,
payload_type: u8,
clock_rate: NonZeroU32,
parameters: RtpCodecParametersParameters,
rtcp_feedback: Vec<RtcpFeedback>,
},
}
impl RtpCodecParameters {
pub(crate) fn is_rtx(&self) -> bool {
match self {
Self::Audio { mime_type, .. } => mime_type == &MimeTypeAudio::Rtx,
Self::Video { mime_type, .. } => mime_type == &MimeTypeVideo::Rtx,
}
}
pub(crate) fn mime_type(&self) -> MimeType {
match self {
Self::Audio { mime_type, .. } => MimeType::Audio(*mime_type),
Self::Video { mime_type, .. } => MimeType::Video(*mime_type),
}
}
pub(crate) fn payload_type(&self) -> u8 {
match self {
Self::Audio { payload_type, .. } => *payload_type,
Self::Video { payload_type, .. } => *payload_type,
}
}
pub(crate) fn parameters(&self) -> &RtpCodecParametersParameters {
match self {
Self::Audio { parameters, .. } => parameters,
Self::Video { parameters, .. } => parameters,
}
}
pub(crate) fn rtcp_feedback_mut(&mut self) -> &mut Vec<RtcpFeedback> {
match self {
Self::Audio { rtcp_feedback, .. } => rtcp_feedback,
Self::Video { rtcp_feedback, .. } => rtcp_feedback,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum RtcpFeedback {
Nack,
NackPli,
CcmFir,
GoogRemb,
TransportCc,
#[doc(hidden)]
Unsupported,
}
impl Serialize for RtcpFeedback {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
let mut rtcp_feedback = serializer.serialize_struct("RtcpFeedback", 2)?;
match self {
RtcpFeedback::Nack => {
rtcp_feedback.serialize_field("type", "nack")?;
rtcp_feedback.serialize_field("parameter", "")?;
}
RtcpFeedback::NackPli => {
rtcp_feedback.serialize_field("type", "nack")?;
rtcp_feedback.serialize_field("parameter", "pli")?;
}
RtcpFeedback::CcmFir => {
rtcp_feedback.serialize_field("type", "ccm")?;
rtcp_feedback.serialize_field("parameter", "fir")?;
}
RtcpFeedback::GoogRemb => {
rtcp_feedback.serialize_field("type", "goog-remb")?;
rtcp_feedback.serialize_field("parameter", "")?;
}
RtcpFeedback::TransportCc => {
rtcp_feedback.serialize_field("type", "transport-cc")?;
rtcp_feedback.serialize_field("parameter", "")?;
}
RtcpFeedback::Unsupported => {
rtcp_feedback.serialize_field("type", "unknown")?;
rtcp_feedback.serialize_field("parameter", "")?;
}
}
rtcp_feedback.end()
}
}
impl<'de> Deserialize<'de> for RtcpFeedback {
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Type,
Parameter,
}
struct RtcpFeedbackVisitor;
impl<'de> Visitor<'de> for RtcpFeedbackVisitor {
type Value = RtcpFeedback;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(
r#"RTCP feedback type and parameter like {"type": "nack", "parameter": ""}"#,
)
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut r#type = None::<Cow<'_, str>>;
let mut parameter = Cow::Borrowed("");
while let Some(key) = map.next_key()? {
match key {
Field::Type => {
if r#type.is_some() {
return Err(de::Error::duplicate_field("type"));
}
r#type = Some(map.next_value()?);
}
Field::Parameter => {
if parameter != "" {
return Err(de::Error::duplicate_field("parameter"));
}
parameter = map.next_value()?;
}
}
}
let r#type = r#type.ok_or_else(|| de::Error::missing_field("type"))?;
Ok(match (r#type.as_ref(), parameter.as_ref()) {
("nack", "") => RtcpFeedback::Nack,
("nack", "pli") => RtcpFeedback::NackPli,
("ccm", "fir") => RtcpFeedback::CcmFir,
("goog-remb", "") => RtcpFeedback::GoogRemb,
("transport-cc", "") => RtcpFeedback::TransportCc,
_ => RtcpFeedback::Unsupported,
})
}
}
const FIELDS: &[&str] = &["type", "parameter"];
deserializer.deserialize_struct("RtcpFeedback", FIELDS, RtcpFeedbackVisitor)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
pub struct RtpEncodingParametersRtx {
pub ssrc: u32,
}
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtpEncodingParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub ssrc: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub codec_payload_type: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rtx: Option<RtpEncodingParametersRtx>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dtx: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scalability_mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scale_resolution_down_by: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_bitrate: Option<u32>,
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
pub struct RtpHeaderExtensionParameters {
pub uri: RtpHeaderExtensionUri,
pub id: u16,
pub encrypt: bool,
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RtcpParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub cname: Option<String>,
pub reduced_size: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub mux: Option<bool>,
}
impl Default for RtcpParameters {
fn default() -> Self {
Self {
cname: None,
reduced_size: true,
mux: None,
}
}
}