Skip to content

Commit cd27d00

Browse files
committed
add struct for rtcp-fb attribute
1 parent 05111db commit cd27d00

File tree

1 file changed

+325
-0
lines changed

1 file changed

+325
-0
lines changed

src/lib.rs

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,294 @@ impl TypedAttribute for Rtcp {
584584
const NAME: &'static str = "rtcp";
585585
}
586586

587+
/// RTCP Positive feedback values
588+
///
589+
/// See [RFC 4585 Section 4.2](https://datatracker.ietf.org/doc/html/rfc4585#section-4.2)
590+
#[derive(Debug, PartialEq, Clone)]
591+
pub enum RtcpFbAck {
592+
/// Reference Picture Selection Indication
593+
Rpsi,
594+
/// Application layer feedback
595+
App(Option<String>),
596+
/// Congestion Control Feedback
597+
///
598+
/// See [RFC 8888 Section 6](https://datatracker.ietf.org/doc/html/rfc8888#section-6)
599+
Ccfb,
600+
/// Other Ack types
601+
Other(String),
602+
}
603+
604+
/// RTCP Negative feedback values
605+
///
606+
/// See [RFC 4585 Section 4.2](https://datatracker.ietf.org/doc/html/rfc4585#section-4.2)
607+
#[derive(Debug, PartialEq, Clone)]
608+
pub enum RtcpFbNack {
609+
/// Picture Loss Indication
610+
Pli,
611+
/// Slice Loss Indication
612+
Sli,
613+
/// Reference Picture Selection Indication
614+
Rpsi,
615+
/// Application layer feedback
616+
App(Option<String>),
617+
/// Explicit Congestion Notification
618+
///
619+
/// See [RFC 6679 Section 6.2](https://datatracker.ietf.org/doc/html/rfc6679#section-6.2)
620+
Ecn,
621+
/// Other Nack types
622+
Other(String),
623+
}
624+
625+
/// Codec Control using RTCP feedback messages
626+
///
627+
/// See [RFC 5104 Section 7.1](https://datatracker.ietf.org/doc/html/rfc5104#section-7.1)
628+
#[derive(Debug, PartialEq, Clone)]
629+
pub enum RtcpFbCcm {
630+
/// Full Intra Request
631+
Fir,
632+
/// Temporary Maximum Media Stream Bit Rate
633+
Tmmbr(Option<String>),
634+
/// Temporal-Spatial Trade-off
635+
Tstr,
636+
/// Video Back Channel Messages
637+
Vbcm(Vec<u8>),
638+
/// Other messages (for future commands/Indications)
639+
Other(String),
640+
}
641+
642+
#[derive(Debug, PartialEq, Clone)]
643+
/// Types of RTCP feedback values
644+
pub enum RtcpFbVal {
645+
/// Positive Acknowledgement
646+
Ack(Option<RtcpFbAck>),
647+
/// Negative Acknowledgement
648+
Nack(Option<RtcpFbNack>),
649+
/// Minimum interval between two Regular RTCP packets in milliseconds
650+
TrrInt(u64),
651+
/// Codec Control messages
652+
Ccm(RtcpFbCcm),
653+
/// Others Rtcp Fb types
654+
Other(String),
655+
}
656+
657+
#[derive(Debug, PartialEq, Clone)]
658+
/// RTCP Feedback Capability
659+
///
660+
/// See [RFC 4585 Section 4.2](https://datatracker.ietf.org/doc/html/rfc4585#section-4.2)
661+
pub struct RtcpFb {
662+
/// Payload format for which feedback messages may be used,
663+
/// wildcard ('*') applies to all formats
664+
pt: String,
665+
/// RTCP Feedback value
666+
val: RtcpFbVal,
667+
}
668+
669+
impl FromStr for RtcpFb {
670+
type Err = AttributeErr;
671+
672+
fn from_str(s: &str) -> Result<Self, Self::Err> {
673+
let mut i = s.split(' ');
674+
let Some(pt) = i.next() else {
675+
return Err(AttributeErr(
676+
"Failed to parse the RtcpFb, no payload format",
677+
));
678+
};
679+
680+
let pt = if let Ok(pt) = pt.parse::<u8>() {
681+
pt.to_string()
682+
} else if pt == "*" {
683+
pt.to_string()
684+
} else {
685+
return Err(AttributeErr(
686+
"Failed to parse the RtcpFb, invalid values in the payload format",
687+
));
688+
};
689+
690+
let Some(val) = i.next() else {
691+
return Err(AttributeErr("Failed to parse the RtcpFb, no Rtcp value"));
692+
};
693+
694+
let rtcp_fb_val = match val {
695+
"ack" => {
696+
if let Some(ack_val) = i.next() {
697+
let ack_val = match ack_val {
698+
"rpsi" => RtcpFbAck::Rpsi,
699+
"app" => {
700+
if let Some(app_param) = i.next() {
701+
RtcpFbAck::App(Some(app_param.to_string()))
702+
} else {
703+
RtcpFbAck::App(None)
704+
}
705+
}
706+
"ccfb" => {
707+
if pt != "*" {
708+
return Err(AttributeErr("The payload type used with \"ccfb\" feedback is not wildcard type '*'"));
709+
} else {
710+
RtcpFbAck::Ccfb
711+
}
712+
}
713+
other => RtcpFbAck::Other(other.to_string()),
714+
};
715+
RtcpFbVal::Ack(Some(ack_val))
716+
} else {
717+
RtcpFbVal::Ack(None)
718+
}
719+
}
720+
"nack" => {
721+
if let Some(nack_val) = i.next() {
722+
let nack_val = match nack_val {
723+
"pli" => RtcpFbNack::Pli,
724+
"sli" => RtcpFbNack::Sli,
725+
"rpsi" => RtcpFbNack::Rpsi,
726+
"app" => {
727+
if let Some(app_param) = i.next() {
728+
RtcpFbNack::App(Some(app_param.to_string()))
729+
} else {
730+
RtcpFbNack::App(None)
731+
}
732+
}
733+
"ecn" => RtcpFbNack::Ecn,
734+
other => RtcpFbNack::Other(other.to_string()),
735+
};
736+
RtcpFbVal::Nack(Some(nack_val))
737+
} else {
738+
RtcpFbVal::Nack(None)
739+
}
740+
}
741+
"trr-int" => {
742+
if let Some(val) = i.next() {
743+
let Ok(i) = val.parse::<u64>() else {
744+
return Err(AttributeErr("Failed to parse trr-int value"));
745+
};
746+
RtcpFbVal::TrrInt(i)
747+
} else {
748+
return Err(AttributeErr("The trr-int has no value"));
749+
}
750+
}
751+
"ccm" => {
752+
if let Some(ccm_val) = i.next() {
753+
let ccm_val = match ccm_val {
754+
"fir" => RtcpFbCcm::Fir,
755+
"tmmbr" => {
756+
if let Some(tmmbr_val) = i.next() {
757+
RtcpFbCcm::Tmmbr(Some(tmmbr_val.to_string()))
758+
} else {
759+
RtcpFbCcm::Tmmbr(None)
760+
}
761+
}
762+
"tstr" => RtcpFbCcm::Tstr,
763+
"vbcm" => {
764+
let mut v = vec![];
765+
for vbcm_val in i {
766+
let Ok(p) = vbcm_val.parse::<u8>() else {
767+
return Err(AttributeErr("Failed to parse vbcm value"));
768+
};
769+
v.push(p);
770+
}
771+
RtcpFbCcm::Vbcm(v)
772+
}
773+
other => RtcpFbCcm::Other(other.to_string()),
774+
};
775+
RtcpFbVal::Ccm(ccm_val)
776+
} else {
777+
return Err(AttributeErr("Ccm param not available "));
778+
}
779+
}
780+
other => RtcpFbVal::Other(other.to_string()),
781+
};
782+
783+
Ok(Self {
784+
pt,
785+
val: rtcp_fb_val,
786+
})
787+
}
788+
}
789+
790+
impl Display for RtcpFbVal {
791+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
792+
let fb_val = match self {
793+
RtcpFbVal::Ack(ack) => {
794+
let mut s = "ack".to_string();
795+
if let Some(ack) = ack {
796+
match ack {
797+
RtcpFbAck::Rpsi => s += " rpsi",
798+
RtcpFbAck::Ccfb => s += " ccfb",
799+
RtcpFbAck::App(app) => {
800+
s += " app";
801+
if let Some(app_param) = app {
802+
s += format!(" {}", app_param).as_str();
803+
}
804+
}
805+
RtcpFbAck::Other(other) => {
806+
s += format!(" {}", other).as_str();
807+
}
808+
}
809+
}
810+
s
811+
}
812+
RtcpFbVal::Nack(nack) => {
813+
let mut s = "nack".to_string();
814+
if let Some(nack) = nack {
815+
match nack {
816+
RtcpFbNack::Pli => s += " pli",
817+
RtcpFbNack::Sli => s += " sli",
818+
RtcpFbNack::Rpsi => s += " rpsi",
819+
RtcpFbNack::Ecn => s += " ecn",
820+
RtcpFbNack::App(app) => {
821+
s += " app";
822+
if let Some(app_param) = app {
823+
s += format!(" {}", app_param).as_str();
824+
}
825+
}
826+
RtcpFbNack::Other(other) => {
827+
s += format!(" {}", other).as_str();
828+
}
829+
}
830+
}
831+
s
832+
}
833+
RtcpFbVal::TrrInt(trr_int) => {
834+
format!("trr-int {}", trr_int)
835+
}
836+
RtcpFbVal::Ccm(ccm) => {
837+
let mut s = "ccm".to_string();
838+
match ccm {
839+
RtcpFbCcm::Fir => s += " fir",
840+
RtcpFbCcm::Tstr => s += " tstr",
841+
RtcpFbCcm::Tmmbr(smaxpr) => {
842+
if let Some(smaxpr) = smaxpr {
843+
s += format!(" {}", smaxpr).as_str();
844+
}
845+
}
846+
RtcpFbCcm::Vbcm(vbcm) => {
847+
vbcm.iter().for_each(|v| {
848+
s += format!(" {}", v).as_str();
849+
});
850+
}
851+
RtcpFbCcm::Other(other) => {
852+
s += format!(" {}", other).as_str();
853+
}
854+
}
855+
s
856+
}
857+
RtcpFbVal::Other(other) => other.clone(),
858+
};
859+
860+
f.write_str(&fb_val)
861+
}
862+
}
863+
864+
impl Display for RtcpFb {
865+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
866+
let s = format!("{} {}", self.pt, self.val);
867+
f.write_str(&s)
868+
}
869+
}
870+
871+
impl TypedAttribute for RtcpFb {
872+
const NAME: &'static str = "rtcp-fb";
873+
}
874+
587875
/// Originator of the session.
588876
///
589877
/// See [RFC 8866 Section 5.2](https://tools.ietf.org/html/rfc8866#section-5.2) for more details.
@@ -1267,4 +1555,41 @@ a=fingerprint:sha-256 3A:96:6D:57:B2:C2:C7:61:A0:46:3E:1C:97:39:D3:F7:0A:88:A0:B
12671555
assert_eq!(a[2].encoding_name, "L16");
12681556
assert_eq!(a[0].payload_type, 99);
12691557
}
1558+
1559+
#[test]
1560+
fn parse_rtcp_fb() {
1561+
let sdp = "v=0\r
1562+
o=alice 3203093520 3203093520 IN IP4 host.example.com\r
1563+
s=Multicast video with feedback\r
1564+
t=3203130148 3203137348\r
1565+
m=audio 49170 RTP/AVP 0\r
1566+
c=IN IP4 224.2.1.183\r
1567+
a=rtpmap:0 PCMU/8000\r
1568+
m=video 51372 RTP/AVPF 98 99\r
1569+
c=IN IP4 224.2.1.184\r
1570+
a=rtpmap:98 H263-1998/90000\r
1571+
a=rtpmap:99 H261/90000\r
1572+
a=rtcp-fb:* nack\r
1573+
a=rtcp-fb:98 nack rpsi\r
1574+
a=rtcp-fb:* trr-int 1000\r
1575+
a=rtcp-fb:98 ccm vbcm 1 2\r
1576+
a=rtcp-fb:* ccm tmmbr smaxpr=120\r
1577+
";
1578+
1579+
let parsed = Session::parse(sdp.as_bytes()).unwrap();
1580+
let mut written = vec![];
1581+
parsed.write(&mut written).unwrap();
1582+
1583+
let v = fallible_iterator::convert(parsed.medias[1].attributes_typed::<RtcpFb>())
1584+
.collect::<Vec<_>>()
1585+
.expect("Valid vector of attributes");
1586+
assert_eq!(v[0].pt, "*");
1587+
assert_eq!(v[1].val, RtcpFbVal::Nack(Some(RtcpFbNack::Rpsi)));
1588+
assert_eq!(v[2].val, RtcpFbVal::TrrInt(1000));
1589+
assert_eq!(v[3].val, RtcpFbVal::Ccm(RtcpFbCcm::Vbcm(vec![1, 2])));
1590+
assert_eq!(
1591+
v[4].val,
1592+
RtcpFbVal::Ccm(RtcpFbCcm::Tmmbr(Some("smaxpr=120".to_string())))
1593+
);
1594+
}
12701595
}

0 commit comments

Comments
 (0)