RTCP stands for Real-time Control Protocol. RTCP is defined by RFC 3550 (replacing the obsolete RFC 1889).

Real-time Transmission Protocol (RTP) and real-time control protocol (RTCP) are used together to monitor data transfer over large multicast networks. RTP hosts media streams, while RTCP monitors transport statistics and quality of service. Monitoring enables the receiver to detect any packet loss and compensate for any delayed jitter.

Both protocols work independently of the underlying transport layer and network layer protocols. The information in the RTP header tells the receiver how to reconstruct the data and describes how the codec bitstream is packaged.

We will focus on the RTCP function and RTCP packet. Finally, RTCP protocol parsing implementation.

What are the functions of RTCP

1. The main function of RTCP is to provide quality feedback data distribution. This is an integral part of the RTP role, transport protocol, control functions related to traffic and congestion with other transport protocols.

RTCP The source of the persistent transport level identifier with RTP is called the specification name or CNAME. Since a conflict is found or the program is restarted, the recipient requires CNAME to track each participant.

The receiver may also ask the CNAME to associate multiple data streams from a given participant with the number of related RTP sessions in the collection, such as synchronizing audio and video. Inter-media synchronization also requires time stamps that NTP and RTP data senders include in THEIR RTCP packets.

3. The first two functions require all participants to send RTCP packets, so the rate must be controlled to allow RTP to scale up to a large number of participants. Each person can independently observe the number of participants by having each participant send its control package to all others.

4. This feature is useful for loose conversation processes where participants can enter and leave at will, without member control or parameter coordination.

Functions 1-3 should be used in all environments, especially in IP multicast environments. RTP application designers should avoid larger numbers with mechanisms that work only in unicast mode and cannot be scaled. RTCP transmissions can be controlled separately for the sender and receiver, applicable for example to one-way links where the receiver has no feedback possible.

RTCP port

RTSP usually uses THE RTP protocol to transmit real-time streams. RTP usually uses the even-numbered ports, while RTCP uses the adjacent odd-numbered ports, namely, RTP port number +1.

RTP port

RTCP port

What are the RTCP packets

In RTCP communication control, the function of RTCP protocol is realized by different types of RTCP packets. RTCP is also based on UDP packets. There are five main types of packets:

  1. SR: Sender report, issued by the application or middle end that sends RTP datagrams.
  2. RR: A receiver report issued by an application or middle end that accepts but does not send an RTP datagram.
  3. SDES: Indicates the source description, which is used to transfer identification information related to session members, such as user names, emails, and phone numbers.
  4. BYE: Notifies other members of the call that they will exit the session.
  5. APP: defined by an application as an extension of RTCP.
#define RTCP_SR      200
#define RTCP_RR      201
#define RTCP_SDES    202
#define RTCP_BYE     203
#define RTCP_APP     204
Copy the code

We can determine the RTCP header based on these five types of packets

