diff --git a/internal/network/srtp.go b/internal/network/srtp.go index e8b98b9f..1b5ed71b 100644 --- a/internal/network/srtp.go +++ b/internal/network/srtp.go @@ -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,27 +83,8 @@ 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 { - f(ssrc) - } - case rtcp.TypeTransportSpecificFeedback: - f(report.(*rtcp.RapidResynchronizationRequest).MediaSSRC) - case rtcp.TypePayloadSpecificFeedback: - f(report.(*rtcp.PictureLossIndication).MediaSSRC) + for _, ssrc := range report.DestinationSSRC() { + f(ssrc) } } } diff --git a/pkg/rtcp/goodbye.go b/pkg/rtcp/goodbye.go index 49224011..b77ec62d 100644 --- a/pkg/rtcp/goodbye.go +++ b/pkg/rtcp/goodbye.go @@ -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 +} diff --git a/pkg/rtcp/packet.go b/pkg/rtcp/packet.go index bd09e619..6114ca64 100644 --- a/pkg/rtcp/packet.go +++ b/pkg/rtcp/packet.go @@ -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: - p = new(RapidResynchronizationRequest) + switch h.Count { + case tlnFMT: + p = new(TransportLayerNack) + case rrrFMT: + p = new(RapidResynchronizationRequest) + default: + p = new(RawPacket) + } case TypePayloadSpecificFeedback: - p = new(PictureLossIndication) + switch h.Count { + case pliFMT: + p = new(PictureLossIndication) + case sliFMT: + p = new(SliceLossIndication) + default: + p = new(RawPacket) + } default: p = new(RawPacket) diff --git a/pkg/rtcp/picture_loss_indication.go b/pkg/rtcp/picture_loss_indication.go index 32a89f72..0910ece4 100644 --- a/pkg/rtcp/picture_loss_indication.go +++ b/pkg/rtcp/picture_loss_indication.go @@ -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} +} diff --git a/pkg/rtcp/rapid_resynchronization_request.go b/pkg/rtcp/rapid_resynchronization_request.go index a4337358..bca32615 100644 --- a/pkg/rtcp/rapid_resynchronization_request.go +++ b/pkg/rtcp/rapid_resynchronization_request.go @@ -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) +} diff --git a/pkg/rtcp/raw_packet.go b/pkg/rtcp/raw_packet.go index 304542d0..8412ea7e 100644 --- a/pkg/rtcp/raw_packet.go +++ b/pkg/rtcp/raw_packet.go @@ -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{} +} diff --git a/pkg/rtcp/reader_test.go b/pkg/rtcp/reader_test.go index ed3a92cb..3314deff 100644 --- a/pkg/rtcp/reader_test.go +++ b/pkg/rtcp/reader_test.go @@ -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) diff --git a/pkg/rtcp/receiver_report.go b/pkg/rtcp/receiver_report.go index 6cb30ed5..4abd1613 100644 --- a/pkg/rtcp/receiver_report.go +++ b/pkg/rtcp/receiver_report.go @@ -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 +} diff --git a/pkg/rtcp/receiver_report_test.go b/pkg/rtcp/receiver_report_test.go index 3cb7845e..3074ced6 100644 --- a/pkg/rtcp/receiver_report_test.go +++ b/pkg/rtcp/receiver_report_test.go @@ -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) { diff --git a/pkg/rtcp/sender_report.go b/pkg/rtcp/sender_report.go index c2688ef4..2cf51e0e 100644 --- a/pkg/rtcp/sender_report.go +++ b/pkg/rtcp/sender_report.go @@ -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 { diff --git a/pkg/rtcp/slice_loss_indication.go b/pkg/rtcp/slice_loss_indication.go new file mode 100644 index 00000000..f0d10dbc --- /dev/null +++ b/pkg/rtcp/slice_loss_indication.go @@ -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} +} diff --git a/pkg/rtcp/source_description.go b/pkg/rtcp/source_description.go index c0815444..3217b129 100644 --- a/pkg/rtcp/source_description.go +++ b/pkg/rtcp/source_description.go @@ -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 +} diff --git a/pkg/rtcp/transport_layer_nack.go b/pkg/rtcp/transport_layer_nack.go new file mode 100644 index 00000000..91d0596b --- /dev/null +++ b/pkg/rtcp/transport_layer_nack.go @@ -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} +} diff --git a/pkg/rtp/packet.go b/pkg/rtp/packet.go index e9f28b2f..90945f76 100644 --- a/pkg/rtp/packet.go +++ b/pkg/rtp/packet.go @@ -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 {