Passa al contenuto principale

RFC 3550 - Appendix A. Algorithms

Appendice A - Algoritmi (Algorithms)

Forniamo esempi di codice C per aspetti degli algoritmi di mittente e ricevitore RTP. Possono esserci altri metodi di implementazione che sono più veloci in particolari ambienti operativi o hanno altri vantaggi. Queste note di implementazione sono solo a scopo informativo e sono intese a chiarire la specifica RTP.

Le seguenti definizioni sono usate per tutti gli esempi; per chiarezza e brevità, le definizioni di struttura sono valide solo per architetture big-endian a 32 bit (ottetto più significativo per primo). Si assume che i campi di bit siano impacchettati strettamente in ordine di bit big-endian, senza padding aggiuntivo. Modifiche sarebbero richieste per costruire un'implementazione portabile.

/* * rtp.h -- file header RTP */ #include <sys/types.h>

/* * Le definizioni di tipo sotto sono valide per architetture a 32 bit e * possono dover essere adeguate per architetture a 16 o 64 bit. */ typedef unsigned char u_int8; typedef unsigned short u_int16; typedef unsigned int u_int32; typedef short int16;

/* * Versione corrente del protocollo. */ #define RTP_VERSION 2

#define RTP_SEQ_MOD (1<<16) #define RTP_MAX_SDES 255 /* lunghezza massima del testo per 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;

/* * Header dati RTP / typedef struct { unsigned int version:2; / versione del protocollo / unsigned int p:1; / flag di padding / unsigned int x:1; / flag di estensione header / unsigned int cc:4; / conteggio CSRC / unsigned int m:1; / bit di marker / unsigned int pt:7; / tipo di payload / unsigned int seq:16; / numero di sequenza / u_int32 ts; / timestamp / u_int32 ssrc; / sorgente di sincronizzazione / u_int32 csrc[1]; / lista CSRC opzionale */ } rtp_hdr_t;

/* * Parola comune di header RTCP / typedef struct { unsigned int version:2; / versione del protocollo / unsigned int p:1; / flag di padding / unsigned int count:5; / varia secondo il tipo di pacchetto / unsigned int pt:8; / tipo di pacchetto RTCP / u_int16 length; / lunghezza pacchetto in parole, senza questa parola */ } rtcp_common_t;

/* * Maschera big-endian per versione, bit di padding e coppia di tipo di pacchetto */ #define RTCP_VALID_MASK (0xc000 | 0x2000 | 0xfe) #define RTCP_VALID_VALUE ((RTP_VERSION << 14) | RTCP_SR)

/* * Blocco di rapporto di ricezione / typedef struct { u_int32 ssrc; / sorgente dati riportata / unsigned int fraction:8; / frazione persa dall'ultimo SR/RR */

   int lost:24;              /* n. cumul. pacchetti persi (con segno!) */
u_int32 last_seq; /* n. seq. esteso ultimo ricevuto */
u_int32 jitter; /* jitter di interarrivo */
u_int32 lsr; /* ultimo pacchetto SR da questa sorgente */
u_int32 dlsr; /* ritardo dall'ultimo pacchetto SR */

} rtcp_rr_t;

/* * Elemento SDES / typedef struct { u_int8 type; / tipo di elemento (rtcp_sdes_type_t) / u_int8 length; / lunghezza dell'elemento (in ottetti) / char data[1]; / testo, non terminato da null */ } rtcp_sdes_item_t;

/* * Un pacchetto RTCP / typedef struct { rtcp_common_t common; / header comune / union { / rapporto mittente (SR) / struct { u_int32 ssrc; / mittente che genera questo rapporto / u_int32 ntp_sec; / timestamp NTP / u_int32 ntp_frac; u_int32 rtp_ts; / timestamp RTP / u_int32 psent; / pacchetti inviati / u_int32 osent; / ottetti inviati / rtcp_rr_t rr[1]; / lista a lunghezza variabile */ } sr;

       /* rapporto ricevitore (RR) */
