附录 A. 算法 (Appendix A - Algorithms)
我们为 RTP 发送方与接收方算法的若干方面提供了 C 代码示例。在特定的运行环境中, 可能存在更快速或具有其他优势的其他实现方法。这些实现说明仅供参考, 旨在澄清 RTP 规范。
以下定义用于所有示例; 为清晰和简洁起见, 这些结构定义仅对 32 位大端 (big-endian, 最高有效字节在前) 体系结构有效。位域 (bit field) 被假定按大端位序紧密打包, 没有额外的填充。若要构造可移植的实现, 则需要进行相应的修改。
/*
* rtp.h -- RTP header file
*/
#include <sys/types.h>
/*
* The type definitions below are valid for 32-bit architectures and
* may have to be adjusted for 16- or 64-bit architectures.
*/
typedef unsigned char u_int8;
typedef unsigned short u_int16;
typedef unsigned int u_int32;
typedef short int16;
/*
* Current protocol version.
*/
#define RTP_VERSION 2
#define RTP_SEQ_MOD (1<<16)
#define RTP_MAX_SDES 255 /* maximum text length for SDES */
typedef enum {
RTCP_SR = 200,
RTCP_RR = 201,
RTCP_SDES = 202,
RTCP_BYE = 203,
RTCP_APP = 204
} rtcp_type_t;
typedef enum {
RTCP_SDES_END = 0,
RTCP_SDES_CNAME = 1,
RTCP_SDES_NAME = 2,
RTCP_SDES_EMAIL = 3,
RTCP_SDES_PHONE = 4,
RTCP_SDES_LOC = 5,
RTCP_SDES_TOOL = 6,
RTCP_SDES_NOTE = 7,
RTCP_SDES_PRIV = 8
} rtcp_sdes_type_t;
/*
* RTP data header
*/
typedef struct {
unsigned int version:2; /* protocol version */
unsigned int p:1; /* padding flag */
unsigned int x:1; /* header extension flag */
unsigned int cc:4; /* CSRC count */
unsigned int m:1; /* marker bit */
unsigned int pt:7; /* payload type */
unsigned int seq:16; /* sequence number */
u_int32 ts; /* timestamp */
u_int32 ssrc; /* synchronization source */
u_int32 csrc[1]; /* optional CSRC list */
} rtp_hdr_t;
/*
* RTCP common header word
*/
typedef struct {
unsigned int version:2; /* protocol version */
unsigned int p:1; /* padding flag */
unsigned int count:5; /* varies by packet type */
unsigned int pt:8; /* RTCP packet type */
u_int16 length; /* pkt len in words, w/o this word */
} rtcp_common_t;
/*
* Big-endian mask for version, padding bit and packet type pair
*/
#define RTCP_VALID_MASK (0xc000 | 0x2000 | 0xfe)
#define RTCP_VALID_VALUE ((RTP_VERSION << 14) | RTCP_SR)
/*
* Reception report block
*/
typedef struct {
u_int32 ssrc; /* data source being reported */
unsigned int fraction:8; /* fraction lost since last SR/RR */
int lost:24; /* cumul. no. pkts lost (signed!) */
u_int32 last_seq; /* extended last seq. no. received */
u_int32 jitter; /* interarrival jitter */
u_int32 lsr; /* last SR packet from this source */
u_int32 dlsr; /* delay since last SR packet */
} rtcp_rr_t;
/*
* SDES item
*/
typedef struct {
u_int8 type; /* type of item (rtcp_sdes_type_t) */
u_int8 length; /* length of item (in octets) */
char data[1]; /* text, not null-terminated */
} rtcp_sdes_item_t;
/*
* One RTCP packet
*/
typedef struct {
rtcp_common_t common; /* common header */
union {
/* sender report (SR) */
struct {
u_int32 ssrc; /* sender generating this report */
u_int32 ntp_sec; /* NTP timestamp */
u_int32 ntp_frac;
u_int32 rtp_ts; /* RTP timestamp */
u_int32 psent; /* packets sent */
u_int32 osent; /* octets sent */
rtcp_rr_t rr[1]; /* variable-length list */
} sr;
/* reception report (RR) */
struct {
u_int32 ssrc; /* receiver generating this report */
rtcp_rr_t rr[1]; /* variable-length list */
} rr;
/* source description (SDES) */
struct rtcp_sdes {
u_int32 src; /* first SSRC/CSRC */
rtcp_sdes_item_t item[1]; /* list of SDES items */
} sdes;
/* BYE */
struct {
u_int32 src[1]; /* list of sources */
/* can't express trailing text for reason */
} bye;
} r;
} rtcp_t;
typedef struct rtcp_sdes rtcp_sdes_t;
/*
* Per-source state information
*/
typedef struct {
u_int16 max_seq; /* highest seq. number seen */
u_int32 cycles; /* shifted count of seq. number cycles */
u_int32 base_seq; /* base seq number */
u_int32 bad_seq; /* last 'bad' seq number + 1 */
u_int32 probation; /* sequ. packets till source is valid */
u_int32 received; /* packets received */
u_int32 expected_prior; /* packet expected at last interval */
u_int32 received_prior; /* packet received at last interval */
u_int32 transit; /* relative trans time for prev pkt */
u_int32 jitter; /* estimated jitter */
/* ... */
} source;
A.1 RTP 数据头有效性检查 (RTP Data Header Validity Checks)
RTP 接收方应当检查 (check) 传入包 (incoming packets) 的 RTP 头 (RTP header) 的有效性 (validity), 因为它们可能已被加密, 也可能来自恰好被误寻址 (misaddressed) 的另一个应用。类似地, 如果启用了第 9 节所述方法的加密 (encryption), 则需要头有效性检查来核实传入包是否已被正确解密 (decrypted), 尽管头有效性检查的失败 (例如未知的负载类型) 不一定表示解密失败。
对于一个此前未听到过的源发出的 RTP 数据包, 只能进行较弱的有效性检查:
o RTP 版本字段必须等于 2。
o 负载类型必须已知, 尤其不能等于 SR 或 RR。
o 若设置了 P 位 (P bit), 则包的最后一个字节必须包含一个有效的字节计数, 具体而言, 应小于包总长度减去头大小。
o 如果配置文件 (profile) 未指定可以使用头扩展机制, 则 X 位必须为零。否则, 扩展长度字段必须小于包总大小减去固定头长度再减去填充。
o 包的长度必须与 CC 和负载类型一致 (如果负载具有已知长度)。
最后三项检查有些复杂且并非总是可行, 因而只剩下前两项, 它们总共仅有几位。如果包中的 SSRC 标识符是此前已经收到过的, 那么该包很可能是有效的, 检查序列号是否落在预期范围内可提供更多验证。如果从未见过该 SSRC 标识符, 那么承载该标识符的数据包可被视为无效, 直到有少量包以连续的序列号到达。那些无效包 MAY 被丢弃, 或者如果由此带来的延迟可以接受, MAY 被存储起来并在验证达成后交付。
下面所示的 update_seq 例程确保一个源只有在收到 MIN_SEQUENTIAL 个连续包之后才被声明为有效。它还会验证新收到包的序列号 seq, 并更新该包所属源在 s 所指向的结构 (structure) 中的序列状态。
当一个新源首次被听到, 即其 SSRC 标识符不在表中 (见第 8.2 节), 并为它分配了每源状态 (per-source state) 时, s->probation 被设为在声明一个源有效之前所需的连续包数量 (参数 MIN_SEQUENTIAL), 其他变量也被初始化:
init_seq(s, seq);
s->max_seq = seq - 1;
s->probation = MIN_SEQUENTIAL;
非零的 s->probation 将该源标记为尚未有效, 因此该状态可在短时超时后而非长时超时后被丢弃, 如第 6.2.1 节所述。
在一个源被视为有效之后, 如果序列号超前 s->max_seq 不超过 MAX_DROPOUT, 且落后不超过 MAX_MISORDER, 则该序列号被视为有效。如果新序列号在 RTP 序列号范围 (16 位) 内超前 max_seq (模运算), 但又小于 max_seq, 则说明它已经回绕 (wrapped around), 此时 (移位的) 序列号循环 (cycle) 计数被加一。返回一个值 1 以表示序列号有效。
否则, 返回零值表示验证失败, 并存储错误序列号加 1。如果收到的下一个包携带了下一个更高的序列号, 则它被视为新包序列的有效起点, 这很可能是由长时间丢包 (extended dropout) 或源重启 (source restart) 引起的。由于可能漏掉了多个完整的序列号循环, 丢包统计被重置。
基于 50 包/秒下 2 秒的最大乱序时间 (misordering time) 和 1 分钟的最大丢包时间, 给出了这些参数的典型值。丢包参数 MAX_DROPOUT 应当只占 16 位序列号空间的一小部分, 以便重启后以较高概率使得新序列号不会落在该重启前序列号的可接受范围内。
void init_seq(source *s, u_int16 seq)
{
s->base_seq = seq;
s->max_seq = seq;
s->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */
s->cycles = 0;
s->received = 0;
s->received_prior = 0;
s->expected_prior = 0;
/* other initialization */
}
int update_seq(source *s, u_int16 seq)
{
u_int16 udelta = seq - s->max_seq;
const int MAX_DROPOUT = 3000;
const int MAX_MISORDER = 100;
const int MIN_SEQUENTIAL = 2;
/*
* Source is not valid until MIN_SEQUENTIAL packets with
* sequential sequence numbers have been received.
*/
if (s->probation) {
/* packet is in sequence */
if (seq == s->max_seq + 1) {
s->probation--;
s->max_seq = seq;
if (s->probation == 0) {
init_seq(s, seq);
s->received++;
return 1;
}
} else {
s->probation = MIN_SEQUENTIAL - 1;
s->max_seq = seq;
}
return 0;
} else if (udelta < MAX_DROPOUT) {
/* in order, with permissible gap */
if (seq < s->max_seq) {
/*
* Sequence number wrapped - count another 64K cycle.
*/
s->cycles += RTP_SEQ_MOD;
}
s->max_seq = seq;
} else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) {
/* the sequence number made a very large jump */
if (seq == s->bad_seq) {
/*
* Two sequential packets -- assume that the other side
* restarted without telling us so just re-sync
* (i.e., pretend this was the first packet).
*/
init_seq(s, seq);
}
else {
s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
return 0;
}
} else {
/* duplicate or reordered packet */
}
s->received++;
return 1;
}
可以要求多于两个连续的包来使有效性检查更强。其缺点是, 更多的初始包将被丢弃 (或在队列中延迟), 并且高丢包率可能使验证无法完成。然而, 由于 RTCP 头验证相对较强, 如果在数据包之前从某个源收到了 RTCP 包, 则可以将该计数调整为只要求两个连续的包。如果能够容忍最初几秒的数据丢失, 应用 MAY 选择丢弃来自某个源的所有数据包, 直到从该源收到有效的 RTCP 包。
根据应用和编码的不同, 算法 MAY 利用关于负载格式的额外知识来进行进一步验证。对于那些所有包的时间戳增量都相同的负载类型, 可以利用序列号之差, 从同一源的上一个收到的包预测时间戳值 (假设负载类型没有变化)。
由于新收到的 RTP 数据包头部的前四个字节有很高的概率与来自同一 SSRC 的上一个包完全相同, 只是序列号加一, 因此可以进行一种强的 "快速路径" (fast-path) 检查。类似地, 在数据通常一次只从一个源接收的应用中, 可以使用单条目缓存 (single-entry cache) 来加快 SSRC 查找。
A.2 RTCP 头有效性检查 (RTCP Header Validity Checks)
应当对 RTCP 包施加以下检查:
o RTP 版本字段必须等于 2。
o 复合包 (compound packet) 中第一个 RTCP 包的负载类型字段必须等于 SR 或 RR。
o 复合 RTCP 包的第一个包的填充位 (P) 应当为零, 因为填充只在需要时才应用, 且只应应用于最后一个包。
o 各个 RTCP 包的长度字段加起来必须等于所收到的复合 RTCP 包的总长度。这是一项相当强的检查。
下面的代码片段执行所有这些检查。对于后续的包不检查包类型, 因为其中可能出现未知的包类型, 它们应当被忽略。
u_int32 len; /* length of compound RTCP packet in words */
rtcp_t *r; /* RTCP header */
rtcp_t *end; /* end of compound RTCP packet */
if ((*(u_int16 *)r & RTCP_VALID_MASK) != RTCP_VALID_VALUE) {
/* something wrong with packet format */
}
end = (rtcp_t *)((u_int32 *)r + len);
do r = (rtcp_t *)((u_int32 *)r + r->common.length + 1);
while (r < end && r->common.version == 2);
if (r != end) {
/* something wrong with packet format */
}
A.3 期望包数与丢包数的确定 (Determining Number of Packets Expected and Lost)
为了计算丢包率, 需要知道从每个源期望收到和实际收到的 RTP 包数量, 使用的是下述代码中通过指针 s 引用的 struct source 结构所定义的每源状态信息。收到的包数就是对包到达时的简单计数, 包括任何迟到或重复的包。期望的包数可由接收方计算为收到的最高序列号 (s->max_seq) 与收到的第一个序列号 (s->base_seq) 之差。由于序列号只有 16 位且会发生回绕, 因此必须用 (移位的) 序列号回绕次数 (s->cycles) 来扩展最高序列号。收到包计数与循环计数二者均由附录 A.1 的 RTP 头有效性检查例程维护。
extended_max = s->cycles + s->max_seq;
expected = extended_max - s->base_seq + 1;
丢包数定义为期望的包数减去实际收到的包数:
lost = expected - s->received;
由于这个带符号数以 24 位承载, 对于正丢失应将其钳位 (clamp) 在 0x7fffff, 对于负丢失应钳位在 0x800000, 而不是让它回绕。
上一个报告间隔内 (自上一个 SR 或 RR 包发送以来) 的丢包比例, 由该间隔前后期望包数与收到包数的差值计算得出, 其中 expected_prior 和 received_prior 是生成上一个接收报告时保存的值:
expected_interval = expected - s->expected_prior;
s->expected_prior = expected;
received_interval = s->received - s->received_prior;
s->received_prior = s->received;
lost_interval = expected_interval - received_interval;
if (expected_interval == 0 || lost_interval <= 0) fraction = 0;
else fraction = (lost_interval << 8) / expected_interval;
得到的比例是一个 8 位定点数 (fixed point number), 二进制小数点位于最左端。
A.4 生成 RTCP SDES 包 (Generating RTCP SDES Packets)
该函数向缓冲区 b 中构建一个 SDES 块 (chunk), 它由数组 type、value 和 length 提供的 argc 个条目组成。它返回指向 b 中下一个可用位置的指针。
char *rtp_write_sdes(char *b, u_int32 src, int argc,
rtcp_sdes_type_t type[], char *value[],
int length[])
{
rtcp_sdes_t *s = (rtcp_sdes_t *)b;
rtcp_sdes_item_t *rsp;
int i;
int len;
int pad;
/* SSRC header */
s->src = src;
rsp = &s->item[0];
/* SDES items */
for (i = 0; i < argc; i++) {
rsp->type = type[i];
len = length[i];
if (len > RTP_MAX_SDES) {
/* invalid length, may want to take other action */
len = RTP_MAX_SDES;
}
rsp->length = len;
memcpy(rsp->data, value[i], len);
rsp = (rtcp_sdes_item_t *)&rsp->data[len];
}
/* terminate with end marker and pad to next 4-octet boundary */
len = ((char *) rsp) - b;
pad = 4 - (len & 0x3);
b = (char *) rsp;
while (pad--) *b++ = RTCP_SDES_END;
return b;
}
A.5 解析 RTCP SDES 包 (Parsing RTCP SDES Packets)
该函数解析一个 SDES 包, 调用函数 find_member() 根据给定的 SSRC 标识符找到指向该会话成员信息的指针, 并调用 member_sdes() 为该成员存储新的 SDES 信息。该函数期望传入指向 RTCP 包头的指针。
void rtp_read_sdes(rtcp_t *r)
{
int count = r->common.count;
rtcp_sdes_t *sd = &r->r.sdes;
rtcp_sdes_item_t *rsp, *rspn;
rtcp_sdes_item_t *end = (rtcp_sdes_item_t *)
((u_int32 *)r + r->common.length + 1);
source *s;
while (--count >= 0) {
rsp = &sd->item[0];
if (rsp >= end) break;
s = find_member(sd->src);
for (; rsp->type; rsp = rspn ) {
rspn = (rtcp_sdes_item_t *)((char*)rsp+rsp->length+2);
if (rspn >= end) {
rsp = rspn;
break;
}
member_sdes(s, rsp->type, rsp->data, rsp->length);
}
sd = (rtcp_sdes_t *)
((u_int32 *)sd + (((char *)rsp - (char *)sd) >> 2)+1);
}
if (count >= 0) {
/* invalid packet format */
}
}
A.6 生成随机的 32 位标识符 (Generating a Random 32-bit Identifier)
以下子例程使用 RFC 1321 [32] 中发布的 MD5 例程生成一个随机的 32 位标识符。系统例程可能并非在所有操作系统上都存在, 但它们应当能提示哪些种类的信息可以使用。其他可能适用的系统调用包括:
o getdomainname(),
o getwd(), 或
o getrusage()。
"实时" (Live) 视频或音频采样也是随机数的良好来源, 但必须注意避免使用关闭的麦克风或遮挡的摄像头作为来源 [17]。
建议使用本例程或类似的例程, 来生成用于产生 RTCP 周期 (如附录 A.7 所示) 的随机数发生器的初始种子 (initial seed), 来生成序列号和时间戳的初始值, 以及生成 SSRC 值。由于本例程很可能占用大量 CPU, 直接用它来生成 RTCP 周期是不恰当的, 因为可预测性在这里不是问题。注意, 除非为 type 参数提供不同的值, 否则在系统时钟值改变之前, 对它的重复调用会产生相同的结果。
/*
* Generate a random 32-bit quantity.
*/
#include <sys/types.h> /* u_long */
#include <sys/time.h> /* gettimeofday() */
#include <unistd.h> /* get..() */
#include <stdio.h> /* printf() */
#include <time.h> /* clock() */
#include <sys/utsname.h> /* uname() */
#include "global.h" /* from RFC 1321 */
#include "md5.h" /* from RFC 1321 */
#define MD_CTX MD5_CTX
#define MDInit MD5Init
#define MDUpdate MD5Update
#define MDFinal MD5Final
static u_long md_32(char *string, int length)
{
MD_CTX context;
union {
char c[16];
u_long x[4];
} digest;
u_long r;
int i;
MDInit (&context);
MDUpdate (&context, string, length);
MDFinal ((unsigned char *)&digest, &context);
r = 0;
for (i = 0; i < 3; i++) {
r ^= digest.x[i];
}
return r;
} /* md_32 */
/*
* Return random unsigned 32-bit quantity. Use 'type' argument if
* you need to generate several different values in close succession.
*/
u_int32 random32(int type)
{
struct {
int type;
struct timeval tv;
clock_t cpu;
pid_t pid;
u_long hid;
uid_t uid;
gid_t gid;
struct utsname name;
} s;
gettimeofday(&s.tv, 0);
uname(&s.name);
s.type = type;
s.cpu = clock();
s.pid = getpid();
s.hid = gethostid();
s.uid = getuid();
s.gid = getgid();
/* also: system uptime */
return md_32((char *)&s, sizeof(s));
} /* random32 */
A.7 计算 RTCP 传输间隔 (Computing the RTCP Transmission Interval)
以下函数实现了第 6.2 节所述的 RTCP 传输与接收规则。这些规则被编写为若干函数:
o rtcp_interval() 计算以秒为单位的确定性 (deterministic) 计算间隔, 其参数在第 6.3 节中定义。
o OnExpire() 在 RTCP 传输定时器 (timer) 到期时被调用。
o OnReceive() 在收到任何 RTCP 包时被调用。
OnExpire() 与 OnReceive() 都以事件 e 作为参数。这是该参与者的下一个已调度 (scheduled) 事件, 要么是 RTCP 报告, 要么是 BYE 包。假定以下函数可用:
o Schedule(time t, event e) 调度事件 e 在时间 t 发生。当时间 t 到达时, 函数 OnExpire 被以 e 为参数调用。
o Reschedule(time t, event e) 将先前已调度的事件 e 重新调度到时间 t。
o SendRTCPReport(event e) 发送一个 RTCP 报告。
o SendBYEPacket(event e) 发送一个 BYE 包。
o TypeOfEvent(event e) 如果所处理的事件是要发送的 BYE 包, 则返回 EVENT_BYE, 否则返回 EVENT_REPORT。
o PacketType(p) 如果包 p 是一个 RTCP 报告 (非 BYE), 返回 PACKET_RTCP_REPORT; 如果是 BYE RTCP 包, 返回 PACKET_BYE; 如果是常规 RTP 数据包, 返回 PACKET_RTP。
o ReceivedPacketSize() 和 SentPacketSize() 返回所引用包的字节数 (octets)。
o NewMember(p) 如果发送包 p 的参与者当前不在成员表中, 返回 1, 否则返回 0。注意这个函数对于一个完整实现并不充分, 因为一个 RTP 包中的每个 CSRC 标识符以及每个 BYE 包中的每个 SSRC 都应被处理。
o NewSender(p) 如果发送包 p 的参与者当前不在成员表的发送方子列表中, 返回 1, 否则返回 0。
o AddMember() 和 RemoveMember() 用于向成员表中添加和移除参与者。
o AddSender() 和 RemoveSender() 用于向成员表的发送方子列表中添加和移除参与者。
这些函数对于一个允许将发送方与非发送方的 RTCP 带宽比例指定为显式参数 (而非固定的 25% 与 75%) 的实现, 必须作相应扩展。扩展后的 rtcp_interval() 实现需要避免在一个参数为零时发生除零错误。
double rtcp_interval(int members,
int senders,
double rtcp_bw,
int we_sent,
double avg_rtcp_size,
int initial)
{
/*
* Minimum average time between RTCP packets from this site (in
* seconds). This time prevents the reports from `clumping' when
* sessions are small and the law of large numbers isn't helping
* to smooth out the traffic. It also keeps the report interval
* from becoming ridiculously small during transient outages like
* a network partition.
*/
double const RTCP_MIN_TIME = 5.;
/*
* Fraction of the RTCP bandwidth to be shared among active
* senders. (This fraction was chosen so that in a typical
* session with one or two active senders, the computed report
* time would be roughly equal to the minimum report time so that
* we don't unnecessarily slow down receiver reports.) The
* receiver fraction must be 1 - the sender fraction.
*/
double const RTCP_SENDER_BW_FRACTION = 0.25;
double const RTCP_RCVR_BW_FRACTION = (1-RTCP_SENDER_BW_FRACTION);
/*
/* To compensate for "timer reconsideration" converging to a
* value below the intended average.
*/
double const COMPENSATION = 2.71828 - 1.5;
double t; /* interval */
double rtcp_min_time = RTCP_MIN_TIME;
int n; /* no. of members for computation */
/*
* Very first call at application start-up uses half the min
* delay for quicker notification while still allowing some time
* before reporting for randomization and to learn about other
* sources so the report interval will converge to the correct
* interval more quickly.
*/
if (initial) {
rtcp_min_time /= 2;
}
/*
* Dedicate a fraction of the RTCP bandwidth to senders unless
* the number of senders is large enough that their share is
* more than that fraction.
*/
n = members;
if (senders <= members * RTCP_SENDER_BW_FRACTION) {
if (we_sent) {
rtcp_bw *= RTCP_SENDER_BW_FRACTION;
n = senders;
} else {
rtcp_bw *= RTCP_RCVR_BW_FRACTION;
n -= senders;
}
}
/*
* The effective number of sites times the average packet size is
* the total number of octets sent when each site sends a report.
* Dividing this by the effective bandwidth gives the time
* interval over which those packets must be sent in order to
* meet the bandwidth target, with a minimum enforced. In that
* time interval we send one report so this time is also our
* average time between reports.
*/
t = avg_rtcp_size * n / rtcp_bw;
if (t < rtcp_min_time) t = rtcp_min_time;
/*
* To avoid traffic bursts from unintended synchronization with
* other sites, we then pick our actual next report interval as a
* random number uniformly distributed between 0.5*t and 1.5*t.
*/
t = t * (drand48() + 0.5);
t = t / COMPENSATION;
return t;
}
void OnExpire(event e,
int members,
int senders,
double rtcp_bw,
int we_sent,
double *avg_rtcp_size,
int *initial,
time_tp tc,
time_tp *tp,
int *pmembers)
{
/* This function is responsible for deciding whether to send an
* RTCP report or BYE packet now, or to reschedule transmission.
* It is also responsible for updating the pmembers, initial, tp,
* and avg_rtcp_size state variables. This function should be
* called upon expiration of the event timer used by Schedule().
*/
double t; /* Interval */
double tn; /* Next transmit time */
/* In the case of a BYE, we use "timer reconsideration" to
* reschedule the transmission of the BYE if necessary */
if (TypeOfEvent(e) == EVENT_BYE) {
t = rtcp_interval(members,
senders,
rtcp_bw,
we_sent,
*avg_rtcp_size,
*initial);
tn = *tp + t;
if (tn <= tc) {
SendBYEPacket(e);
exit(1);
} else {
Schedule(tn, e);
}
} else if (TypeOfEvent(e) == EVENT_REPORT) {
t = rtcp_interval(members,
senders,
rtcp_bw,
we_sent,
*avg_rtcp_size,
*initial);
tn = *tp + t;
if (tn <= tc) {
SendRTCPReport(e);
*avg_rtcp_size = (1./16.)*SentPacketSize(e) +
(15./16.)*(*avg_rtcp_size);
*tp = tc;
/* We must redraw the interval. Don't reuse the
one computed above, since its not actually
distributed the same, as we are conditioned
on it being small enough to cause a packet to
be sent */
t = rtcp_interval(members,
senders,
rtcp_bw,
we_sent,
*avg_rtcp_size,
*initial);
Schedule(t+tc,e);
*initial = 0;
} else {
Schedule(tn, e);
}
*pmembers = members;
}
}
void OnReceive(packet p,
event e,
int *members,
int *pmembers,
int *senders,
double *avg_rtcp_size,
double *tp,
double tc,
double tn)
{
/* What we do depends on whether we have left the group, and are
* waiting to send a BYE (TypeOfEvent(e) == EVENT_BYE) or an RTCP
* report. p represents the packet that was just received. */
if (PacketType(p) == PACKET_RTCP_REPORT) {
if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
AddMember(p);
*members += 1;
}
*avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) +
(15./16.)*(*avg_rtcp_size);
} else if (PacketType(p) == PACKET_RTP) {
if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
AddMember(p);
*members += 1;
}
if (NewSender(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
AddSender(p);
*senders += 1;
}
} else if (PacketType(p) == PACKET_BYE) {
*avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) +
(15./16.)*(*avg_rtcp_size);
if (TypeOfEvent(e) == EVENT_REPORT) {
if (NewSender(p) == FALSE) {
RemoveSender(p);
*senders -= 1;
}
if (NewMember(p) == FALSE) {
RemoveMember(p);
*members -= 1;
}
if (*members < *pmembers) {
tn = tc +
(((double) *members)/(*pmembers))*(tn - tc);
*tp = tc -
(((double) *members)/(*pmembers))*(tc - *tp);
/* Reschedule the next report for time tn */
Reschedule(tn, e);
*pmembers = *members;
}
} else if (TypeOfEvent(e) == EVENT_BYE) {
*members += 1;
}
}
}
A.8 估计到达间隔抖动 (Estimating the Interarrival Jitter)
以下代码片段实现了第 6.4.1 节给出的算法, 用于计算 RTP 数据到达时间间隔统计方差的估计值, 以插入到接收报告的到达间隔抖动 (interarrival jitter) 字段。输入为 r->ts (来自传入包的时间戳) 与 arrival (以相同单位为度量的当前时间)。此处 s 指向该源的状态; s->transit 保存前一个包的相传输时间 (relative transit time), s->jitter 保存估计的抖动。接收报告的抖动字段以时间戳单位度量并表示为无符号整数, 但抖动估计值以浮点数 (floating point) 保存。每当一个数据包到达, 抖动估计值就被更新:
int transit = arrival - r->ts;
int d = transit - s->transit;
s->transit = transit;
if (d < 0) d = -d;
s->jitter += (1./16.) * ((double)d - s->jitter);
当为该成员生成接收报告块 (rr 指向它) 时, 返回当前抖动估计值:
rr->jitter = (u_int32) s->jitter;
或者, 抖动估计值可以整数形式保存, 但需进行缩放 (scaled) 以减少舍入误差 (round-off error)。除了最后一行外, 计算相同:
s->jitter += d - ((s->jitter + 8) >> 4);
在此情形下, 为该接收报告采样估计值的方式为:
rr->jitter = s->jitter >> 4;