Add RTCP Messages

*Avoid a type switch in srtp.go
*Add support for transport layer nack and
    slice loss indication messages
*Add String() functions for several RTCP
    packets to aid in debugging
*Add support for transparent profile
    extensions to ReceiverReport messages

Relates to #119
This commit is contained in:
Woodrow Douglass
2018-12-04 09:11:21 -05:00
committed by Woodrow Douglass
parent 0ef6602adb
commit f62abe50e8
14 changed files with 357 additions and 32 deletions

View File

@@ -56,7 +56,7 @@ func handleRTCP(getBufferTransports func(uint32) *TransportPair, buffer []byte)
//decrypted packets can also be compound packets, so we have to nest our reader loop here.
compoundPacket := rtcp.NewReader(bytes.NewReader(buffer))
for {
header, rawrtcp, err := compoundPacket.ReadPacket()
_, rawrtcp, err := compoundPacket.ReadPacket()
if err != nil {
if err == io.EOF {
@@ -67,7 +67,7 @@ func handleRTCP(getBufferTransports func(uint32) *TransportPair, buffer []byte)
}
var report rtcp.Packet
report, header, err = rtcp.Unmarshal(rawrtcp)
report, _, err = rtcp.Unmarshal(rawrtcp)
if err != nil {
fmt.Println(err)
return
@@ -83,28 +83,9 @@ func handleRTCP(getBufferTransports func(uint32) *TransportPair, buffer []byte)
}
}
switch header.Type {
case rtcp.TypeSenderReport:
for _, ssrc := range report.(*rtcp.SenderReport).Reports {
f(ssrc.SSRC)
}
case rtcp.TypeReceiverReport:
for _, ssrc := range report.(*rtcp.ReceiverReport).Reports {
f(ssrc.SSRC)
}
case rtcp.TypeSourceDescription:
for _, ssrc := range report.(*rtcp.SourceDescription).Chunks {
f(ssrc.Source)
}
case rtcp.TypeGoodbye:
for _, ssrc := range report.(*rtcp.Goodbye).Sources {
for _, ssrc := range report.DestinationSSRC() {
f(ssrc)
}
case rtcp.TypeTransportSpecificFeedback:
f(report.(*rtcp.RapidResynchronizationRequest).MediaSSRC)
case rtcp.TypePayloadSpecificFeedback:
f(report.(*rtcp.PictureLossIndication).MediaSSRC)
}
}
}

View File

@@ -138,3 +138,10 @@ func (g *Goodbye) len() int {
// align to 32-bit boundary
return l + util.GetPadding(l)
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (g *Goodbye) DestinationSSRC() []uint32 {
out := make([]uint32, len(g.Sources))
copy(out, g.Sources)
return out
}

View File

@@ -3,6 +3,8 @@ package rtcp
// Packet represents an RTCP packet, a protocol used for out-of-band statistics and control information for an RTP session
type Packet interface {
Header() Header
// DestinationSSRC returns an array of SSRC values that this packet refers to.
DestinationSSRC() []uint32
Marshal() ([]byte, error)
Unmarshal(rawPacket []byte) error
@@ -32,10 +34,24 @@ func Unmarshal(rawPacket []byte) (Packet, Header, error) {
p = new(Goodbye)
case TypeTransportSpecificFeedback:
switch h.Count {
case tlnFMT:
p = new(TransportLayerNack)
case rrrFMT:
p = new(RapidResynchronizationRequest)
default:
p = new(RawPacket)
}
case TypePayloadSpecificFeedback:
switch h.Count {
case pliFMT:
p = new(PictureLossIndication)
case sliFMT:
p = new(SliceLossIndication)
default:
p = new(RawPacket)
}
default:
p = new(RawPacket)

View File

@@ -2,6 +2,7 @@ package rtcp
import (
"encoding/binary"
"fmt"
)
// The PictureLossIndication packet informs the encoder about the loss of an undefined amount of coded video data belonging to one or more pictures
@@ -76,3 +77,12 @@ func (p *PictureLossIndication) Header() Header {
func (p *PictureLossIndication) len() int {
return headerLength + ssrcLength*2
}
func (p *PictureLossIndication) String() string {
return fmt.Sprintf("PictureLossIndication %x %x", p.SenderSSRC, p.MediaSSRC)
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *PictureLossIndication) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}

View File

@@ -2,6 +2,7 @@ package rtcp
import (
"encoding/binary"
"fmt"
)
// The RapidResynchronizationRequest packet informs the encoder about the loss of an undefined amount of coded video data belonging to one or more pictures
@@ -76,3 +77,12 @@ func (p *RapidResynchronizationRequest) Header() Header {
Length: rrrLength,
}
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *RapidResynchronizationRequest) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}
func (p *RapidResynchronizationRequest) String() string {
return fmt.Sprintf("RapidResynchronizationRequest %x %x", p.SenderSSRC, p.MediaSSRC)
}

View File

@@ -32,3 +32,8 @@ func (r RawPacket) Header() Header {
}
return h
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *RawPacket) DestinationSSRC() []uint32 {
return []uint32{}
}

