@@ -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