struct {
u_int32 ssrc; /* ricevitore che genera questo rapporto */
rtcp_rr_t rr[1]; /* lista a lunghezza variabile */
} rr;

/* descrizione sorgente (SDES) */
struct rtcp_sdes {
u_int32 src; /* primo SSRC/CSRC */
rtcp_sdes_item_t item[1]; /* lista di elementi SDES */
} sdes;

/* BYE */
struct {
u_int32 src[1]; /* lista di sorgenti */

/* non può esprimere il testo finale per il motivo */
} bye;
} r;

} rtcp_t;

typedef struct rtcp_sdes rtcp_sdes_t;

/* * Informazioni di stato per sorgente / typedef struct { u_int16 max_seq; / numero di seq. più alto visto / u_int32 cycles; / conteggio spostato dei cicli del n. di seq. / u_int32 base_seq; / numero di seq. di base / u_int32 bad_seq; / ultimo 'cattivo' n. di seq. + 1 / u_int32 probation; / pacchetti sequenziali finché la sorgente è valida / u_int32 received; / pacchetti ricevuti / u_int32 expected_prior; / pacchetto atteso all'ultimo intervallo / u_int32 received_prior; / pacchetto ricevuto all'ultimo intervallo / u_int32 transit; / tempo di transito relativo per il pacchetto prec. / u_int32 jitter; / jitter stimato / / ... */ } source;

A.1 Controlli di validità dell'header dei dati RTP (RTP Data Header Validity Checks)

Un ricevitore RTP dovrebbe controllare la validità dell'header RTP sui pacchetti in arrivo poiché potrebbero essere cifrati o potrebbero provenire da una diversa applicazione che capita sia indirizzata in modo errato. Similmente, se è abilitata la cifratura secondo il metodo descritto nella Sezione 9, il controllo di validità dell'header è necessario per verificare che i pacchetti in arrivo siano stati decifrati correttamente, sebbene un fallimento del controllo di validità dell'header (es. tipo di payload sconosciuto) non indichi necessariamente un fallimento di decifratura.

Solo deboli controlli di validità sono possibili su un pacchetto dati RTP da una sorgente che non è stata ancora sentita:

o Il campo della versione RTP deve essere uguale a 2.

o Il tipo di payload deve essere noto, e in particolare non deve essere uguale a SR o RR.

o Se il bit P è impostato, allora l'ultimo ottetto del pacchetto deve contenere un conteggio di ottetti valido, in particolare, minore della lunghezza totale del pacchetto meno la dimensione dell'header.

o Il bit X deve essere zero se il profilo non specifica che il meccanismo di estensione dell'header può essere usato. Altrimenti, il campo della lunghezza dell'estensione deve essere minore della dimensione totale del pacchetto meno la lunghezza fissa dell'header e il padding.

o La lunghezza del pacchetto deve essere coerente con CC e il tipo di payload (se i payload hanno una lunghezza nota).

Gli ultimi tre controlli sono alquanto complessi e non sempre possibili, lasciando solo i primi due che totalizzano appena alcuni bit. Se l'identificatore SSRC nel pacchetto è uno che è stato ricevuto prima, allora il pacchetto è probabilmente valido e controllare se il numero di sequenza è nell'intervallo atteso fornisce un'ulteriore validazione. Se l'identificatore SSRC non è stato visto prima, allora i pacchetti dati che trasportano quell'identificatore possono essere considerati non validi finché non ne arriva un piccolo numero con numeri di sequenza consecutivi. Quei pacchetti non validi POSSONO essere scartati o POSSONO essere memorizzati e consegnati una volta ottenuta la validazione se il ritardo risultante è accettabile.

La routine update_seq mostrata sotto assicura che una sorgente sia dichiarata valida solo dopo che MIN_SEQUENTIAL pacchetti sono stati ricevuti in sequenza. Essa convalida anche il numero di sequenza seq di un pacchetto appena ricevuto e aggiorna lo stato di sequenza per la sorgente del pacchetto nella struttura a cui punta s.

