mirror of
https://github.com/pion/webrtc.git
synced 2025-10-04 06:46:35 +08:00
@@ -9,7 +9,6 @@ linters:
|
||||
disable:
|
||||
- maligned
|
||||
- lll
|
||||
- dupl
|
||||
- gocritic
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
|
@@ -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() {
|
||||
|
@@ -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() {
|
||||
|
@@ -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
6
go.mod
@@ -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
12
go.sum
@@ -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=
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
@@ -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")
|
||||
)
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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{}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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),
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -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}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
package codecs
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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")
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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")
|
||||
}
|
||||
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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"
|
||||
)
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
Reference in New Issue
Block a user