Aller au contenu principal

Appendix A - Algorithms (Appendice A - Algorithmes)

Nous fournissons des exemples de code C pour des aspects des algorithmes d'émetteur et de récepteur RTP. Il peut y avoir d'autres méthodes d'implémentation qui sont plus rapides dans des environnements d'exploitation particuliers ou présentent d'autres avantages. Ces notes d'implémentation sont à des fins d'information uniquement et visent à clarifier la spécification RTP.

Les définitions suivantes sont utilisées pour tous les exemples ; pour la clarté et la concision, les définitions de structure ne sont valides que pour les architectures gros-boutistes (octet le plus significatif en premier) sur 32 bits. Les champs de bits sont supposés être compactés étroitement dans l'ordre des bits gros-boutiste, sans remplissage supplémentaire. Des modifications seraient nécessaires pour construire une implémentation portable.

/*
* 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 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 (Vérifications de validité de l'en-tête de données RTP)​

Un récepteur RTP devrait vérifier la validité de l'en-tête RTP sur les paquets entrants car ils pourraient être chiffrés ou provenir d'une application différente qui arrive à être mal adressée. De même, si le chiffrement selon la méthode décrite à la section 9 est activé, la vérification de validité d'en-tête est nécessaire pour vérifier que les paquets entrants ont été correctement déchiffrés, bien qu'un échec de la vérification de validité d'en-tête (par exemple, type de charge utile inconnu) ne puisse pas nécessairement indiquer un échec de déchiffrement.

Seules de faibles vérifications de validité sont possibles sur un paquet de données RTP d'une source qui n'a pas encore été entendue :

  • Le champ de version RTP doit être égal à 2.

  • Le type de charge utile doit être connu, et en particulier il ne doit pas être égal à SR ou RR.

  • Si le bit P est placé, alors le dernier octet du paquet doit contenir un compte d'octets valide, en particulier, inférieur à la longueur totale du paquet moins la taille de l'en-tête.

  • Le bit X doit être zéro si le profil ne spécifie pas que le mécanisme d'extension d'en-tête peut être utilisé. Sinon, le champ de longueur d'extension doit être inférieur à la taille totale du paquet moins la longueur de l'en-tête fixe et du remplissage.

  • La longueur du paquet doit être cohérente avec CC et le type de charge utile (si les charges utiles ont une longueur connue).

Les trois dernières vérifications sont quelque peu complexes et pas toujours possibles, ne laissant que les deux premières qui totalisent seulement quelques bits. Si l'identificateur SSRC dans le paquet est celui qui a été reçu auparavant, alors le paquet est probablement valide et vérifier si le numéro de séquence est dans la plage attendue fournit une validation supplémentaire. Si l'identificateur SSRC n'a pas encore été vu, alors les paquets de données portant cet identificateur peuvent être considérés invalides jusqu'à ce qu'un petit nombre d'entre eux arrivent avec des numéros de séquence consécutifs. Ces paquets invalides PEUVENT être ignorés ou PEUVENT être stockés et livrés une fois la validation obtenue si le délai résultant est acceptable.

La routine update_seq montrée ci-dessous s'assure qu'une source n'est déclarée valide qu'après que MIN_SEQUENTIAL paquets ont été reçus en séquence. Elle valide aussi le numéro de séquence seq d'un paquet nouvellement reçu et met à jour l'état de séquence pour la source du paquet dans la structure vers laquelle s pointe.

Quand une nouvelle source est entendue pour la première fois, c'est-à-dire que son identificateur SSRC n'est pas dans la table (voir la section 8.2), et que l'état par source lui est alloué, s->probation est fixé au nombre de paquets séquentiels requis avant de déclarer une source valide (paramètre MIN_SEQUENTIAL) et les autres variables sont initialisées :

   init_seq(s, seq);
s->max_seq = seq - 1;
s->probation = MIN_SEQUENTIAL;

Un s->probation non nul marque la source comme pas encore valide de sorte que l'état peut être ignoré après un court délai plutôt qu'un long, comme discuté à la section 6.2.1.

Après qu'une source est considérée valide, le numéro de séquence est considéré valide s'il n'est pas plus de MAX_DROPOUT en avance de s->max_seq ni plus de MAX_MISORDER en arrière. Si le nouveau numéro de séquence est en avance de max_seq modulo la plage du numéro de séquence RTP (16 bits), mais est inférieur à max_seq, il a fait le tour et le compte (décalé) des cycles de numéro de séquence est incrémenté. Une valeur de un est retournée pour indiquer un numéro de séquence valide.

Sinon, la valeur zéro est retournée pour indiquer que la validation a échoué, et le mauvais numéro de séquence plus 1 est stocké. Si le paquet suivant reçu porte le numéro de séquence supérieur suivant, il est considéré comme le début valide d'une nouvelle séquence de paquets présumée causée par une perte prolongée ou un redémarrage de source. Puisque de multiples cycles complets de numéro de séquence peuvent avoir été manqués, les statistiques de perte de paquets sont réinitialisées.

Des valeurs typiques pour les paramètres sont montrées, basées sur un temps de désordre maximal de 2 secondes à 50 paquets/seconde et une perte maximale de 1 minute. Le paramètre de perte MAX_DROPOUT devrait être une petite fraction de l'espace de numérotation à 16 bits pour donner une probabilité raisonnable que les nouveaux numéros de séquence après un redémarrage ne tombent pas dans la plage acceptable pour les numéros de séquence d'avant le redémarrage.

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;
}

La vérification de validité peut être renforcée en exigeant plus de deux paquets en séquence. Les inconvénients sont qu'un plus grand nombre de paquets initiaux seront ignorés (ou retardés dans une file) et qu'un taux de perte de paquets élevé pourrait empêcher la validation. Toutefois, parce que la validation d'en-tête RTCP est relativement forte, si un paquet RTCP est reçu d'une source avant les paquets de données, le compte pourrait être ajusté afin que seulement deux paquets soient requis en séquence. Si une perte initiale de données pendant quelques secondes peut être tolérée, une application PEUT choisir d'ignorer tous les paquets de données d'une source jusqu'à ce qu'un paquet RTCP valide ait été reçu de cette source.

Selon l'application et l'encodage, des algorithmes peuvent exploiter une connaissance supplémentaire au sujet du format de charge utile pour une validation supplémentaire. Pour les types de charge utile où l'incrément d'horodatage est le même pour tous les paquets, les valeurs d'horodatage peuvent être prédites à partir du paquet précédent reçu de la même source en utilisant la différence de numéro de séquence (en supposant aucun changement de type de charge utile).

Une vérification « fast-path » forte est possible puisqu'avec une forte probabilité les quatre premiers octets dans l'en-tête d'un paquet de données RTP nouvellement reçu seront exactement les mêmes que ceux du paquet précédent de la même SSRC sauf que le numéro de séquence aura augmenté de un. De même, un cache à une entrée peut être utilisé pour des recherches SSRC plus rapides dans les applications où les données sont typiquement reçues d'une seule source à la fois.

A.2 RTCP Header Validity Checks (Vérifications de validité de l'en-tête RTCP)​

Les vérifications suivantes devraient être appliquées aux paquets RTCP.

  • Le champ de version RTP doit être égal à 2.

  • Le champ de type de charge utile du premier paquet RTCP dans un paquet composé doit être égal à SR ou RR.

  • Le bit de remplissage (P) devrait être zéro pour le premier paquet d'un paquet RTCP composé parce que le remplissage ne devrait être appliqué, si nécessaire, qu'au dernier paquet.

  • Les champs de longueur des paquets RTCP individuels doivent s'additionner à la longueur globale du paquet RTCP composé tel que reçu. C'est une vérification assez forte.

Le fragment de code ci-dessous effectue toutes ces vérifications. Le type de paquet n'est pas vérifié pour les paquets suivants puisque des types de paquets inconnus peuvent être présents et devraient être ignorés.

   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 (Détermination du nombre de paquets attendus et perdus)​

Afin de calculer les taux de perte de paquets, le nombre de paquets RTP attendus et réellement reçus de chaque source doit être connu, en utilisant l'information d'état par source définie dans struct source référencée via le pointeur s dans le code ci-dessous. Le nombre de paquets reçus est simplement le compte des paquets tels qu'ils arrivent, incluant tout paquet tardif ou dupliqué. Le nombre de paquets attendus peut être calculé par le récepteur comme la différence entre le numéro de séquence le plus élevé reçu (s->max_seq) et le premier numéro de séquence reçu (s->base_seq). Puisque le numéro de séquence n'est que sur 16 bits et fera le tour, il est nécessaire d'étendre le numéro de séquence le plus élevé avec le compte (décalé) des retours à zéro du numéro de séquence (s->cycles). Tant le compte de paquets reçus que le compte de cycles sont maintenus par la routine de vérification de validité d'en-tête RTP à l'appendice A.1.

   extended_max = s->cycles + s->max_seq;
expected = extended_max - s->base_seq + 1;

Le nombre de paquets perdus est défini comme le nombre de paquets attendus moins le nombre de paquets réellement reçus :

   lost = expected - s->received;

Puisque ce nombre signé est porté sur 24 bits, il devrait être limité à 0x7fffff pour une perte positive ou 0x800000 pour une perte négative plutôt que de faire le tour.

La fraction de paquets perdus pendant le dernier intervalle de rapport (depuis que le paquet SR ou RR précédent a été envoyé) est calculée à partir des différences dans les comptes de paquets attendus et reçus à travers l'intervalle, où expected_prior et received_prior sont les valeurs sauvegardées quand le rapport de réception précédent a été généré :

   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;

La fraction résultante est un nombre à virgule fixe de 8 bits avec la virgule binaire sur le bord gauche.

A.4 Generating RTCP SDES Packets (Génération de paquets RTCP SDES)​

Cette fonction construit un morceau SDES dans le tampon b composé de argc éléments fournis dans les tableaux type, value et length. Elle retourne un pointeur vers le prochain emplacement disponible dans 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 Parsing RTCP SDES Packets (Analyse des paquets RTCP SDES)​

Cette fonction analyse un paquet SDES, appelant les fonctions find_member() pour trouver un pointeur vers l'information pour un membre de session donné l'identificateur SSRC et member_sdes() pour stocker la nouvelle information SDES pour ce membre. Cette fonction attend un pointeur vers l'en-tête du paquet 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 Generating a Random 32-bit Identifier (Génération d'un identificateur aléatoire de 32 bits)​

La sous-routine suivante génère un identificateur aléatoire de 32 bits en utilisant les routines MD5 publiées dans la RFC 1321 [32]. Les routines système peuvent ne pas être présentes sur tous les systèmes d'exploitation, mais elles devraient servir d'indice quant aux genres d'informations qui peuvent être utilisées. D'autres appels système qui pourraient être appropriés incluent

  • getdomainname(),

  • getwd(), ou

  • getrusage().

Des échantillons vidéo ou audio « en direct » sont aussi une bonne source de nombres aléatoires, mais il faut prendre soin d'éviter d'utiliser un microphone éteint ou une caméra aveuglée comme source [17].

L'usage de cette routine ou d'une routine similaire est recommandé pour générer la graine initiale pour le générateur de nombres aléatoires produisant la période RTCP (comme montré à l'appendice A.7), pour générer les valeurs initiales pour le numéro de séquence et l'horodatage, et pour générer les valeurs SSRC. Puisque cette routine est susceptible d'être gourmande en CPU, son usage direct pour générer des périodes RTCP est inapproprié car la prévisibilité n'est pas un problème. Notez que cette routine produit le même résultat sur des appels répétés jusqu'à ce que la valeur de l'horloge système change à moins que des valeurs différentes ne soient fournies pour l'argument 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 Computing the RTCP Transmission Interval (Calcul de l'intervalle de transmission RTCP)​

Les fonctions suivantes implémentent les règles d'émission et de réception RTCP décrites à la section 6.2. Ces règles sont codées dans plusieurs fonctions :

  • rtcp_interval() calcule l'intervalle calculé déterministe, mesuré en secondes. Les paramètres sont définis à la section 6.3.

  • OnExpire() est appelée quand le minuteur de transmission RTCP expire.

  • OnReceive() est appelée chaque fois qu'un paquet RTCP est reçu.

Tant OnExpire() que OnReceive() ont l'événement e comme argument. C'est le prochain événement planifié pour ce participant, soit un rapport RTCP soit un paquet BYE. On suppose que les fonctions suivantes sont disponibles :

  • Schedule(time t, event e) planifie un événement e pour se produire au temps t. Quand le temps t arrive, la fonction OnExpire est appelée avec e comme argument.

  • Reschedule(time t, event e) replanifie un événement e précédemment planifié pour le temps t.

  • SendRTCPReport(event e) envoie un rapport RTCP.

  • SendBYEPacket(event e) envoie un paquet BYE.

  • TypeOfEvent(event e) retourne EVENT_BYE si l'événement en cours de traitement est pour un paquet BYE à envoyer, sinon elle retourne EVENT_REPORT.

  • PacketType(p) retourne PACKET_RTCP_REPORT si le paquet p est un rapport RTCP (pas BYE), PACKET_BYE si c'est un paquet RTCP BYE, et PACKET_RTP si c'est un paquet de données RTP régulier.

  • ReceivedPacketSize() et SentPacketSize() retournent la taille du paquet référencé en octets.

  • NewMember(p) retourne 1 si le participant qui a envoyé le paquet p n'est pas actuellement dans la liste des membres, 0 sinon. Notez que cette fonction n'est pas suffisante pour une implémentation complète parce que chaque identificateur CSRC dans un paquet RTP et chaque SSRC dans un paquet BYE devrait être traité.

  • NewSender(p) retourne 1 si le participant qui a envoyé le paquet p n'est pas actuellement dans la sous-liste des émetteurs de la liste des membres, 0 sinon.

  • AddMember() et RemoveMember() pour ajouter et retirer des participants de la liste des membres.

  • AddSender() et RemoveSender() pour ajouter et retirer des participants de la sous-liste des émetteurs de la liste des membres.

Ces fonctions devraient être étendues pour une implémentation qui permet aux fractions de bande passante RTCP pour les émetteurs et les non-émetteurs d'être spécifiées comme paramètres explicites plutôt que des valeurs fixes de 25 % et 75 %. L'implémentation étendue de rtcp_interval() devrait éviter la division par zéro si l'un des paramètres était zéro.

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 (Estimation de la gigue d'inter-arrivée)​

Les fragments de code ci-dessous implémentent l'algorithme donné à la section 6.4.1 pour calculer une estimation de la variance statistique du temps d'inter-arrivée des données RTP à insérer dans le champ de gigue d'inter-arrivée des rapports de réception. Les entrées sont r->ts, l'horodatage du paquet entrant, et arrival, le temps courant dans les mêmes unités. Ici s pointe vers l'état pour la source ; s->transit contient le temps de transit relatif pour le paquet précédent, et s->jitter contient la gigue estimée. Le champ de gigue du rapport de réception est mesuré en unités d'horodatage et exprimé comme un entier non signé, mais l'estimation de gigue est gardée en virgule flottante. À mesure que chaque paquet de données arrive, l'estimation de gigue est mise à jour :

   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);

Quand un bloc de rapport de réception (vers lequel rr pointe) est généré pour ce membre, l'estimation de gigue courante est retournée :

   rr->jitter = (u_int32) s->jitter;

Alternativement, l'estimation de gigue peut être gardée comme un entier, mais mise à l'échelle pour réduire l'erreur d'arrondi. Le calcul est le même excepté pour la dernière ligne :

   s->jitter += d - ((s->jitter + 8) >> 4);

Dans ce cas, l'estimation est échantillonnée pour le rapport de réception comme :

   rr->jitter = s->jitter >> 4;