Quando una nuova sorgente è sentita per la prima volta, cioè il suo identificatore SSRC non è nella tabella (vedere la Sezione 8.2), e lo stato per sorgente è allocato per essa, s->probation è impostato al numero di pacchetti sequenziali richiesti prima di dichiarare una sorgente valida (parametro MIN_SEQUENTIAL) e altre variabili sono inizializzate:

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

Un s->probation non zero marca la sorgente come non ancora valida così lo stato può essere scartato dopo un breve timeout anziché uno lungo, come discusso nella Sezione 6.2.1.

Dopo che una sorgente è considerata valida, il numero di sequenza è considerato valido se è non più di MAX_DROPOUT avanti a s->max_seq né più di MAX_MISORDER indietro. Se il nuovo numero di sequenza è avanti a max_seq modulo l'intervallo del numero di sequenza RTP (16 bit), ma è minore di max_seq, ha fatto wraparound e il (spostato) conteggio dei cicli del numero di sequenza è incrementato. È restituito il valore uno per indicare un numero di sequenza valido.

Altrimenti, è restituito il valore zero per indicare che la validazione è fallita, e il cattivo numero di sequenza più 1 è memorizzato. Se il pacchetto ricevuto successivo porta il numero di sequenza immediatamente superiore, è considerato l'inizio valido di una nuova sequenza di pacchetti presumibilmente causata da un dropout esteso o da un riavvio della sorgente. Poiché possono essere stati mancati multipli cicli completi del numero di sequenza, le statistiche di perdita dei pacchetti sono resettate.

Sono mostrati valori tipici per i parametri, basati su un tempo massimo di disordine di 2 secondi a 50 pacchetti/secondo e un dropout massimo di 1 minuto. Il parametro di dropout MAX_DROPOUT dovrebbe essere una piccola frazione dello spazio a 16 bit del numero di sequenza per dare una ragionevole probabilità che nuovi numeri di sequenza dopo un riavvio non cadano nell'intervallo accettabile per i numeri di sequenza di prima del riavvio.

void init_seq(source s, u_int16 seq) { s->base_seq = seq; s->max_seq = seq; s->bad_seq = RTP_SEQ_MOD + 1; / così seq == bad_seq è falso / s->cycles = 0; s->received = 0; s->received_prior = 0; s->expected_prior = 0; / altre inizializzazioni */ }

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;

   /*
* La sorgente non è valida finché MIN_SEQUENTIAL pacchetti con
* numeri di sequenza sequenziali non sono stati ricevuti.
*/
if (s->probation) {
/* il pacchetto è in sequenza */
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 ordine, con gap permesso */
if (seq < s->max_seq) {
/*
* Numero di sequenza andato in wraparound - conta un altro
* ciclo di 64K.
*/
s->cycles += RTP_SEQ_MOD;
}
s->max_seq = seq;
} else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) {
/* il numero di sequenza ha fatto un salto molto grande */
if (seq == s->bad_seq) {
/*
* Due pacchetti sequenziali -- supponi che l'altro lato
* si sia riavviato senza dirci così re-sync semplicemente
* (cioè, fingi che questo fosse il primo pacchetto).
*/
init_seq(s, seq);
}
else {
s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
return 0;
}
} else {
/* pacchetto duplicato o riordinato */
}
s->received++;
return 1;

}

Il controllo di validità può essere reso più forte richiedendo più di due pacchetti in sequenza. Gli svantaggi sono che un numero maggiore di pacchetti iniziali sarà scartato (o ritardato in una coda) e che alte frequenze di perdita dei pacchetti potrebbero impedire la validazione. Tuttavia, poiché il controllo di validità dell'header RTCP è relativamente forte, se un pacchetto RTCP è ricevuto da una sorgente prima dei pacchetti dati, il conteggio potrebbe essere aggiustato così che solo due pacchetti sono richiesti in sequenza. Se una perdita iniziale di dati per alcuni secondi può essere tollerata, un'applicazione PUÒ scegliere di scartare tutti i pacchetti dati da una sorgente finché un pacchetto RTCP valido non è stato ricevuto da quella sorgente.

