Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep track of expected RTP time stamp and control drift. #1681

Merged
merged 4 commits into from
May 4, 2023
Merged

Conversation

boks1971
Copy link
Contributor

@boks1971 boks1971 commented May 4, 2023

  • Use monotonic clock in RTCP Sender Report and packet times
  • Keep the time stamp close to expected time stamp on layer/SSRC switches

A few things TODO if this looks good

  • Clean up Smoothed OWD and reliance on publisher side sender report for subscriber side sender report.

- Use monotonic clock in RTCP Sender Report and packet times
- Keep the time stamp close to expected time stamp on layer/SSRC
  switches
)

type pendingPacket struct {
arrivalTime int64
arrivalTime time.Time
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to golang time.Time so that we have a monotonic clock available for elapsed time calculations.

// submit to TWCC even if it is a padding only packet. Clients use padding only packets as probes
// for bandwidth estimation
if b.twcc != nil && b.twccExt != 0 {
if ext := p.GetExtension(b.twccExt); ext != nil {
b.twcc.Push(binary.BigEndian.Uint16(ext[0:2]), arrivalTime, p.Marker)
b.twcc.Push(binary.BigEndian.Uint16(ext[0:2]), arrivalTime.UnixNano(), p.Marker)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to change this also to use time.Time so that we have monotonic clock in TWCC also. But, did not want to introduce that dependency in this PR. Will look at it later.

"highestTS", r.highestTS,
"highestTime", r.highestTime.String(),
)
return uint32(expectedExtRTP), nil
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea here is to check for time elapsed since first packet was sent and calculate expected time stamp based on clock rate. This is used to decide next time stamp on layer switch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea here (if necessary) is to use something like a PID controller to push/pull the difference between expected and actual when doing sender report so that the timestamp does not drift.

now := time.Now()
// construct current time based on monotonic clock
timeSinceFirst := time.Since(r.firstTime)
now := r.firstTime.Add(timeSinceFirst)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done this way to ensure that monotonic clock is used compared to the first packet, i. e. first packet time acts as the base and then everything else is an offset.

@@ -638,7 +643,7 @@ func (d *DownTrack) WriteRTP(extPkt *buffer.ExtPacket, layer int32) error {
}
}

d.rtpStats.Update(hdr, len(payload), 0, time.Now().UnixNano())
d.rtpStats.Update(hdr, len(payload), 0, extPkt.Arrival)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just using the arrival time stamp in the forwarding path so that it hugs the publish side stamping on SFU.

normalizedTS := srLayer.SenderReportData.RTPTimestamp + uint32(ntpDiff*float64(s.clockRate))
ntpDiff := srRef.SenderReportData.NTPTimestamp.Time().Sub(srLayer.SenderReportData.NTPTimestamp.Time())
rtpDiff := ntpDiff.Nanoseconds() * int64(s.clockRate) / 1e9
normalizedTS := srLayer.SenderReportData.RTPTimestamp + uint32(rtpDiff)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing this to time.Time based code to make it a bit easier to read.

switch {
case rl && el && er: // lastTS < refTS < expectedTS
nextTS = uint32(float64(refTS) + 0.95*float64(expectedTS-refTS))
explain = fmt.Sprintf("l < r < e, %d, %d", refTS-lastTS, expectedTS-refTS)
Copy link
Contributor Author

@boks1971 boks1971 May 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting case. Using 95% towards expected because of time warp seen. When muting/unmuting audio, instead of applying mute/unmute on the track, if the track is paused and replaced with an empty track, there is time warp. There are time windows where wall clock moves by 5 seconds, but RTP time stamp moves only by 3 seconds worth or so. So, on an unmute, using the publisher's time stamp as is mean there is
significant drift. Not exactly sure why that happens when replacing with an empty track, but that drift could be significant.

In this example, the three time stamps could be something like
lastTS = t
refTS = t + mutedDuration - 2 seconds
expectedTS = t + mutedDuration

@boks1971 boks1971 merged commit 15078eb into master May 4, 2023
@boks1971 boks1971 deleted the raja_av branch May 4, 2023 07:30
hautvfami pushed a commit to Stark-Industries-Ltd/livekit that referenced this pull request Jul 21, 2023
* Keep track of expected RTP time stamp and control drift.

- Use monotonic clock in RTCP Sender Report and packet times
- Keep the time stamp close to expected time stamp on layer/SSRC
  switches

* clean up

* fix test compile

* more test compile failures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants