1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

Squash: Fix rtc to rtmp sync timestamp using sender report. #2470

This commit is contained in:
winlin 2021-08-17 07:25:03 +08:00
parent 3d58e98d1c
commit 85620a34f5
309 changed files with 14837 additions and 8525 deletions

View file

@ -6,5 +6,5 @@ require (
github.com/pion/logging v0.2.2
github.com/pion/rtcp v1.2.6
github.com/pion/rtp v1.6.2
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
)

View file

@ -13,6 +13,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

View file

@ -0,0 +1,166 @@
package report
import (
"sync"
"time"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
// ReceiverInterceptor interceptor generates receiver reports.
type ReceiverInterceptor struct {
interceptor.NoOp
interval time.Duration
now func() time.Time
streams sync.Map
log logging.LeveledLogger
m sync.Mutex
wg sync.WaitGroup
close chan struct{}
}
// NewReceiverInterceptor returns a new ReceiverInterceptor interceptor.
func NewReceiverInterceptor(opts ...ReceiverOption) (*ReceiverInterceptor, error) {
r := &ReceiverInterceptor{
interval: 1 * time.Second,
now: time.Now,
log: logging.NewDefaultLoggerFactory().NewLogger("receiver_interceptor"),
close: make(chan struct{}),
}
for _, opt := range opts {
if err := opt(r); err != nil {
return nil, err
}
}
return r, nil
}
func (r *ReceiverInterceptor) isClosed() bool {
select {
case <-r.close:
return true
default:
return false
}
}
// Close closes the interceptor.
func (r *ReceiverInterceptor) Close() error {
defer r.wg.Wait()
r.m.Lock()
defer r.m.Unlock()
if !r.isClosed() {
close(r.close)
}
return nil
}
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func (r *ReceiverInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
r.m.Lock()
defer r.m.Unlock()
if r.isClosed() {
return writer
}
r.wg.Add(1)
go r.loop(writer)
return writer
}
func (r *ReceiverInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
defer r.wg.Done()
ticker := time.NewTicker(r.interval)
for {
select {
case <-ticker.C:
now := r.now()
r.streams.Range(func(key, value interface{}) bool {
stream := value.(*receiverStream)
var pkts []rtcp.Packet
pkts = append(pkts, stream.generateReport(now))
if _, err := rtcpWriter.Write(pkts, interceptor.Attributes{}); err != nil {
r.log.Warnf("failed sending: %+v", err)
}
return true
})
case <-r.close:
return
}
}
}
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
// will be called once per rtp packet.
func (r *ReceiverInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
stream := newReceiverStream(info.SSRC, info.ClockRate)
r.streams.Store(info.SSRC, stream)
return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
i, attr, err := reader.Read(b, a)
if err != nil {
return 0, nil, err
}
pkt := rtp.Packet{}
if err = pkt.Unmarshal(b[:i]); err != nil {
return 0, nil, err
}
stream.processRTP(r.now(), &pkt)
return i, attr, nil
})
}
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
func (r *ReceiverInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
r.streams.Delete(info.SSRC)
}
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
// change in the future. The returned method will be called once per packet batch.
func (r *ReceiverInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
return interceptor.RTCPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
i, attr, err := reader.Read(b, a)
if err != nil {
return 0, nil, err
}
pkts, err := rtcp.Unmarshal(b[:i])
if err != nil {
return 0, nil, err
}
for _, pkt := range pkts {
if sr, ok := (pkt).(*rtcp.SenderReport); ok {
value, ok := r.streams.Load(sr.SSRC)
if !ok {
continue
}
stream := value.(*receiverStream)
stream.processSenderReport(r.now(), sr)
}
}
return i, attr, nil
})
}

View file

@ -0,0 +1,34 @@
package report
import (
"time"
"github.com/pion/logging"
)
// ReceiverOption can be used to configure ReceiverInterceptor.
type ReceiverOption func(r *ReceiverInterceptor) error
// ReceiverLog sets a logger for the interceptor.
func ReceiverLog(log logging.LeveledLogger) ReceiverOption {
return func(r *ReceiverInterceptor) error {
r.log = log
return nil
}
}
// ReceiverInterval sets send interval for the interceptor.
func ReceiverInterval(interval time.Duration) ReceiverOption {
return func(r *ReceiverInterceptor) error {
r.interval = interval
return nil
}
}
// ReceiverNow sets an alternative for the time.Now function.
func ReceiverNow(f func() time.Time) ReceiverOption {
return func(r *ReceiverInterceptor) error {
r.now = f
return nil
}
}

View file