A seconda dell'applicazione e della codifica, gli algoritmi possono sfruttare conoscenze aggiuntive sul formato del payload per ulteriore validazione. Per tipi di payload dove l'incremento del timestamp è lo stesso per tutti i pacchetti, i valori del timestamp possono essere predetti dal pacchetto precedente ricevuto dalla stessa sorgente usando la differenza del numero di sequenza (assumendo nessun cambiamento nel tipo di payload).

Un forte controllo "fast-path" è possibile poiché con alta probabilità i primi quattro ottetti nell'header di un pacchetto dati RTP appena ricevuto saranno gli stessi di quelli del pacchetto precedente dalla stessa SSRC eccetto che il numero di sequenza sarà aumentato di uno. Similmente, una cache a una voce può essere usata per ricerche SSRC più veloci in applicazioni dove i dati sono tipicamente ricevuti da una sorgente alla volta.

A.2 Controlli di validità dell'header RTCP (RTCP Header Validity Checks)

I seguenti controlli dovrebbero essere applicati ai pacchetti RTCP.

o Il campo della versione RTP deve essere uguale a 2.

o Il campo del tipo di payload del primo pacchetto RTCP in un pacchetto composto deve essere uguale a SR o RR.

o Il bit di padding (P) dovrebbe essere zero per il primo pacchetto di un pacchetto RTCP composto poiché il padding dovrebbe essere applicato, se necessario, solo all'ultimo pacchetto.

o I campi di lunghezza dei singoli pacchetti RTCP devono sommarsi alla lunghezza complessiva del pacchetto RTCP composto come ricevuto. Questo è un controllo abbastanza forte.

Il frammento di codice sotto esegue tutti questi controlli. Il tipo di pacchetto non è controllato per i pacchetti successivi poiché possono essere presenti tipi di pacchetto sconosciuti e dovrebbero essere ignorati.

  u_int32 len;        /* lunghezza del pacchetto RTCP composto in parole */
rtcp_t *r; /* header RTCP */
rtcp_t *end; /* fine del pacchetto RTCP composto */

if ((*(u_int16 *)r & RTCP_VALID_MASK) != RTCP_VALID_VALUE) {
/* qualcosa di sbagliato nel formato del pacchetto */
}
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) {
/* qualcosa di sbagliato nel formato del pacchetto */
}

A.3 Determinazione del numero di pacchetti attesi e persi (Determining Number of Packets Expected and Lost)

Al fine di calcolare le frequenze di perdita dei pacchetti, il numero di pacchetti RTP attesi e effettivamente ricevuti da ciascuna sorgente deve essere noto, usando le informazioni di stato per sorgente definite nella struct source referenziate tramite il puntatore s nel codice sotto. Il numero di pacchetti ricevuti è semplicemente il conteggio dei pacchetti man mano che arrivano, includendo eventuali pacchetti in ritardo o duplicati. Il numero di pacchetti attesi può essere calcolato dal ricevitore come la differenza tra il numero di sequenza più alto ricevuto (s->max_seq) e il primo numero di sequenza ricevuto (s->base_seq). Poiché il numero di sequenza è solo a 16 bit e farà wraparound, è necessario estendere il numero di sequenza più alto con il (spostato) conteggio dei wraparound del numero di sequenza (s->cycles). Sia il conteggio dei pacchetti ricevuti che il conteggio dei cicli sono mantenuti dalla routine di controllo di validità dell'header RTP nell'Appendice A.1.

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

Il numero di pacchetti persi è definito come il numero di pacchetti attesi meno il numero di pacchetti effettivamente ricevuti:

  lost = expected - s->received;

Poiché questo numero con segno è trasportato in 24 bit, dovrebbe essere limitato a 0x7fffff per perdita positiva o 0x800000 per perdita negativa anziché andare in wraparound.

