Export RTP/RTCP to unique packages

Resolves #272
This commit is contained in:
Sean DuBois
2019-01-27 23:03:04 -08:00
parent 2b2c09fc3e
commit 2863555984
55 changed files with 32 additions and 4530 deletions

View File

@@ -9,7 +9,6 @@ linters:
disable:
- maligned
- lll
- dupl
- gocritic
- gochecknoinits
- gochecknoglobals

View File

@@ -4,11 +4,11 @@ import (
"fmt"
"time"
"github.com/pions/rtcp"
"github.com/pions/webrtc"
"github.com/pions/webrtc/examples/util"
gst "github.com/pions/webrtc/examples/util/gstreamer-sink"
"github.com/pions/webrtc/pkg/ice"
"github.com/pions/webrtc/pkg/rtcp"
)
func main() {

View File

@@ -4,11 +4,11 @@ import (
"fmt"
"time"
"github.com/pions/rtcp"
"github.com/pions/webrtc"
"github.com/pions/webrtc/examples/util"
"github.com/pions/webrtc/pkg/ice"
"github.com/pions/webrtc/pkg/media/ivfwriter"
"github.com/pions/webrtc/pkg/rtcp"
)
func main() {

View File

@@ -7,10 +7,10 @@ import (
"sync"
"time"
"github.com/pions/rtcp"
"github.com/pions/rtp"
"github.com/pions/webrtc"
"github.com/pions/webrtc/examples/util"
"github.com/pions/webrtc/pkg/rtcp"
"github.com/pions/webrtc/pkg/rtp"
)
var peerConnectionConfig = webrtc.RTCConfiguration{

6
go.mod
View File

@@ -3,9 +3,12 @@ module github.com/pions/webrtc
require (
github.com/pions/datachannel v1.2.0
github.com/pions/dtls v1.1.0
github.com/pions/rtcp v1.0.0
github.com/pions/rtp v1.0.0
github.com/pions/sctp v1.3.0
github.com/pions/sdp v1.3.0
github.com/pions/stun v0.1.0
github.com/pions/srtp v1.0.1
github.com/pions/stun v0.2.0
github.com/pions/transport v0.1.0
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.3.0
@@ -19,5 +22,4 @@ require (
github.com/marten-seemann/qtls v0.0.0-20190110111248-7a090b30c3ab // indirect
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/pions/srtp v1.0.0
)

12
go.sum
View File

@@ -25,14 +25,18 @@ github.com/pions/datachannel v1.2.0 h1:N12qhHSRVlgBcaal2Hi4skdz7VI4yz6bNC5IJDMzC
github.com/pions/datachannel v1.2.0/go.mod h1:MKPEKJRwX/a9/tyQvcVTUI9szyf8ZuUyZxSA9AVMSro=
github.com/pions/dtls v1.1.0 h1:NR/F/+gifVWvFOX/v2rHkIUpJ0UOISAyiS53d19nimU=
github.com/pions/dtls v1.1.0/go.mod h1:OgJcO0SqrDdQzqkCTdAp4xCQlbCmwZtGyhbthbq9zIA=
github.com/pions/rtcp v1.0.0 h1:kYGe6RegZ63yVDkqXaru1+kHZAqHEufP3zfRAGKPycI=
github.com/pions/rtcp v1.0.0/go.mod h1:Q5twXlqiz775Yn37X0cl4lAsfSk8EiHgeNkte59jBY4=
github.com/pions/rtp v1.0.0 h1:H/TUg7bhgBT/mQsUx0adW3cmgwqPmygoYbbRTc3Y7Ek=
github.com/pions/rtp v1.0.0/go.mod h1:GDIt4UYlSz7za4vfaLqihGJJ+yLvgPshnqrF/lm3vcM=
github.com/pions/sctp v1.3.0 h1:FSoWK1yFhNHj7ZLfGw0+h0/YOXqkMR9C58lz59uiTMY=
github.com/pions/sctp v1.3.0/go.mod h1:GZTG/xApE7wdUFEQq2Rmzgxl/+YaB/L1k8xUl1D5bmo=
github.com/pions/sdp v1.3.0 h1:HIv6ZvdzRPq+H4T8EV8K9pZ4EGnYOr14HFZ9Rksh6/0=
github.com/pions/sdp v1.3.0/go.mod h1:moNMmnVSlx8rBBb39U9t0Rdr7xvMlqiJjHlMESRad5k=
github.com/pions/srtp v1.0.0 h1:o6z6IN0fqctOSGWTLvl4PRwlAUxVFeRsOhDFspgXRq4=
github.com/pions/srtp v1.0.0/go.mod h1:9Bd1FxrpHN0kvbV6qjL3HHnixqnSZ4ZNpEXEXP9/wM8=
github.com/pions/stun v0.1.0 h1:q2Sh13EV5tGGAAm9BkCemO9SAAIjWAFf6qijwpMOfA4=
github.com/pions/stun v0.1.0/go.mod h1:NW0iSMbGXxYmO5/g6m73x8UlUoZbKN5pyraXOMonyS0=
github.com/pions/srtp v1.0.1 h1:4yjEChdlRYDP46Mc7NqvzQX+DHlJaH9cu627jNbCCVw=
github.com/pions/srtp v1.0.1/go.mod h1:egXe0STDyQDXLm7hjOMzuk7rkAhJ1SHOx+tTgtw/cQs=
github.com/pions/stun v0.2.0 h1:spIzpfkEg6HV+2iIo6qeOsAjtadZKzbXbrd2e9ZCCcs=
github.com/pions/stun v0.2.0/go.mod h1:rMdCIsqqnTLC4MOHJE3LNiFQRfIjUDzI1kzx//7oPOM=
github.com/pions/transport v0.0.0-20190110151433-e7cbf7d5f464/go.mod h1:HLhzI7I0k8TyiQ99hfRZNRf84lG76eaFnZHnVy/wFnM=
github.com/pions/transport v0.1.0 h1:9IEn3i8pmK8rMyQIqhT2RozgXJNH4k+IuNDzV5y+ddw=
github.com/pions/transport v0.1.0/go.mod h1:HLhzI7I0k8TyiQ99hfRZNRf84lG76eaFnZHnVy/wFnM=

View File

@@ -15,11 +15,3 @@ func RandSeq(n int) string {
}
return string(b)
}
// GetPadding Returns the padding required to make the length a multiple of 4
func GetPadding(len int) int {
if len%4 == 0 {
return 0
}
return 4 - (len % 4)
}

View File

@@ -3,8 +3,6 @@ package util
import (
"regexp"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRandSeq(t *testing.T) {
@@ -17,24 +15,3 @@ func TestRandSeq(t *testing.T) {
t.Errorf("RandSeq should be AlphaNumeric only")
}
}
func TestGetPadding(t *testing.T) {
assert := assert.New(t)
type testCase struct {
input int
result int
}
cases := []testCase{
{input: 0, result: 0},
{input: 1, result: 3},
{input: 2, result: 2},
{input: 3, result: 1},
{input: 4, result: 0},
{input: 100, result: 0},
{input: 500, result: 0},
}
for _, testCase := range cases {
assert.Equalf(GetPadding(testCase.input), testCase.result, "Test case returned wrong value for input %d", testCase.input)
}
}

View File

@@ -3,9 +3,9 @@ package webrtc
import (
"strconv"
"github.com/pions/rtp"
"github.com/pions/rtp/codecs"
"github.com/pions/sdp"
"github.com/pions/webrtc/pkg/rtp"
"github.com/pions/webrtc/pkg/rtp/codecs"
)
// PayloadTypes for the default codecs

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"os"
"github.com/pions/webrtc/pkg/rtp"
"github.com/pions/webrtc/pkg/rtp/codecs"
"github.com/pions/rtp"
"github.com/pions/rtp/codecs"
)
// IVFWriter is used to take RTP packets and write them to an IVF on disk

View File

@@ -1,8 +1,8 @@
package samplebuilder
import (
"github.com/pions/rtp"
"github.com/pions/webrtc/pkg/media"
"github.com/pions/webrtc/pkg/rtp"
)
// SampleBuilder contains all packets

View File

@@ -3,8 +3,8 @@ package samplebuilder
import (
"testing"
"github.com/pions/rtp"
"github.com/pions/webrtc/pkg/media"
"github.com/pions/webrtc/pkg/rtp"
"github.com/stretchr/testify/assert"
)

View File

@@ -1,17 +0,0 @@
package rtcp
import "errors"
var (
errInvalidTotalLost = errors.New("rtcp: invalid total lost count")
errInvalidHeader = errors.New("rtcp: invalid header")
errTooManyReports = errors.New("rtcp: too many reports")
errTooManyChunks = errors.New("rtcp: too many chunks")
errTooManySources = errors.New("rtcp: too many sources")
errPacketTooShort = errors.New("rtcp: packet too short")
errWrongType = errors.New("rtcp: wrong packet type")
errSDESTextTooLong = errors.New("rtcp: sdes must be < 255 octets long")
errSDESMissingType = errors.New("rtcp: sdes item missing type")
errReasonTooLong = errors.New("rtcp: reason must be < 255 octets long")
errBadVersion = errors.New("rtcp: invalid packet version")
)

View File

@@ -1,51 +0,0 @@
// +build gofuzz
package rtcp
import (
"bytes"
"io"
)
// Fuzz implements a randomized fuzz test of the rtcp
// parser using go-fuzz.
//
// To run the fuzzer, first download go-fuzz:
// `go get github.com/dvyukov/go-fuzz/...`
//
// Then build the testing package:
// `go-fuzz-build github.com/pions/webrtc`
//
// And run the fuzzer on the corpus:
// ```
// mkdir workdir
//
// # optionally add a starter corpus of valid rtcp packets.
// # the corpus should be as compact and diverse as possible.
// cp -r ~/my-rtcp-packets workdir/corpus
//
// go-fuzz -bin=ase-fuzz.zip -workdir=workdir
// ````
func Fuzz(data []byte) int {
r := NewReader(bytes.NewReader(data))
for {
_, data, err := r.ReadPacket()
if err == io.EOF {
break
}
if err != nil {
return 0
}
packet, _, err := Unmarshal(data)
if err != nil {
return 0
}
if _, err := packet.Marshal(); err != nil {
return 0
}
}
return 1
}

View File

@@ -1,147 +0,0 @@
package rtcp
import (
"encoding/binary"
"github.com/pions/webrtc/internal/util"
)
// The Goodbye packet indicates that one or more sources are no longer active.
type Goodbye struct {
// The SSRC/CSRC identifiers that are no longer active
Sources []uint32
// Optional text indicating the reason for leaving, e.g., "camera malfunction" or "RTP loop detected"
Reason string
}
// Marshal encodes the Goodbye packet in binary
func (g Goodbye) 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P| SC | PT=BYE=203 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC/CSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* (opt) | length | reason for leaving ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, g.len())
packetBody := rawPacket[headerLength:]
if len(g.Sources) > countMax {
return nil, errTooManySources
}
for i, s := range g.Sources {
binary.BigEndian.PutUint32(packetBody[i*ssrcLength:], s)
}
if g.Reason != "" {
reason := []byte(g.Reason)
if len(reason) > sdesMaxOctetCount {
return nil, errReasonTooLong
}
reasonOffset := len(g.Sources) * ssrcLength
packetBody[reasonOffset] = uint8(len(reason))
copy(packetBody[reasonOffset+1:], reason)
}
hData, err := g.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the Goodbye packet from binary
func (g *Goodbye) Unmarshal(rawPacket []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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P| SC | PT=BYE=203 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC/CSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* (opt) | length | reason for leaving ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
var header Header
if err := header.Unmarshal(rawPacket); err != nil {
return err
}
if header.Type != TypeGoodbye {
return errWrongType
}
if util.GetPadding(len(rawPacket)) != 0 {
return errPacketTooShort
}
g.Sources = make([]uint32, header.Count)
reasonOffset := int(headerLength + header.Count*ssrcLength)
if reasonOffset > len(rawPacket) {
return errPacketTooShort
}
for i := 0; i < int(header.Count); i++ {
offset := headerLength + i*ssrcLength
g.Sources[i] = binary.BigEndian.Uint32(rawPacket[offset:])
}
if reasonOffset < len(rawPacket) {
reasonLen := int(rawPacket[reasonOffset])
reasonEnd := reasonOffset + 1 + reasonLen
if reasonEnd > len(rawPacket) {
return errPacketTooShort
}
g.Reason = string(rawPacket[reasonOffset+1 : reasonEnd])
}
return nil
}
// Header returns the Header associated with this packet.
func (g *Goodbye) Header() Header {
return Header{
Padding: false,
Count: uint8(len(g.Sources)),
Type: TypeGoodbye,
Length: uint16((g.len() / 4) - 1),
}
}
func (g *Goodbye) len() int {
srcsLength := len(g.Sources) * ssrcLength
reasonLength := len(g.Reason) + 1
l := headerLength + srcsLength + reasonLength
// 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

@@ -1,207 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestGoodbyeUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want Goodbye
WantError error
}{
{
Name: "valid",
Data: []byte{
// v=2, p=0, count=1, BYE, len=12
0x81, 0xcb, 0x00, 0x0c,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// len=3, text=FOO
0x03, 0x46, 0x4f, 0x4f,
},
Want: Goodbye{
Sources: []uint32{0x902f9e2e},
Reason: "FOO",
},
},
{
Name: "invalid octet count",
Data: []byte{
// v=2, p=0, count=1, BYE, len=12
0x81, 0xcb, 0x00, 0x0c,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// len=4, text=FOO
0x04, 0x46, 0x4f, 0x4f,
},
WantError: errPacketTooShort,
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, count=1, SDES, len=12
0x81, 0xca, 0x00, 0x0c,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// len=3, text=FOO
0x03, 0x46, 0x4f, 0x4f,
},
WantError: errWrongType,
},
{
Name: "short reason",
Data: []byte{
// v=2, p=0, count=1, BYE, len=12
0x81, 0xcb, 0x00, 0x0c,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// len=3, text=F + padding
0x01, 0x46, 0x00, 0x00,
},
Want: Goodbye{
Sources: []uint32{0x902f9e2e},
Reason: "F",
},
},
{
Name: "not byte aligned",
Data: []byte{
// v=2, p=0, count=1, BYE, len=10
0x81, 0xcb, 0x00, 0x0a,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// len=1, text=F
0x01, 0x46,
},
WantError: errPacketTooShort,
},
{
Name: "bad count in header",
Data: []byte{
// v=2, p=0, count=2, BYE, len=8
0x82, 0xcb, 0x00, 0x0c,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
},
WantError: errPacketTooShort,
},
{
Name: "empty packet",
Data: []byte{
// v=2, p=0, count=0, BYE, len=4
0x80, 0xcb, 0x00, 0x04,
},
Want: Goodbye{
Sources: []uint32{},
Reason: "",
},
},
{
Name: "nil",
Data: nil,
WantError: errInvalidHeader,
},
} {
var bye Goodbye
err := bye.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q bye: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := bye, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q bye: got %v, want %v", test.Name, got, want)
}
}
}
func TestGoodbyeRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Bye Goodbye
WantError error
}{
{
Name: "empty",
Bye: Goodbye{
Sources: []uint32{},
},
},
{
Name: "valid",
Bye: Goodbye{
Sources: []uint32{
0x01020304,
0x05060708,
},
Reason: "because",
},
},
{
Name: "empty reason",
Bye: Goodbye{
Sources: []uint32{0x01020304},
Reason: "",
},
},
{
Name: "reason no source",
Bye: Goodbye{
Sources: []uint32{},
Reason: "foo",
},
},
{
Name: "short reason",
Bye: Goodbye{
Sources: []uint32{},
Reason: "f",
},
},
{
Name: "count overflow",
Bye: Goodbye{
Sources: tooManySources,
},
WantError: errTooManySources,
},
{
Name: "reason too long",
Bye: Goodbye{
Sources: []uint32{},
Reason: tooLongText,
},
WantError: errReasonTooLong,
},
} {
data, err := test.Bye.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 bye Goodbye
if err := bye.Unmarshal(data); err != nil {
t.Fatalf("Unmarshal %q: %v", test.Name, err)
}
if got, want := bye, test.Bye; !reflect.DeepEqual(got, want) {
t.Fatalf("%q sdes round trip: got %#v, want %#v", test.Name, got, want)
}
}
}
// a slice with enough sources to overflow an 5-bit int
var tooManySources []uint32
func init() {
for i := 0; i < (1 << 5); i++ {
tooManySources = append(tooManySources, 0x00)
}
}