@ -0,0 +1,159 @@
package report
import (
"math/rand"
"sync"
"time"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
type receiverStream struct {
ssrc uint32
receiverSSRC uint32
clockRate float64
m sync.Mutex
size uint16
packets []uint64
started bool
seqnumCycles uint16
lastSeqnum uint16
lastReportSeqnum uint16
lastRTPTimeRTP uint32
lastRTPTimeTime time.Time
jitter float64
lastSenderReport uint32
lastSenderReportTime time.Time
totalLost uint32
}
func newReceiverStream(ssrc uint32, clockRate uint32) *receiverStream {
receiverSSRC := rand.Uint32() // #nosec
return &receiverStream{
ssrc: ssrc,
receiverSSRC: receiverSSRC,
clockRate: float64(clockRate),
size: 128,
packets: make([]uint64, 128),
}
}
func (stream *receiverStream) processRTP(now time.Time, pkt *rtp.Packet) {
stream.m.Lock()
defer stream.m.Unlock()
if !stream.started { // first frame
stream.started = true
stream.setReceived(pkt.SequenceNumber)
stream.lastSeqnum = pkt.SequenceNumber
stream.lastReportSeqnum = pkt.SequenceNumber - 1
stream.lastRTPTimeRTP = pkt.Timestamp
stream.lastRTPTimeTime = now
} else { // following frames
stream.setReceived(pkt.SequenceNumber)
diff := int32(pkt.SequenceNumber) - int32(stream.lastSeqnum)
if diff > 0 || diff < -0x0FFF {
// overflow
if diff < -0x0FFF {
stream.seqnumCycles++
}
// set missing packets as missing
for i := stream.lastSeqnum + 1; i != pkt.SequenceNumber; i++ {
stream.delReceived(i)
}
stream.lastSeqnum = pkt.SequenceNumber
}
// compute jitter
// https://tools.ietf.org/html/rfc3550#page-39
D := now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate -
(float64(pkt.Timestamp) - float64(stream.lastRTPTimeRTP))
if D < 0 {
D = -D
}
stream.jitter += (D - stream.jitter) / 16
stream.lastRTPTimeRTP = pkt.Timestamp
stream.lastRTPTimeTime = now
}
}
func (stream *receiverStream) setReceived(seq uint16) {
pos := seq % stream.size
stream.packets[pos/64] |= 1 << (pos % 64)
}
func (stream *receiverStream) delReceived(seq uint16) {
pos := seq % stream.size
stream.packets[pos/64] &^= 1 << (pos % 64)
}
func (stream *receiverStream) getReceived(seq uint16) bool {
pos := seq % stream.size
return (stream.packets[pos/64] & (1 << (pos % 64))) != 0
}
func (stream *receiverStream) processSenderReport(now time.Time, sr *rtcp.SenderReport) {
stream.m.Lock()
defer stream.m.Unlock()
stream.lastSenderReport = uint32(sr.NTPTime >> 16)
stream.lastSenderReportTime = now
}
func (stream *receiverStream) generateReport(now time.Time) *rtcp.ReceiverReport {
stream.m.Lock()
defer stream.m.Unlock()
totalSinceReport := stream.lastSeqnum - stream.lastReportSeqnum
totalLostSinceReport := func() uint32 {
if stream.lastSeqnum == stream.lastReportSeqnum {
return 0
}
ret := uint32(0)
for i := stream.lastReportSeqnum + 1; i != stream.lastSeqnum; i++ {
if !stream.getReceived(i) {
ret++
}
}
return ret
}()
stream.totalLost += totalLostSinceReport
// allow up to 24 bits
if totalLostSinceReport > 0xFFFFFF {
totalLostSinceReport = 0xFFFFFF
}
if stream.totalLost > 0xFFFFFF {
stream.totalLost = 0xFFFFFF
}
r := &rtcp.ReceiverReport{
SSRC: stream.receiverSSRC,
Reports: []rtcp.ReceptionReport{
{
SSRC: stream.ssrc,
LastSequenceNumber: uint32(stream.seqnumCycles)<<16 | uint32(stream.lastSeqnum),
LastSenderReport: stream.lastSenderReport,
FractionLost: uint8(float64(totalLostSinceReport*256) / float64(totalSinceReport)),
TotalLost: stream.totalLost,
Delay: func() uint32 {
if stream.lastSenderReportTime.IsZero() {
return 0
}
return uint32(now.Sub(stream.lastSenderReportTime).Seconds() * 65536)
}(),
Jitter: uint32(stream.jitter),
},
},
}
stream.lastReportSeqnum = stream.lastSeqnum
return r
}

View file

@ -0,0 +1,2 @@
// Package report provides interceptors to implement sending sender and receiver reports.
package report

View file

@ -0,0 +1,139 @@
package report
import (
"sync"
"time"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
func ntpTime(t time.Time) uint64 {
// seconds since 1st January 1900
s := (float64(t.UnixNano()) / 1000000000) + 2208988800
// higher 32 bits are the integer part, lower 32 bits are the fractional part
integerPart := uint32(s)
fractionalPart := uint32((s - float64(integerPart)) * 0xFFFFFFFF)
return uint64(integerPart)<<32 | uint64(fractionalPart)
}
// SenderInterceptor interceptor generates sender reports.
type SenderInterceptor struct {
interceptor.NoOp
interval time.Duration
now func() time.Time
streams sync.Map
log logging.LeveledLogger
m sync.Mutex
wg sync.WaitGroup
close chan struct{}
}
// NewSenderInterceptor returns a new SenderInterceptor interceptor.
func NewSenderInterceptor(opts ...SenderOption) (*SenderInterceptor, error) {
s := &SenderInterceptor{
interval: 1 * time.Second,
now: time.Now,
log: logging.NewDefaultLoggerFactory().NewLogger("sender_interceptor"),
close: make(chan struct{}),
}
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, err
}
}
return s, nil
}
func (s *SenderInterceptor) isClosed() bool {
select {
case <-s.close:
return true
default:
return false
}
}
// Close closes the interceptor.
func (s *SenderInterceptor) Close() error {
defer s.wg.Wait()
s.m.Lock()
defer s.m.Unlock()
if !s.isClosed() {
close(s.close)
}
return nil
}
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
s.m.Lock()
defer s.m.Unlock()
if s.isClosed() {
return writer
}
s.wg.Add(1)
go s.loop(writer)
return writer
}
func (s *SenderInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
defer s.wg.Done()
ticker := time.NewTicker(s.interval)
for {
select {
case <-ticker.C:
now := s.now()
s.streams.Range(func(key, value interface{}) bool {
ssrc := key.(uint32)
stream := value.(*senderStream)
stream.m.Lock()
defer stream.m.Unlock()
sr := &rtcp.SenderReport{
SSRC: ssrc,
NTPTime: ntpTime(now),
RTPTime: stream.lastRTPTimeRTP + uint32(now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate),
PacketCount: stream.packetCount,
OctetCount: stream.octetCount,
}
if _, err := rtcpWriter.Write([]rtcp.Packet{sr}, interceptor.Attributes{}); err != nil {
s.log.Warnf("failed sending: %+v", err)
}
return true
})
case <-s.close:
return
}
}
}
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
// will be called once per rtp packet.
func (s *SenderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
stream := newSenderStream(info.ClockRate)
s.streams.Store(info.SSRC, stream)
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, a interceptor.Attributes) (int, error) {
stream.processRTP(s.now(), header, payload)
return writer.Write(header, payload, a)
})
}