static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen) { unsigned int offset = 0; unsigned int first_byte = 0; unsigned int packet_type = 0; /* check the first byte */ first_byte = rtcp_info[offset]; / * version of the bit is set to 2 * / / / printf (" \ n version: % d ", ((first_byte & 0 xc0) > > 6)); if (((first_byte & 0xC0) >> 6) ! = 2) { return false; } / / offset += 1; packet_type = rtcp_info[offset]; //printf("packet_type: %d\n",packet_type); /* The first packet in a composite packet should be reported by the sender or receiver */ if (! ((packet_type == RTCP_SR) || (packet_type == RTCP_RR) || (packet_type == RTCP_BYE) || (packet_type == RTCP_APP) || (packet_type == RTCP_PSFB))) { return false; } /* The total length must be a multiple of 4 bytes */ /printf("PayloadLen: %d\n",PayloadLen); if (PayloadLen % 4) { return false; } /* OK, dissect as RTCP */ dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen); return true; }Copy the code

RTCP packet format

SR: reports from the sender

Version (V) : same as RTP header

Fill (P) : same as RTP header.

Receive Report counter (RC) :5b The number of report blocks received in this SR packet.

Packet type (PT) : 8bit the SR packet type is 200

Length (length) : The length of the SR packet is reduced by 1 in 32-bit units

Synchronous source (SSRC) : The identifier of the synchronous source sent by the SR packet. Same as the SSRC in the corresponding RTP package.

Network Time Protocol (NTP) timestamp: indicates the absolute Time when the SR packet is sent. Used to synchronize different streams.

RTP timestamp: Corresponds to the NTP timestamp and has the same initial value as the timestamp in the RTP packet.

Send’s Packet Count: Indicates the total number of bytes of valid data sent by the sender during the period from the beginning of sending the Packet to the generation of the SR Packet, excluding headers and padding. This field is cleared when the sender changes the SSRC.

Synchronous SOURCE N’s SSRC identifier: This report contains statistics on packets received from that source.

Loss rate: Indicates the loss rate of RTP packets sent from the previous SR or RR packet and then sent from synchronous source N.

Cumulative lost data: Indicates the total number of lost RTP packets sent by SSRC_n during the period from receiving SSRC_n packets to sending SRS.

Extended maximum serial number received: Maximum serial number received from SSRC_n from RTP packets.

Interarrival Jitter: statistical variance estimation of RTP packet receiving time.

Last SR timestamp: The middle 32bit of the NTP timestamp in the SR package recently received from SSRC_n. If no SR package has been received, the value is 0.

Delay since Last SR: Indicates the Delay between the Last time SSRC_n received an SR packet and the time when SSRC_n sent the SR packet

Wireshark caught:

Participants of an active session use SR when sending and receiving RTP packets. The SR has three distinct parts: header information, sender information, and a number of receiver report blocks. SR can also have an extended domain associated with an outline.

Wireshark caught:

SDES: indicates the source description

SDES provides a carrier for conveying identification information related to session members, such as user names, emails, and phone numbers. Each RTCP mixed packet must have an SDES packet.

The header contains a length field, a net charge type field (PT=202), and a source count (RC) field. The RC field contains five bits, indicating the number of information blocks in a group.

Each block of information contains an SSRC or CSRC value, followed by one or more identifiers and some information available for the SSRC or CSRC.

The SDES package for the CNAME item must be included in each combined RTCP package. SDES packages may include additional source descriptors, depending on specific application needs and taking into account bandwidth constraints.

Wireshark caught:

The SDES source description package provides intuitive text information to describe participants in a session, including source description items such as CNAME, NAME, EMAIL, PHONE, and LOC.

These provide convenience for the receiver to obtain information about the sender. An SDES packet consists of a packet header and data blocks. There may be no or multiple data blocks. The packet header consists of version (V), fill (P), length indication, packet type (PT), and source count (SC).

PT has 8 bits, used to identify the SDES packet of RTCP, and SC has 5 bits, indicating the number of SSRC/CSRC blocks contained in the SDES packet. Zero is valid but meaningless.

BYE: Notice to leave

The BYE group is used to indicate that one or more media sources are no longer active.

Wireshark caught:

Optionally, the BYE package can include an 8-bit octal count followed by text information indicating the reason for leaving.

Finally, each RTCP packet in a composite packet can be processed independently, rather than in the order in which packets are combined.

There are several mandatory constraints in a composite package.

  1. Receive statistics in SR or RR packets should be sent as often as bandwidth allows, so the combined RTCP packets sent each cycle should contain report packets.
  2. The SDES CNAME should be included in each composite package because the new receiver needs to receive the CNAME to identify the source and synchronize with the media contact.
  3. Composite packages are preceded by the number of package types, whose growth should be limited.

How to synchronize media streams using RTCP

Through packet capture and analysis of RTCP sender reports, RTP synchronization actually depends on these three fields:

  1. Sender SSRC: Synchronization source identifier sent by the SR packet. Same as the SSRC in the corresponding RTP package.
  2. NTP TIMESTAMP: Indicates the absolute time when the SR packet is sent. Used to synchronize different streams.
  3. RTP timestamp: Corresponds to the NTP timestamp and has the same initial value as the timestamp in the RTP packet.

How do you calculate NTP time? In RTCP, NTP time is stored in eight bytes, including MSW and LSW, which occupy four bytes respectively.

const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset) { uint32_t tempstmp = 0; time_t temptime = 0; struct tm *bd; char *buff = NULL; tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset)); if (tempstmp == 0){ return "NULL"; } /* We need a temporary variable here so the unsigned math * works correctly (for years > 2036 according to RFC 2030 * chapter 3). */ temptime = (time_t)(tempstmp - NTP_BASETIME); bd = gmtime(&temptime); if (! bd){ return "Not representable"; } buff = (char *)malloc(NTP_TS_SIZE); snprintf(buff, NTP_TS_SIZE, "%s %2d, %d %02d:%02d:%02d UTC", mon_names[bd->tm_mon], bd->tm_mday, bd->tm_year + 1900, bd->tm_hour, bd->tm_min, bd->tm_sec); return buff; }Copy the code