View File

@@ -1,135 +0,0 @@
package rtcp
import (
"encoding/binary"
)
// PacketType specifies the type of an RTCP packet
type PacketType uint8
// RTCP packet types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4
const (
TypeSenderReport PacketType = 200 // RFC 3550, 6.4.1
TypeReceiverReport PacketType = 201 // RFC 3550, 6.4.2
TypeSourceDescription PacketType = 202 // RFC 3550, 6.5
TypeGoodbye PacketType = 203 // RFC 3550, 6.6
TypeApplicationDefined PacketType = 204 // RFC 3550, 6.7 (unimplemented)
TypeTransportSpecificFeedback PacketType = 205 // RFC 4585, 6051
TypePayloadSpecificFeedback PacketType = 206 // RFC 4585, 6.3
)
// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
const (
FormatSLI uint8 = 2
FormatPLI uint8 = 1
FormatTLN uint8 = 1
FormatRRR uint8 = 5
)
func (p PacketType) String() string {
switch p {
case TypeSenderReport:
return "SR"
case TypeReceiverReport:
return "RR"
case TypeSourceDescription:
return "SDES"
case TypeGoodbye:
return "BYE"
case TypeApplicationDefined:
return "APP"
case TypeTransportSpecificFeedback:
return "TSFB"
case TypePayloadSpecificFeedback:
return "PSFB"
default:
return string(p)
}
}
const rtpVersion = 2
// A Header is the common header shared by all RTCP packets
type Header struct {
// If the padding bit is set, this individual RTCP packet contains
// some additional padding octets at the end which are not part of
// the control information but are included in the length field.
Padding bool
// The number of reception reports, sources contained or FMT in this packet (depending on the Type)
Count uint8
// The RTCP packet type for this packet
Type PacketType
// The length of this RTCP packet in 32-bit words minus one,
// including the header and any padding.
Length uint16
}
const (
headerLength = 4
versionShift = 6
versionMask = 0x3
paddingShift = 5
paddingMask = 0x1
countShift = 0
countMask = 0x1f
countMax = (1 << 5) - 1
)
// Marshal encodes the Header in binary
func (h Header) 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P| RC | PT=SR=200 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, headerLength)
rawPacket[0] |= rtpVersion << versionShift
if h.Padding {
rawPacket[0] |= 1 << paddingShift
}
if h.Count > 31 {
return nil, errInvalidHeader
}
rawPacket[0] |= h.Count << countShift
rawPacket[1] = uint8(h.Type)
binary.BigEndian.PutUint16(rawPacket[2:], h.Length)
return rawPacket, nil
}
// Unmarshal decodes the Header from binary
func (h *Header) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < headerLength {
return errInvalidHeader
}
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P| RC | PT | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
version := rawPacket[0] >> versionShift & versionMask
if version != rtpVersion {
return errBadVersion
}
h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0
h.Count = rawPacket[0] >> countShift & countMask
h.Type = PacketType(rawPacket[1])
h.Length = binary.BigEndian.Uint16(rawPacket[2:])
return nil
}

View File

@@ -1,113 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestHeaderUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want Header
WantError error
}{
{
Name: "valid",
Data: []byte{
// v=2, p=0, count=1, RR, len=7
0x81, 0xc9, 0x00, 0x07,
},
Want: Header{
Padding: false,
Count: 1,
Type: TypeReceiverReport,
Length: 7,
},
},
{
Name: "also valid",
Data: []byte{
// v=2, p=1, count=1, BYE, len=7
0xa1, 0xcc, 0x00, 0x07,
},
Want: Header{
Padding: true,
Count: 1,
Type: TypeApplicationDefined,
Length: 7,
},
},
{
Name: "bad version",
Data: []byte{
// v=0, p=0, count=0, RR, len=4
0x00, 0xc9, 0x00, 0x04,
},
WantError: errBadVersion,
},
} {
var h Header
err := h.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q header: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := h, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q header: got %v, want %v", test.Name, got, want)
}
}
}
func TestHeaderRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Header Header
WantError error
}{
{
Name: "valid",
Header: Header{
Padding: true,
Count: 31,
Type: TypeSenderReport,
Length: 4,
},
},
{
Name: "also valid",
Header: Header{
Padding: false,
Count: 28,
Type: TypeReceiverReport,
Length: 65535,
},
},
{
Name: "invalid count",
Header: Header{
Count: 40,
},
WantError: errInvalidHeader,
},
} {
data, err := test.Header.Marshal()
if got, want := err, test.WantError; got != want {
t.Errorf("Marshal %q: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
var decoded Header
if err := decoded.Unmarshal(data); err != nil {
t.Errorf("Unmarshal %q: %v", test.Name, err)
}
if got, want := decoded, test.Header; !reflect.DeepEqual(got, want) {
t.Errorf("%q header round trip: got %#v, want %#v", test.Name, got, want)
}
}
}

View File

@@ -1,62 +0,0 @@
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
}
// Unmarshal is a factory a polymorphic RTCP packet, and its header,
func Unmarshal(rawPacket []byte) (Packet, Header, error) {
var h Header
var p Packet
err := h.Unmarshal(rawPacket)
if err != nil {
return nil, h, err
}
switch h.Type {
case TypeSenderReport:
p = new(SenderReport)
case TypeReceiverReport:
p = new(ReceiverReport)
case TypeSourceDescription:
p = new(SourceDescription)
case TypeGoodbye:
p = new(Goodbye)
case TypeTransportSpecificFeedback:
switch h.Count {
case FormatTLN:
p = new(TransportLayerNack)
case FormatRRR:
p = new(RapidResynchronizationRequest)
default:
p = new(RawPacket)
}
case TypePayloadSpecificFeedback:
switch h.Count {
case FormatPLI:
p = new(PictureLossIndication)
case FormatSLI:
p = new(SliceLossIndication)
default:
p = new(RawPacket)
}
default:
p = new(RawPacket)
}
err = p.Unmarshal(rawPacket)
return p, h, err
}

View File