View file

@ -0,0 +1,34 @@
package report
import (
"time"
"github.com/pion/logging"
)
// SenderOption can be used to configure SenderInterceptor.
type SenderOption func(r *SenderInterceptor) error
// SenderLog sets a logger for the interceptor.
func SenderLog(log logging.LeveledLogger) SenderOption {
return func(r *SenderInterceptor) error {
r.log = log
return nil
}
}
// SenderInterval sets send interval for the interceptor.
func SenderInterval(interval time.Duration) SenderOption {
return func(r *SenderInterceptor) error {
r.interval = interval
return nil
}
}
// SenderNow sets an alternative for the time.Now function.
func SenderNow(f func() time.Time) SenderOption {
return func(r *SenderInterceptor) error {
r.now = f
return nil
}
}

View file

@ -0,0 +1,37 @@
package report
import (
"sync"
"time"
"github.com/pion/rtp"
)
type senderStream struct {
clockRate float64
m sync.Mutex
// data from rtp packets
lastRTPTimeRTP uint32
lastRTPTimeTime time.Time
packetCount uint32
octetCount uint32
}
func newSenderStream(clockRate uint32) *senderStream {
return &senderStream{
clockRate: float64(clockRate),
}
}
func (stream *senderStream) processRTP(now time.Time, header *rtp.Header, payload []byte) {
stream.m.Lock()
defer stream.m.Unlock()
// always update time to minimize errors
stream.lastRTPTimeRTP = header.Timestamp
stream.lastRTPTimeTime = now
stream.packetCount++
stream.octetCount += uint32(len(payload))
}