Add rtcp sr and rr packet types

Relates to #119
This commit is contained in:
Max Hawkins
2018-09-10 23:37:31 -07:00
committed by Sean DuBois
parent cc4160f3ad
commit 3fee11e486
7 changed files with 440 additions and 9 deletions

View File

@@ -33,6 +33,13 @@ type Header struct {
Length uint16 Length uint16
} }
var (
errInvalidVersion = errors.New("invalid version")
errInvalidReportCount = errors.New("invalid report count")
errInvalidTotalLost = errors.New("invalid total lost count")
errPacketTooShort = errors.New("packet too short")
)
const ( const (
headerLength = 4 headerLength = 4
versionShift = 6 versionShift = 6
@@ -43,12 +50,6 @@ const (
reportCountMask = 0x1f reportCountMask = 0x1f
) )
var (
errInvalidVersion = errors.New("invalid version")
errInvalidReportCount = errors.New("invalid report count")
errHeaderTooShort = errors.New("rtcp header too short")
)
// Marshal encodes the Header in binary // Marshal encodes the Header in binary
func (h Header) Marshal() ([]byte, error) { func (h Header) Marshal() ([]byte, error) {
/* /*
@@ -84,7 +85,7 @@ func (h Header) Marshal() ([]byte, error) {
// Unmarshal decodes the Header from binary // Unmarshal decodes the Header from binary
func (h *Header) Unmarshal(rawPacket []byte) error { func (h *Header) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < headerLength { if len(rawPacket) < headerLength {
return errHeaderTooShort return errPacketTooShort
} }
/* /*

View File

@@ -33,8 +33,8 @@ func TestHeaderUnmarshal(t *testing.T) {
func TestHeaderUnmarshalNil(t *testing.T) { func TestHeaderUnmarshalNil(t *testing.T) {
var header Header var header Header
err := header.Unmarshal(nil) err := header.Unmarshal(nil)
if got, want := err, errHeaderTooShort; got != want { if got, want := err, errPacketTooShort; got != want {
t.Errorf("unmarshal nil header: err = %v, want %v", got, want) t.Fatalf("unmarshal nil header: err = %v, want %v", got, want)
} }
} }
func TestHeaderRoundTrip(t *testing.T) { func TestHeaderRoundTrip(t *testing.T) {

View File

@@ -0,0 +1,50 @@
package rtcp
import "encoding/binary"
// A ReceiverReport (RR) packet provides reception quality feedback for an RTP stream
type ReceiverReport struct {
// The synchronization source identifier for the originator of this RR packet.
SSRC uint32
// Zero or more reception report blocks depending on the number of other
// sources heard by this sender since the last report. Each reception report
// block conveys statistics on the reception of RTP packets from a
// single synchronization source.
Reports []ReceptionReport
}
// Marshal encodes the ReceiverReport in binary
func (r ReceiverReport) Marshal() ([]byte, error) {
rawPacket := make([]byte, 4)
binary.BigEndian.PutUint32(rawPacket, r.SSRC)
for _, rp := range r.Reports {
data, err := rp.Marshal()
if err != nil {
return nil, err
}
rawPacket = append(rawPacket, data...)
}
return rawPacket, nil
}
// Unmarshal decodes the ReceiverReport from binary
func (r *ReceiverReport) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < 4 {
return errPacketTooShort
}
r.SSRC = binary.BigEndian.Uint32(rawPacket)
for i := 4; i < len(rawPacket); i += receptionReportLength {
var rr ReceptionReport
if err := rr.Unmarshal(rawPacket[i:]); err != nil {
return err
}
r.Reports = append(r.Reports, rr)
}
return nil
}

View File

@@ -0,0 +1,87 @@
package rtcp
import (
"reflect"
"testing"
)
func TestReceiverReportUnmarshalNil(t *testing.T) {
var rr ReceiverReport
err := rr.Unmarshal(nil)
if got, want := err, errPacketTooShort; got != want {
t.Fatalf("unmarshal nil rr: err = %v, want %v", got, want)
}
}
func TestReceiverReportRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Report ReceiverReport
WantError error
}{
{
Name: "valid",
Report: ReceiverReport{
SSRC: 1,
Reports: []ReceptionReport{
{
SSRC: 2,
FractionLost: 2,
TotalLost: 3,
LastSeq: 4,
Jitter: 5,
LastSR: 6,
Delay: 7,
},
{
SSRC: 0,
},
},
},
},
{
Name: "also valid",
Report: ReceiverReport{
SSRC: 2,
Reports: []ReceptionReport{
{
SSRC: 999,
FractionLost: 30,
TotalLost: 12345,
LastSeq: 99,
Jitter: 22,
LastSR: 92,
Delay: 46,
},
},
},
},
{
Name: "totallost overflow",
Report: ReceiverReport{
SSRC: 1,
Reports: []ReceptionReport{{
TotalLost: 1 << 25,
}},
},
WantError: errInvalidTotalLost,
},
} {
data, err := test.Report.Marshal()
if got, want := err, test.WantError; got != want {
t.Fatalf("Marshal %q: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
var decoded ReceiverReport
if err := decoded.Unmarshal(data); err != nil {
t.Fatalf("Unmarshal %q: %v", test.Name, err)
}
if got, want := decoded, test.Report; !reflect.DeepEqual(got, want) {
t.Fatalf("%q rr round trip: got %#v, want %#v", test.Name, got, want)
}
}
}

View File

@@ -0,0 +1,126 @@
package rtcp
import "encoding/binary"
// A ReceptionReport block conveys statistics on the reception of RTP packets
// from a single synchronization source.
type ReceptionReport struct {
// The SSRC identifier of the source to which the information in this
// reception report block pertains.
SSRC uint32
// The fraction of RTP data packets from source SSRC lost since the
// previous SR or RR packet was sent, expressed as a fixed point
// number with the binary point at the left edge of the field.
FractionLost uint8
// The total number of RTP data packets from source SSRC that have
// been lost since the beginning of reception.
TotalLost uint32
// The low 16 bits contain the highest sequence number received in an
// RTP data packet from source SSRC, and the most significant 16
// bits extend that sequence number with the corresponding count of
// sequence number cycles.
LastSeq uint32
// An estimate of the statistical variance of the RTP data packet
// interarrival time, measured in timestamp units and expressed as an
// unsigned integer.
Jitter uint32
// The middle 32 bits out of 64 in the NTP timestamp received as part of
// the most recent RTCP sender report (SR) packet from source SSRC. If no
// SR has been received yet, the field is set to zero.
LastSR uint32
// The delay, expressed in units of 1/65536 seconds, between receiving the
// last SR packet from source SSRC and sending this reception report block.
// If no SR packet has been received yet from SSRC, the field is set to zero.
Delay uint32
}
var (
receptionReportLength = 24
fractionLostOffset = 4
totalLostOffset = 5
lastSeqOffset = 8
jitterOffset = 12
lastSROffset = 16
delayOffset = 20
)
// Marshal encodes the ReceptionReport in binary
func (r ReceptionReport) Marshal() ([]byte, error) {
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
rawPacket := make([]byte, receptionReportLength)
binary.BigEndian.PutUint32(rawPacket, r.SSRC)
rawPacket[fractionLostOffset] = r.FractionLost
// pack TotalLost into 24 bits
if r.TotalLost >= (1 << 25) {
return nil, errInvalidTotalLost
}
tlBytes := rawPacket[totalLostOffset:]
tlBytes[0] = byte(r.TotalLost >> 16)
tlBytes[1] = byte(r.TotalLost >> 8)
tlBytes[2] = byte(r.TotalLost)
binary.BigEndian.PutUint32(rawPacket[lastSeqOffset:], r.LastSeq)
binary.BigEndian.PutUint32(rawPacket[jitterOffset:], r.Jitter)
binary.BigEndian.PutUint32(rawPacket[lastSROffset:], r.LastSR)
binary.BigEndian.PutUint32(rawPacket[delayOffset:], r.Delay)
return rawPacket, nil
}
// Unmarshal decodes the ReceptionReport from binary
func (r *ReceptionReport) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < receptionReportLength {
return errPacketTooShort
}
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
r.SSRC = binary.BigEndian.Uint32(rawPacket)
r.FractionLost = rawPacket[fractionLostOffset]
tlBytes := rawPacket[totalLostOffset:]
r.TotalLost = uint32(tlBytes[2]) | uint32(tlBytes[1])<<8 | uint32(tlBytes[0])<<16
r.LastSeq = binary.BigEndian.Uint32(rawPacket[lastSeqOffset:])
r.Jitter = binary.BigEndian.Uint32(rawPacket[jitterOffset:])
r.LastSR = binary.BigEndian.Uint32(rawPacket[lastSROffset:])
r.Delay = binary.BigEndian.Uint32(rawPacket[delayOffset:])
return nil
}

86
pkg/rtcp/sender_report.go Normal file
View File

@@ -0,0 +1,86 @@
package rtcp
import "encoding/binary"
// A SenderReport (SR) packet provides reception quality feedback for an RTP stream
type SenderReport struct {
// The synchronization source identifier for the originator of this SR packet.
SSRC uint32
// The wallclock time when this report was sent so that it may be used in
// combination with timestamps returned in reception reports from other
// receivers to measure round-trip propagation to those receivers.
NTPTime uint64
// Corresponds to the same time as the NTP timestamp (above), but in
// the same units and with the same random offset as the RTP
// timestamps in data packets. This correspondence may be used for
// intra- and inter-media synchronization for sources whose NTP
// timestamps are synchronized, and may be used by media-independent
// receivers to estimate the nominal RTP clock frequency.
RTPTime uint32
// The total number of RTP data packets transmitted by the sender
// since starting transmission up until the time this SR packet was
// generated.
PacketCount uint32
// The total number of payload octets (i.e., not including header or
// padding) transmitted in RTP data packets by the sender since
// starting transmission up until the time this SR packet was
// generated.
OctetCount uint32
// Zero or more reception report blocks depending on the number of other
// sources heard by this sender since the last report. Each reception report
// block conveys statistics on the reception of RTP packets from a
// single synchronization source.
Reports []ReceptionReport
}
var (
senderReportLength = 24
ntpTimeOffset = 4
rtpTimeOffset = 12
packetCountOffset = 16
octetCountOffset = 20
)
// Marshal encodes the SenderReport in binary
func (r SenderReport) Marshal() ([]byte, error) {
rawPacket := make([]byte, senderReportLength)
binary.BigEndian.PutUint32(rawPacket, r.SSRC)
binary.BigEndian.PutUint64(rawPacket[ntpTimeOffset:], r.NTPTime)
binary.BigEndian.PutUint32(rawPacket[rtpTimeOffset:], r.RTPTime)
binary.BigEndian.PutUint32(rawPacket[packetCountOffset:], r.PacketCount)
binary.BigEndian.PutUint32(rawPacket[octetCountOffset:], r.OctetCount)
for _, rp := range r.Reports {
data, err := rp.Marshal()
if err != nil {
return nil, err
}
rawPacket = append(rawPacket, data...)
}
return rawPacket, nil
}
// Unmarshal decodes the SenderReport from binary
func (r *SenderReport) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < senderReportLength {
return errPacketTooShort
}
r.SSRC = binary.BigEndian.Uint32(rawPacket)
r.NTPTime = binary.BigEndian.Uint64(rawPacket[ntpTimeOffset:])
r.RTPTime = binary.BigEndian.Uint32(rawPacket[rtpTimeOffset:])
r.PacketCount = binary.BigEndian.Uint32(rawPacket[packetCountOffset:])
r.OctetCount = binary.BigEndian.Uint32(rawPacket[octetCountOffset:])
for i := senderReportLength; i < len(rawPacket); i += receptionReportLength {
var rr ReceptionReport
if err := rr.Unmarshal(rawPacket[i:]); err != nil {
return err
}
r.Reports = append(r.Reports, rr)
}
return nil
}

View File

@@ -0,0 +1,81 @@
package rtcp
import (
"reflect"
"testing"
)
func TestSenderReportUnmarshalNil(t *testing.T) {
var sr SenderReport
err := sr.Unmarshal(nil)
if got, want := err, errPacketTooShort; got != want {
t.Fatalf("unmarshal nil sr: err = %v, want %v", got, want)
}
}
func TestSenderReportRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Report SenderReport
WantError error
}{
{
Name: "valid",
Report: SenderReport{
SSRC: 1,
NTPTime: 999,
RTPTime: 555,
PacketCount: 32,
OctetCount: 11,
Reports: []ReceptionReport{
{
SSRC: 2,
FractionLost: 2,
TotalLost: 3,
LastSeq: 4,
Jitter: 5,
LastSR: 6,
Delay: 7,
},
{
SSRC: 0,
},
},
},
},
{
Name: "also valid",
Report: SenderReport{
SSRC: 2,
Reports: []ReceptionReport{
{
SSRC: 999,
FractionLost: 30,
TotalLost: 12345,
LastSeq: 99,
Jitter: 22,
LastSR: 92,
Delay: 46,
},
},
},
},
} {
data, err := test.Report.Marshal()
if got, want := err, test.WantError; got != want {
t.Fatalf("Marshal %q: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
var decoded SenderReport
if err := decoded.Unmarshal(data); err != nil {
t.Fatalf("Unmarshal %q: %v", test.Name, err)
}
if got, want := decoded, test.Report; !reflect.DeepEqual(got, want) {
t.Fatalf("%q sr round trip: got %#v, want %#v", test.Name, got, want)
}
}
}