@@ -1,87 +0,0 @@
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
type PictureLossIndication struct {
// SSRC of sender
SenderSSRC uint32
// SSRC where the loss was experienced
MediaSSRC uint32
header Header
}
const (
pliLength = 2
)
// Marshal encodes the PictureLossIndication in binary
func (p PictureLossIndication) Marshal() ([]byte, error) {
/*
* PLI does not require parameters. Therefore, the length field MUST be
* 2, and there MUST NOT be any Feedback Control Information.
*
* The semantics of this FB message is independent of the payload type.
*/
rawPacket := make([]byte, p.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody, p.SenderSSRC)
binary.BigEndian.PutUint32(packetBody[4:], p.MediaSSRC)
h := Header{
Count: FormatPLI,
Type: TypePayloadSpecificFeedback,
Length: pliLength,
}
hData, err := h.Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the PictureLossIndication from binary
func (p *PictureLossIndication) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < (headerLength + (ssrcLength * 2)) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypePayloadSpecificFeedback || h.Count != FormatPLI {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
return nil
}
// Header returns the Header associated with this packet.
func (p *PictureLossIndication) Header() Header {
return p.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

@@ -1,124 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestPictureLossIndicationUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want PictureLossIndication
WantError error
}{
{
Name: "valid",
Data: []byte{
// v=2, p=0, FMT=1, PSFB, len=1
0x81, 0xce, 0x00, 0x02,
// ssrc=0x0
0x00, 0x00, 0x00, 0x00,
// ssrc=0x4bc4fcb4
0x4b, 0xc4, 0xfc, 0xb4,
},
Want: PictureLossIndication{
SenderSSRC: 0x0,
MediaSSRC: 0x4bc4fcb4,
},
},
{
Name: "packet too short",
Data: []byte{
0x00, 0x00, 0x00, 0x00,
},
WantError: errPacketTooShort,
},
{
Name: "invalid header",
Data: []byte{
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
},
WantError: errBadVersion,
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, FMT=1, RR, len=1
0x81, 0xc9, 0x00, 0x02,
// ssrc=0x0
0x00, 0x00, 0x00, 0x00,
// ssrc=0x4bc4fcb4
0x4b, 0xc4, 0xfc, 0xb4,
},
WantError: errWrongType,
},
{
Name: "wrong fmt",
Data: []byte{
// v=2, p=0, FMT=2, RR, len=1
0x82, 0xc9, 0x00, 0x02,
// ssrc=0x0
0x00, 0x00, 0x00, 0x00,
// ssrc=0x4bc4fcb4
0x4b, 0xc4, 0xfc, 0xb4,
},
WantError: errWrongType,
},
} {
var pli PictureLossIndication
err := pli.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q rr: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := pli, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q rr: got %v, want %v", test.Name, got, want)
}
}
}
func TestPictureLossIndicationRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Packet PictureLossIndication
WantError error
}{
{
Name: "valid",
Packet: PictureLossIndication{
SenderSSRC: 1,
MediaSSRC: 2,
},
},
{
Name: "also valid",
Packet: PictureLossIndication{
SenderSSRC: 5000,
MediaSSRC: 6000,
},
},
} {
data, err := test.Packet.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 PictureLossIndication
if err := decoded.Unmarshal(data); err != nil {
t.Fatalf("Unmarshal %q: %v", test.Name, err)
}
if got, want := decoded, test.Packet; !reflect.DeepEqual(got, want) {
t.Fatalf("%q rr round trip: got %#v, want %#v", test.Name, got, want)
}
}
}

View File

@@ -1,87 +0,0 @@
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
type RapidResynchronizationRequest struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
}
const (
rrrLength = 2
rrrHeaderLength = ssrcLength * 2
rrrMediaOffset = 4
)
// Marshal encodes the RapidResynchronizationRequest in binary
func (p RapidResynchronizationRequest) Marshal() ([]byte, error) {
/*
* RRR does not require parameters. Therefore, the length field MUST be
* 2, and there MUST NOT be any Feedback Control Information.
*
* The semantics of this FB message is independent of the payload type.
*/
rawPacket := make([]byte, p.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody, p.SenderSSRC)
binary.BigEndian.PutUint32(packetBody[rrrMediaOffset:], p.MediaSSRC)
hData, err := p.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the RapidResynchronizationRequest from binary
func (p *RapidResynchronizationRequest) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < (headerLength + (ssrcLength * 2)) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeTransportSpecificFeedback || h.Count != FormatRRR {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
return nil
}
func (p *RapidResynchronizationRequest) len() int {
return headerLength + rrrHeaderLength
}
// Header returns the Header associated with this packet.
func (p *RapidResynchronizationRequest) Header() Header {
return Header{
Count: FormatRRR,
Type: TypeTransportSpecificFeedback,
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

@@ -1,114 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestRapidResynchronizationRequestUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want RapidResynchronizationRequest
WantError error
}{
{
Name: "valid",
Data: []byte{
// RapidResynchronizationRequest
0x85, 0xcd, 0x0, 0x2,
// sender=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// media=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
},
Want: RapidResynchronizationRequest{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
},
},
{
Name: "short report",
Data: []byte{
0x85, 0xcd, 0x0, 0x2,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// report ends early
},
WantError: errPacketTooShort,
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, count=1, SR, len=7
0x81, 0xc8, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
WantError: errWrongType,
},
{
Name: "nil",
Data: nil,
WantError: errPacketTooShort,
},
} {
var rrr RapidResynchronizationRequest
err := rrr.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q rr: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := rrr, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q rr: got %v, want %v", test.Name, got, want)
}
}
}
func TestRapidResynchronizationRequestRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Report RapidResynchronizationRequest
WantError error
}{
{
Name: "valid",
Report: RapidResynchronizationRequest{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
},
},
} {
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 RapidResynchronizationRequest
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 rrr round trip: got %#v, want %#v", test.Name, got, want)
}
}
}

View File

@@ -1,35 +0,0 @@
package rtcp
// RawPacket represents an unparsed RTCP packet. It's returned by Unmarshal when
// a packet with an unknown type is encountered.
type RawPacket []byte
// Marshal encodes the packet in binary.
func (r RawPacket) Marshal() ([]byte, error) {
return r, nil
}
// Unmarshal decodes the packet from binary.
func (r *RawPacket) Unmarshal(b []byte) error {
if len(b) < (headerLength) {
return errPacketTooShort
}
*r = b
var h Header
return h.Unmarshal(b)
}
// Header returns the Header associated with this packet.
func (r RawPacket) Header() Header {
var h Header
if err := h.Unmarshal(r); err != nil {
return Header{}
}
return h
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *RawPacket) DestinationSSRC() []uint32 {
return []uint32{}
}

View File