NTP timestamp

  
    /* NTP timestamp */
    ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
	printf("ts_msw: 0x%x\n",ts_msw);
    ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
	printf("ts_lsw: 0x%x\n",ts_lsw);
	printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
    offset += 8;
Copy the code

RTCP protocol implementation

The following I give the RTCP protocol parsing implementation code, according to the playback packet, parsing field information.

/* 接收者/发送者计数是最后5位   */
#define RTCP_COUNT(octet)   ((octet) & 0x1F)


#define RTCP_PT_MIN  192
/* Supplemental H.261 specific RTCP packet types according to Section C.3.5 */
#define RTCP_FIR     192
#define RTCP_NACK    193
#define RTCP_SMPTETC 194
#define RTCP_IJ      195
/* RTCP packet types according to Section A.11.1 */
/* And https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
#define RTCP_SR      200
#define RTCP_RR      201
#define RTCP_SDES    202
#define RTCP_BYE     203
#define RTCP_APP     204
#define RTCP_RTPFB   205
#define RTCP_PSFB    206
#define RTCP_XR      207
#define RTCP_AVB     208
#define RTCP_RSI     209
#define RTCP_TOKEN   210

#define RTCP_PT_MAX  210


static const char mon_names[12][4] = {
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec"
};

/** data structure to hold time values with nanosecond resolution*/
typedef struct {
	time_t	secs;
	int	nsecs;
} nstime_t;


/*
 * 1900-01-01 00:00:00 (proleptic?) UTC.
 * Used by a number of time formats.
 */
#define EPOCH_DELTA_1900_01_01_00_00_00_UTC 2208988800U

/*
 * NTP_BASETIME is in fact epoch - ntp_start_time; ntp_start_time
 * is January 1, 2036, 00:00:00 UTC.
 */
#define NTP_BASETIME EPOCH_DELTA_1900_01_01_00_00_00_UTC
#define NTP_FLOAT_DENOM 4294967296.0
#define NTP_TS_SIZE 100


/* 解剖长度字段。附加到此字段的文字表示转换为的实际字节数 (即 (原始值 + 1) * 4) */
static int dissect_rtcp_length_field(u_char *rtcp_info, int offset)
{

    uint16_t  raw_length = ntohs(*(uint16_t*)(rtcp_info + offset));
    printf("(%u bytes)\n", (raw_length+1)*4);
    offset += 2;
    return offset;
}
static int dissect_rtcp_rr(u_char *rtcp_info, int offset,int count, int packet_length )
{
    int counter = 0;
    uint8_t  rr_flt = 0;
    int    rr_offset = offset;
	
    counter = 1;
    while ( counter <= count ) {
        uint32_t lsr = 0, dlsr = 0;

        /* Create a new subtree for a length of 24 bytes */

        /* SSRC_n source identifier, 32 bits */

        offset += 4;

        /* Fraction lost, 8bits */
        rr_flt = rtcp_info[offset];

        offset++;

        /* Cumulative number of packets lost, 24 bits */
        offset += 3;


        /* Sequence number cycles */

        offset += 2;
        /* highest sequence number received */

        offset += 2;

        /* Interarrival jitter */

        offset += 4;

        /* Last SR timestamp */
        lsr = ntohl(*(uint32_t*)(rtcp_info + offset));
		printf("Last SR timestamp: 0x%x\n",lsr);
        offset += 4;

        /* Delay since last SR timestamp */
        dlsr = ntohl(*(uint32_t*)(rtcp_info + offset));

        printf("(%d milliseconds)\n",(int)(((double)dlsr/(double)65536) * 1000.0));
        offset += 4;

        counter++;
    }

    return offset;
}

