RFC 3550 - 付録 A. アルゴリズム
付録 A. アルゴリズム (Appendix A - Algorithms)
本覚書の状態 (Status of This Memo)
本書はインターネットコミュニティ向けのインターネット標準トラックプロトコルを規定し、改善のための議論と提案を求める。このプロトコルの標準化状態およびステータスについては、現在版の「インターネット公式プロトコル標準 (Internet Official Protocol Standards)」(STD 1)を参照されたい。本覚書の配布は無制限である。
著作権表示 (Copyright Notice)
Copyright (C) The Internet Society (2003). All Rights Reserved.
本書およびその翻訳の複製を他者に提供し、本書について論評または説明を行う、あるいはその実装を支援する派生著作物を、いかなる制限なく全体または一部について作成、複製、刊行、配布してよい。ただし上記の著作権表示およびこの段落がすべてのそのような複製および派生著作物に含まれなければならない。しかし本書自体は、著作権表示やインターネットソサエティその他のインターネット関連団体への参照を除去する等いかなる方法でも改変してはならない。ただしインターネット標準を策定する目的で必要な場合はインターネット標準プロセスで定められた著作権手順に従わなければならず、また英語以外の言語へ翻訳するために必要な場合はこの限りではない。
本書は [RFC 1889] の更新版である。原本は著者の公開ドラフトに基づき、IETF 音声/映像トランスポートワーキンググループのメーリングリストでの議論を経て改訂された。
本翻訳は参考目的のみであり、英文原文と齟齬がある場合は英文原文が優先される。
A.1 RTP データヘッダ妥当性検査 (RTP Data Header Validity Checks)
RTP 受信側は、受信パケットの RTP ヘッダが有効であるか検査すべきである (SHOULD)。なぜならそれらは暗号化されているか、あるいは単に誤った宛先指定により別のアプリケーションから来たものかもしれないからである。同様に、セクション 9.1 の方式による暗号化が有効な場合、ヘッダ妥当性検査は着信パケットが正しく復号されたか確認するために必要である。ただしヘッダ妥当性検査の失敗(たとえば未知のペイロード種別)が必ずしも復号失敗を示すわけではない。
以前に聞いたことのない送信元からの RTP データパケットに対しては、より弱い妥当性検査のみを行える。
o RTP バージョンフィールドは 2 でなければならない (MUST)。
o ペイロード種別は既知でなければならず (MUST)、SR または RR であってはならない (MUST NOT)。
o P ビットがセットされている場合、パケットの最後のバイトは有効なバイトカウントを含まなければならず (MUST)、具体的にはパケット全体の長さからヘッダ長を引いた値より小さくなければならない。
o プロファイルがヘッダ拡張機構の使用を規定していない場合、X ビットはゼロでなければならない (MUST)。さもなければ拡張長フィールドは、固定ヘッダ長とパディングを除いたパケット全体の長さより小さくなければならない (MUST)。
o パケットの長さは、CC およびペイロード種別と整合していなければならない (MUST)(ペイロードの長さが既知の場合)。
最後の 3 項目の検査はやや複雑であり常に可能とは限らないため、最初の 2 項目のみが残り、これらは合わせて数ビットに過ぎない。SSRC 識別子がパケットのヘッダにあり、かつそれが以前に受信したことのない場合、そのパケットは無効とみなされ、連続したシーケンス番号を持つ少数のパケットが到着するまで無効とされる可能性がある。それらの無効パケットは破棄されてよい (MAY) し、あるいはその遅延が許容されるなら保存し妥当性確認後に配送されてよい (MAY)。
以下の update_seq ルーチンは、新たなソースが MIN_SEQUENTIAL 個の連続パケットを受信した後にのみ有効と宣言されることを保証する。また、s が指す構造のシーケンス状態を検証し更新するため、新たに受信したパケットのシーケンス番号 seq を用いる。
新たなソースが(セクション 8.2 参照)初めて聞こえ、その SSRC 識別子が表にないためにそのソース用の状態が割り当てられたとき、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 より小さい場合、それは折り返ったことを意味し、シフトされたシーケンス番号サイクル(cycle)カウントが 1 増やされる。値 1 を返しシーケンス番号が有効であることを示す。
さもなければ、0 を返し検証失敗を示し、誤ったシーケンス番号に 1 を加えたものを格納する。受信した次のパケットが次に高いシーケンス番号を運んでいれば、それは長時間の欠落 (extended dropout) または送信元再起動 (source restart) により生じた可能性が高い新たなパケット列の有効な開始点とみなされる。複数の完全なシーケンス番号サイクルを見落とし得るため、欠落統計はリセットされる。
これらのパラメータの典型的な値は、50 パケット/秒で 2 秒の最大乱順時間、および 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;
/*
* 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) {
/* out of order */
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;
}
A.2 RTCP ヘッダ妥当性検査 (RTCP Header Validity Checks)
着信 RTCP パケットも妥当性を検査しなければならない (MUST)。処理を単純にするため、連続するすべての RTCP パケットはまず 1 つの複合 RTCP パケットに連結され、その各構成部分が個別に検査される。以下のルーチンは各 RTCP パケットの受信時に呼ばれ、最初の RTCP パケットはフラグで識別される。
まずバージョン番号が予期されたもの(パケット種別が既知、ヘッダ長およびパディング等が正しい)であるか検査される。異常な RTCP パケットは無視されなければならない (MUST)。いくつかの検査を通過し状態が更新された後にのみ状態は更新される。これにより無効なパケットは状態を破壊しない。
以下のルーチンは異常を検出した場合 FALSE を、さもなければ TRUE を返す。FALSE を返した場合、呼び出し側はそれ以上の措置(たとえば送信元をブラックリストに加える)をとってよい (MAY) が、それは本書の範囲外である。
int rtcp_valid_packet(char *buffer, int len, int first)
{
rtcp_t *r = (rtcp_t *)buffer;
int bx;
int count;
#define xbuf (char *)&(r->r)
/* Packet size must be a multiple of 4, and must be large enough
* to hold the header and a SDES chunk if present. */
if (len & 0x3) return FALSE;
count = ntohs(r->common.length) + 1;
if (len < count*4) return FALSE;
/* Version, payload type, and padding must be valid, too. */
if (r->common.version != RTP_VERSION) return FALSE;
if (r->common.pt != RTCP_SR && r->common.pt != RTCP_RR &&
r->common.pt != RTCP_SDES && r->common.pt != RTCP_BYE &&
r->common.pt != RTCP_APP) return FALSE;
/* Mixing of padding and non-padding is not allowed, so it
* must be present only on the last packet. */
if (r->common.p && !first) return FALSE;
/* The encryption bit MUST NOT be set on the first packet, and
* SHOULD NOT be set on any packet. */
if (r->common.e && first) return FALSE;
if (r->common.pt == RTCP_SDES) {
int n = r->common.count;
bx = sizeof(rtcp_common_t);
while (n--) {
/* Every SDES chunk must have a null (type 0) item,
* and every item must fit inside the packet. */
int nl = 0;
u_char *p = (u_char *)(xbuf + bx);
while (*p) {
int t = *p++;
int l = *p++;
nl += l + 2;
if (p + l >= (u_char *)(buffer + len))
return FALSE;
p += l;
}
if (nl != (count*4 - bx)) return FALSE;
bx += nl;
}
}
return TRUE;
}
上記ルーチンは SDES 項の値や、SR または RR パケット内のフィールドの妥当性を検査しない。アプリケーションはこれらのフィールドを検査するか、独自の妥当性検査を提供してよい (MAY)。
A.3 失われたパケット数の推定 (Estimating the Number of Packets Lost)
以下のコード断片は、セクション 6.4.1 で規定される累積欠落パケット統計を維持する方法を示す。
if (s->received == 0) {
/* First time. */
s->base_seq = seq;
s->max_seq = seq;
s->bad_seq = RTP_SEQ_MOD + 1;
s->cycles = 0;
s->received = 0;
s->received_prior = 0;
s->expected_prior = 0;
} else {
u_int16 udelta = seq - s->max_seq;
if (udelta < MAX_DROPOUT) {
if (seq < s->max_seq) {
s->cycles += RTP_SEQ_MOD;
}
s->max_seq = seq;
} else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) {
if (seq == s->bad_seq) {
init_seq(s, seq);
} else {
s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
return 0;
}
}
}
/* The following code is called after packet validation but before
* updating s->received. */
extended_max = s->cycles + s->max_seq;
expected = extended_max - s->base_seq + 1;
if (expected > s->expected_prior) {
lost = expected - s->received - 1;
if (lost > 0x7fffff) lost = 0x7fffff;
s->cumulative_lost = (s->cumulative_lost + lost) & 0xffffff;
}
s->expected_prior = expected;
s->received_prior = s->received;
A.4 シーケンス番号の展開 (Sequence Number Exploration)
以下のコードは、セクション 6.4.1 の拡張最高シーケンス番号を維持する方法を示す。これは 16 ビットのシーケンス番号と、シーケンス番号が折り返うごとに 1 増加する 16 ビットのサイクルカウントからなる。
extended_seq = s->cycles + s->max_seq;
拡張シーケンス番号は、サイクルカウント(上位 16 ビット)とシーケンス番号(下位 16 ビット)を連結して形成される 32 ビット値である。
実装はこの拡張シーケンス番号を維持するため任意の適切な方法を用いてよい (MAY)。上記の update_seq ルーチンにもこの論理が含まれている。
A.5 RTP データ構造定義 (RTP Data Structure Definitions)
以下の C コードは、RTP および RTCP パケットと送信元ごとの状態を格納するために用いられるデータ構造を示す。これらの定義は説明を目的としたものであり、プロトコル仕様の一部ではない。
/*
* RTP data header from RFC 1889.
*/
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 (from RFC 1889).
*/
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; /* packet length in words, minus one */
} rtcp_common_t;
/*
* Reception report block (RFC 1889).
*/
typedef struct {
u_int32 ssrc; /* data source being reported */
unsigned int fraction:8; /* fraction lost since last SR/RR */
int lost:24; /* cumulative number of packets lost */
u_int32 last_seq; /* extended last seq. number 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 (RFC 1889).
*/
typedef struct {
u_int8 type; /* type of item (rfc 1889, A.5) */
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.6 ランダムな 32 ビット識別子の生成 (Generating a Random 32-bit Identifier)
以下のサブルーチンは、RFC 1321 [32] で公表された MD5 ルーチンを用いてランダムな 32 ビット識別子を生成する。システムルーチンはすべてのオペレーティングシステムに存在するとは限らないが、どのような情報が利用可能かを示唆するはずである。他にも適切なシステムコールとして getdomainname()、getwd()、getrusage() がある。
「生 (live)」の音声または映像サンプルも乱数の良好な源となり得るが、マイクロフォンを閉じたりカメラを覆ったりしたソースを用いてはならない [17]。
本ルーチンまたは類似のルーチンは、付録 A.7 に示す RTCP 間隔を生成するために、乱数発生器の初期シード、シーケンス番号およびタイムスタンプの初期値、および SSRC 値を生成するために用いることが推奨される (RECOMMENDED)。本ルーチンは CPU を大量に消費する可能性が高いため、予測可能性が問題とならない RTCP 間隔の生成に直接用いるべきではない。型引数に異なる値を与えなければ、システムクロックの値が変化するまでこれを繰り返し呼び出すと同じ結果を生じることに注意。
/*
* 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() は、セクション 6.3 で定義されたパラメータを用い、秒単位の決定論的計算間隔を計算する。
o OnExpire() は RTCP 送信タイマが満了したときに呼ばれる。
o OnReceive() は任意の RTCP パケットを受信したときに呼ばれる。
OnExpire() および OnReceive() はともにイベント e を引数とする。これは、RTCP 報告であるか BYE パケットであるかの、参加者の次に予定されたイベントである。以下の関数が利用可能と仮定される。
o Schedule(time t, event e):時刻 t にイベント e を予定する。時刻 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():参照されるパケットのバイト数(オクテット)を返す。
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% ではなく明示的パラメータとして指定できる実装向けに拡張されなければならない (MUST)。拡張された rtcp_interval() の実装は、いずれかのパラメータがゼロのときのゼロ除算を避けるよう拡張されなければならない (MUST)。
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 で与えられたアルゴリズムを実装し、受信報告の到着間ジッタ (interarrival jitter) フィールドへ代入される、RTP データ到着の時間間隔の統計的分散の推定値を計算する。入力は r->ts(着信パケットのタイムスタンプ)および arrival(同じ単位で測られた現在時刻)。ここで s はその送信元の状態を指す。s->transit は前のパケットの相対伝播時間 (relative transit time) を保持し、s->jitter は推定ジッタを保持する。受信報告ブロックが生成されるたび(rr がそれを指す)、現在のジッタ推定値が返される。
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->jitter = (u_int32) s->jitter;
あるいは、ジッタ推定値は浮動小数点でなく整数として保持され、丸め誤差 (round-off error) を低減するためスケーリングされてよい (MAY)。最後の行以外の計算は同一である。
s->jitter += d - ((s->jitter + 8) >> 4);
この場合、受信報告向けの推定値のサンプリングは次のようになる。
rr->jitter = s->jitter >> 4;