@@ -1,61 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestRawPacketRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Packet RawPacket
WantMarshalError error
WantUnmarshalError error
}{
{
Name: "valid",
Packet: RawPacket([]byte{
// v=2, p=0, count=1, BYE, len=12
0x81, 0xcb, 0x00, 0x0c,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// len=3, text=FOO
0x03, 0x46, 0x4f, 0x4f,
}),
},
{
Name: "short header",
Packet: RawPacket([]byte{0x00}),
WantUnmarshalError: errPacketTooShort,
},
{
Name: "invalid header",
Packet: RawPacket([]byte{
// v=0, p=0, count=0, RR, len=4
0x00, 0xc9, 0x00, 0x04,
}),
WantUnmarshalError: errBadVersion,
},
} {
data, err := test.Packet.Marshal()
if got, want := err, test.WantMarshalError; got != want {
t.Fatalf("Marshal %q: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
var decoded RawPacket
err = decoded.Unmarshal(data)
if got, want := err, test.WantUnmarshalError; got != want {
t.Fatalf("Unmarshal %q: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := decoded, test.Packet; !reflect.DeepEqual(got, want) {
t.Fatalf("%q raw round trip: got %#v, want %#v", test.Name, got, want)
}
}
}

View File

@@ -1,41 +0,0 @@
package rtcp
import "io"
// A Reader reads packets from an RTCP combined packet.
type Reader struct {
r io.Reader
}
// NewReader creates a new Reader reading from r.
func NewReader(r io.Reader) *Reader {
return &Reader{r}
}
// ReadPacket reads one packet from r.
//
// It returns the parsed packet Header and a byte slice containing the encoded
// packet data (including the header). How the packet data is parsed depends on
// the Type field contained in the Header.
func (r *Reader) ReadPacket() (header Header, data []byte, err error) {
// First grab the header
headerBuf := make([]byte, headerLength)
if _, err := io.ReadFull(r.r, headerBuf); err != nil {
return header, data, err
}
if err := header.Unmarshal(headerBuf); err != nil {
return header, data, err
}
packetLen := (header.Length + 1) * 4
// Then grab the rest
bodyBuf := make([]byte, packetLen-headerLength)
if _, err := io.ReadFull(r.r, bodyBuf); err != nil {
return header, data, err
}
data = append(headerBuf, bodyBuf...)
return header, data, nil
}

View File

@@ -1,204 +0,0 @@
package rtcp
import (
"bytes"
"io"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
// An RTCP packet from a packet dump
var realPacket = []byte{
// Receiver Report (offset=0)
// v=2, p=0, count=1, RR, len=7
0x81, 0xc9, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
// Source Description (offset=32)
// v=2, p=0, count=1, SDES, len=12
0x81, 0xca, 0x0, 0xc,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// CNAME, len=38
0x1, 0x26,
// text="{9c00eb92-1afb-9d49-a47d-91f64eee69f5}"
0x7b, 0x39, 0x63, 0x30,
0x30, 0x65, 0x62, 0x39,
0x32, 0x2d, 0x31, 0x61,
0x66, 0x62, 0x2d, 0x39,
0x64, 0x34, 0x39, 0x2d,
0x61, 0x34, 0x37, 0x64,
0x2d, 0x39, 0x31, 0x66,
0x36, 0x34, 0x65, 0x65,
0x65, 0x36, 0x39, 0x66,
0x35, 0x7d,
// END + padding
0x0, 0x0, 0x0, 0x0,
// Goodbye (offset=84)
// v=2, p=0, count=1, BYE, len=1
0x81, 0xcb, 0x0, 0x1,
// source=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// Picture Loss Indication (offset=92)
0x81, 0xce, 0x0, 0x2,
// sender=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// media=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// RapidResynchronizationRequest (offset=104)
0x85, 0xcd, 0x0, 0x2,
// sender=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// media=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
}
func TestUnmarshal(t *testing.T) {
r := NewReader(bytes.NewReader(realPacket))
// ReceiverReport
_, packet, err := r.ReadPacket()
if err != nil {
t.Fatalf("Read rr: %v", err)
}
var parsed Packet
if parsed, _, err = Unmarshal(packet); err != nil {
t.Errorf("Unmarshal parsed: %v", err)
}
assert.IsType(t, parsed, (*ReceiverReport)(nil), "Unmarshalled to incorrect type")
wantRR := &ReceiverReport{
SSRC: 0x902f9e2e,
Reports: []ReceptionReport{{
SSRC: 0xbc5e9a40,
FractionLost: 0,
TotalLost: 0,
LastSequenceNumber: 0x46e1,
Jitter: 273,
LastSenderReport: 0x9f36432,
Delay: 150137,
}},
ProfileExtensions: []byte{},
}
if got, want := wantRR, parsed; !reflect.DeepEqual(got, want) {
t.Errorf("Unmarshal rr: got %#v, want %#v", got, want)
}
// SourceDescription
_, packet, err = r.ReadPacket()
if err != nil {
t.Fatalf("Read sdes: %v", err)
}
if parsed, _, err = Unmarshal(packet); err != nil {
t.Errorf("Unmarshal parsed: %v", err)
}
assert.IsType(t, parsed, (*SourceDescription)(nil), "Unmarshalled to incorrect type")
wantSdes := &SourceDescription{
Chunks: []SourceDescriptionChunk{
{
Source: 0x902f9e2e,
Items: []SourceDescriptionItem{
{
Type: SDESCNAME,
Text: "{9c00eb92-1afb-9d49-a47d-91f64eee69f5}",
},
},
},
},
}
if got, want := parsed, wantSdes; !reflect.DeepEqual(got, want) {
t.Errorf("Unmarshal sdes: got %#v, want %#v", got, want)
}
// Goodbye
_, packet, err = r.ReadPacket()
if err != nil {
t.Fatalf("Read bye: %v", err)
}
if parsed, _, err = Unmarshal(packet); err != nil {
t.Errorf("Unmarshal parsed: %v", err)
}
assert.IsType(t, parsed, (*Goodbye)(nil), "Unmarshalled to incorrect type")
wantBye := &Goodbye{
Sources: []uint32{0x902f9e2e},
}
if got, want := parsed, wantBye; !reflect.DeepEqual(got, want) {
t.Errorf("Unmarshal bye: got %#v, want %#v", got, want)
}
// PictureLossIndication
_, packet, err = r.ReadPacket()
if err != nil {
t.Fatalf("Read pli: %v", err)
}
if parsed, _, err = Unmarshal(packet); err != nil {
t.Errorf("Unmarshal parsed: %v", err)
}
assert.IsType(t, parsed, (*PictureLossIndication)(nil), "Unmarshalled to incorrect type")
wantPli := &PictureLossIndication{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
}
if got, want := parsed, wantPli; !reflect.DeepEqual(got, want) {
t.Errorf("Unmarshal pli: got %#v, want %#v", got, want)
}
// RapidResynchronizationRequest
_, packet, err = r.ReadPacket()
if err != nil {
t.Fatalf("Read rrr: %v", err)
}
if parsed, _, err = Unmarshal(packet); err != nil {
t.Errorf("Unmarshal parsed: %v", err)
}
assert.IsType(t, parsed, (*RapidResynchronizationRequest)(nil), "Unmarshalled to incorrect type")
wantRrr := &RapidResynchronizationRequest{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
}
if got, want := parsed, wantRrr; !reflect.DeepEqual(got, want) {
t.Errorf("Unmarshal rrr: got %#v, want %#v", got, want)
}
}
func TestReadEOF(t *testing.T) {
shortHeader := []byte{
0x81, 0xc9, // missing type & len
}
r := NewReader(bytes.NewReader(shortHeader))
_, _, err := r.ReadPacket()
if got, want := err, io.ErrUnexpectedEOF; got != want {
t.Fatalf("read short header: got err = %v, want %v", got, want)
}
}

View File

@@ -1,193 +0,0 @@
package rtcp
import (
"encoding/binary"
"fmt"
"github.com/pions/webrtc/internal/util"
)
// 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
// extra data from the end of the packet; the application can parse this if needed.
ProfileExtensions []byte
}
const (
ssrcLength = 4
rrSSRCOffset = headerLength
rrReportOffset = rrSSRCOffset + ssrcLength
)
// Marshal encodes the ReceiverReport in binary
func (r ReceiverReport) 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=RR=201 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of packet sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, r.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody, r.SSRC)
for i, rp := range r.Reports {
data, err := rp.Marshal()
if err != nil {
return nil, err
}
offset := ssrcLength + receptionReportLength*i
copy(packetBody[offset:], data)
}
if len(r.Reports) > countMax {
return nil, errTooManyReports
}
pe := make([]byte, len(r.ProfileExtensions))
copy(pe, r.ProfileExtensions)
//if the length of the profile extensions isn't devisible
//by 4, we need to pad the end.
for (len(pe) & 0x3) != 0 {
pe = append(pe, 0)
}
rawPacket = append(rawPacket, pe...)
hData, err := r.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the ReceiverReport from binary
func (r *ReceiverReport) Unmarshal(rawPacket []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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=RR=201 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of packet sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeReceiverReport {
return errWrongType
}
r.SSRC = binary.BigEndian.Uint32(rawPacket[rrSSRCOffset:])
for i := rrReportOffset; i < len(rawPacket) && len(r.Reports) < int(h.Count); i += receptionReportLength {
var rr ReceptionReport
if err := rr.Unmarshal(rawPacket[i:]); err != nil {
return err
}
r.Reports = append(r.Reports, rr)
}
r.ProfileExtensions = rawPacket[rrReportOffset+(len(r.Reports)*receptionReportLength):]
if uint8(len(r.Reports)) != h.Count {
return errInvalidHeader
}
return nil
}
func (r *ReceiverReport) len() int {
repsLength := 0
for _, rep := range r.Reports {
repsLength += rep.len()
}
return headerLength + ssrcLength + repsLength
}
// Header returns the Header associated with this packet.
func (r *ReceiverReport) Header() Header {
return Header{
Count: uint8(len(r.Reports)),
Type: TypeReceiverReport,
Length: uint16((r.len()/4)-1) + uint16(util.GetPadding(len(r.ProfileExtensions))),
}
}
// 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

@@ -1,264 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestReceiverReportUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want ReceiverReport
WantError error
}{
{
Name: "valid",
Data: []byte{
// v=2, p=0, count=1, RR, len=7
0x81, 0xc9, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
Want: ReceiverReport{
SSRC: 0x902f9e2e,
Reports: []ReceptionReport{{
SSRC: 0xbc5e9a40,
FractionLost: 0,
TotalLost: 0,
LastSequenceNumber: 0x46e1,
Jitter: 273,
LastSenderReport: 0x9f36432,
Delay: 150137,
}},
ProfileExtensions: []byte{},
},
},
{
Name: "valid with extension data",
Data: []byte{
// v=2, p=0, count=1, RR, len=9
0x81, 0xc9, 0x0, 0x9,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
// profile-specific extension data
0x54, 0x45, 0x53, 0x54,
0x44, 0x41, 0x54, 0x41,
},
Want: ReceiverReport{
SSRC: 0x902f9e2e,
Reports: []ReceptionReport{{
SSRC: 0xbc5e9a40,
FractionLost: 0,
TotalLost: 0,
LastSequenceNumber: 0x46e1,
Jitter: 273,
LastSenderReport: 0x9f36432,
Delay: 150137,
}},
ProfileExtensions: []byte{
0x54, 0x45, 0x53, 0x54,
0x44, 0x41, 0x54, 0x41},
},
},
{
Name: "short report",
Data: []byte{
// v=2, p=0, count=1, RR, len=7
0x81, 0xc9, 0x00, 0x0c,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// fracLost=0, totalLost=0
0x00, 0x00, 0x00, 0x00,
// report ends early
},
WantError: errPacketTooShort,
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, count=1, SR, len=7
0x81, 0xc8, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
WantError: errWrongType,
},
{
Name: "bad count in header",
Data: []byte{
// v=2, p=0, count=2, RR, len=7
0x82, 0xc9, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
WantError: errInvalidHeader,
},
{
Name: "nil",
Data: nil,
WantError: errPacketTooShort,
},
} {
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)
}
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)
}
}
}
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,
LastSequenceNumber: 4,
Jitter: 5,
LastSenderReport: 6,
Delay: 7,
},
{
SSRC: 0,
},
},
ProfileExtensions: []byte{},
},
},
{
Name: "also valid",
Report: ReceiverReport{
SSRC: 2,
Reports: []ReceptionReport{
{
SSRC: 999,
FractionLost: 30,
TotalLost: 12345,
LastSequenceNumber: 99,
Jitter: 22,
LastSenderReport: 92,
Delay: 46,
},
},
ProfileExtensions: []byte{},
},
},
{
Name: "totallost overflow",
Report: ReceiverReport{
SSRC: 1,
Reports: []ReceptionReport{{
TotalLost: 1 << 25,
}},
},
WantError: errInvalidTotalLost,
},
{
Name: "count overflow",
Report: ReceiverReport{
SSRC: 1,
Reports: tooManyReports,
},
WantError: errTooManyReports,
},
} {
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)
}
}
}
// a slice with enough ReceptionReports to overflow an 5-bit int
var tooManyReports []ReceptionReport
func init() {
for i := 0; i < (1 << 5); i++ {
tooManyReports = append(tooManyReports, ReceptionReport{
SSRC: 2,
FractionLost: 2,
TotalLost: 3,
LastSequenceNumber: 4,
Jitter: 5,
LastSenderReport: 6,
Delay: 7,
})
}
}

View File

@@ -1,130 +0,0 @@
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.
LastSequenceNumber 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.
LastSenderReport 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.LastSequenceNumber)
binary.BigEndian.PutUint32(rawPacket[jitterOffset:], r.Jitter)
binary.BigEndian.PutUint32(rawPacket[lastSROffset:], r.LastSenderReport)
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.LastSequenceNumber = binary.BigEndian.Uint32(rawPacket[lastSeqOffset:])
r.Jitter = binary.BigEndian.Uint32(rawPacket[jitterOffset:])
r.LastSenderReport = binary.BigEndian.Uint32(rawPacket[lastSROffset:])
r.Delay = binary.BigEndian.Uint32(rawPacket[delayOffset:])
return nil
}
func (r *ReceptionReport) len() int {
return receptionReportLength
}

View File

@@ -1,221 +0,0 @@
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 (
srHeaderLength = 24
srSSRCOffset = 0
srNTPOffset = srSSRCOffset + ssrcLength
ntpTimeLength = 8
srRTPOffset = srNTPOffset + ntpTimeLength
rtpTimeLength = 4
srPacketCountOffset = srRTPOffset + rtpTimeLength
srPacketCountLength = 4
srOctetCountOffset = srPacketCountOffset + srPacketCountLength
srOctetCountLength = 4
srReportOffset = srOctetCountOffset + srOctetCountLength
)
// Marshal encodes the SenderReport in binary
func (r SenderReport) 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=SR=200 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* sender | NTP timestamp, most significant word |
* info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | NTP timestamp, least significant word |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | RTP timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's packet count |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's octet count |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, r.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody[srSSRCOffset:], r.SSRC)
binary.BigEndian.PutUint64(packetBody[srNTPOffset:], r.NTPTime)
binary.BigEndian.PutUint32(packetBody[srRTPOffset:], r.RTPTime)
binary.BigEndian.PutUint32(packetBody[srPacketCountOffset:], r.PacketCount)
binary.BigEndian.PutUint32(packetBody[srOctetCountOffset:], r.OctetCount)
for i, rp := range r.Reports {
data, err := rp.Marshal()
if err != nil {
return nil, err
}
offset := srHeaderLength + receptionReportLength*i
copy(packetBody[offset:], data)
}
if len(r.Reports) > countMax {
return nil, errTooManyReports
}
hData, err := r.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the SenderReport from binary
func (r *SenderReport) Unmarshal(rawPacket []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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=SR=200 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* sender | NTP timestamp, most significant word |
* info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | NTP timestamp, least significant word |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | RTP timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's packet count |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's octet count |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if len(rawPacket) < (headerLength + srHeaderLength) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeSenderReport {
return errWrongType
}
packetBody := rawPacket[headerLength:]
r.SSRC = binary.BigEndian.Uint32(packetBody[srSSRCOffset:])
r.NTPTime = binary.BigEndian.Uint64(packetBody[srNTPOffset:])
r.RTPTime = binary.BigEndian.Uint32(packetBody[srRTPOffset:])
r.PacketCount = binary.BigEndian.Uint32(packetBody[srPacketCountOffset:])
r.OctetCount = binary.BigEndian.Uint32(packetBody[srOctetCountOffset:])
for i := srReportOffset; i < len(packetBody); i += receptionReportLength {
var rr ReceptionReport
if err := rr.Unmarshal(packetBody[i:]); err != nil {
return err
}
r.Reports = append(r.Reports, rr)
}
if uint8(len(r.Reports)) != h.Count {
return errInvalidHeader
}
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 {
repsLength += rep.len()
}
return headerLength + srHeaderLength + repsLength
}
// Header returns the Header associated with this packet.
func (r *SenderReport) Header() Header {
return Header{
Count: uint8(len(r.Reports)),
Type: TypeSenderReport,
Length: uint16((r.len() / 4) - 1),
}
}

