use crate::peer_connection::configuration::UNSPECIFIED_STR;
use crate::peer_connection::configuration::media_engine::*;
use crate::rtp_transceiver::rtp_sender::rtcp_parameters::RTCPFeedback;
use crate::rtp_transceiver::rtp_sender::rtp_codec_parameters::RTCRtpCodecParameters;
use crate::rtp_transceiver::rtp_sender::rtp_encoding_parameters::RTCRtpEncodingParameters;
use crate::rtp_transceiver::{PayloadType, fmtp};
use serde::{Deserialize, Serialize};
use shared::error::{Error, Result};
use std::fmt;
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RtpCodecKind {
#[default]
Unspecified = 0,
#[serde(rename = "audio")]
Audio = 1,
#[serde(rename = "video")]
Video = 2,
}
impl From<&str> for RtpCodecKind {
fn from(raw: &str) -> Self {
match raw {
"audio" => RtpCodecKind::Audio,
"video" => RtpCodecKind::Video,
_ => RtpCodecKind::Unspecified,
}
}
}
impl From<u8> for RtpCodecKind {
fn from(v: u8) -> Self {
match v {
1 => RtpCodecKind::Audio,
2 => RtpCodecKind::Video,
_ => RtpCodecKind::Unspecified,
}
}
}
impl fmt::Display for RtpCodecKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match *self {
RtpCodecKind::Audio => "audio",
RtpCodecKind::Video => "video",
RtpCodecKind::Unspecified => UNSPECIFIED_STR,
};
write!(f, "{s}")
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct RTCRtpCodec {
pub mime_type: String,
pub clock_rate: u32,
pub channels: u16,
pub sdp_fmtp_line: String,
pub rtcp_feedback: Vec<RTCPFeedback>, }
impl RTCRtpCodec {
pub fn payloader(&self) -> Result<Box<dyn rtp::packetizer::Payloader>> {
let mime_type = self.mime_type.to_lowercase();
if mime_type == MIME_TYPE_H264.to_lowercase() {
Ok(Box::<rtp::codec::h264::H264Payloader>::default())
} else if mime_type == MIME_TYPE_HEVC.to_lowercase() {
Ok(Box::<rtp::codec::h265::HevcPayloader>::default())
} else if mime_type == MIME_TYPE_VP8.to_lowercase() {
let mut vp8_payloader = rtp::codec::vp8::Vp8Payloader::default();
vp8_payloader.enable_picture_id = true;
Ok(Box::new(vp8_payloader))
} else if mime_type == MIME_TYPE_VP9.to_lowercase() {
Ok(Box::<rtp::codec::vp9::Vp9Payloader>::default())
} else if mime_type == MIME_TYPE_OPUS.to_lowercase() {
Ok(Box::<rtp::codec::opus::OpusPayloader>::default())
} else if mime_type == MIME_TYPE_G722.to_lowercase()
|| mime_type == MIME_TYPE_PCMU.to_lowercase()
|| mime_type == MIME_TYPE_PCMA.to_lowercase()
|| mime_type == MIME_TYPE_TELEPHONE_EVENT.to_lowercase()
{
Ok(Box::<rtp::codec::g7xx::G7xxPayloader>::default())
} else if mime_type == MIME_TYPE_AV1.to_lowercase() {
Ok(Box::<rtp::codec::av1::Av1Payloader>::default())
} else {
Err(Error::ErrNoPayloaderForCodec)
}
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq)]
pub(crate) enum CodecMatch {
#[default]
None = 0,
Partial = 1,
Exact = 2,
}
pub(crate) fn codec_parameters_fuzzy_search(
needle_rtp_codec: &RTCRtpCodec,
haystack: &[RTCRtpCodecParameters],
) -> (RTCRtpCodecParameters, CodecMatch) {
let needle_fmtp = fmtp::parse(&needle_rtp_codec.mime_type, &needle_rtp_codec.sdp_fmtp_line);
for c in haystack {
let cfmpt = fmtp::parse(&c.rtp_codec.mime_type, &c.rtp_codec.sdp_fmtp_line);
if needle_fmtp.match_fmtp(&*cfmpt) {
return (c.clone(), CodecMatch::Exact);
}
}
for c in haystack {
if c.rtp_codec.mime_type.to_uppercase() == needle_rtp_codec.mime_type.to_uppercase() {
return (c.clone(), CodecMatch::Partial);
}
}
(RTCRtpCodecParameters::default(), CodecMatch::None)
}
pub(crate) fn encoding_parameters_fuzzy_search(
encodings: &[RTCRtpEncodingParameters],
haystack: &[RTCRtpCodecParameters],
) -> (RTCRtpEncodingParameters, CodecMatch) {
let mut result = None;
for encoding in encodings {
let (_, codec_match_type) = codec_parameters_fuzzy_search(&encoding.codec, haystack);
if codec_match_type == CodecMatch::Exact {
return (encoding.clone(), codec_match_type);
} else if result.is_none() {
result = Some((encoding.clone(), CodecMatch::Partial));
}
}
if let Some((encoding, code_match_type)) = result {
(encoding, code_match_type)
} else {
(RTCRtpEncodingParameters::default(), CodecMatch::None)
}
}
pub(crate) fn find_rtx_payload_type(
needle: PayloadType,
haystack: &[RTCRtpCodecParameters],
) -> Option<PayloadType> {
for c in haystack {
if parse_rtx_apt(&c.rtp_codec.sdp_fmtp_line) == Some(needle) {
return Some(c.payload_type);
}
}
None
}
pub(crate) fn parse_rtx_apt(sdp_fmtp_line: &str) -> Option<PayloadType> {
sdp_fmtp_line
.split(';')
.filter_map(|param| param.trim().strip_prefix("apt="))
.find_map(|value| value.trim().parse::<PayloadType>().ok())
}
pub(crate) fn find_fec_payload_type(haystack: &[RTCRtpCodecParameters]) -> Option<PayloadType> {
for c in haystack {
if c.rtp_codec
.mime_type
.to_lowercase()
.contains(MIME_TYPE_FLEX_FEC)
{
return Some(c.payload_type);
}
}
None
}
pub(crate) fn rtcp_feedback_intersection(
a: &[RTCPFeedback],
b: &[RTCPFeedback],
) -> Vec<RTCPFeedback> {
let mut out = vec![];
for a_feedback in a {
for b_feeback in b {
if a_feedback.typ == b_feeback.typ && a_feedback.parameter == b_feeback.parameter {
out.push(a_feedback.clone());
break;
}
}
}
out
}
#[cfg(test)]
mod rtx_apt_tests {
use super::{RTCRtpCodec, RTCRtpCodecParameters, find_rtx_payload_type, parse_rtx_apt};
fn rtx(payload_type: u8, fmtp: &str) -> RTCRtpCodecParameters {
RTCRtpCodecParameters {
rtp_codec: RTCRtpCodec {
mime_type: "video/rtx".to_owned(),
clock_rate: 90000,
channels: 0,
sdp_fmtp_line: fmtp.to_owned(),
rtcp_feedback: vec![],
},
payload_type,
}
}
#[test]
fn find_rtx_payload_type_tolerates_extra_apt_params() {
let haystack = vec![rtx(97, "apt=96;rtx-time=3000"), rtx(99, "apt=98")];
assert_eq!(find_rtx_payload_type(96, &haystack), Some(97));
assert_eq!(find_rtx_payload_type(98, &haystack), Some(99));
assert_eq!(find_rtx_payload_type(100, &haystack), None);
}
#[test]
fn parses_plain_apt() {
assert_eq!(parse_rtx_apt("apt=96"), Some(96));
assert_eq!(parse_rtx_apt("apt=127"), Some(127));
assert_eq!(parse_rtx_apt("apt=0"), Some(0));
}
#[test]
fn parses_apt_with_extra_params() {
assert_eq!(parse_rtx_apt("apt=96;rtx-time=3000"), Some(96));
assert_eq!(parse_rtx_apt("rtx-time=3000;apt=102"), Some(102));
assert_eq!(parse_rtx_apt("apt=98 ; rtx-time=1000"), Some(98));
}
#[test]
fn returns_none_without_apt() {
assert_eq!(parse_rtx_apt(""), None);
assert_eq!(parse_rtx_apt("profile-id=0"), None);
assert_eq!(parse_rtx_apt("apt="), None);
assert_eq!(parse_rtx_apt("apt=notanumber"), None);
assert_eq!(parse_rtx_apt("apt=300"), None);
}
}