Skip to content

Commit 8cf0fda

Browse files
committed
Implement RTP/RTCP related stats
This Commit adds the RTP and RTCP related statistics from the WebRTC statistics API (https://w3c.github.io/webrtc-stats/) draft, that are easy to gather by intercepting RTP/RTCP streams.
1 parent 079fa7c commit 8cf0fda

File tree

10 files changed

+1430
-0
lines changed

10 files changed

+1430
-0
lines changed

internal/ntp/ntp.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Package ntp provides conversion methods between time.Time and NTP timestamps
2+
// stored in uint64
3+
package ntp
4+
5+
import (
6+
"time"
7+
)
8+
9+
// ToNTP converts a time.Time oboject to an uint64 NTP timestamp
10+
func ToNTP(t time.Time) uint64 {
11+
// seconds since 1st January 1900
12+
s := (float64(t.UnixNano()) / 1000000000) + 2208988800
13+
14+
// higher 32 bits are the integer part, lower 32 bits are the fractional part
15+
integerPart := uint32(s)
16+
fractionalPart := uint32((s - float64(integerPart)) * 0xFFFFFFFF)
17+
return uint64(integerPart)<<32 | uint64(fractionalPart)
18+
}
19+
20+
// ToTime converts a uint64 NTP timestamps to a time.Time object
21+
func ToTime(t uint64) time.Time {
22+
seconds := (t & 0xFFFFFFFF00000000) >> 32
23+
fractional := float64(t&0x00000000FFFFFFFF) / float64(0xFFFFFFFF)
24+
d := time.Duration(seconds)*time.Second + time.Duration(fractional*1e9)*time.Nanosecond
25+
26+
return time.Unix(0, 0).Add(-2208988800 * time.Second).Add(d)
27+
}

internal/ntp/ntp_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package ntp
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestNTPTimeConverstion(t *testing.T) {
12+
for i, cc := range []struct {
13+
ts time.Time
14+
}{
15+
{
16+
ts: time.Now(),
17+
},
18+
{
19+
ts: time.Unix(0, 0),
20+
},
21+
} {
22+
t.Run(fmt.Sprintf("TimeToNTP/%v", i), func(t *testing.T) {
23+
assert.InDelta(t, cc.ts.UnixNano(), ToTime(ToNTP(cc.ts)).UnixNano(), float64(time.Millisecond.Nanoseconds()))
24+
})
25+
}
26+
}
27+
28+
func TestTimeToNTPConverstion(t *testing.T) {
29+
for i, cc := range []struct {
30+
ts uint64
31+
}{
32+
{
33+
ts: 0,
34+
},
35+
{
36+
ts: 65535,
37+
},
38+
{
39+
ts: 16606669245815957503,
40+
},
41+
{
42+
ts: 9487534653230284800,
43+
},
44+
} {
45+
t.Run(fmt.Sprintf("TimeToNTP/%v", i), func(t *testing.T) {
46+
assert.Equal(t, cc.ts, ToNTP(ToTime(cc.ts)))
47+
})
48+
}
49+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Package sequencenumber provides a sequence number unwrapper
2+
package sequencenumber
3+
4+
const (
5+
maxSequenceNumberPlusOne = int64(65536)
6+
breakpoint = 32768 // half of max uint16
7+
)
8+
9+
// Unwrapper stores an unwrapped sequence number
10+
type Unwrapper struct {
11+
init bool
12+
lastUnwrapped int64
13+
}
14+
15+
func isNewer(value, previous uint16) bool {
16+
if value-previous == breakpoint {
17+
return value > previous
18+
}
19+
return value != previous && (value-previous) < breakpoint
20+
}
21+
22+
// Unwrap unwraps the next sequencenumber
23+
func (u *Unwrapper) Unwrap(i uint16) int64 {
24+
if !u.init {
25+
u.init = true
26+
u.lastUnwrapped = int64(i)
27+
return u.lastUnwrapped
28+
}
29+
30+
lastWrapped := uint16(u.lastUnwrapped)
31+
delta := int64(i - lastWrapped)
32+
if isNewer(i, lastWrapped) {
33+
if delta < 0 {
34+
delta += maxSequenceNumberPlusOne
35+
}
36+
} else if delta > 0 && u.lastUnwrapped+delta-maxSequenceNumberPlusOne >= 0 {
37+
delta -= maxSequenceNumberPlusOne
38+
}
39+
40+
u.lastUnwrapped += delta
41+
return u.lastUnwrapped
42+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package sequencenumber
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestIsNewer(t *testing.T) {
11+
cases := []struct {
12+
a, b uint16
13+
expected bool
14+
}{
15+
{
16+
a: 1,
17+
b: 0,
18+
expected: true,
19+
},
20+
{
21+
a: 65534,
22+
b: 65535,
23+
expected: false,
24+
},
25+
{
26+
a: 65535,
27+
b: 65535,
28+
expected: false,
29+
},
30+
{
31+
a: 0,
32+
b: 65535,
33+
expected: true,
34+
},
35+
{
36+
a: 0,
37+
b: 32767,
38+
expected: false,
39+
},
40+
{
41+
a: 32770,
42+
b: 2,
43+
expected: true,
44+
},
45+
{
46+
a: 3,
47+
b: 32770,
48+
expected: false,
49+
},
50+
}
51+
for i, tc := range cases {
52+
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
53+
assert.Equalf(t, tc.expected, isNewer(tc.a, tc.b), "expected isNewer(%v, %v) to be %v", tc.a, tc.b, tc.expected)
54+
})
55+
}
56+
}
57+
58+
func TestUnwrapper(t *testing.T) {
59+
cases := []struct {
60+
input []uint16
61+
expected []int64
62+
}{
63+
{
64+
input: []uint16{},
65+
expected: []int64{},
66+
},
67+
{
68+
input: []uint16{0, 1, 2, 3, 4},
69+
expected: []int64{0, 1, 2, 3, 4},
70+
},
71+
{
72+
input: []uint16{65534, 65535, 0, 1, 2},
73+
expected: []int64{65534, 65535, 65536, 65537, 65538},
74+
},
75+
{
76+
input: []uint16{32769, 0},
77+
expected: []int64{32769, 65536},
78+
},
79+
{
80+
input: []uint16{32767, 0},
81+
expected: []int64{32767, 0},
82+
},
83+
{
84+
input: []uint16{
85+
0, 32767, 32768, 32769, 32770,
86+
1, 2, 32765, 32770, 65535,
87+
},
88+
expected: []int64{
89+
0, 32767, 32768, 32769, 32770,
90+
65537, 65538, 98301, 98306, 131071,
91+
},
92+
},
93+
}
94+
for i, tc := range cases {
95+
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
96+
u := &Unwrapper{}
97+
result := []int64{}
98+
for _, i := range tc.input {
99+
result = append(result, u.Unwrap(i))
100+
}
101+
assert.Equal(t, tc.expected, result)
102+
})
103+
}
104+
}

0 commit comments

Comments
 (0)