View File

@@ -1,217 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestSenderReportUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want SenderReport
WantError error
}{
{
Name: "nil",
Data: nil,
WantError: errPacketTooShort,
},
{
Name: "valid",
Data: []byte{
// v=2, p=0, count=1, SR, len=7
0x81, 0xc8, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ntp=0xda8bd1fcdddda05a
0xda, 0x8b, 0xd1, 0xfc,
0xdd, 0xdd, 0xa0, 0x5a,
// rtp=0xaaf4edd5
0xaa, 0xf4, 0xed, 0xd5,
// packetCount=1
0x00, 0x00, 0x00, 0x01,
// octetCount=2
0x00, 0x00, 0x00, 0x02,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
Want: SenderReport{
SSRC: 0x902f9e2e,
NTPTime: 0xda8bd1fcdddda05a,
RTPTime: 0xaaf4edd5,
PacketCount: 1,
OctetCount: 2,
Reports: []ReceptionReport{{
SSRC: 0xbc5e9a40,
FractionLost: 0,
TotalLost: 0,
LastSequenceNumber: 0x46e1,
Jitter: 273,
LastSenderReport: 0x9f36432,
Delay: 150137,
}},
},
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, count=1, RR, len=7
0x81, 0xc9, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ntp=0xda8bd1fcdddda05a
0xda, 0x8b, 0xd1, 0xfc,
0xdd, 0xdd, 0xa0, 0x5a,
// rtp=0xaaf4edd5
0xaa, 0xf4, 0xed, 0xd5,
// packetCount=1
0x00, 0x00, 0x00, 0x01,
// octetCount=2
0x00, 0x00, 0x00, 0x02,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
WantError: errWrongType,
},
{
Name: "bad count in header",
Data: []byte{
// v=2, p=0, count=1, SR, len=7
0x82, 0xc8, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ntp=0xda8bd1fcdddda05a
0xda, 0x8b, 0xd1, 0xfc,
0xdd, 0xdd, 0xa0, 0x5a,
// rtp=0xaaf4edd5
0xaa, 0xf4, 0xed, 0xd5,
// packetCount=1
0x00, 0x00, 0x00, 0x01,
// octetCount=2
0x00, 0x00, 0x00, 0x02,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
WantError: errInvalidHeader,
},
} {
var sr SenderReport
err := sr.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q sr: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := sr, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q sr: got %v, want %v", test.Name, 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,
LastSequenceNumber: 4,
Jitter: 5,
LastSenderReport: 6,
Delay: 7,
},
{
SSRC: 0,
},
},
},
},
{
Name: "also valid",
Report: SenderReport{
SSRC: 2,
Reports: []ReceptionReport{
{
SSRC: 999,
FractionLost: 30,
TotalLost: 12345,
LastSequenceNumber: 99,
Jitter: 22,
LastSenderReport: 92,
Delay: 46,
},
},
},
},
{
Name: "count overflow",
Report: SenderReport{
SSRC: 1,
Reports: tooManyReports,
},
WantError: errTooManyReports,
},
} {
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)
}
}
}

View File

@@ -1,113 +0,0 @@
package rtcp
import (
"encoding/binary"
"fmt"
"math"
)
// SLIEntry represents a single entry to the SLI packet's
// list of lost slices.
type SLIEntry struct {
// ID of first lost slice
First uint16
// Number of lost slices
Number uint16
// ID of related picture
Picture uint8
}
// 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 []SLIEntry
}
const (
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, s := range p.SLI {
sli := ((uint32(s.First) & 0x1FFF) << 19) |
((uint32(s.Number) & 0x1FFF) << 6) |
(uint32(s.Picture) & 0x3F)
binary.BigEndian.PutUint32(rawPacket[sliOffset+(4*i):], sli)
}
hData, err := p.Header().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 {
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
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 != FormatSLI {
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, SLIEntry{
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: FormatSLI,
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

@@ -1,118 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestSliceLossIndicationUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want SliceLossIndication
WantError error
}{
{
Name: "valid",
Data: []byte{
// SliceLossIndication
0x82, 0xcd, 0x0, 0x3,
// sender=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// media=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// nack 0xAAAA, 0x5555
0x55, 0x50, 0x00, 0x2C,
},
Want: SliceLossIndication{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
SLI: []SLIEntry{{0xaaa, 0, 0x2C}},
},
},
{
Name: "short report",
Data: []byte{
0x81, 0xcd, 0x0, 0x2,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// report ends early
},
WantError: errPacketTooShort,
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, count=1, SR, len=7
0x81, 0xc8, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
WantError: errWrongType,
},
{
Name: "nil",
Data: nil,
WantError: errPacketTooShort,
},
} {
var sli SliceLossIndication
err := sli.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q rr: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := sli, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q rr: got %v, want %v", test.Name, got, want)
}
}
}
func TestSliceLossIndicationRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Report SliceLossIndication
WantError error
}{
{
Name: "valid",
Report: SliceLossIndication{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
SLI: []SLIEntry{{1, 0xAA, 0x1F}, {1034, 0x05, 0x6}},
},
},
} {
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 SliceLossIndication
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 sli round trip: got %#v, want %#v", test.Name, got, want)
}
}
}

View File

