diff --git a/internal/ntp/ntp.go b/internal/ntp/ntp.go index 69f98d6c..6d32bb5e 100644 --- a/internal/ntp/ntp.go +++ b/internal/ntp/ntp.go @@ -20,6 +20,11 @@ func ToNTP(t time.Time) uint64 { return uint64(integerPart)<<32 | uint64(fractionalPart) } +// ToNTP32 converts a time.Time object to a uint32 NTP timestamp +func ToNTP32(t time.Time) uint32 { + return uint32(ToNTP(t) >> 16) +} + // ToTime converts a uint64 NTP timestamps to a time.Time object func ToTime(t uint64) time.Time { seconds := (t & 0xFFFFFFFF00000000) >> 32 @@ -28,3 +33,12 @@ func ToTime(t uint64) time.Time { return time.Unix(0, 0).Add(-2208988800 * time.Second).Add(d) } + +// ToTime32 converts a uint32 NTP timestamp to a time.Time object, using the +// highest 16 bit of the reference to recover the lost bits. The low 16 bits are +// not recovered. +func ToTime32(t uint32, reference time.Time) time.Time { + referenceNTP := ToNTP(reference) & 0xFFFF000000000000 + tu64 := ((uint64(t) << 16) & 0x0000FFFFFFFF0000) | referenceNTP + return ToTime(tu64) +} diff --git a/internal/ntp/ntp_test.go b/internal/ntp/ntp_test.go index 8bbe79e1..73658095 100644 --- a/internal/ntp/ntp_test.go +++ b/internal/ntp/ntp_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNTPTimeConverstion(t *testing.T) { +func TestNTPToTimeConverstion(t *testing.T) { for i, cc := range []struct { ts time.Time }{ @@ -24,6 +24,7 @@ func TestNTPTimeConverstion(t *testing.T) { } { t.Run(fmt.Sprintf("TimeToNTP/%v", i), func(t *testing.T) { assert.InDelta(t, cc.ts.UnixNano(), ToTime(ToNTP(cc.ts)).UnixNano(), float64(time.Millisecond.Nanoseconds())) + assert.InDelta(t, cc.ts.UnixNano(), ToTime32(ToNTP32(cc.ts), cc.ts).UnixNano(), float64(time.Millisecond.Nanoseconds())) }) } } @@ -50,3 +51,38 @@ func TestTimeToNTPConverstion(t *testing.T) { }) } } + +func TestNTPTime32(t *testing.T) { + zero := time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC) + notSoLongAgo := time.Date(2022, time.May, 5, 14, 48, 20, 0, time.UTC) + for i, cc := range []struct { + input time.Time + expected uint32 + }{ + { + input: zero, + expected: 0, + }, + { + input: zero.Add(time.Second), + expected: 1 << 16, + }, + { + input: notSoLongAgo, + expected: uint32(uint(notSoLongAgo.Sub(zero).Seconds())&0xffff) << 16, + }, + { + input: zero.Add(400 * time.Millisecond), + expected: 26214, + }, + { + input: zero.Add(1400 * time.Millisecond), + expected: 1<<16 + 26214, + }, + } { + t.Run(fmt.Sprintf("%v", i), func(t *testing.T) { + res := ToNTP32(cc.input) + assert.Equalf(t, cc.expected, res, "%b != %b", cc.expected, res) + }) + } +} diff --git a/pkg/rfc8888/recorder.go b/pkg/rfc8888/recorder.go index 4423df1e..e1dba0da 100644 --- a/pkg/rfc8888/recorder.go +++ b/pkg/rfc8888/recorder.go @@ -6,6 +6,7 @@ package rfc8888 import ( "time" + "github.com/pion/interceptor/internal/ntp" "github.com/pion/rtcp" ) @@ -44,7 +45,7 @@ func (r *Recorder) BuildReport(now time.Time, maxSize int) *rtcp.CCFeedbackRepor report := &rtcp.CCFeedbackReport{ SenderSSRC: r.ssrc, ReportBlocks: []rtcp.CCFeedbackReportBlock{}, - ReportTimestamp: ntpTime32(now), + ReportTimestamp: ntp.ToNTP32(now), } maxReportBlocks := (maxSize - 12 - (8 * len(r.streams))) / 2 @@ -65,14 +66,3 @@ func (r *Recorder) BuildReport(now time.Time, maxSize int) *rtcp.CCFeedbackRepor return report } - -func ntpTime32(t time.Time) uint32 { - // seconds since 1st January 1900 - s := (float64(t.UnixNano()) / 1000000000.0) + 2208988800 - - integerPart := uint32(s) - fractionalPart := uint32((s - float64(integerPart)) * 0xFFFFFFFF) - - // higher 32 bits are the integer part, lower 32 bits are the fractional part - return uint32(((uint64(integerPart)<<32 | uint64(fractionalPart)) >> 16) & 0xFFFFFFFF) -} diff --git a/pkg/rfc8888/recorder_test.go b/pkg/rfc8888/recorder_test.go index d5a86db3..7e38b9a6 100644 --- a/pkg/rfc8888/recorder_test.go +++ b/pkg/rfc8888/recorder_test.go @@ -4,7 +4,6 @@ package rfc8888 import ( - "fmt" "testing" "time" @@ -161,38 +160,3 @@ func TestRecorder(t *testing.T) { } }) } - -func TestNTPTime32(t *testing.T) { - zero := time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC) - notSoLongAgo := time.Date(2022, time.May, 5, 14, 48, 20, 0, time.UTC) - for i, cc := range []struct { - input time.Time - expected uint32 - }{ - { - input: zero, - expected: 0, - }, - { - input: zero.Add(time.Second), - expected: 1 << 16, - }, - { - input: notSoLongAgo, - expected: uint32(uint(notSoLongAgo.Sub(zero).Seconds())&0xffff) << 16, - }, - { - input: zero.Add(400 * time.Millisecond), - expected: 26214, - }, - { - input: zero.Add(1400 * time.Millisecond), - expected: 1<<16 + 26214, - }, - } { - t.Run(fmt.Sprintf("%v", i), func(t *testing.T) { - res := ntpTime32(cc.input) - assert.Equalf(t, cc.expected, res, "%b != %b", cc.expected, res) - }) - } -}