fix encoding and decoding of NTP fractional part (#869) (#870)

Fractional part now is in 1/(2^32) units, while it was in 1/(1^9) units.
This commit is contained in:
Alessandro Ros
2025-08-31 12:36:17 +02:00
committed by GitHub
parent d5e677fc72
commit 367eb4dffd
10 changed files with 85 additions and 44 deletions

View File

@@ -24,6 +24,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
)
@@ -2570,7 +2571,7 @@ func TestClientPlayRTCPReport(t *testing.T) {
_, err2 = l2.WriteTo(mustMarshalPacketRTCP(&rtcp.SenderReport{
SSRC: 753621,
NTPTime: ntpTimeGoToRTCP(time.Date(2017, 8, 12, 15, 30, 0, 0, time.UTC)),
NTPTime: ntp.Encode(time.Date(2017, 8, 12, 15, 30, 0, 0, time.UTC)),
RTPTime: 54352,
PacketCount: 1,
OctetCount: 4,
@@ -3581,7 +3582,7 @@ func TestClientPlayPacketNTP(t *testing.T) {
_, err2 = l2.WriteTo(mustMarshalPacketRTCP(&rtcp.SenderReport{
SSRC: 753621,
NTPTime: ntpTimeGoToRTCP(time.Date(2017, 8, 12, 15, 30, 0, 0, time.UTC)),
NTPTime: ntp.Encode(time.Date(2017, 8, 12, 15, 30, 0, 0, time.UTC)),
RTPTime: 54352,
PacketCount: 1,
OctetCount: 4,

View File

@@ -21,6 +21,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
)
@@ -69,11 +70,6 @@ var testRTCPPacket = rtcp.SourceDescription{
var testRTCPPacketMarshaled = mustMarshalPacketRTCP(&testRTCPPacket)
func ntpTimeGoToRTCP(v time.Time) uint64 {
s := uint64(v.UnixNano()) + 2208988800*1000000000
return (s/1000000000)<<32 | (s % 1000000000)
}
func record(c *Client, ur string, medias []*description.Media, cb func(*description.Media, rtcp.Packet)) error {
u, err := base.ParseURL(ur)
if err != nil {
@@ -1371,7 +1367,7 @@ func TestClientRecordRTCPReport(t *testing.T) {
require.Equal(t, []rtcp.Packet{
&rtcp.SenderReport{
SSRC: packets[0].(*rtcp.SenderReport).SSRC,
NTPTime: ntpTimeGoToRTCP(time.Date(1996, 2, 13, 14, 33, 5, 0, time.UTC)),
NTPTime: ntp.Encode(time.Date(1996, 2, 13, 14, 33, 5, 0, time.UTC)),
RTPTime: 1300000 + 60*90000,
PacketCount: 1,
OctetCount: 1,

24
pkg/ntp/ntp.go Normal file
View File

@@ -0,0 +1,24 @@
// Package ntp contains functions to encode and decode timestamps to/from NTP format.
package ntp
import (
"math"
"time"
)
// Encode encodes a timestamp in NTP format.
// Specification: RFC3550, section 4
func Encode(t time.Time) uint64 {
ntp := uint64(t.UnixNano()) + 2208988800*1000000000
secs := ntp / 1000000000
fractional := uint64(math.Round(float64((ntp%1000000000)*(1<<32)) / 1000000000))
return secs<<32 | fractional
}
// Decode decodes a timestamp from NTP format.
// Specification: RFC3550, section 4
func Decode(v uint64) time.Time {
secs := int64((v >> 32) - 2208988800)
nanos := int64(math.Round(float64(((v & 0xFFFFFFFF) * 1000000000) / (1 << 32))))
return time.Unix(secs, nanos)
}

43
pkg/ntp/ntp_test.go Normal file
View File

@@ -0,0 +1,43 @@
package ntp
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
var cases = []struct {
name string
dec time.Time
enc uint64
}{
{
"a",
time.Date(2013, 4, 15, 11, 15, 17, 958404853, time.UTC).Local(),
15354565283395798332,
},
{
"b",
time.Date(2013, 4, 15, 11, 15, 18, 0, time.UTC).Local(),
15354565283574448128,
},
}
func TestEncode(t *testing.T) {
for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) {
v := Encode(ca.dec)
require.Equal(t, ca.enc, v)
})
}
}
func TestDecode(t *testing.T) {
for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) {
v := Decode(ca.enc)
require.Equal(t, ca.dec, v)
})
}
}

View File

@@ -7,17 +7,11 @@ import (
"sync"
"time"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
// seconds since 1st January 1900
// higher 32 bits are the integer part, lower 32 bits are the fractional part
func ntpTimeRTCPToGo(v uint64) time.Time {
nano := int64((v>>32)*1000000000+(v&0xFFFFFFFF)) - 2208988800*1000000000
return time.Unix(0, nano)
}
func randUint32() (uint32, error) {
var b [4]byte
_, err := rand.Read(b[:])
@@ -418,7 +412,7 @@ func (rr *RTCPReceiver) packetNTPUnsafe(ts uint32) (time.Time, bool) {
timeDiff := int32(ts - rr.lastSenderReportTimeRTP)
timeDiffGo := (time.Duration(timeDiff) * time.Second) / time.Duration(rr.ClockRate)
return ntpTimeRTCPToGo(rr.lastSenderReportTimeNTP).Add(timeDiffGo), true
return ntp.Decode(rr.lastSenderReportTimeNTP).Add(timeDiffGo), true
}
// PacketNTP returns the NTP (absolute timestamp) of the packet.

View File

@@ -5,17 +5,11 @@ import (
"sync"
"time"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
// seconds since 1st January 1900
// higher 32 bits are the integer part, lower 32 bits are the fractional part
func ntpTimeGoToRTCP(v time.Time) uint64 {
s := uint64(v.UnixNano()) + 2208988800*1000000000
return (s/1000000000)<<32 | (s % 1000000000)
}
// RTCPSender is a utility to send RTP packets.
// It is in charge of generating RTCP sender reports.
type RTCPSender struct {
@@ -112,7 +106,7 @@ func (rs *RTCPSender) report() rtcp.Packet {
return &rtcp.SenderReport{
SSRC: rs.localSSRC,
NTPTime: ntpTimeGoToRTCP(ntpTime),
NTPTime: ntp.Encode(ntpTime),
RTPTime: rtpTime,
PacketCount: rs.packetCount,
OctetCount: rs.octetCount,

View File

@@ -19,6 +19,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
)
func getSessionID(header base.Header) string {
@@ -87,7 +88,7 @@ func mikeyGenerate(ctx *wrappedSRTPContext) (*mikey.Message, error) {
msg.Payloads = []mikey.Payload{
&mikey.PayloadT{
TSType: 0,
TSValue: mikeyEncodeTime(time.Now()),
TSValue: ntp.Encode(time.Now()),
},
&mikey.PayloadRAND{
Data: randData,

View File

@@ -23,6 +23,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
)
@@ -1526,7 +1527,7 @@ func TestServerPlayRTCPReport(t *testing.T) {
require.Equal(t, []rtcp.Packet{
&rtcp.SenderReport{
SSRC: packets[0].(*rtcp.SenderReport).SSRC,
NTPTime: ntpTimeGoToRTCP(time.Date(2017, 8, 10, 12, 22, 30, 0, time.UTC)),
NTPTime: ntp.Encode(time.Date(2017, 8, 10, 12, 22, 30, 0, time.UTC)),
RTPTime: 240000 + 90000*30,
PacketCount: 1,
OctetCount: 1,

View File

@@ -20,6 +20,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
)
@@ -1079,7 +1080,7 @@ func TestServerRecordRTCPReport(t *testing.T) {
_, err = l2.WriteTo(mustMarshalPacketRTCP(&rtcp.SenderReport{
SSRC: 753621,
NTPTime: ntpTimeGoToRTCP(time.Date(2018, 2, 20, 19, 0, 0, 0, time.UTC)),
NTPTime: ntp.Encode(time.Date(2018, 2, 20, 19, 0, 0, 0, time.UTC)),
RTPTime: 54352,
PacketCount: 1,
OctetCount: 4,
@@ -1682,7 +1683,7 @@ func TestServerRecordPacketNTP(t *testing.T) {
_, err = l2.WriteTo(mustMarshalPacketRTCP(&rtcp.SenderReport{
SSRC: 753621,
NTPTime: ntpTimeGoToRTCP(time.Date(2018, 2, 20, 19, 0, 0, 0, time.UTC)),
NTPTime: ntp.Encode(time.Date(2018, 2, 20, 19, 0, 0, 0, time.UTC)),
RTPTime: 54352,
PacketCount: 1,
OctetCount: 4,

View File

@@ -22,6 +22,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
"github.com/bluenviron/gortsplib/v4/pkg/ntp"
"github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver"
"github.com/bluenviron/gortsplib/v4/pkg/rtcpsender"
"github.com/bluenviron/gortsplib/v4/pkg/rtptime"
@@ -201,21 +202,6 @@ func pickFirstSupportedTransport(s *Server, tsh headers.Transports) *headers.Tra
return nil
}
func mikeyDecodeTime(t uint64) time.Time {
sec := t >> 32
dec := t & 0xFFFFFFFF
sec -= 2208988800
return time.Unix(int64(sec), int64(dec))
}
func mikeyEncodeTime(n time.Time) uint64 {
nano := uint64(n.UnixNano())
sec := nano / 1000000000
dec := nano % 1000000000
sec += 2208988800
return sec<<32 | dec
}
func mikeyGetPayload[T mikey.Payload](mikeyMsg *mikey.Message) (T, bool) {
var zero T
for _, wrapped := range mikeyMsg.Payloads {
@@ -241,7 +227,7 @@ func mikeyToContext(mikeyMsg *mikey.Message) (*wrappedSRTPContext, error) {
return nil, fmt.Errorf("time payload not present")
}
ts := mikeyDecodeTime(timePayload.TSValue)
ts := ntp.Decode(timePayload.TSValue)
diff := time.Since(ts)
if diff < -time.Hour || diff > time.Hour {
return nil, fmt.Errorf("NTP difference is too high")