@@ -1,343 +0,0 @@
package rtcp
import (
"encoding/binary"
"github.com/pions/webrtc/internal/util"
)
// SDESType is the item type used in the RTCP SDES control packet.
type SDESType uint8
// RTP SDES item types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5
const (
SDESEnd SDESType = iota // end of SDES list RFC 3550, 6.5
SDESCNAME // canonical name RFC 3550, 6.5.1
SDESName // user name RFC 3550, 6.5.2
SDESEmail // user's electronic mail address RFC 3550, 6.5.3
SDESPhone // user's phone number RFC 3550, 6.5.4
SDESLocation // geographic user location RFC 3550, 6.5.5
SDESTool // name of application or tool RFC 3550, 6.5.6
SDESNote // notice about the source RFC 3550, 6.5.7
SDESPrivate // private extensions RFC 3550, 6.5.8 (not implemented)
)
func (s SDESType) String() string {
switch s {
case SDESEnd:
return "END"
case SDESCNAME:
return "CNAME"
case SDESName:
return "NAME"
case SDESEmail:
return "EMAIL"
case SDESPhone:
return "PHONE"
case SDESLocation:
return "LOC"
case SDESTool:
return "TOOL"
case SDESNote:
return "NOTE"
case SDESPrivate:
return "PRIV"
default:
return string(s)
}
}
const (
sdesSourceLen = 4
sdesTypeLen = 1
sdesTypeOffset = 0
sdesOctetCountLen = 1
sdesOctetCountOffset = 1
sdesMaxOctetCount = (1 << 8) - 1
sdesTextOffset = 2
)
// A SourceDescription (SDES) packet describes the sources in an RTP stream.
type SourceDescription struct {
Chunks []SourceDescriptionChunk
}
// Marshal encodes the SourceDescription in binary
func (s SourceDescription) 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| SC | PT=SDES=202 | length |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_1 |
* 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_2 |
* 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
rawPacket := make([]byte, s.len())
packetBody := rawPacket[headerLength:]
chunkOffset := 0
for _, c := range s.Chunks {
data, err := c.Marshal()
if err != nil {
return nil, err
}
copy(packetBody[chunkOffset:], data)
chunkOffset += len(data)
}
if len(s.Chunks) > countMax {
return nil, errTooManyChunks
}
hData, err := s.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the SourceDescription from binary
func (s *SourceDescription) Unmarshal(rawPacket []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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| SC | PT=SDES=202 | length |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_1 |
* 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_2 |
* 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeSourceDescription {
return errWrongType
}
for i := headerLength; i < len(rawPacket); {
var chunk SourceDescriptionChunk
if err := chunk.Unmarshal(rawPacket[i:]); err != nil {
return err
}
s.Chunks = append(s.Chunks, chunk)
i += chunk.len()
}
if len(s.Chunks) != int(h.Count) {
return errInvalidHeader
}
return nil
}
func (s *SourceDescription) len() int {
chunksLength := 0
for _, c := range s.Chunks {
chunksLength += c.len()
}
return headerLength + chunksLength
}
// Header returns the Header associated with this packet.
func (s *SourceDescription) Header() Header {
return Header{
Count: uint8(len(s.Chunks)),
Type: TypeSourceDescription,
Length: uint16((s.len() / 4) - 1),
}
}
// A SourceDescriptionChunk contains items describing a single RTP source
type SourceDescriptionChunk struct {
// The source (ssrc) or contributing source (csrc) identifier this packet describes
Source uint32
Items []SourceDescriptionItem
}
// Marshal encodes the SourceDescriptionChunk in binary
func (s SourceDescriptionChunk) Marshal() ([]byte, error) {
/*
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC/CSRC_1 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
rawPacket := make([]byte, sdesSourceLen)
binary.BigEndian.PutUint32(rawPacket, s.Source)
for _, it := range s.Items {
data, err := it.Marshal()
if err != nil {
return nil, err
}
rawPacket = append(rawPacket, data...)
}
// The list of items in each chunk MUST be terminated by one or more null octets
rawPacket = append(rawPacket, uint8(SDESEnd))
// additional null octets MUST be included if needed to pad until the next 32-bit boundary
rawPacket = append(rawPacket, make([]byte, util.GetPadding(len(rawPacket)))...)
return rawPacket, nil
}
// Unmarshal decodes the SourceDescriptionChunk from binary
func (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error {
/*
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC/CSRC_1 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
if len(rawPacket) < (sdesSourceLen + sdesTypeLen) {
return errPacketTooShort
}
s.Source = binary.BigEndian.Uint32(rawPacket)
for i := 4; i < len(rawPacket); {
if pktType := SDESType(rawPacket[i]); pktType == SDESEnd {
return nil
}
var it SourceDescriptionItem
if err := it.Unmarshal(rawPacket[i:]); err != nil {
return err
}
s.Items = append(s.Items, it)
i += it.len()
}
return errPacketTooShort
}
func (s SourceDescriptionChunk) len() int {
len := sdesSourceLen
for _, it := range s.Items {
len += it.len()
}
len += sdesTypeLen // for terminating null octet
// align to 32-bit boundary
len += util.GetPadding(len)
return len
}
// A SourceDescriptionItem is a part of a SourceDescription that describes a stream.
type SourceDescriptionItem struct {
// The type identifier for this item. eg, SDESCNAME for canonical name description.
//
// Type zero or SDESEnd is interpreted as the end of an item list and cannot be used.
Type SDESType
// Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type.
Text string
}
func (s SourceDescriptionItem) len() int {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | CNAME=1 | length | user and domain name ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
return sdesTypeLen + sdesOctetCountLen + len([]byte(s.Text))
}
// Marshal encodes the SourceDescriptionItem in binary
func (s SourceDescriptionItem) 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | CNAME=1 | length | user and domain name ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if s.Type == SDESEnd {
return nil, errSDESMissingType
}
rawPacket := make([]byte, sdesTypeLen+sdesOctetCountLen)
rawPacket[sdesTypeOffset] = uint8(s.Type)
txtBytes := []byte(s.Text)
octetCount := len(txtBytes)
if octetCount > sdesMaxOctetCount {
return nil, errSDESTextTooLong
}
rawPacket[sdesOctetCountOffset] = uint8(octetCount)
rawPacket = append(rawPacket, txtBytes...)
return rawPacket, nil
}
// Unmarshal decodes the SourceDescriptionItem from binary
func (s *SourceDescriptionItem) Unmarshal(rawPacket []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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | CNAME=1 | length | user and domain name ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if len(rawPacket) < (sdesTypeLen + sdesOctetCountLen) {
return errPacketTooShort
}
s.Type = SDESType(rawPacket[sdesTypeOffset])
octetCount := int(rawPacket[sdesOctetCountOffset])
if sdesTextOffset+octetCount > len(rawPacket) {
return errPacketTooShort
}
txtBytes := rawPacket[sdesTextOffset : sdesTextOffset+octetCount]
s.Text = string(txtBytes)
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

@@ -1,370 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestSourceDescriptionUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want SourceDescription
WantError error
}{
{
Name: "nil",
Data: nil,
WantError: errInvalidHeader,
},
{
Name: "no chunks",
Data: []byte{
// v=2, p=0, count=1, SDES, len=8
0x80, 0xca, 0x00, 0x04,
},
Want: SourceDescription{
Chunks: nil,
},
},
{
Name: "missing type",
Data: []byte{
// v=2, p=0, count=1, SDES, len=8
0x81, 0xca, 0x00, 0x08,
// ssrc=0x00000000
0x00, 0x00, 0x00, 0x00,
},
WantError: errPacketTooShort,
},
{
Name: "bad cname length",
Data: []byte{
// v=2, p=0, count=1, SDES, len=10
0x81, 0xca, 0x00, 0x0a,
// ssrc=0x00000000
0x00, 0x00, 0x00, 0x00,
// CNAME, len = 1
0x01, 0x01,
},
WantError: errPacketTooShort,
},
{
Name: "short cname",
Data: []byte{
// v=2, p=0, count=1, SDES, len=9
0x81, 0xca, 0x00, 0x09,
// ssrc=0x00000000
0x00, 0x00, 0x00, 0x00,
// CNAME, Missing length
0x01,
},
WantError: errPacketTooShort,
},
{
Name: "no end",
Data: []byte{
// v=2, p=0, count=1, SDES, len=11
0x81, 0xca, 0x00, 0x0b,
// ssrc=0x00000000
0x00, 0x00, 0x00, 0x00,
// CNAME, len=1, content=A
0x01, 0x02, 0x41,
// Missing END
},
WantError: errPacketTooShort,
},
{
Name: "bad octet count",
Data: []byte{
// v=2, p=0, count=1, SDES, len=10
0x81, 0xca, 0x00, 0x0a,
// ssrc=0x00000000
0x00, 0x00, 0x00, 0x00,
// CNAME, len=1
0x01, 0x01,
},
WantError: errPacketTooShort,
},
{
Name: "zero item chunk",
Data: []byte{
// v=2, p=0, count=1, SDES, len=12
0x81, 0xca, 0x00, 0x0c,
// ssrc=0x01020304
0x01, 0x02, 0x03, 0x04,
// END + padding
0x00, 0x00, 0x00, 0x00,
},
Want: SourceDescription{
Chunks: []SourceDescriptionChunk{{
Source: 0x01020304,
Items: nil,
}},
},
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, count=1, SR, len=12
0x81, 0xc8, 0x00, 0x0c,
// ssrc=0x01020304
0x01, 0x02, 0x03, 0x04,
// END + padding
0x00, 0x00, 0x00, 0x00,
},
WantError: errWrongType,
},
{
Name: "bad count in header",
Data: []byte{
// v=2, p=0, count=1, SDES, len=12
0x81, 0xca, 0x00, 0x0c,
},
WantError: errInvalidHeader,
},
{
Name: "empty string",
Data: []byte{
// v=2, p=0, count=1, SDES, len=12
0x81, 0xca, 0x00, 0x0c,
// ssrc=0x01020304
0x01, 0x02, 0x03, 0x04,
// CNAME, len=0
0x01, 0x00,
// END + padding
0x00, 0x00,
},
Want: SourceDescription{
Chunks: []SourceDescriptionChunk{{
Source: 0x01020304,
Items: []SourceDescriptionItem{
{
Type: SDESCNAME,
Text: "",
},
},
}},
},
},
{
Name: "two items",
Data: []byte{
// v=2, p=0, count=1, SDES, len=16
0x81, 0xca, 0x00, 0x10,
// ssrc=0x10000000
0x10, 0x00, 0x00, 0x00,
// CNAME, len=1, content=A
0x01, 0x01, 0x41,
// PHONE, len=1, content=B
0x04, 0x01, 0x42,
// END + padding
0x00, 0x00,
},
Want: SourceDescription{
Chunks: []SourceDescriptionChunk{
{
Source: 0x10000000,
Items: []SourceDescriptionItem{
{
Type: SDESCNAME,
Text: "A",
},
{
Type: SDESPhone,
Text: "B",
},
},
},
},
},
},
{
Name: "two chunks",
Data: []byte{
// v=2, p=0, count=2, SDES, len=24
0x82, 0xca, 0x00, 0x18,
// ssrc=0x01020304
0x01, 0x02, 0x03, 0x04,
// Chunk 1
// CNAME, len=1, content=A
0x01, 0x01, 0x41,
// END
0x00,
// Chunk 2
// SSRC 0x05060708
0x05, 0x06, 0x07, 0x08,
// CNAME, len=3, content=BCD
0x01, 0x03, 0x42, 0x43, 0x44,
// END
0x00, 0x00, 0x00,
},
Want: SourceDescription{
Chunks: []SourceDescriptionChunk{
{
Source: 0x01020304,
Items: []SourceDescriptionItem{
{
Type: SDESCNAME,
Text: "A",
},
},
},
{
Source: 0x05060708,
Items: []SourceDescriptionItem{
{
Type: SDESCNAME,
Text: "BCD",
},
},
},
},
},
},
} {
var sdes SourceDescription
err := sdes.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := sdes, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q: got %#v, want %#v", test.Name, got, want)
}
}
}
func TestSourceDescriptionRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Desc SourceDescription
WantError error
}{
{
Name: "valid",
Desc: SourceDescription{
Chunks: []SourceDescriptionChunk{
{
Source: 1,
Items: []SourceDescriptionItem{
{
Type: SDESCNAME,
Text: "test@example.com",
},
},
},
{
Source: 2,
Items: []SourceDescriptionItem{
{
Type: SDESNote,
Text: "some note",
},
{
Type: SDESNote,
Text: "another note",
},
},
},
},
},
},
{
Name: "item without type",
Desc: SourceDescription{
Chunks: []SourceDescriptionChunk{{
Source: 1,
Items: []SourceDescriptionItem{{
Text: "test@example.com",
}},
}},
},
WantError: errSDESMissingType,
},
{
Name: "zero items",
Desc: SourceDescription{
Chunks: []SourceDescriptionChunk{{
Source: 1,
}},
},
},
{
Name: "email item",
Desc: SourceDescription{
Chunks: []SourceDescriptionChunk{{
Source: 1,
Items: []SourceDescriptionItem{{
Type: SDESEmail,
Text: "test@example.com",
}},
}},
},
},
{
Name: "empty text",
Desc: SourceDescription{
Chunks: []SourceDescriptionChunk{{
Source: 1,
Items: []SourceDescriptionItem{{
Type: SDESCNAME,
Text: "",
}},
}},
},
},
{
Name: "text too long",
Desc: SourceDescription{
Chunks: []SourceDescriptionChunk{{
Items: []SourceDescriptionItem{{
Type: SDESCNAME,
Text: tooLongText,
}},
}},
},
WantError: errSDESTextTooLong,
},
{
Name: "count overflow",
Desc: SourceDescription{
Chunks: tooManyChunks,
},
WantError: errTooManyChunks,
},
} {
data, err := test.Desc.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 SourceDescription
if err := decoded.Unmarshal(data); err != nil {
t.Fatalf("Unmarshal %q: %v", test.Name, err)
}
if got, want := decoded, test.Desc; !reflect.DeepEqual(got, want) {
t.Fatalf("%q sdes round trip: got %#v, want %#v", test.Name, got, want)
}
}
}
// a slice with enough SourceDescriptionChunks to overflow an 5-bit int
var tooManyChunks []SourceDescriptionChunk
var tooLongText string
func init() {
for i := 0; i < (1 << 5); i++ {
tooManyChunks = append(tooManyChunks, SourceDescriptionChunk{})
}
for i := 0; i < (1 << 8); i++ {
tooLongText += "x"
}
}

View File

@@ -1,134 +0,0 @@
package rtcp
import (
"encoding/binary"
"fmt"
"math"
)
// PacketBitmap shouldn't be used like a normal integral,
// so it's type is masked here. Access it with PacketList().
type PacketBitmap uint16
// NackPair is a wire-representation of a collection of
// Lost RTP packets
type NackPair struct {
// ID of lost packets
PacketID uint16
// Bitmask of following lost packets
LostPackets PacketBitmap
}
// The TransportLayerNack packet informs the encoder about the loss of a transport packet
// IETF RFC 4585, Section 6.2.1
// https://tools.ietf.org/html/rfc4585#section-6.2.1
type TransportLayerNack struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
Nacks []NackPair
}
// PacketList returns a list of Nack'd packets that's referenced by a NackPair
func (n *NackPair) PacketList() []uint16 {
out := make([]uint16, 1, 17)
out[0] = n.PacketID
b := n.LostPackets
for i := uint16(0); b != 0; i++ {
if (b & (1 << i)) != 0 {
b &^= (1 << i)
out = append(out, n.PacketID+i+1)
}
}
return out
}
const (
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:], uint16(p.Nacks[i].LostPackets))
}
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 {
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
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 != FormatTLN {
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, NackPair{
binary.BigEndian.Uint16(rawPacket[i:]),
PacketBitmap(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: FormatTLN,
Type: TypeTransportSpecificFeedback,
Length: uint16((p.len() / 4) - 1),
}
}
func (p *TransportLayerNack) String() string {
o := "Packets Lost:\n"
for _, n := range p.Nacks {
for _, m := range n.PacketList() {
o += fmt.Sprintf("\t%d\n", m)
}
}
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

@@ -1,118 +0,0 @@
package rtcp
import (
"reflect"
"testing"
)
func TestTransportLayerNackUnmarshal(t *testing.T) {
for _, test := range []struct {
Name string
Data []byte
Want TransportLayerNack
WantError error
}{
{
Name: "valid",
Data: []byte{
// TransportLayerNack
0x81, 0xcd, 0x0, 0x3,
// sender=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// media=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// nack 0xAAAA, 0x5555
0xaa, 0xaa, 0x55, 0x55,
},
Want: TransportLayerNack{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
Nacks: []NackPair{{0xaaaa, 0x5555}},
},
},
{
Name: "short report",
Data: []byte{
0x81, 0xcd, 0x0, 0x2,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// report ends early
},
WantError: errPacketTooShort,
},
{
Name: "wrong type",
Data: []byte{
// v=2, p=0, count=1, SR, len=7
0x81, 0xc8, 0x0, 0x7,
// ssrc=0x902f9e2e
0x90, 0x2f, 0x9e, 0x2e,
// ssrc=0xbc5e9a40
0xbc, 0x5e, 0x9a, 0x40,
// fracLost=0, totalLost=0
0x0, 0x0, 0x0, 0x0,
// lastSeq=0x46e1
0x0, 0x0, 0x46, 0xe1,
// jitter=273
0x0, 0x0, 0x1, 0x11,
// lsr=0x9f36432
0x9, 0xf3, 0x64, 0x32,
// delay=150137
0x0, 0x2, 0x4a, 0x79,
},
WantError: errWrongType,
},
{
Name: "nil",
Data: nil,
WantError: errPacketTooShort,
},
} {
var tln TransportLayerNack
err := tln.Unmarshal(test.Data)
if got, want := err, test.WantError; got != want {
t.Fatalf("Unmarshal %q rr: err = %v, want %v", test.Name, got, want)
}
if err != nil {
continue
}
if got, want := tln, test.Want; !reflect.DeepEqual(got, want) {
t.Fatalf("Unmarshal %q rr: got %v, want %v", test.Name, got, want)
}
}
}
func TestTransportLayerNackRoundTrip(t *testing.T) {
for _, test := range []struct {
Name string
Report TransportLayerNack
WantError error
}{
{
Name: "valid",
Report: TransportLayerNack{
SenderSSRC: 0x902f9e2e,
MediaSSRC: 0x902f9e2e,
Nacks: []NackPair{{1, 0xAA}, {1034, 0x05}},
},
},
} {
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 TransportLayerNack
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 tln round trip: got %#v, want %#v", test.Name, got, want)
}
}
}

View File

@@ -1,8 +0,0 @@
package codecs
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@@ -1,18 +0,0 @@
package codecs
// G722Payloader payloads G722 packets
type G722Payloader struct{}
// Payload fragments an G722 packet across one or more byte arrays
func (p *G722Payloader) Payload(mtu int, payload []byte) [][]byte {
var out [][]byte
for len(payload) > mtu {
o := make([]byte, mtu)
copy(o, payload[:mtu])
payload = payload[mtu:]
out = append(out, o)
}
o := make([]byte, len(payload))
copy(o, payload)
return append(out, o)
}

View File

@@ -1,47 +0,0 @@
package codecs
import (
"bytes"
"crypto/rand"
"math"
"testing"
)
func TestG722Payloader(t *testing.T) {
p := G722Payloader{}
const (
testlen = 10000
testmtu = 1500
)
//generate random 8-bit g722 samples
samples := make([]byte, testlen)
_, err := rand.Read(samples)
if err != nil {
t.Fatal("RNG Error: ", err)
}
//make a copy, for payloader input
samplesIn := make([]byte, testlen)
copy(samplesIn, samples)
//split our samples into payloads
payloads := p.Payload(testmtu, samplesIn)
outcnt := int(math.Ceil(float64(testlen) / testmtu))
if len(payloads) != outcnt {
t.Fatalf("Generated %d payloads instead of %d", len(payloads), outcnt)
}
if !bytes.Equal(samplesIn, samples) {
t.Fatal("Modified input samples")
}
samplesOut := bytes.Join(payloads, []byte{})
if !bytes.Equal(samplesIn, samplesOut) {
t.Fatal("Output samples don't match")
}
}

View File

@@ -1,123 +0,0 @@
package codecs
// H264Payloader payloads H264 packets
type H264Payloader struct{}
const (
fuaHeaderSize = 2
)
func emitNalus(nals []byte, emit func([]byte)) {
nextInd := func(nalu []byte, start int) (indStart int, indLen int) {
zeroCount := 0
for i, b := range nalu[start:] {
if b == 0 {
zeroCount++
continue
} else if b == 1 {
if zeroCount >= 2 {
return start + i - zeroCount, zeroCount + 1
}
}
zeroCount = 0
}
return -1, -1
}
nextIndStart, nextIndLen := nextInd(nals, 0)
if nextIndStart == -1 {
emit(nals)
} else {
for nextIndStart != -1 {
prevStart := nextIndStart + nextIndLen
nextIndStart, nextIndLen = nextInd(nals, prevStart)
if nextIndStart != -1 {
emit(nals[prevStart:nextIndStart])
} else {
// Emit until end of stream, no end indicator found
emit(nals[prevStart:])
}
}
}
}
// Payload fragments a H264 packet across one or more byte arrays
func (p *H264Payloader) Payload(mtu int, payload []byte) [][]byte {
var payloads [][]byte
emitNalus(payload, func(nalu []byte) {
naluType := nalu[0] & 0x1F
naluRefIdc := nalu[0] & 0x60
if naluType == 9 || naluType == 12 {
return
}
// Single NALU
if len(nalu) <= mtu {
out := make([]byte, len(nalu))
copy(out, nalu)
payloads = append(payloads, out)
return
}
// FU-A
maxFragmentSize := mtu - fuaHeaderSize
// The FU payload consists of fragments of the payload of the fragmented
// NAL unit so that if the fragmentation unit payloads of consecutive
// FUs are sequentially concatenated, the payload of the fragmented NAL
// unit can be reconstructed. The NAL unit type octet of the fragmented
// NAL unit is not included as such in the fragmentation unit payload,
// but rather the information of the NAL unit type octet of the
// fragmented NAL unit is conveyed in the F and NRI fields of the FU
// indicator octet of the fragmentation unit and in the type field of
// the FU header. An FU payload MAY have any number of octets and MAY
// be empty.
naluData := nalu
// According to the RFC, the first octet is skipped due to redundant information
naluDataIndex := 1
naluDataLength := len(nalu) - naluDataIndex
naluDataRemaining := naluDataLength
for naluDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, naluDataRemaining)
out := make([]byte, fuaHeaderSize+currentFragmentSize)
// +---------------+
// |0|1|2|3|4|5|6|7|
// +-+-+-+-+-+-+-+-+
// |F|NRI| Type |
// +---------------+
out[0] = 28
out[0] |= naluRefIdc
// +---------------+
//|0|1|2|3|4|5|6|7|
//+-+-+-+-+-+-+-+-+
//|S|E|R| Type |
//+---------------+
out[1] = naluType
if naluDataRemaining == naluDataLength {
// Set start bit
out[1] |= 1 << 7
} else if naluDataRemaining-currentFragmentSize == 0 {
// Set end bit
out[1] |= 1 << 6
}
copy(out[fuaHeaderSize:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize])
payloads = append(payloads, out)
naluDataRemaining -= currentFragmentSize
naluDataIndex += currentFragmentSize
}
})
return payloads
}

View File

@@ -1,24 +0,0 @@
package codecs
import "github.com/pions/webrtc/pkg/rtp"
// OpusPayloader payloads Opus packets
type OpusPayloader struct{}
// Payload fragments an Opus packet across one or more byte arrays
func (p *OpusPayloader) Payload(mtu int, payload []byte) [][]byte {
out := make([]byte, len(payload))
copy(out, payload)
return [][]byte{out}
}
// OpusPacket represents the VP8 header that is stored in the payload of an RTP Packet
type OpusPacket struct {
Payload []byte
}
// Unmarshal parses the passed byte slice and stores the result in the OpusPacket this method is called upon
func (p *OpusPacket) Unmarshal(packet *rtp.Packet) ([]byte, error) {
p.Payload = packet.Payload
return p.Payload, nil
}

View File

@@ -1,117 +0,0 @@
package codecs
import "github.com/pions/webrtc/pkg/rtp"
// VP8Payloader payloads VP8 packets
type VP8Payloader struct{}
const (
vp8HeaderSize = 1
)
// Payload fragments a VP8 packet across one or more byte arrays
func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte {
/*
* https://tools.ietf.org/html/rfc7741#section-4.2
*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |X|R|N|S|R| PID | (REQUIRED)
* +-+-+-+-+-+-+-+-+
* X: |I|L|T|K| RSV | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* I: |M| PictureID | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* L: | TL0PICIDX | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* T/K: |TID|Y| KEYIDX | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* S: Start of VP8 partition. SHOULD be set to 1 when the first payload
* octet of the RTP packet is the beginning of a new VP8 partition,
* and MUST NOT be 1 otherwise. The S bit MUST be set to 1 for the
* first packet of each encoded frame.
*/
maxFragmentSize := mtu - vp8HeaderSize
payloadData := payload
payloadDataRemaining := len(payload)
payloadDataIndex := 0
var payloads [][]byte
for payloadDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
out := make([]byte, vp8HeaderSize+currentFragmentSize)
if payloadDataRemaining == len(payload) {
out[0] = 0x10
}
copy(out[vp8HeaderSize:], payloadData[payloadDataIndex:payloadDataIndex+currentFragmentSize])
payloads = append(payloads, out)
payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize
}
return payloads
}
// VP8Packet represents the VP8 header that is stored in the payload of an RTP Packet
type VP8Packet struct {
// Required Header
X uint8 /* extended controlbits present */
N uint8 /* (non-reference frame) when set to 1 this frame can be discarded */
S uint8 /* start of VP8 partition */
PID uint8 /* partition index */
// Optional Header
I uint8 /* 1 if PictureID is present */
L uint8 /* 1 if TL0PICIDX is present */
T uint8 /* 1 if TID is present */
K uint8 /* 1 if KEYIDX is present */
PictureID uint16 /* 8 or 16 bits, picture ID */
TL0PICIDX uint8 /* 8 bits temporal level zero index */
Payload []byte
}
// Unmarshal parses the passed byte slice and stores the result in the VP8Packet this method is called upon
func (p *VP8Packet) Unmarshal(packet *rtp.Packet) ([]byte, error) {
payload := packet.Payload
payloadIndex := 0
p.X = (payload[payloadIndex] & 0x80) >> 7
p.N = (payload[payloadIndex] & 0x20) >> 5
p.S = (payload[payloadIndex] & 0x10) >> 4
p.PID = payload[payloadIndex] & 0x07
payloadIndex++
if p.X == 1 {
p.I = (payload[payloadIndex] & 0x80) >> 7
p.L = (payload[payloadIndex] & 0x40) >> 6
p.T = (payload[payloadIndex] & 0x20) >> 5
p.K = (payload[payloadIndex] & 0x10) >> 4
payloadIndex++
}
if p.I == 1 { // PID present?
if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit
payloadIndex += 2
} else {
payloadIndex++
}
}
if p.L == 1 {
payloadIndex++
}
if p.T == 1 || p.K == 1 {
payloadIndex++
}
p.Payload = payload[payloadIndex:]
return p.Payload, nil
}