View File

@@ -96,6 +96,7 @@ func TestUnmarshal(t *testing.T) {
LastSenderReport: 0x9f36432,
Delay: 150137,
}},
ProfileExtensions: []uint8{},
}
if got, want := wantRR, parsed; !reflect.DeepEqual(got, want) {
t.Errorf("Unmarshal rr: got %#v, want %#v", got, want)

View File

@@ -1,6 +1,9 @@
package rtcp
import "encoding/binary"
import (
"encoding/binary"
"fmt"
)
// A ReceiverReport (RR) packet provides reception quality feedback for an RTP stream
type ReceiverReport struct {
@@ -11,6 +14,8 @@ type ReceiverReport struct {
// block conveys statistics on the reception of RTP packets from a
// single synchronization source.
Reports []ReceptionReport
// extra data from the end of the packet; the application can parse this if needed.
ProfileExtensions []byte
}
const (
@@ -67,7 +72,10 @@ func (r ReceiverReport) Marshal() ([]byte, error) {
return nil, errTooManyReports
}
rawPacket = append(rawPacket, r.ProfileExtensions...)
hData, err := r.Header().Marshal()
if err != nil {
return nil, err
}
@@ -128,6 +136,7 @@ func (r *ReceiverReport) Unmarshal(rawPacket []byte) error {
}
r.Reports = append(r.Reports, rr)
}
r.ProfileExtensions = rawPacket[rrReportOffset+(len(r.Reports)*receptionReportLength):]
if uint8(len(r.Reports)) != h.Count {
return errInvalidHeader
@@ -152,3 +161,22 @@ func (r *ReceiverReport) Header() Header {
Length: uint16((r.len() / 4) - 1),
}
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *ReceiverReport) DestinationSSRC() []uint32 {
out := make([]uint32, len(r.Reports))
for i, v := range r.Reports {
out[i] = v.SSRC
}
return out
}
func (r ReceiverReport) String() string {
out := fmt.Sprintf("ReceiverReport from %x\n", r.SSRC)
out += fmt.Sprintf("\tSSRC \tLost\tLastSequence\n")
for _, i := range r.Reports {
out += fmt.Sprintf("\t%x\t%d/%d\t%d\n", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber)
}
out += fmt.Sprintf("\tProfile Extension Data: %v\n", r.ProfileExtensions)
return out
}

View File

@@ -43,6 +43,7 @@ func TestReceiverReportUnmarshal(t *testing.T) {
LastSenderReport: 0x9f36432,
Delay: 150137,
}},
ProfileExtensions: []uint8{},
},
},
{
@@ -79,6 +80,9 @@ func TestReceiverReportUnmarshal(t *testing.T) {
LastSenderReport: 0x9f36432,
Delay: 150137,
}},
ProfileExtensions: []uint8{
0x54, 0x45, 0x53, 0x54,
0x44, 0x41, 0x54, 0x41},
},
},
{
@@ -147,14 +151,14 @@ func TestReceiverReportUnmarshal(t *testing.T) {
var rr ReceiverReport
err := rr.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q rr: err = %v, want %v", test.Name, got, want)
t.Fatalf("Unmarshal %q rr: err = %#v, want %#v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := rr, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q rr: got %v, want %v", test.Name, got, want)
t.Fatalf("Unmarshal %q rr: got %#v, want %#v", test.Name, got, want)
}
}
}
@@ -183,6 +187,7 @@ func TestReceiverReportRoundTrip(t *testing.T) {
SSRC: 0,
},
},
ProfileExtensions: []uint8{},
},
},
{
@@ -200,6 +205,7 @@ func TestReceiverReportRoundTrip(t *testing.T) {
Delay: 46,
},
},
ProfileExtensions: []uint8{},
},
},
{
@@ -223,7 +229,7 @@ func TestReceiverReportRoundTrip(t *testing.T) {
} {
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)
t.Fatalf("Marshal %q: err = %#v, want %#v", test.Name, got, want)
}
if err != nil {
continue
@@ -231,7 +237,7 @@ func TestReceiverReportRoundTrip(t *testing.T) {
var decoded ReceiverReport
if err := decoded.Unmarshal(data); err != nil {
t.Fatalf("Unmarshal %q: %v", test.Name, err)
t.Fatalf("Unmarshal %q: %#v", test.Name, err)
}
if got, want := decoded, test.Report; !reflect.DeepEqual(got, want) {

View File

@@ -31,8 +31,6 @@ type SenderReport struct {
// block conveys statistics on the reception of RTP packets from a
// single synchronization source.
Reports []ReceptionReport
header Header
}
var (
@@ -196,6 +194,15 @@ func (r *SenderReport) Unmarshal(rawPacket []byte) error {
return nil
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *SenderReport) DestinationSSRC() []uint32 {
out := make([]uint32, len(r.Reports))
for i, v := range r.Reports {
out[i] = v.SSRC
}
return out
}
func (r *SenderReport) len() int {
repsLength := 0
for _, rep := range r.Reports {

View File

@@ -0,0 +1,115 @@
package rtcp
import (
"encoding/binary"
"fmt"
"math"
)
// The SliceLossIndication packet informs the encoder about the loss of a picture slice
type SliceLossIndication struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
SLI []struct {
// ID of first lost slice
First uint16
// Number of lost slices
Number uint16
// ID of related picture
Picture uint8
}
}
const (
sliFMT = 2
sliLength = 2
sliOffset = 8
)
// Marshal encodes the SliceLossIndication in binary
func (p SliceLossIndication) Marshal() ([]byte, error) {
if len(p.SLI)+sliLength > math.MaxUint8 {
return nil, errTooManyReports
}
rawPacket := make([]byte, sliOffset+(len(p.SLI)*4))
binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)
binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)
for i := 0; i < len(p.SLI); i++ {
sli := ((uint32(p.SLI[i].First) & 0x1FFF) << 19) |
((uint32(p.SLI[i].Number) & 0x1FFF) << 6) |
(uint32(p.SLI[i].Picture) & 0x3F)
binary.BigEndian.PutUint32(rawPacket[sliOffset+(4*i):], sli)
}
h := Header{
Count: tlnFMT,
Type: TypeTransportSpecificFeedback,
Length: uint16(tlnLength + len(p.SLI)),
}
hData, err := h.Marshal()
if err != nil {
return nil, err
}
return append(hData, rawPacket...), nil
}
// Unmarshal decodes the SliceLossIndication from binary
func (p *SliceLossIndication) Unmarshal(rawPacket []byte) error {
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if len(rawPacket) < (headerLength + int(4*h.Length)) {
return errPacketTooShort
}
if h.Type != TypeTransportSpecificFeedback || h.Count != tlnFMT {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
for i := headerLength + sliOffset; i < (headerLength + int(h.Length*4)); i += 4 {
sli := binary.BigEndian.Uint32(rawPacket[i:])
p.SLI = append(p.SLI, struct {
First uint16
Number uint16
Picture uint8
}{
uint16((sli >> 19) & 0x1FFF),
uint16((sli >> 6) & 0x1FFF),
uint8(sli & 0x3F)})
}
return nil
}
func (p *SliceLossIndication) len() int {
return headerLength + sliOffset + (len(p.SLI) * 4)
}
// Header returns the Header associated with this packet.
func (p *SliceLossIndication) Header() Header {
return Header{
Count: sliFMT,
Type: TypeTransportSpecificFeedback,
Length: uint16((p.len() / 4) - 1),
}
}
func (p *SliceLossIndication) String() string {
return fmt.Sprintf("SliceLossIndication %x %x %+v", p.SenderSSRC, p.MediaSSRC, p.SLI)
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *SliceLossIndication) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}

View File

@@ -332,3 +332,12 @@ func (s *SourceDescriptionItem) Unmarshal(rawPacket []byte) error {
return nil
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (s *SourceDescription) DestinationSSRC() []uint32 {
out := make([]uint32, len(s.Chunks))
for i, v := range s.Chunks {
out[i] = v.Source
}
return out
}

View File

@@ -0,0 +1,114 @@
package rtcp
import (
"encoding/binary"
"fmt"
"math"
)
// The TransportLayerNack packet informs the encoder about the loss of a transport packet
type TransportLayerNack struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
Nacks []struct {
// ID of lost packets
PacketID uint16
// Bitmask of following lost packets
BLP uint16
}
}
const (
tlnFMT = 1
tlnLength = 2
nackOffset = 8
)
// Marshal encodes the TransportLayerNack in binary
func (p TransportLayerNack) Marshal() ([]byte, error) {
if len(p.Nacks)+tlnLength > math.MaxUint8 {
return nil, errTooManyReports
}
rawPacket := make([]byte, nackOffset+(len(p.Nacks)*4))
binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)
binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)
for i := 0; i < len(p.Nacks); i++ {
binary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i):], p.Nacks[i].PacketID)
binary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i)+2:], p.Nacks[i].BLP)
}
h := p.Header()
hData, err := h.Marshal()
if err != nil {
return nil, err
}
return append(hData, rawPacket...), nil
}
// Unmarshal decodes the TransportLayerNack from binary
func (p *TransportLayerNack) Unmarshal(rawPacket []byte) error {
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if len(rawPacket) < (headerLength + int(4*h.Length)) {
return errPacketTooShort
}
if h.Type != TypeTransportSpecificFeedback || h.Count != tlnFMT {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
for i := headerLength + nackOffset; i < (headerLength + int(h.Length*4)); i += 4 {
p.Nacks = append(p.Nacks, struct {
PacketID uint16
BLP uint16
}{
binary.BigEndian.Uint16(rawPacket[i:]),
binary.BigEndian.Uint16(rawPacket[i+2:])})
}
return nil
}
func (p *TransportLayerNack) len() int {
return headerLength + nackOffset + (len(p.Nacks) * 4)
}
// Header returns the Header associated with this packet.
func (p *TransportLayerNack) Header() Header {
return Header{
Count: tlnFMT,
Type: TypeTransportSpecificFeedback,
Length: uint16((p.len() / 4) - 1),
}
}
func (p *TransportLayerNack) String() string {
o := "Packets Lost:\n"
for _, n := range p.Nacks {
b := n.BLP
o += fmt.Sprintf("\t%d\n", n.PacketID)
for i := uint16(0); b != 0; i++ {
if (b & (1 << i)) != 0 {
b &^= (1 << i)
o += fmt.Sprintf("\t%d\n", n.PacketID+i+1)
}
}
}
return o
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *TransportLayerNack) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}

View File

@@ -2,6 +2,7 @@ package rtp
import (
"encoding/binary"
"fmt"
"github.com/pkg/errors"
)
@@ -47,6 +48,21 @@ const (
csrcLength = 4
)
// String helps with debugging by printing packet information in a readable way
func (p Packet) String() string {
out := "RTP PACKET:\n"
out += fmt.Sprintf("\tVersion: %v\n", p.Version)
out += fmt.Sprintf("\tMarker: %v\n", p.Marker)
out += fmt.Sprintf("\tPayload Type: %d\n", p.PayloadType)
out += fmt.Sprintf("\tSequence Number: %d\n", p.SequenceNumber)
out += fmt.Sprintf("\tTimestamp: %d\n", p.Timestamp)
out += fmt.Sprintf("\tSSRC: %d (%x)\n", p.SSRC, p.SSRC)
out += fmt.Sprintf("\tPayload Length: %d\n", len(p.Payload))
return out
}
// Unmarshal parses the passed byte slice and stores the result in the Packet this method is called upon
func (p *Packet) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < headerLength {