+182
@@ -0,0 +1,182 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
)
|
||||
|
||||
// ReceiverInterceptorFactory is a interceptor.Factory for a ReceiverInterceptor
|
||||
type ReceiverInterceptorFactory struct {
|
||||
opts []ReceiverOption
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new ReceiverInterceptor
|
||||
func (r *ReceiverInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
|
||||
i := &ReceiverInterceptor{
|
||||
interval: 1 * time.Second,
|
||||
now: time.Now,
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("receiver_interceptor"),
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
|
||||
for _, opt := range r.opts {
|
||||
if err := opt(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// NewReceiverInterceptor returns a new ReceiverInterceptorFactory
|
||||
func NewReceiverInterceptor(opts ...ReceiverOption) (*ReceiverInterceptorFactory, error) {
|
||||
return &ReceiverInterceptorFactory{opts}, nil
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
|
||||
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)
|
||||
defer ticker.Stop()
|
||||
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
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
attr = make(interceptor.Attributes)
|
||||
}
|
||||
header, err := attr.GetRTPHeader(b[:i])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
stream.processRTP(r.now(), header)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
attr = make(interceptor.Attributes)
|
||||
}
|
||||
pkts, err := attr.GetRTCPPackets(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
|
||||
})
|
||||
}
|
||||
+34
@@ -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
|
||||
}
|
||||
}
|
||||
+159
@@ -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, pktHeader *rtp.Header) {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
if !stream.started { // first frame
|
||||
stream.started = true
|
||||
stream.setReceived(pktHeader.SequenceNumber)
|
||||
stream.lastSeqnum = pktHeader.SequenceNumber
|
||||
stream.lastReportSeqnum = pktHeader.SequenceNumber - 1
|
||||
stream.lastRTPTimeRTP = pktHeader.Timestamp
|
||||
stream.lastRTPTimeTime = now
|
||||
} else { // following frames
|
||||
stream.setReceived(pktHeader.SequenceNumber)
|
||||
|
||||
diff := int32(pktHeader.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 != pktHeader.SequenceNumber; i++ {
|
||||
stream.delReceived(i)
|
||||
}
|
||||
|
||||
stream.lastSeqnum = pktHeader.SequenceNumber
|
||||
}
|
||||
|
||||
// compute jitter
|
||||
// https://tools.ietf.org/html/rfc3550#page-39
|
||||
D := now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate -
|
||||
(float64(pktHeader.Timestamp) - float64(stream.lastRTPTimeRTP))
|
||||
if D < 0 {
|
||||
D = -D
|
||||
}
|
||||
stream.jitter += (D - stream.jitter) / 16
|
||||
stream.lastRTPTimeRTP = pktHeader.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
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package report provides interceptors to implement sending sender and receiver reports.
|
||||
package report
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor
|
||||
type SenderInterceptorFactory struct {
|
||||
opts []SenderOption
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new SenderInterceptor
|
||||
func (s *SenderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
|
||||
i := &SenderInterceptor{
|
||||
interval: 1 * time.Second,
|
||||
now: time.Now,
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("sender_interceptor"),
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
|
||||
for _, opt := range s.opts {
|
||||
if err := opt(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// NewSenderInterceptor returns a new SenderInterceptorFactory
|
||||
func NewSenderInterceptor(opts ...SenderOption) (*SenderInterceptorFactory, error) {
|
||||
return &SenderInterceptorFactory{opts}, nil
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
|
||||
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)
|
||||
defer ticker.Stop()
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
+34
@@ -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
|
||||
}
|
||||
}
|
||||
+37
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user