View File

@@ -1,6 +0,0 @@
package rtp
// Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload
type Depacketizer interface {
Unmarshal(packet *Packet) ([]byte, error)
}

View File

@@ -1,219 +0,0 @@
package rtp
import (
"encoding/binary"
"fmt"
"github.com/pkg/errors"
)
// Header represents an RTP packet header
type Header struct {
Version uint8
Padding bool
Extension bool
Marker bool
PayloadOffset int
PayloadType uint8
SequenceNumber uint16
Timestamp uint32
SSRC uint32
CSRC []uint32
ExtensionProfile uint16
ExtensionPayload []byte
}
// Packet represents an RTP Packet
type Packet struct {
Header
Raw []byte
Payload []byte
}
const (
headerLength = 4
versionShift = 6
versionMask = 0x3
paddingShift = 5
paddingMask = 0x1
extensionShift = 4
extensionMask = 0x1
ccMask = 0xF
markerShift = 7
markerMask = 0x1
ptMask = 0x7F
seqNumOffset = 2
seqNumLength = 2
timestampOffset = 4
timestampLength = 4
ssrcOffset = 8
ssrcLength = 4
csrcOffset = 12
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 Header this method is called upon
func (h *Header) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < headerLength {
return errors.Errorf("RTP header size insufficient; %d < %d", len(rawPacket), headerLength)
}
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P|X| CC |M| PT | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | synchronization source (SSRC) identifier |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | contributing source (CSRC) identifiers |
* | .... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
h.Version = rawPacket[0] >> versionShift & versionMask
h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0
h.Extension = (rawPacket[0] >> extensionShift & extensionMask) > 0
h.CSRC = make([]uint32, rawPacket[0]&ccMask)
h.Marker = (rawPacket[1] >> markerShift & markerMask) > 0
h.PayloadType = rawPacket[1] & ptMask
h.SequenceNumber = binary.BigEndian.Uint16(rawPacket[seqNumOffset : seqNumOffset+seqNumLength])
h.Timestamp = binary.BigEndian.Uint32(rawPacket[timestampOffset : timestampOffset+timestampLength])
h.SSRC = binary.BigEndian.Uint32(rawPacket[ssrcOffset : ssrcOffset+ssrcLength])
currOffset := csrcOffset + (len(h.CSRC) * csrcLength)
if len(rawPacket) < currOffset {
return errors.Errorf("RTP header size insufficient; %d < %d", len(rawPacket), currOffset)
}
for i := range h.CSRC {
offset := csrcOffset + (i * csrcLength)
h.CSRC[i] = binary.BigEndian.Uint32(rawPacket[offset:])
}
if h.Extension {
if len(rawPacket) < currOffset+4 {
return errors.Errorf("RTP header size insufficient for extension; %d < %d", len(rawPacket), currOffset)
}
h.ExtensionProfile = binary.BigEndian.Uint16(rawPacket[currOffset:])
currOffset += 2
extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:])) * 4
currOffset += 2
if len(rawPacket) < currOffset+extensionLength {
return errors.Errorf("RTP header size insufficient for extension length; %d < %d", len(rawPacket), currOffset+extensionLength)
}
h.ExtensionPayload = rawPacket[currOffset : currOffset+extensionLength]
currOffset += len(h.ExtensionPayload)
}
h.PayloadOffset = currOffset
return nil
}
// 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 err := p.Header.Unmarshal(rawPacket); err != nil {
return err
}
p.Payload = rawPacket[p.PayloadOffset:]
p.Raw = rawPacket
return nil
}
// Marshal returns a raw RTP header for the instance it is called upon
func (h *Header) 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P|X| CC |M| PT | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | synchronization source (SSRC) identifier |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | contributing source (CSRC) identifiers |
* | .... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawHeaderLength := 12 + (len(h.CSRC) * csrcLength)
if h.Extension {
rawHeaderLength += 4 + len(h.ExtensionPayload)
}
rawHeader := make([]byte, rawHeaderLength)
rawHeader[0] |= h.Version << versionShift
if h.Padding {
rawHeader[0] |= 1 << paddingShift
}
if h.Extension {
rawHeader[0] |= 1 << extensionShift
}
rawHeader[0] |= uint8(len(h.CSRC))
if h.Marker {
rawHeader[1] |= 1 << markerShift
}
rawHeader[1] |= h.PayloadType
binary.BigEndian.PutUint16(rawHeader[seqNumOffset:], h.SequenceNumber)
binary.BigEndian.PutUint32(rawHeader[timestampOffset:], h.Timestamp)
binary.BigEndian.PutUint32(rawHeader[ssrcOffset:], h.SSRC)
for i, csrc := range h.CSRC {
binary.BigEndian.PutUint32(rawHeader[csrcOffset+(i*csrcLength):], csrc)
}
currOffset := csrcOffset + (len(h.CSRC) * csrcLength)
for i := range h.CSRC {
offset := csrcOffset + (i * csrcLength)
h.CSRC[i] = binary.BigEndian.Uint32(rawHeader[offset:])
}
if h.Extension {
binary.BigEndian.PutUint16(rawHeader[currOffset:], h.ExtensionProfile)
currOffset += 2
binary.BigEndian.PutUint16(rawHeader[currOffset:], uint16(len(h.ExtensionPayload))/4)
currOffset += 2
copy(rawHeader[currOffset:], h.ExtensionPayload)
}
h.PayloadOffset = csrcOffset + (len(h.CSRC) * csrcLength)
return rawHeader, nil
}
// Marshal returns a raw RTP packet for the instance it is called upon
func (p *Packet) Marshal() ([]byte, error) {
rawPacket, err := p.Header.Marshal()
if err != nil {
return nil, err
}
rawPacket = append(rawPacket, p.Payload...)
p.Raw = rawPacket
return rawPacket, nil
}