La frazione di pacchetti persi durante l'ultimo intervallo di rapporto (dall'invio del precedente pacchetto SR o RR) è calcolata dalle differenze nei conteggi di pacchetti attesi e ricevuti attraverso l'intervallo, dove expected_prior e received_prior sono i valori salvati quando il precedente rapporto di ricezione è stato generato:

  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 frazione risultante è un numero a virgola fissa a 8 bit con la virgola binaria al margine sinistro.

A.4 Generazione di pacchetti RTCP SDES (Generating RTCP SDES Packets)

Questa funzione costruisce un chunk SDES nel buffer b composto da argc elementi forniti negli array type, value e length. Restituisce un puntatore alla successiva posizione disponibile entro 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;

   /* header SSRC */
s->src = src;
rsp = &s->item[0];

/* elementi SDES */
for (i = 0; i < argc; i++) {
rsp->type = type[i];
len = length[i];
if (len > RTP_MAX_SDES) {
/* lunghezza non valida, si può volere un'altra azione */
len = RTP_MAX_SDES;
}
rsp->length = len;
memcpy(rsp->data, value[i], len);
rsp = (rtcp_sdes_item_t *)&rsp->data[len];
}

/* termina con marcatore di fine e padda al successivo confine di 4 ottetti */
len = ((char *) rsp) - b;
pad = 4 - (len & 0x3);
b = (char *) rsp;
while (pad--) *b++ = RTCP_SDES_END;

return b;

}

A.5 Analisi dei pacchetti RTCP SDES (Parsing RTCP SDES Packets)

Questa funzione analizza un pacchetto SDES, chiamando le funzioni find_member() per trovare un puntatore alle informazioni per un membro della sessione dato l'identificatore SSRC e member_sdes() per memorizzare le nuove informazioni SDES per quel membro. Questa funzione si aspetta un puntatore all'header del pacchetto 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) {
/* formato pacchetto non valido */
}

}

A.6 Generazione di un identificatore casuale a 32 bit (Generating a Random 32-bit Identifier)

La seguente subroutine genera un identificatore casuale a 32 bit usando le routine MD5 pubblicate in RFC 1321 [32]. Le routine di sistema possono non essere presenti su tutti i sistemi operativi, ma dovrebbero servire come suggerimenti circa quali tipi di informazione possono essere usati. Altre chiamate di sistema che possono essere appropriate includono

o getdomainname(),

o getwd(), o

o getrusage().

Campioni video o audio "dal vivo" sono anch'essi una buona fonte di numeri casuali, ma si deve fare attenzione a evitare di usare un microfono spento o una telecamera coperta come fonte [17].

L'uso di questa o di una routine simile è raccomandato per generare il seme iniziale per il generatore di numeri casuali che produce il periodo RTCP (come mostrato nell'Appendice A.7), per generare i valori iniziali per il numero di sequenza e il timestamp, e per generare i valori SSRC. Poiché questa routine è probabile che sia intensiva di CPU, il suo uso diretto per generare periodi RTCP è inappropriato poiché la prevedibilità non è un problema. Si noti che questa routine produce lo stesso risultato su chiamate ripetute finché il valore dell'orologio di sistema non cambia a meno che non siano forniti valori diversi per l'argomento type.

/* * Genera una quantità casuale a 32 bit. / #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" / da RFC 1321 / #include "md5.h" / da 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 */

/* * Restituisce una quantità casuale senza segno a 32 bit. Usa l'argomento * 'type' se hai bisogno di generare diversi valori diversi in successione * ravvicinata. */ 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();
/* anche: uptime di sistema */

return md_32((char *)&s, sizeof(s));

} /* random32 */

A.7 Calcolo dell'intervallo di trasmissione RTCP (Computing the RTCP Transmission Interval)

Le seguenti funzioni implementano le regole di trasmissione e ricezione RTCP descritte nella Sezione 6.2. Queste regole sono codificate in diverse funzioni:

o rtcp_interval() calcola l'intervallo calcolato deterministico, misurato in secondi. I parametri sono definiti nella Sezione 6.3.

o OnExpire() è chiamata quando il timer di trasmissione RTCP scade.

