File
Blob: firmware/vendor/str0m/src/rtp/rtcp/header.rs
| 1 | #![allow(clippy::unusual_byte_groupings)] |
| 2 | |
| 3 | use super::{FeedbackMessageType, PayloadType, TransportType}; |
| 4 | |
| 5 | pub(crate) const LEN_HEADER: usize = 4; |
| 6 | |
| 7 | #[derive(Debug, PartialEq, Eq)] |
| 8 | pub struct RtcpHeader { |
| 9 | pub(crate) rtcp_type: RtcpType, |
| 10 | pub(crate) feedback_message_type: FeedbackMessageType, |
| 11 | pub(crate) words_less_one: u16, |
| 12 | } |
| 13 | |
| 14 | impl RtcpHeader { |
| 15 | /// Type of RTCP packet. This is further divided into subtypes by |
| 16 | /// `feedback_message_type`. |
| 17 | pub fn rtcp_type(&self) -> RtcpType { |
| 18 | self.rtcp_type |
| 19 | } |
| 20 | |
| 21 | /// Subtype of RTCP message. |
| 22 | pub fn feedback_message_type(&self) -> FeedbackMessageType { |
| 23 | self.feedback_message_type |
| 24 | } |
| 25 | |
| 26 | /// Number of reports stacked in this message. |
| 27 | /// |
| 28 | /// Depending `rtcp_type` and `feedback_message_type` multiple reports of the same type can be |
| 29 | /// stacked into one RTCP packet under the same header. |
| 30 | pub fn count(&self) -> usize { |
| 31 | match self.rtcp_type { |
| 32 | RtcpType::SenderReport |
| 33 | | RtcpType::ReceiverReport |
| 34 | | RtcpType::SourceDescription |
| 35 | | RtcpType::Goodbye => self.feedback_message_type.count() as usize, |
| 36 | RtcpType::ApplicationDefined => 1, |
| 37 | RtcpType::TransportLayerFeedback => { |
| 38 | let transport_type = match self.feedback_message_type { |
| 39 | FeedbackMessageType::TransportFeedback(v) => v, |
| 40 | _ => unreachable!(), |
| 41 | }; |
| 42 | |
| 43 | match transport_type { |
| 44 | TransportType::Nack => { |
| 45 | // [ssrc_sender, ssrc_media_source, fci, fci, ...] |
| 46 | let fci_length = self.length_words() * 4 - LEN_HEADER - 2 * 4; |
| 47 | |
| 48 | // each fci is one word: [pid, blp] |
| 49 | fci_length / 4 |
| 50 | } |
| 51 | TransportType::TransportWide => { |
| 52 | // TODO |
| 53 | 0 |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | RtcpType::PayloadSpecificFeedback => { |
| 58 | let payload_type = match self.feedback_message_type { |
| 59 | FeedbackMessageType::PayloadFeedback(v) => v, |
| 60 | _ => unreachable!(), |
| 61 | }; |
| 62 | |
| 63 | match payload_type { |
| 64 | PayloadType::PictureLossIndication => { |
| 65 | // PLI does not require parameters. Therefore, the length field MUST be |
| 66 | // 2, and there MUST NOT be any Feedback Control Information. |
| 67 | 1 |
| 68 | } |
| 69 | |
| 70 | PayloadType::FullIntraRequest => { |
| 71 | // [ssrc_sender, ssrc_media_source, fci, fci, ...] |
| 72 | let fci_length = self.length_words() * 4 - LEN_HEADER - 2 * 4; |
| 73 | |
| 74 | // each fci is two words: [ssrc, [seq_no, reserved]] |
| 75 | fci_length / 8 |
| 76 | } |
| 77 | |
| 78 | _ => { |
| 79 | // PayloadType::SliceLossIndication => {}, |
| 80 | // PayloadType::ReferencePictureSelectionIndication => {}, |
| 81 | // PayloadType::ApplicationLayer => {}, |
| 82 | 0 |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | RtcpType::ExtendedReport => 0, |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | /// Length of entire RTCP packet (including header) in words (4 bytes). |
| 91 | pub fn length_words(&self) -> usize { |
| 92 | self.words_less_one as usize + 1 |
| 93 | } |
| 94 | |
| 95 | /// Write header to buffer. |
| 96 | pub(crate) fn write_to(&self, buf: &mut [u8]) -> usize { |
| 97 | let fmt: u8 = self.feedback_message_type.into(); |
| 98 | |
| 99 | buf[0] = 0b10_0_00000 | fmt; |
| 100 | buf[1] = self.rtcp_type as u8; |
| 101 | |
| 102 | buf[2..4].copy_from_slice(&self.words_less_one.to_be_bytes()); |
| 103 | |
| 104 | 4 |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | /// Kind of RTCP packet. |
| 109 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 110 | pub enum RtcpType { |
| 111 | /// RTCP_PT_SR |
| 112 | SenderReport = 200, |
| 113 | |
| 114 | /// RTCP_PT_RR |
| 115 | ReceiverReport = 201, |
| 116 | |
| 117 | /// RTCP_PT_SDES |
| 118 | SourceDescription = 202, |
| 119 | |
| 120 | /// RTCP_PT_BYE |
| 121 | Goodbye = 203, |
| 122 | |
| 123 | /// RTCP_PT_APP |
| 124 | ApplicationDefined = 204, |
| 125 | |
| 126 | /// RTCP_PT_RTPFB |
| 127 | // https://tools.ietf.org/html/rfc4585 |
| 128 | TransportLayerFeedback = 205, |
| 129 | |
| 130 | /// RTCP_PT_PSFB |
| 131 | // https://tools.ietf.org/html/rfc4585 |
| 132 | PayloadSpecificFeedback = 206, |
| 133 | |
| 134 | /// RTCP_PT_XR |
| 135 | ExtendedReport = 207, |
| 136 | } |
| 137 | |
| 138 | impl TryFrom<u8> for RtcpType { |
| 139 | type Error = &'static str; |
| 140 | |
| 141 | fn try_from(v: u8) -> Result<Self, Self::Error> { |
| 142 | use RtcpType::*; |
| 143 | match v { |
| 144 | 200 => Ok(SenderReport), // sr |
| 145 | 201 => Ok(ReceiverReport), // rr |
| 146 | 202 => Ok(SourceDescription), |
| 147 | 203 => Ok(Goodbye), |
| 148 | 204 => Ok(ApplicationDefined), |
| 149 | 205 => Ok(TransportLayerFeedback), |
| 150 | 206 => Ok(PayloadSpecificFeedback), |
| 151 | 207 => Ok(ExtendedReport), |
| 152 | _ => { |
| 153 | trace!("Unknown RtcpType: {}", v); |
| 154 | Err("Unknown RtcpType") |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | impl<'a> TryFrom<&'a [u8]> for RtcpHeader { |
| 161 | type Error = &'static str; |
| 162 | |
| 163 | fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> { |
| 164 | if buf.len() < 4 { |
| 165 | return Err("Need 4 bytes for RTCP header"); |
| 166 | } |
| 167 | |
| 168 | let version = (buf[0] & 0b11_0_00000) >> 6; |
| 169 | if version != 2 { |
| 170 | return Err("RTCP header version should be 2"); |
| 171 | } |
| 172 | |
| 173 | let fmt = buf[0] & 0b00_0_11111; |
| 174 | let rtcp_type: RtcpType = buf[1].try_into()?; |
| 175 | |
| 176 | let feedback_message_type = { |
| 177 | use FeedbackMessageType::*; |
| 178 | match rtcp_type { |
| 179 | RtcpType::SenderReport => ReceptionReport(fmt), |
| 180 | RtcpType::ReceiverReport => ReceptionReport(fmt), |
| 181 | RtcpType::SourceDescription => SourceCount(fmt), |
| 182 | RtcpType::Goodbye => SourceCount(fmt), |
| 183 | RtcpType::ApplicationDefined => Subtype(fmt), |
| 184 | RtcpType::TransportLayerFeedback => TransportFeedback(fmt.try_into()?), |
| 185 | RtcpType::PayloadSpecificFeedback => PayloadFeedback(fmt.try_into()?), |
| 186 | RtcpType::ExtendedReport => NotUsed, |
| 187 | } |
| 188 | }; |
| 189 | |
| 190 | let words_less_one = u16::from_be_bytes([buf[2], buf[3]]); |
| 191 | |
| 192 | Ok(RtcpHeader { |
| 193 | rtcp_type, |
| 194 | feedback_message_type, |
| 195 | words_less_one, |
| 196 | }) |
| 197 | } |
| 198 | } |