View File

@@ -1,70 +0,0 @@
package rtp
import (
"reflect"
"testing"
)
func TestBasic(t *testing.T) {
p := &Packet{}
if err := p.Unmarshal([]byte{}); err == nil {
t.Fatal("Unmarshal did not error on zero length packet")
}
rawPkt := []byte{
0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e,
}
parsedPacket := &Packet{
Header: Header{
Extension: true,
ExtensionProfile: 1,
ExtensionPayload: []byte{0xFF, 0xFF, 0xFF, 0xFF},
Version: 2,
PayloadOffset: 20,
PayloadType: 96,
SequenceNumber: 27023,
Timestamp: 3653407706,
SSRC: 476325762,
CSRC: []uint32{},
},
Payload: rawPkt[20:],
Raw: rawPkt,
}
if err := p.Unmarshal(rawPkt); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(p, parsedPacket) {
t.Errorf("TestBasic unmarshal: got %#v, want %#v", p, parsedPacket)
}
raw, err := p.Marshal()
if err != nil {
t.Error(err)
} else if !reflect.DeepEqual(raw, rawPkt) {
t.Errorf("TestBasic marshal: got %#v, want %#v", raw, rawPkt)
}
}
func TestExtension(t *testing.T) {
p := &Packet{}
missingExtensionPkt := []byte{
0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
0x27, 0x82,
}
if err := p.Unmarshal(missingExtensionPkt); err == nil {
t.Fatal("Unmarshal did not error on packet with missing extension data")
}
invalidExtensionLengthPkt := []byte{
0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
0x27, 0x82, 0x99, 0x99, 0x99, 0x99,
}
if err := p.Unmarshal(invalidExtensionLengthPkt); err == nil {
t.Fatal("Unmarshal did not error on packet with invalid extension length")
}
}

View File

@@ -1,72 +0,0 @@
package rtp
import (
"math/rand"
"time"
)
// Payloader payloads a byte array for use as rtp.Packet payloads
type Payloader interface {
Payload(mtu int, payload []byte) [][]byte
}
// Packetizer packetizes a payload
type Packetizer interface {
Packetize(payload []byte, samples uint32) []*Packet
}
type packetizer struct {
MTU int
PayloadType uint8
SSRC uint32
Payloader Payloader
Sequencer Sequencer
Timestamp uint32
ClockRate uint32
}
// NewPacketizer returns a new instance of a Packetizer for a specific payloader
func NewPacketizer(mtu int, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer, clockRate uint32) Packetizer {
rs := rand.NewSource(time.Now().UnixNano())
r := rand.New(rs)
return &packetizer{
MTU: mtu,
PayloadType: pt,
SSRC: ssrc,
Payloader: payloader,
Sequencer: sequencer,
Timestamp: r.Uint32(),
ClockRate: clockRate,
}
}
// Packetize packetizes the payload of an RTP packet and returns one or more RTP packets
func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet {
// Guard against an empty payload
if len(payload) == 0 {
return nil
}
payloads := p.Payloader.Payload(p.MTU-12, payload)
packets := make([]*Packet, len(payloads))
for i, pp := range payloads {
packets[i] = &Packet{
Header: Header{
Version: 2,
Padding: false,
Extension: false,
Marker: i == len(payloads)-1,
PayloadType: p.PayloadType,
SequenceNumber: p.Sequencer.NextSequenceNumber(),
Timestamp: p.Timestamp, // Figure out how to do timestamps
SSRC: p.SSRC,
},
Payload: pp,
}
}
p.Timestamp += samples
return packets
}

View File

@@ -1,62 +0,0 @@
package rtp
import (
"math"
"math/rand"
"sync"
"time"
)
// Sequencer generates sequential sequence numbers for building RTP packets
type Sequencer interface {
NextSequenceNumber() uint16
RollOverCount() uint64
}
// NewRandomSequencer returns a new sequencer starting from a random sequence
// number
func NewRandomSequencer() Sequencer {
rs := rand.NewSource(time.Now().UnixNano())
r := rand.New(rs)
return &sequencer{
sequenceNumber: uint16(r.Uint32() % math.MaxUint16),
}
}
// NewFixedSequencer returns a new sequencer starting from a specific
// sequence number
func NewFixedSequencer(s uint16) Sequencer {
return &sequencer{
sequenceNumber: s - 1, // -1 because the first sequence number prepends 1
}
}
type sequencer struct {
sequenceNumber uint16
rollOverCount uint64
mutex sync.Mutex
}
// NextSequenceNumber increment and returns a new sequence number for
// building RTP packets
func (s *sequencer) NextSequenceNumber() uint16 {
s.mutex.Lock()
defer s.mutex.Unlock()
s.sequenceNumber++
if s.sequenceNumber == 0 {
s.rollOverCount++
}
return s.sequenceNumber
}
// RollOverCount returns the amount of times the 16bit sequence number
// has wrapped
func (s *sequencer) RollOverCount() uint64 {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.rollOverCount
}

View File

@@ -12,12 +12,12 @@ import (
"sync"
"time"
"github.com/pions/rtcp"
"github.com/pions/rtp"
"github.com/pions/sdp"
"github.com/pions/webrtc/pkg/ice"
"github.com/pions/webrtc/pkg/logging"
"github.com/pions/webrtc/pkg/rtcerr"
"github.com/pions/webrtc/pkg/rtcp"
"github.com/pions/webrtc/pkg/rtp"
"github.com/pkg/errors"
)

View File

@@ -5,9 +5,9 @@ import (
"testing"
"time"
"github.com/pions/rtcp"
"github.com/pions/transport/test"
"github.com/pions/webrtc/pkg/media"
"github.com/pions/webrtc/pkg/rtcp"
)
func TestRTCPeerConnection_Media_Sample(t *testing.T) {

View File

@@ -9,9 +9,9 @@ import (
"testing"
"time"
"github.com/pions/rtp"
"github.com/pions/webrtc/pkg/ice"
"github.com/pions/webrtc/pkg/media"
"github.com/pions/webrtc/pkg/rtp"
"github.com/pions/webrtc/pkg/rtcerr"
"github.com/stretchr/testify/assert"

View File

@@ -1,8 +1,8 @@
package webrtc
import (
"github.com/pions/webrtc/pkg/rtcp"
"github.com/pions/webrtc/pkg/rtp"
"github.com/pions/rtcp"
"github.com/pions/rtp"
)
// RTCRtpReceiver allows an application to inspect the receipt of a RTCTrack

View File

@@ -1,9 +1,9 @@
package webrtc
import (
"github.com/pions/rtcp"
"github.com/pions/rtp"
"github.com/pions/webrtc/pkg/media"
"github.com/pions/webrtc/pkg/rtcp"
"github.com/pions/webrtc/pkg/rtp"
)
const rtpOutboundMTU = 1400

View File

@@ -4,9 +4,9 @@ import (
"crypto/rand"
"encoding/binary"
"github.com/pions/rtcp"
"github.com/pions/rtp"
"github.com/pions/webrtc/pkg/media"
"github.com/pions/webrtc/pkg/rtcp"
"github.com/pions/webrtc/pkg/rtp"
"github.com/pkg/errors"
)