o OnReceive() è chiamata ogni volta che un pacchetto RTCP è ricevuto.

Sia OnExpire() che OnReceive() hanno l'evento e come argomento. Questo è il successivo evento pianificato per quel partecipante, sia un rapporto RTCP o un pacchetto BYE. Si assume che le seguenti funzioni siano disponibili:

o Schedule(time t, event e) pianifica un evento e per occorrere al tempo t. Quando il tempo t arriva, la funzione OnExpire è chiamata con e come argomento.

o Reschedule(time t, event e) riprogramma un evento e precedentemente pianificato per il tempo t.

o SendRTCPReport(event e) invia un rapporto RTCP.

o SendBYEPacket(event e) invia un pacchetto BYE.

o TypeOfEvent(event e) restituisce EVENT_BYE se l'evento in elaborazione è per un pacchetto BYE da inviare, altrimenti restituisce EVENT_REPORT.

o PacketType(p) restituisce PACKET_RTCP_REPORT se il pacchetto p è un rapporto RTCP (non BYE), PACKET_BYE se è un pacchetto RTCP BYE, e PACKET_RTP se è un normale pacchetto dati RTP.

o ReceivedPacketSize() e SentPacketSize() restituiscono la dimensione del pacchetto referenziato in ottetti.

o NewMember(p) restituisce 1 se il partecipante che ha inviato il pacchetto p non è attualmente nell'elenco dei membri, 0 altrimenti. Si noti che questa funzione non è sufficiente per una implementazione completa poiché ciascun identificatore CSRC in un pacchetto RTP e ciascun SSRC in un pacchetto BYE dovrebbero essere elaborati.

o NewSender(p) restituisce 1 se il partecipante che ha inviato il pacchetto p non è attualmente nell'elenco dei mittenti dell'elenco dei membri, 0 altrimenti.

o AddMember() e RemoveMember() per aggiungere e rimuovere partecipanti dall'elenco dei membri.

o AddSender() e RemoveSender() per aggiungere e rimuovere partecipanti dall'elenco dei mittenti dell'elenco dei membri.

Queste funzioni dovrebbero essere estese per un'implementazione che consente che le frazioni di banda RTCP per mittenti e non-mittenti siano specificate come parametri espliciti anziché valori fissi del 25% e 75%. L'implementazione estesa di rtcp_interval() dovrebbe evitare la divisione per zero se uno dei parametri fosse zero.

double rtcp_interval(int members, int senders, double rtcp_bw, int we_sent, double avg_rtcp_size, int initial) { /* * Tempo medio minimo tra i pacchetti RTCP da questo sito (in * secondi). Questo tempo impedisce ai rapporti di "ammassarsi" quando * le sessioni sono piccole e la legge dei grandi numeri non sta * aiutando a lisciare il traffico. Mantiene anche l'intervallo di * rapporto dal diventare ridicolmente piccolo durante interruzioni * transitorie come una partizione di rete. / double const RTCP_MIN_TIME = 5.; / * Frazione della banda RTCP da condividere tra i mittenti attivi. * (Questa frazione è stata scelta così che in una tipica sessione con * uno o due mittenti attivi, il tempo di rapporto calcolato sarebbe * approssimativamente uguale al tempo di rapporto minimo così che * non rallentiamo inutilmente i rapporti dei ricevitori.) La * frazione del ricevitore deve essere 1 - la frazione del mittente. / double const RTCP_SENDER_BW_FRACTION = 0.25; double const RTCP_RCVR_BW_FRACTION = (1-RTCP_SENDER_BW_FRACTION); / /* Per compensare la "riconsiderazione del timer" che converge a un * valore sotto la media intesa. */ double const COMPENSATION = 2.71828 - 1.5;

   double t;                   /* intervallo */
double rtcp_min_time = RTCP_MIN_TIME;
int n; /* n. di membri per il calcolo */

/*
* La primissima chiamata all'avvio dell'applicazione usa metà del
* ritardo minimo per una notifica più rapida pur lasciando un po' di
* tempo prima di riportare per la randomizzazione e per apprendere
* delle altre sorgenti così l'intervallo di rapporto convergerà
* al corretto intervallo più rapidamente.
*/
if (initial) {
rtcp_min_time /= 2;
}
/*
* Dedica una frazione della banda RTCP ai mittenti a meno che il
* numero di mittenti non sia abbastanza grande che la loro parte è
* più di quella frazione.
*/
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;
}
}

