Files
gortsplib/pkg/rtptime/global_decoder2.go
Alessandro Ros 2ca0bffa20 use native timestamps instead of time.Duration (#627)
this improves timestamp precision
2024-10-07 15:58:43 +02:00

104 lines
2.2 KiB
Go

package rtptime
import (
"sync"
"time"
"github.com/pion/rtp"
)
// avoid an int64 overflow and preserve resolution by splitting division into two parts:
// first add the integer part, then the decimal part.
func multiplyAndDivide2(v, m, d int64) int64 {
secs := v / d
dec := v % d
return (secs*m + dec*m/d)
}
type globalDecoder2TrackData struct {
overall int64
prev uint32
}
func (d *globalDecoder2TrackData) decode(ts uint32) int64 {
d.overall += int64(int32(ts - d.prev))
d.prev = ts
return d.overall
}
// GlobalDecoder2Track is a track (RTSP format or WebRTC track) of GlobalDecoder2.
type GlobalDecoder2Track interface {
ClockRate() int
PTSEqualsDTS(*rtp.Packet) bool
}
// GlobalDecoder2 is a RTP timestamp decoder.
type GlobalDecoder2 struct {
mutex sync.Mutex
leadingTrack GlobalDecoderTrack
startNTP time.Time
startPTS int64
startPTSClockRate int64
tracks map[GlobalDecoder2Track]*globalDecoder2TrackData
}
// NewGlobalDecoder2 allocates a GlobalDecoder.
func NewGlobalDecoder2() *GlobalDecoder2 {
return &GlobalDecoder2{
tracks: make(map[GlobalDecoder2Track]*globalDecoder2TrackData),
}
}
// Decode decodes a timestamp.
func (d *GlobalDecoder2) Decode(
track GlobalDecoder2Track,
pkt *rtp.Packet,
) (int64, bool) {
if track.ClockRate() == 0 {
return 0, false
}
d.mutex.Lock()
defer d.mutex.Unlock()
df, ok := d.tracks[track]
// never seen before track
if !ok {
if !track.PTSEqualsDTS(pkt) {
return 0, false
}
now := timeNow()
if d.leadingTrack == nil {
d.leadingTrack = track
d.startNTP = now
d.startPTS = 0
d.startPTSClockRate = int64(track.ClockRate())
}
// start from the PTS of the leading track
startPTS := multiplyAndDivide2(d.startPTS, int64(track.ClockRate()), d.startPTSClockRate)
startPTS += multiplyAndDivide2(int64(now.Sub(d.startNTP)), int64(track.ClockRate()), int64(time.Second))
d.tracks[track] = &globalDecoder2TrackData{
overall: startPTS,
prev: pkt.Timestamp,
}
return startPTS, true
}
pts := df.decode(pkt.Timestamp)
// update startNTP / startPTS
if d.leadingTrack == track && track.PTSEqualsDTS(pkt) {
now := timeNow()
d.startNTP = now
d.startPTS = pts
}
return pts, true
}