You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

240 lines
6.5 KiB

#include "ssrc.h"
#include <glib.h>
#include "aux.h"
#include "call.h"
#include "rtplib.h"
static void init_ssrc_ctx(struct ssrc_ctx *c, struct ssrc_entry *parent) {
c->parent = parent;
}
static struct ssrc_entry *create_ssrc_entry(u_int32_t ssrc) {
struct ssrc_entry *ent;
ent = g_slice_alloc0(sizeof(struct ssrc_entry));
ent->ssrc = ssrc;
mutex_init(&ent->lock);
ent->payload_type = -1;
init_ssrc_ctx(&ent->input_ctx, ent);
init_ssrc_ctx(&ent->output_ctx, ent);
return ent;
}
static void add_ssrc_entry(struct ssrc_entry *ent, struct ssrc_hash *ht) {
g_hash_table_replace(ht->ht, &ent->ssrc, ent);
}
static void free_sender_report(void *p) {
struct ssrc_sender_report_item *i = p;
g_slice_free1(sizeof(*i), i);
}
static void free_stats_block(void *p) {
struct ssrc_stats_block *ssb = p;
g_slice_free1(sizeof(*ssb), ssb);
}
static void free_ssrc_entry(void *p) {
struct ssrc_entry *e = p;
g_queue_clear_full(&e->sender_reports, free_sender_report);
g_queue_clear_full(&e->stats_blocks, free_stats_block);
g_slice_free1(sizeof(*e), e);
}
// returned as mos * 10 (i.e. 10 - 50 for 1.0 to 5.0)
static void mos_calc(struct ssrc_stats_block *ssb) {
// as per https://www.pingman.com/kb/article/how-is-mos-calculated-in-pingplotter-pro-50.html
int eff_rtt = ssb->rtt / 1000 + ssb->jitter * 2 + 10;
double r; // XXX can this be done with int math?
if (eff_rtt < 160)
r = 93.2 - eff_rtt / 40.0;
else
r = 93.2 - (eff_rtt - 120) / 40.0;
r = r - (ssb->packetloss * 2.5);
double mos = 1.0 + (0.035) * r + (.000007) * r * (r-60) * (100-r);
ssb->mos = mos * 10;
}
struct ssrc_entry *find_ssrc(u_int32_t ssrc, struct ssrc_hash *ht) {
rwlock_lock_r(&ht->lock);
struct ssrc_entry *ret = g_hash_table_lookup(ht->ht, &ssrc);
rwlock_unlock_r(&ht->lock);
return ret;
}
struct ssrc_entry *get_ssrc(u_int32_t ssrc, struct ssrc_hash *ht /* , int *created */) {
struct ssrc_entry *ent;
restart:
ent = find_ssrc(ssrc, ht);
if (G_LIKELY(ent)) {
// if (created)
// *created = 0;
return ent;
}
ent = create_ssrc_entry(ssrc);
rwlock_lock_w(&ht->lock);
if (g_hash_table_lookup(ht->ht, &ssrc)) {
// preempted
rwlock_unlock_w(&ht->lock);
free_ssrc_entry(ent);
goto restart;
}
add_ssrc_entry(ent, ht);
rwlock_unlock_w(&ht->lock);
// if (created)
// *created = 1;
return ent;
}
void free_ssrc_hash(struct ssrc_hash **ht) {
if (!*ht)
return;
g_hash_table_destroy((*ht)->ht);
g_slice_free1(sizeof(**ht), *ht);
*ht = NULL;
}
struct ssrc_hash *create_ssrc_hash(void) {
struct ssrc_hash *ret;
ret = g_slice_alloc0(sizeof(*ret));
ret->ht = g_hash_table_new_full(uint32_hash, uint32_eq, NULL, free_ssrc_entry);
rwlock_init(&ret->lock);
return ret;
}
struct ssrc_ctx *get_ssrc_ctx(u_int32_t ssrc, struct ssrc_hash *ht, enum ssrc_dir dir) {
struct ssrc_entry *s = get_ssrc(ssrc, ht /* , NULL */);
return ((void *) s) + dir;
}
void ssrc_sender_report(struct call_media *m, const struct ssrc_sender_report *sr,
const struct timeval *tv)
{
struct call *c = m->call;
struct ssrc_entry *e;
struct ssrc_sender_report_item *seri;
seri = g_slice_alloc(sizeof(*seri));
seri->received = *tv;
seri->report = *sr;
seri->ntp_middle_bits = sr->ntp_msw << 16 | sr->ntp_lsw >> 16;
ilog(LOG_DEBUG, "SR from %u: RTP TS %u PC %u OC %u NTP TS %u/%u=%f",
sr->ssrc, sr->timestamp, sr->packet_count, sr->octet_count,
sr->ntp_msw, sr->ntp_lsw, sr->ntp_ts);
e = get_ssrc(sr->ssrc, c->ssrc_hash);
mutex_lock(&e->lock);
g_queue_push_tail(&e->sender_reports, seri);
while (e->sender_reports.length > 10)
free_sender_report(g_queue_pop_head(&e->sender_reports));
mutex_unlock(&e->lock);
}
void ssrc_receiver_report(struct call_media *m, const struct ssrc_receiver_report *rr,
const struct timeval *tv)
{
struct call *c = m->call;
ilog(LOG_DEBUG, "RR from %u about %u: FL %u TL %u HSR %u J %u LSR %u DLSR %u",
rr->from, rr->ssrc, rr->fraction_lost, rr->packets_lost,
rr->high_seq_received, rr->jitter, rr->lsr, rr->dlsr);
if (!rr->lsr || !rr->dlsr)
return; // no delay to be known
struct ssrc_entry *e = get_ssrc(rr->ssrc, c->ssrc_hash);
struct ssrc_sender_report_item *seri;
mutex_lock(&e->lock);
// go through the list backwards until we find the SR referenced
for (GList *l = e->sender_reports.tail; l; l = l->prev) {
seri = l->data;
if (seri->ntp_middle_bits != rr->lsr)
continue;
goto found;
}
// not found
goto out;
found:
// `e` remains locked for access to `seri`
ilog(LOG_DEBUG, "RR from %u reports delay %u from %u", rr->from, rr->dlsr, rr->ssrc);
long long rtt = timeval_diff(tv, &seri->received);
mutex_unlock(&e->lock);
rtt -= (long long) rr->dlsr * 1000000LL / 65536LL;
ilog(LOG_DEBUG, "Calculated round-trip time for %u is %lli us", rr->ssrc, rtt);
if (rtt <= 0 || rtt > 10000000) {
ilog(LOG_DEBUG, "Invalid RTT - discarding");
goto out_nl;
}
e->last_rtt = rtt;
struct ssrc_entry *other_e = get_ssrc(rr->from, c->ssrc_hash);
// determine the clock rate for jitter values
int pt = e->payload_type;
if (pt < 0) {
pt = other_e->payload_type;
if (pt < 0) {
ilog(LOG_DEBUG, "No payload type known for RTCP RR, discarding");
goto out_nl;
}
}
const struct rtp_payload_type *rpt = rtp_payload_type(pt, m->rtp_payload_types);
if (!rpt) {
ilog(LOG_INFO, "Invalid RTP payload type %i, discarding RTCP RR", pt);
goto out_nl;
}
unsigned int jitter = rpt->clock_rate ? (rr->jitter * 1000 / rpt->clock_rate) : rr->jitter;
ilog(LOG_DEBUG, "Calculated jitter for %u is %u ms", rr->ssrc, jitter);
ilog(LOG_DEBUG, "Adding opposide side RTT of %u us", other_e->last_rtt);
struct ssrc_stats_block *ssb = g_slice_alloc(sizeof(*ssb));
*ssb = (struct ssrc_stats_block) {
.jitter = jitter,
.rtt = rtt + other_e->last_rtt,
.reported = *tv,
.packetloss = (unsigned int) rr->fraction_lost * 100 / 256,
};
mos_calc(ssb);
ilog(LOG_DEBUG, "Calculated MOS from RR for %u is %.1f", rr->from, (double) ssb->mos / 10.0);
// got a new stats block, add it to reporting ssrc
e = get_ssrc(rr->from, c->ssrc_hash);
mutex_lock(&e->lock);
// discard stats block if last has been received less than a second ago
if (G_LIKELY(e->stats_blocks.length > 0)) {
struct ssrc_stats_block *last_ssb = g_queue_peek_tail(&e->stats_blocks);
if (G_UNLIKELY(timeval_diff(tv, &last_ssb->reported) < 1000000)) {
free_stats_block(ssb);
goto out;
}
}
g_queue_push_tail(&e->stats_blocks, ssb);
if (G_UNLIKELY(!e->lowest_mos) || ssb->mos < e->lowest_mos->mos)
e->lowest_mos = ssb;
if (G_UNLIKELY(!e->highest_mos) || ssb->mos > e->highest_mos->mos)
e->highest_mos = ssb;
e->mos_sum += ssb->mos;
goto out;
out:
mutex_unlock(&e->lock);
out_nl:
;
}