/*
* Il numero effettivo di siti volte la dimensione media del pacchetto
* è il numero totale di ottetti inviati quando ciascun sito invia un
* rapporto. Dividendo questo per la banda effettiva si ottiene
* l'intervallo di tempo entro il quale quei pacchetti devono essere
* inviati per soddisfare l'obiettivo di banda, con un minimo
* applicato. In quell'intervallo di tempo inviamo un rapporto così
* questo tempo è anche il nostro tempo medio tra i rapporti.
*/
t = avg_rtcp_size * n / rtcp_bw;
if (t < rtcp_min_time) t = rtcp_min_time;

/*
* Per evitare raffiche di traffico da sincronizzazione non intenzionale
* con altri siti, scegliamo poi il nostro effettivo intervallo di
* rapporto successivo come un numero casuale uniformemente distribuito
* tra 0,5*t e 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)

{ /* Questa funzione è responsabile di decidere se inviare un rapporto * RTCP o un pacchetto BYE ora, o riprogrammare la trasmissione. * È anche responsabile di aggiornare le variabili di stato pmembers, * initial, tp e avg_rtcp_size. Questa funzione dovrebbe essere * chiamata alla scadenza del timer di evento usato da Schedule(). */

   double t;     /* Intervallo */
double tn; /* Tempo di trasmissione successivo */

/* Nel caso di un BYE, usiamo la "riconsiderazione del timer" per
* riprogrammare la trasmissione del BYE se necessario */

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;

/* Dobbiamo ridisegnare l'intervallo. Non riusare quello
calcolato sopra, poiché non è effettivamente distribuito
allo stesso modo, poiché siamo condizionati sul fatto che
esso sia abbastanza piccolo da causare l'invio di un
pacchetto */

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) { / Ciò che facciamo dipende dal fatto che abbiamo lasciato il gruppo, * e stiamo aspettando di inviare un BYE (TypeOfEvent(e) == EVENT_BYE) o * un rapporto RTCP. p rappresenta il pacchetto appena ricevuto. */

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

/* Riprogramma il prossimo rapporto per il tempo tn */

Reschedule(tn, e);
*pmembers = *members;
}

} else if (TypeOfEvent(e) == EVENT_BYE) {
*members += 1;
}
}

}

A.8 Stima del jitter di interarrivo (Estimating the Interarrival Jitter)

I frammenti di codice sotto implementano l'algoritmo dato nella Sezione 6.4.1 per calcolare una stima della varianza statistica del tempo di interarrivo dei dati RTP da inserire nel campo del jitter di interarrivo dei rapporti di ricezione. Gli input sono r->ts, il timestamp dal pacchetto in arrivo, e arrival, il tempo corrente nelle stesse unità. Qui s punta allo stato per la sorgente; s->transit contiene il tempo di transito relativo per il pacchetto precedente, e s->jitter contiene il jitter stimato. Il campo del jitter del rapporto di ricezione è misurato in unità di timestamp ed espresso come intero senza segno, ma la stima del jitter è mantenuta in virgola mobile. A mano a mano che ciascun pacchetto dati arriva, la stima del jitter è aggiornata:

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

Quando un blocco di rapporto di ricezione (a cui punta rr) è generato per questo membro, è restituita la stima corrente del jitter:

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

In alternativa, la stima del jitter può essere mantenuta come intero, ma scalata per ridurre l'errore di arrotondamento. Il calcolo è lo stesso eccetto per l'ultima riga:

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

In questo caso, la stima è campionata per il rapporto di ricezione come:

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