const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset)
{
	uint32_t tempstmp = 0;
	time_t temptime = 0;
	struct tm *bd;
	char *buff = NULL;
	
	tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset));
	if (tempstmp == 0){
		return "NULL";
	}

	/* We need a temporary variable here so the unsigned math
	* works correctly (for years > 2036 according to RFC 2030
	* chapter 3).
	*/
	temptime = (time_t)(tempstmp - NTP_BASETIME);
	bd = gmtime(&temptime);
	if (!bd){
		return "Not representable";
	}

	buff = (char *)malloc(NTP_TS_SIZE);
	snprintf(buff, NTP_TS_SIZE,
		"%s %2d, %d %02d:%02d:%02d UTC",
		mon_names[bd->tm_mon],
		bd->tm_mday,
		bd->tm_year + 1900,
		bd->tm_hour,
		bd->tm_min,
		bd->tm_sec);
	return buff;
}

static int dissect_rtcp_sr(u_char *rtcp_info, int offset,int count, int packet_length)
{

    uint32_t     ts_msw = 0, ts_lsw = 0;
    int         sr_offset = offset;

    /* NTP timestamp */
    ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
	printf("ts_msw: 0x%x\n",ts_msw);
    ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
	printf("ts_lsw: 0x%x\n",ts_lsw);

	//printf("offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]);
	printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
    offset += 8;

    /* RTP timestamp, 32 bits */
	
    offset += 4;
    /* Sender's packet count, 32 bits */

    offset += 4;
    /* Sender's octet count, 32 bits */

    offset += 4;


    /* The rest of the packet is equal to the RR packet */
    if ( count != 0 )
        offset = dissect_rtcp_rr(rtcp_info, offset, count, packet_length-(offset-sr_offset));
    else
    {
        /* If length remaining, assume profile-specific extension bytes */
        if ((offset-sr_offset) < packet_length)
        {

            offset = sr_offset + packet_length;
        }
    }

    return offset;
}

static int dissect_rtcp_sdes(u_char *rtcp_info, int offset, int count)
{
    int           chunk = 0;

    int           start_offset = 0;
    int           items_start_offset = 0;
    uint32_t       ssrc = 0;
    unsigned int  item_len = 0;
    unsigned int  sdes_type = 0;
    unsigned int  prefix_len = 0;

    chunk = 1;
    while ( chunk <= count ) 
	{
		/* Create a subtree for this chunk; we don't yet know
		   the length. */
		start_offset = offset;

		ssrc = ntohl(*(uint32_t*)(rtcp_info + offset));
		printf("Chunk %u, SSRC/CSRC 0x%X\n", chunk, ssrc);

		/* SSRC_n source identifier, 32 bits */
		offset += 4;

		/* Create a subtree for the SDES items; we don't yet know
		   the length */


		/*
		 * Not every message is ended with "null" bytes, so check for
		 * end of frame as well.
		 */
		 
		  /* ID, 8 bits */
		sdes_type = rtcp_info[offset];
		printf("Type: %d\n",sdes_type);
		if (sdes_type == 0)
			break;
		offset++;
		/* Item length, 8 bits */
		item_len = rtcp_info[offset];
		printf("Length: %d\n",item_len);
		offset++;
		
		char *pszText = (char*)malloc(item_len);
		if (pszText != 0)
		{
			memcpy(pszText, rtcp_info + offset,item_len);
			pszText[item_len] = '\0';
			printf("Text = %s\n",pszText);
		}	

		chunk++;
    }

    return offset;
}

