Appendix A - Algorithms (Anhang A - Algorithmen)
Wir stellen C-Codebeispiele für Aspekte der RTP-Sender- und Empfängeralgorithmen zur Verfügung. Es kann andere Implementierungsmethoden geben, die in bestimmten Betriebsumgebungen schneller sind oder andere Vorteile bieten. Diese Implementierungshinweise dienen nur zu Informationszwecken und sollen die RTP-Spezifikation klären.
Für alle Beispiele werden die folgenden Definitionen verwendet; für Klarheit und Kürze sind die Strukturdefinitionen nur für 32-Bit-Big-Endian-Architekturen (höchstwertiges Byte zuerst) gültig. Die Bitfelder werden eng gepackt in Big-Endian-Bitreihenfolge angenommen, ohne zusätzliche Füllung. Für portable Implementierungen wären Änderungen nötig.
/*
* 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 data {
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 max_seq_base; /* 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 Data Header Validity Checks (Gültigkeitsprüfungen für den RTP-Datenheader)
Ein RTP-Empfänger sollte die Gültigkeit des RTP-Headers auf eingehenden Paketen prüfen, da diese verschlüsselt sein könnten oder von einer anderen Anwendung stammen könnten, die zufällig falsch adressiert wurde. Ebenso, wenn die in Abschnitt 9 beschriebene Verschlüsselung aktiviert ist, ist die Header-Gültigkeitsprüfung notwendig, um zu verifizieren, dass eingehende Pakete korrekt entschlüsselt wurden, obwohl ein Fehlschlagen der Header-Gültigkeitsprüfung (beispielsweise unbekannter Nutzlasttyp) nicht notwendigerweise einen Entschlüsselungsfehlschlag anzeigen kann.
Nur schwache Gültigkeitsprüfungen sind für ein RTP-Datenpaket einer Quelle möglich, die noch nicht gehört wurde:
-
Das RTP-Versionsfeld muss gleich 2 sein.
-
Der Nutzlasttyp muss bekannt sein und insbesondere darf er nicht gleich SR oder RR sein.
-
Wenn das P-Bit gesetzt ist, muss das letzte Oktett des Pakets einen gültigen Oktettzähler enthalten, insbesondere kleiner als die Gesamtlänge des Pakets minus der Headergröße.
-
Das X-Bit muss Null sein, falls das Profil nicht spezifiziert, dass der Header-Erweiterungsmechanismus verwendet werden kann. Andernfalls muss das Erweiterungslängenfeld kleiner als die Gesamtlänge des Pakets minus der festen Headergröße und Füllung sein.
-
Die Paketlänge muss mit CC und dem Nutzlasttyp konsistent sein (falls Nutzlasten eine bekannte Länge haben).
Die letzten drei Prüfungen sind etwas komplex und nicht immer möglich, was nur die ersten beiden übrig lässt, die insgesamt nur wenige Bits umfassen. Wenn der SSRC-Identifikator im Paket der ist, der zuvor empfangen wurde, ist das Paket wahrscheinlich gültig, und zu prüfen, ob die Sequenznummer im erwarteten Bereich liegt, bietet zusätzliche Validierung. Wenn der SSRC-Identifikator noch nicht gesehen wurde, können Datenpakete mit diesem Identifikator als ungültig betrachtet werden, bis eine kleine Anzahl von ihnen mit aufeinanderfolgenden Sequenznummern ankommt. Diese ungültigen Pakete KÖNNEN ignoriert oder KÖNNEN gespeichert und bei Erhalt der Validierung geliefert werden, falls die resultierende Verzögerung akzeptabel ist.
Die unten gezeigte Routine update_seq stellt sicher, dass eine Quelle erst dann als gültig deklariert wird, nachdem MIN_SEQUENTIAL Pakete in Sequenz empfangen wurden. Sie validiert auch die Sequenznummer seq eines neu empfangenen Pakets und aktualisiert den Sequenzzustand für die Paketquelle in der Struktur, auf die s zeigt.
Wenn eine neue Quelle zum ersten Mal gehört wird, das heißt, ihr SSRC-Identifikator ist nicht in der Tabelle (siehe Abschnitt 8.2), und der quellenspezifische Zustand ihr zugewiesen wird, wird s->probation auf die Anzahl der für die Deklaration einer gültigen Quelle erforderlichen sequentiellen Pakete (Parameter MIN_SEQUENTIAL) gesetzt und die anderen Variablen werden initialisiert:
init_seq(s, seq);
s->max_seq = seq - 1;
s->probation = MIN_SEQUENTIAL;
Ein von Null verschiedenes s->probation markiert die Quelle als noch nicht gültig, sodass der Zustand nach kurzer statt langer Zeit wie in Abschnitt 6.2.1 diskutiert ignoriert werden kann.
Nachdem eine Quelle als gültig betrachtet wird, gilt die Sequenznummer als gültig, wenn sie nicht mehr als MAX_DROPOUT über s->max_seq hinaus liegt und nicht mehr als MAX_MISORDER zurück. Wenn die neue Sequenznummer über max_seq modulo des 16-Bit-RTP-Sequenznummernraums (RTP_SEQ_MOD) hinaus liegt, aber kleiner als max_seq ist, hat sie den Umlauf vollzogen und der (verschobene) Zähler der Sequenznummernumläufe wird erhöht. Ein Wert von eins wird zurückgegeben, um eine gültige Sequenznummer anzuzeigen.
Andernfalls wird der Wert Null zurückgegeben, um anzuzeigen, dass die Validierung fehlschlug, und die fehlerhafte Sequenznummer plus 1 wird gespeichert. Wenn das nächste empfangene Paket die übernächste Sequenznummer trägt, wird es als der gültige Beginn einer neuen, vermutlich durch längeren Verlust oder Neustart der Quelle verursachten Paketsequenz betrachtet. Da mehrere volle Sequenznummernumläufe verpasst worden sein könnten, werden die Paketverluststatistiken zurückgesetzt.
Typische Werte für die Parameter, basierend auf maximal 2 Sekunden Unordnungszeit bei 50 Paketen/Sekunde und maximal 1 Minute Verlust, sind gezeigt. Der MAX_DROPOUT-Verlustparameter sollte ein kleiner Bruchteil des 16-Bit-Zahlraums sein, um eine angemessene Wahrscheinlichkeit zu geben, dass neue Sequenznummern nach einem Neustart nicht in den zulässigen Bereich der vor-Neustart-Sequenznummern fallen.
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;
}
Die Gültigkeitsprüfung kann verstärkt werden, indem mehr als zwei Pakete in Sequenz verlangt werden. Die Nachteile sind, dass eine größere Anzahl anfänglicher Pakete ignoriert (oder in einer Warteschlange verzögert) würden und dass eine hohe Paketverlustrate die Validierung verhindern könnte. Da jedoch die RTCP-Header-Validierung relativ stark ist, könnte, wenn ein RTCP-Paket von einer Quelle vor den Datenpaketen empfangen wird, der Zähler angepasst werden, sodass nur zwei Pakete in Sequenz erforderlich sind. Wenn ein anfänglicher Datenverlust über einige Sekunden toleriert werden kann, KANN eine Anwendung wählen, alle Datenpakete einer Quelle zu ignorieren, bis ein gültiges RTCP-Paket von dieser Quelle empfangen wurde.
Je nach Anwendung und Kodierung können Algorithmen zusätzliches Wissen über das Nutzlastformat für weitere Validierung nutzen. Für Nutzlasttypen, bei denen der Zeitstempelzuwachs für alle Pakete gleich ist, können die Zeitstempelwerte aus dem zuvor von derselben Quelle empfangenen Paket unter Verwendung der Sequenznummerndifferenz (bei angenommener keinem Nutzlasttypwechsel) vorhergesagt werden.
Eine starke „Fast-Path"-Prüfung ist möglich, da mit hoher Wahrscheinlichkeit die ersten vier Oktette im Header eines neu empfangenen RTP-Datenpakets genau dieselben wie die des vorherigen Pakets derselben SSRC sind, außer dass die Sequenznummer um eins erhöht wurde. Ebenso kann ein Ein-Eintrag-Cache für schnellere SSRC-Suchen in Anwendungen verwendet werden, in denen Daten typischerweise von einer Quelle gleichzeitig empfangen werden.
A.2 RTCP Header Validity Checks (Gültigkeitsprüfungen für den RTCP-Header)
Die folgenden Prüfungen sollten auf RTCP-Pakete angewandt werden.
-
Das RTP-Versionsfeld muss gleich 2 sein.
-
Das Pakettypfeld des ersten RTCP-Pakets in einem zusammengesetzten Paket muss gleich SR oder RR sein.
-
Das Füllbit (P) sollte für das erste Paket eines zusammengesetzten RTCP-Pakets Null sein, da Füllung, falls nötig, nur auf das letzte Paket angewandt werden sollte.
-
Die Längenfelder der einzelnen RTCP-Pakete müssen zur globalen Länge des wie empfangen zusammengesetzten RTCP-Pakets addieren. Dies ist eine ziemlich starke Prüfung.
Das folgende Code-Fragment führt all diese Prüfungen durch. Der Pakettyp wird für nachfolgende Pakete nicht geprüft, da unbekannte Pakettypen vorhanden sein könnten und ignoriert werden sollten.
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 (Bestimmung der erwarteten und verlorenen Paketanzahl)
Um Paketverlustraten zu berechnen, muss die Anzahl der von jeder Quelle erwarteten und tatsächlich empfangenen RTP-Pakete bekannt sein, unter Verwendung des in struct source definierten, über den Zeiger s im unten stehenden Code referenzierten quellenspezifischen Zustands. Die Anzahl empfangener Pakete ist einfach der Zähler der eintreffenden Pakete, einschließlich verspäteter oder duplizierter Pakete. Die Anzahl erwarteter Pakete kann vom Empfänger als Differenz zwischen der höchsten empfangenen Sequenznummer (s->max_seq) und der ersten empfangenen Sequenznummer (s->base_seq) berechnet werden. Da die Sequenznummer nur 16 Bits umfasst und umlaufen wird, ist es notwendig, die höchste Sequenznummer mit dem (verschobenen) Zähler der Sequenznummerumläufe (s->cycles) zu erweitern. Sowohl der empfangene Paketzähler als auch der Zyklenzähler werden von der RTP-Header-Validierungsroutine in Anhang A.1 gepflegt.
extended_max = s->cycles + s->max_seq;
expected = extended_max - s->base_seq + 1;
Die Anzahl verlorener Pakete ist definiert als die Anzahl erwarteter minus die Anzahl tatsächlich empfangener Pakete:
lost = expected - s->received;
Da diese vorzeichenbehaftete Zahl über 24 Bits getragen wird, sollte sie auf 0x7fffff für positiven Verlust bzw. 0x800000 für negativen Verlust begrenzt werden, anstatt umzulaufen.
Der Bruchteil der während des letzten Berichtsintervalls (seit dem Senden des vorherigen SR- oder RR-Pakets) verlorenen Pakete wird aus den Differenzen in den Paket-Zählungen über das Intervall berechnet, wobei expected_prior und received_prior die Werte sind, die beim Erzeugen des vorherigen Empfangsberichts gespeichert wurden:
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;
Der resultierende Bruchteil ist eine 8-Bit-Festkommazahl mit dem Binärkomma am linken Rand.
A.4 Generating RTCP SDES Packets (Erzeugung von RTCP-SDES-Paketen)
Diese Funktion baut einen SDES-Block im Puffer b aus argc Elementen auf, die in den Arrays type, value und length übergeben werden. Sie gibt einen Zeiger auf den nächsten verfügbaren Ort in b zurück.
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 Parsing RTCP SDES Packets (Analyse von RTCP-SDES-Paketen)
Diese Funktion analysiert ein SDES-Paket und ruft die Funktionen find_member() auf, um einen Zeiger auf die Information für ein Sitzungsmitglied anhand des SSRC-Identifikators zu finden, und member_sdes(), um die neue SDES-Information für dieses Mitglied zu speichern. Diese Funktion erwartet einen Zeiger auf den Header des RTCP-Pakets.
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 Generating a Random 32-bit Identifier (Erzeugung eines zufälligen 32-Bit-Identifikators)
Die folgende Unterroutine erzeugt einen zufälligen 32-Bit-Identifikator unter Verwendung der in RFC 1321 [32] veröffentlichten MD5-Routinen. Die Systemroutinen sind möglicherweise nicht auf allen Betriebssystemen vorhanden, sollten aber als Hinweis auf die Arten von Informationen dienen, die verwendet werden können. Andere Systemaufrufe, die angemessen sein könnten, umfassen
-
getdomainname(),
-
getwd(), oder
-
getrusage().
„Live"-Videos oder -audios sind ebenfalls eine gute Quelle für Zufallszahlen, aber es ist Vorsicht geboten, einen ausgeschalteten Mikrofon oder eine verdeckte Kamera als Quelle zu vermeiden [17].
Die Verwendung dieser Routine oder einer ähnlichen wird zur Erzeugung der Startwerte für den Zufallszahlengenerator für die RTCP-Periode (wie in Anhang A.7 gezeigt), für die Anfangswerte von Sequenznummer und Zeitstempel und für die Erzeugung der SSRC-Werte empfohlen. Da diese Routine CPU-intensiv sein dürfte, ist ihre direkte Verwendung zur Erzeugung von RTCP-Perioden unangemessen, da Vorhersehbarkeit kein Problem ist. Beachten Sie, dass diese Routine dasselbe Ergebnis bei wiederholten Aufrufen liefert, bis sich der Wert der Systemuhr ändert, sofern nicht unterschiedliche Werte für das type-Argument bereitgestellt werden.
/*
* 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 Computing the RTCP Transmission Interval (Berechnung des RTCP-Übertragungsintervalls)
Die folgenden Funktionen implementieren die in Abschnitt 6.2 beschriebenen RTCP-Sende- und -Empfangsregeln. Diese Regeln sind in mehreren Funktionen kodiert:
-
rtcp_interval() berechnet das deterministische berechnete Intervall, gemessen in Sekunden. Die Parameter sind in Abschnitt 6.3 definiert.
-
OnExpire() wird aufgerufen, wenn der RTCP-Sendezeitgeber abläuft.
-
OnReceive() wird jedes Mal aufgerufen, wenn ein RTCP-Paket empfangen wird.
Sowohl OnExpire() als auch OnReceive() haben das Ereignis e als Argument. Dies ist das nächste für diesen Teilnehmer geplante Ereignis, entweder ein RTCP-Bericht oder ein BYE-Paket. Es wird angenommen, dass die folgenden Funktionen verfügbar sind:
-
Schedule(time t, event e) plant ein Ereignis e für den Zeitpunkt t. Wenn die Zeit t erreicht ist, wird die Funktion OnExpire mit e als Argument aufgerufen.
-
Reschedule(time t, event e) plant ein zuvor geplantes Ereignis e für die Zeit t neu.
-
SendRTCPReport(event e) sendet einen RTCP-Bericht.
-
SendBYEPacket(event e) sendet ein BYE-Paket.
-
TypeOfEvent(event e) gibt EVENT_BYE zurück, wenn das zu verarbeitende Ereignis für ein zu sendendes BYE-Paket ist, sonst gibt es EVENT_REPORT zurück.
-
PacketType(p) gibt PACKET_RTCP_REPORT zurück, wenn das Paket p ein RTCP-Bericht (kein BYE) ist, PACKET_BYE, wenn es ein RTCP-BYE-Paket ist, und PACKET_RTP, wenn es ein reguläres RTP-Datenpaket ist.
-
ReceivedPacketSize() und SentPacketSize() geben die Größe des referenzierten Pakets in Oktetten zurück.
-
NewMember(p) gibt 1 zurück, wenn der Teilnehmer, der das Paket p gesendet hat, noch nicht in der Mitgliederliste ist, sonst 0. Beachten Sie, dass diese Funktion für eine vollständige Implementierung nicht ausreicht, da jeder CSRC in einem RTP-Paket und jeder SSRC in einem BYE-Paket behandelt werden sollte.
-
NewSender(p) gibt 1 zurück, wenn der Teilnehmer, der das Paket p gesendet hat, noch nicht in der Unterliste der Sender der Mitgliederliste ist, sonst 0.
-
AddMember() und RemoveMember() zum Hinzufügen und Entfernen von Teilnehmern aus der Mitgliederliste.
-
AddSender() und RemoveSender() zum Hinzufügen und Entfernen von Teilnehmern aus der Senderunterliste der Mitgliederliste.
Diese Funktionen sollten für eine Implementierung erweitert werden, die es erlaubt, dass RTCP-Bandbreitenbruchteile für Sender und Nicht-Sender als explizite Parameter statt fester Werte von 25 % und 75 % spezifiziert werden. Die erweiterte Implementierung von rtcp_interval() sollte eine Division durch Null vermeiden, falls einer der Parameter Null wäre.
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 (Schätzung des Inter-Arrival-Jitter)
Die folgenden Code-Fragmente implementieren den in Abschnitt 6.4.1 gegebenen Algorithmus zur Berechnung einer Schätzung der statistischen Varianz der Inter-Arrival-Zeit der RTP-Daten, die in das Inter-Arrival-Jitter-Feld der Empfangsberichte eingefügt wird. Die Eingaben sind r->ts, der Zeitstempel des eingehenden Pakets, und arrival, die aktuelle Zeit in denselben Einheiten. Hier zeigt s auf den Zustand für die Quelle; s->transit enthält die relative Transportzeit für das vorherige Paket und s->jitter die geschätzte Jitter. Das Jitter-Feld des Berichts wird in Zeitstempeleinheiten gemessen und als vorzeichenlose Ganzzahl ausgedrückt, aber die Jitter-Schätzung wird als Gleitkommazahl gehalten. Mit dem Eintreffen jedes Datenpakets wird die Jitter-Schätzung aktualisiert:
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);
Wenn ein Empfangsberichtsblock (auf den rr zeigt) für dieses Mitglied erzeugt wird, wird die aktuelle Jitter-Schätzung zurückgegeben:
rr->jitter = (u_int32) s->jitter;
Alternativ kann die Jitter-Schätzung als Ganzzahl gehalten werden, aber skaliert, um den Rundungsfehler zu reduzieren. Die Berechnung ist dieselbe, außer der letzten Zeile:
s->jitter += d - ((s->jitter + 8) >> 4);
In diesem Fall wird die Schätzung für den Bericht wie folgt abgetastet:
rr->jitter = s->jitter >> 4;