static void dissect_rtcp(u_char *rtcp_info,int packet_type, int offset,int PayloadLen)
{
	unsigned int  temp_byte = 0;
	int  elem_count = 0;
	int  packet_length = 0;
	int  total_packet_length = 0;
	int loop = 2;
	bool flag_rtcp = false;

		/*检查是否为有效类型*/
		if ( ( packet_type < RTCP_PT_MIN ) || ( packet_type >  RTCP_PT_MAX ) )
			exit(-1);

		/*
		 * 获取完整的RTCP数据包的长度
		 */
		 
		packet_length = (ntohs(*(uint16_t*)(rtcp_info + offset + 1)) + 1) * 4 ;
		//printf("packet_length: %d\n",packet_length);

		
		temp_byte = rtcp_info[offset-1];
		elem_count = RTCP_COUNT( temp_byte );/* Source count, 5 bits */
		printf("Reception report count: %d\n",elem_count);  

		switch ( packet_type ) 
		{
			
			case RTCP_SR:
			case RTCP_RR:
				/*
					Real-time Transport Control Protocol (Receiver Report)
					10.. .... = Version: RFC 1889 Version (2)
					..0. .... = Padding: False
					...0 0001 = Reception report count: 1
					Packet type: Receiver Report (201)
					Length: 7 (32 bytes)
					Sender SSRC: 0xb584b03e (3045371966)
					Source 1
				*/

				/* Packet type, 8 bits */
				offset++;
				/* Packet length in 32 bit words MINUS one, 16 bits */
				offset = dissect_rtcp_length_field(rtcp_info, offset);
				/* Sender Synchronization source, 32 bits */
				offset += 4;

				if ( packet_type == RTCP_SR )
				{
					offset = dissect_rtcp_sr(rtcp_info, offset, elem_count, packet_length-8 );
					printf("dissect_rtcp_sr\n");
				}
				else
				{	
					offset = dissect_rtcp_rr(rtcp_info, offset, elem_count, packet_length-8 );							
				}
				
				//uint16_t second_packet_type = ntohs(*(uint16_t*)(rtcp_info + offset));

				//printf("111offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]);
				
				if (rtcp_info[offset + 1] == RTCP_SDES)
				{
					
					/* Source count, 5 bits */
					offset++;
					/* Packet type, 8 bits */
					offset++;
					/* Packet length in 32 bit words MINUS one, 16 bits */
					offset = dissect_rtcp_length_field(rtcp_info, offset);
					offset = dissect_rtcp_sdes(rtcp_info,offset,elem_count);

				}

			break;

			default:
				/*
				 * To prevent endless loops in case of an unknown message type
				 * increase offset. Some time the while will end :-)
				 */
				offset++;
				break;

		}
		
}


static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen)
{
    unsigned int offset = 0;
    unsigned int first_byte = 0;
    unsigned int packet_type = 0;


    /* 查看第一个字节 */
    first_byte = rtcp_info[offset];

    /* 版本位是否设置为2*/
	//printf("version: %d\n",((first_byte & 0xC0) >> 6));
    if (((first_byte & 0xC0) >> 6) != 2)
    {
        return false;
    }

    /* 看包类型 */
	offset += 1;
    packet_type = rtcp_info[offset];
	//printf("packet_type: %d\n",packet_type);
    /* 复合数据包中的第一个数据包应该是发送方或接收者报告 */
    if (!((packet_type == RTCP_SR)  || (packet_type == RTCP_RR) ||
          (packet_type == RTCP_BYE) || (packet_type == RTCP_APP) ||
          (packet_type == RTCP_PSFB)))
    {
        return false;
    }

    /*总长度必须是4个字节的倍数*/
	//printf("PayloadLen: %d\n",PayloadLen);
    if (PayloadLen % 4)
    {
        return false;
    }

    /* OK, dissect as RTCP */
    dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen);
    return true;
}


static void confirm_rtcp_packet(struct ip *pIp)
{
	int iIpTotalLen = ntohs(pIp->ip_len);
	int offset = 0;
	int nFragSeq = 0;
	struct udphdr* pUdpHdr = (struct udphdr*)((char*)pIp + (pIp->ip_hl<<2));
	if (pIp->ip_p == IPPROTO_UDP) 
	{
		printf("\n");
		
		int iPayloadLen = iIpTotalLen - (pIp->ip_hl<<2) - 8;
		printf("UDP Payload Len %d\n", iPayloadLen);
		
		u_char *pDnsHdr = (u_char*)(pUdpHdr+1);
		dissect_rtcp_heur(pDnsHdr,iPayloadLen);
		
	}	
}
Copy the code

Compile and run:

conclusion

RTCP protocol is the cornerstone of streaming media communication. RTCP protocol is responsible for reliable transmission, traffic control and congestion control and other quality of service assurance. Above explained RTCP function, RTCP packet format and code implementation. Finally, to learn a new protocol, it is best to study the official documentation, as this is the most authoritative source.