mirror of https://bitbucket.org/ausocean/av.git
Merged in rtsp-protocol (pull request #185)
protocol: added basic RTCP protocol client implementation. Approved-by: Alan Noble <anoble@gmail.com> Approved-by: kortschak <dan@kortschak.io>
This commit is contained in:
commit
a5e40472d4
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
NAME
|
||||
client.go
|
||||
|
||||
DESCRIPTION
|
||||
Client.go provides an implemntation of a basic RTCP Client that will send
|
||||
receiver reports, and receive sender reports to parse relevant statistics.
|
||||
|
||||
AUTHORS
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
senderSSRC = 1 // Any non-zero value will do.
|
||||
defaultClientName = "Client"
|
||||
delayUnit = 1.0 / 65536.0
|
||||
pkg = "rtcp: "
|
||||
rtcpVer = 2
|
||||
receiverBufSize = 200
|
||||
)
|
||||
|
||||
// Log describes a function signature required by the RTCP for the purpose of
|
||||
// logging.
|
||||
type Log func(lvl int8, msg string, args ...interface{})
|
||||
|
||||
// Client is an RTCP Client that will handle receiving SenderReports from a server
|
||||
// and sending out ReceiverReports.
|
||||
type Client struct {
|
||||
cAddr *net.UDPAddr // Address of client.
|
||||
sAddr *net.UDPAddr // Address of RTSP server.
|
||||
name string // Name of the client for source description purposes.
|
||||
sourceSSRC uint32 // Source identifier of this client.
|
||||
mu sync.Mutex // Will be used to change parameters during operation safely.
|
||||
seq uint32 // Last RTP sequence number.
|
||||
senderTs [8]byte // The timestamp of the last sender report.
|
||||
interval time.Duration // Interval between sender report and receiver report.
|
||||
receiveTime time.Time // Time last sender report was received.
|
||||
buf [receiverBufSize]byte // Buf used to store the receiver report and source descriptions.
|
||||
conn *net.UDPConn // The UDP connection used for receiving and sending RTSP packets.
|
||||
wg sync.WaitGroup // This is used to wait for send and recv routines to stop when Client is stopped.
|
||||
quit chan struct{} // Channel used to communicate quit signal to send and recv routines.
|
||||
log Log // Used to log any messages.
|
||||
|
||||
err chan error // Client will send any errors through this chan. Can be accessed by Err().
|
||||
}
|
||||
|
||||
// NewClient returns a pointer to a new Client.
|
||||
func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32, l Log) (*Client, error) {
|
||||
if name == "" {
|
||||
name = defaultClientName
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
name: name,
|
||||
err: make(chan error),
|
||||
quit: make(chan struct{}),
|
||||
interval: sendInterval,
|
||||
sourceSSRC: rtpSSRC,
|
||||
log: l,
|
||||
}
|
||||
|
||||
var err error
|
||||
c.cAddr, err = net.ResolveUDPAddr("udp", clientAddress)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("can't resolve Client address, failed with error: %v\n", err))
|
||||
}
|
||||
|
||||
c.sAddr, err = net.ResolveUDPAddr("udp", serverAddress)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("can't resolve server address, failed with error: %v\n", err))
|
||||
}
|
||||
|
||||
c.conn, err = net.DialUDP("udp", c.cAddr, c.sAddr)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("can't dial, failed with error: %v\n", err))
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Start starts the listen and send routines. This will start the process of
|
||||
// receiving and parsing sender reports, and the process of sending receiver
|
||||
// reports to the server.
|
||||
func (c *Client) Start() {
|
||||
c.log(logger.Debug, pkg+"Client is starting")
|
||||
c.wg.Add(2)
|
||||
go c.recv()
|
||||
go c.send()
|
||||
}
|
||||
|
||||
// Stop sends a quit signal to the send and receive routines and closes the
|
||||
// UDP connection. It will wait until both routines have returned.
|
||||
func (c *Client) Stop() {
|
||||
c.log(logger.Debug, pkg+"Client is stopping")
|
||||
close(c.quit)
|
||||
c.conn.Close()
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
// Err provides read access to the Client err channel. This must be checked
|
||||
// otherwise the client will block if an error encountered.
|
||||
func (c *Client) Err() <-chan error {
|
||||
return c.err
|
||||
}
|
||||
|
||||
// recv reads from the UDP connection and parses SenderReports.
|
||||
func (c *Client) recv() {
|
||||
defer c.wg.Done()
|
||||
c.log(logger.Debug, pkg+"Client is receiving")
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
select {
|
||||
case <-c.quit:
|
||||
return
|
||||
default:
|
||||
n, _, err := c.conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
c.err <- err
|
||||
continue
|
||||
}
|
||||
c.log(logger.Debug, pkg+"sender report received", "report", buf[:n])
|
||||
c.parse(buf[:n])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send writes receiver reports to the server.
|
||||
func (c *Client) send() {
|
||||
defer c.wg.Done()
|
||||
c.log(logger.Debug, pkg+"Client is sending")
|
||||
for {
|
||||
select {
|
||||
case <-c.quit:
|
||||
return
|
||||
default:
|
||||
time.Sleep(c.interval)
|
||||
|
||||
report := ReceiverReport{
|
||||
Header: Header{
|
||||
Version: rtcpVer,
|
||||
Padding: false,
|
||||
ReportCount: 1,
|
||||
Type: typeReceiverReport,
|
||||
},
|
||||
SenderSSRC: senderSSRC,
|
||||
Blocks: []ReportBlock{
|
||||
ReportBlock{
|
||||
SourceIdentifier: c.sourceSSRC,
|
||||
FractionLost: 0,
|
||||
PacketsLost: math.MaxUint32,
|
||||
HighestSequence: c.sequence(),
|
||||
Jitter: c.jitter(),
|
||||
SenderReportTs: c.lastSenderTs(),
|
||||
SenderReportDelay: c.delay(),
|
||||
},
|
||||
},
|
||||
Extensions: nil,
|
||||
}
|
||||
|
||||
description := Description{
|
||||
Header: Header{
|
||||
Version: rtcpVer,
|
||||
Padding: false,
|
||||
ReportCount: 1,
|
||||
Type: typeDescription,
|
||||
},
|
||||
Chunks: []Chunk{
|
||||
Chunk{
|
||||
SSRC: senderSSRC,
|
||||
Items: []SDESItem{
|
||||
SDESItem{
|
||||
Type: typeCName,
|
||||
Text: []byte(c.name),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c.log(logger.Debug, pkg+"sending receiver report")
|
||||
_, err := c.conn.Write(c.formPayload(&report, &description))
|
||||
if err != nil {
|
||||
c.err <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formPayload takes a pointer to a ReceiverReport and a pointer to a
|
||||
// Source Description and calls Bytes on both, writing to the underlying Client
|
||||
// buf. A slice to the combined writtem memory is returned.
|
||||
func (c *Client) formPayload(r *ReceiverReport, d *Description) []byte {
|
||||
rl := len(r.Bytes(c.buf[:]))
|
||||
dl := len(d.Bytes(c.buf[rl:]))
|
||||
t := rl + dl
|
||||
if t > cap(c.buf) {
|
||||
panic("Client buf not big enough")
|
||||
}
|
||||
return c.buf[:t]
|
||||
}
|
||||
|
||||
// parse will read important statistics from sender reports.
|
||||
func (c *Client) parse(buf []byte) {
|
||||
c.markReceivedTime()
|
||||
t, err := ParseTimestamp(buf)
|
||||
if err != nil {
|
||||
c.err <- fmt.Errorf("could not get timestamp from sender report, failed with error: %v", err)
|
||||
}
|
||||
c.setSenderTs(t)
|
||||
}
|
||||
|
||||
// SetSequence will allow updating of the highest sequence number received
|
||||
// through an RTP stream.
|
||||
func (c *Client) SetSequence(s uint32) {
|
||||
c.mu.Lock()
|
||||
c.seq = s
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// sequence will return the highest sequence number received through RTP.
|
||||
func (c *Client) sequence() uint32 {
|
||||
c.mu.Lock()
|
||||
s := c.seq
|
||||
c.mu.Unlock()
|
||||
return s
|
||||
}
|
||||
|
||||
// jitter returns the interarrival jitter as described by RTCP specifications:
|
||||
// https://tools.ietf.org/html/rfc3550
|
||||
// TODO(saxon): complete this.
|
||||
func (c *Client) jitter() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// setSenderTs allows us to safely set the current sender report timestamp.
|
||||
func (c *Client) setSenderTs(t Timestamp) {
|
||||
c.mu.Lock()
|
||||
binary.BigEndian.PutUint32(c.senderTs[:], t.Seconds)
|
||||
binary.BigEndian.PutUint32(c.senderTs[4:], t.Fraction)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// lastSenderTs returns the timestamp of the most recent sender report.
|
||||
func (c *Client) lastSenderTs() uint32 {
|
||||
c.mu.Lock()
|
||||
t := binary.BigEndian.Uint32(c.senderTs[2:])
|
||||
c.mu.Unlock()
|
||||
return t
|
||||
}
|
||||
|
||||
// delay returns the duration between the receive time of the last sender report
|
||||
// and now. This is called when forming a receiver report.
|
||||
func (c *Client) delay() uint32 {
|
||||
c.mu.Lock()
|
||||
t := c.receiveTime
|
||||
c.mu.Unlock()
|
||||
return uint32(time.Now().Sub(t).Seconds() / delayUnit)
|
||||
}
|
||||
|
||||
// markReceivedTime is called when a sender report is received to mark the receive time.
|
||||
func (c *Client) markReceivedTime() {
|
||||
c.mu.Lock()
|
||||
c.receiveTime = time.Now()
|
||||
c.mu.Unlock()
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
NAME
|
||||
client_test.go
|
||||
|
||||
DESCRIPTION
|
||||
client_test.go contains testing utilities for functionality provided in client.go.
|
||||
|
||||
AUTHORS
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
// TestFromPayload checks that formPayload is working as expected.
|
||||
func TestFormPayload(t *testing.T) {
|
||||
// Expected data from a valid RTCP packet.
|
||||
expect := []byte{
|
||||
0x81, 0xc9, 0x00, 0x07,
|
||||
0xd6, 0xe0, 0x98, 0xda,
|
||||
0x6f, 0xad, 0x40, 0xc6,
|
||||
0x00, 0xff, 0xff, 0xff,
|
||||
0x00, 0x01, 0x83, 0x08,
|
||||
0x00, 0x00, 0x00, 0x20,
|
||||
0xb9, 0xe1, 0x25, 0x2a,
|
||||
0x00, 0x00, 0x2b, 0xf9,
|
||||
0x81, 0xca, 0x00, 0x04,
|
||||
0xd6, 0xe0, 0x98, 0xda,
|
||||
0x01, 0x08, 0x73, 0x61,
|
||||
0x78, 0x6f, 0x6e, 0x2d,
|
||||
0x70, 0x63, 0x00, 0x00,
|
||||
}
|
||||
|
||||
report := ReceiverReport{
|
||||
Header: Header{
|
||||
Version: 2,
|
||||
Padding: false,
|
||||
ReportCount: 1,
|
||||
Type: typeReceiverReport,
|
||||
},
|
||||
SenderSSRC: 3605043418,
|
||||
Blocks: []ReportBlock{
|
||||
ReportBlock{
|
||||
SourceIdentifier: 1873625286,
|
||||
FractionLost: 0,
|
||||
PacketsLost: math.MaxUint32,
|
||||
HighestSequence: 99080,
|
||||
Jitter: 32,
|
||||
SenderReportTs: 3118540074,
|
||||
SenderReportDelay: 11257,
|
||||
},
|
||||
},
|
||||
Extensions: nil,
|
||||
}
|
||||
|
||||
description := Description{
|
||||
Header: Header{
|
||||
Version: 2,
|
||||
Padding: false,
|
||||
ReportCount: 1,
|
||||
Type: typeDescription,
|
||||
},
|
||||
Chunks: []Chunk{
|
||||
Chunk{
|
||||
SSRC: 3605043418,
|
||||
Items: []SDESItem{
|
||||
SDESItem{
|
||||
Type: typeCName,
|
||||
Text: []byte("saxon-pc"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := &Client{}
|
||||
p := c.formPayload(&report, &description)
|
||||
|
||||
if !bytes.Equal(p, expect) {
|
||||
t.Fatalf("unexpected result.\nGot: %v\n Want: %v\n", p, expect)
|
||||
}
|
||||
|
||||
bufAddr := fmt.Sprintf("%p", c.buf[:])
|
||||
pAddr := fmt.Sprintf("%p", p)
|
||||
if bufAddr != pAddr {
|
||||
t.Errorf("unexpected result.\nGot: %v\n want: %v\n", pAddr, bufAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// dummyLogger will allow logging to be done by the testing pkg.
|
||||
type dummyLogger testing.T
|
||||
|
||||
func (dl *dummyLogger) log(lvl int8, msg string, args ...interface{}) {
|
||||
var l string
|
||||
switch lvl {
|
||||
case logger.Warning:
|
||||
l = "warning"
|
||||
case logger.Debug:
|
||||
l = "debug"
|
||||
case logger.Info:
|
||||
l = "info"
|
||||
case logger.Error:
|
||||
l = "error"
|
||||
case logger.Fatal:
|
||||
l = "fatal"
|
||||
}
|
||||
msg = l + ": " + msg
|
||||
for i := 0; i < len(args); i++ {
|
||||
msg += " %v"
|
||||
}
|
||||
if len(args) == 0 {
|
||||
dl.Log(msg + "\n")
|
||||
return
|
||||
}
|
||||
dl.Logf(msg+"\n", args)
|
||||
}
|
||||
|
||||
// TestReceiveAndSend tests basic RTCP client behaviour with a basic RTCP server.
|
||||
// The RTCP client will send through receiver reports, and the RTCP server will
|
||||
// respond with sender reports.
|
||||
func TestReceiveAndSend(t *testing.T) {
|
||||
const clientAddr, serverAddr = "localhost:8000", "localhost:8001"
|
||||
c, err := NewClient(
|
||||
clientAddr,
|
||||
serverAddr,
|
||||
"testClient",
|
||||
10*time.Millisecond,
|
||||
12345,
|
||||
(*dummyLogger)(t).log,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when creating client: %v\n", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case err := <-c.Err():
|
||||
const errConnClosed = "use of closed network connection"
|
||||
if !strings.Contains(err.Error(), errConnClosed) {
|
||||
t.Fatalf("error received from client error chan: %v\n", err)
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.Start()
|
||||
|
||||
sAddr, err := net.ResolveUDPAddr("udp", serverAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("could not resolve test server address, failed with error: %v", err)
|
||||
}
|
||||
|
||||
cAddr, err := net.ResolveUDPAddr("udp", clientAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("could not resolve client address, failed with error: %v", err)
|
||||
}
|
||||
|
||||
conn, err := net.DialUDP("udp", sAddr, cAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("could not dial, failed with error: %v\n", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
for i := 0; i < 5; i++ {
|
||||
t.Log("SERVER: waiting for receiver report\n")
|
||||
n, _, _ := conn.ReadFromUDP(buf)
|
||||
t.Logf("SERVER: receiver report received: \n%v\n", buf[:n])
|
||||
|
||||
c.SetSequence(uint32(i))
|
||||
|
||||
now := time.Now().Second()
|
||||
var time [8]byte
|
||||
binary.BigEndian.PutUint64(time[:], uint64(now))
|
||||
msw := binary.BigEndian.Uint32(time[:4])
|
||||
lsw := binary.BigEndian.Uint32(time[4:])
|
||||
|
||||
report := SenderReport{
|
||||
Header: Header{
|
||||
Version: rtcpVer,
|
||||
Padding: false,
|
||||
ReportCount: 0,
|
||||
Type: typeSenderReport,
|
||||
},
|
||||
SSRC: 1234567,
|
||||
TimestampMSW: msw,
|
||||
TimestampLSW: lsw,
|
||||
RTPTimestamp: 0,
|
||||
PacketCount: 0,
|
||||
OctetCount: 0,
|
||||
}
|
||||
r := report.Bytes()
|
||||
t.Logf("SERVER: sending sender report: \n%v\n", r)
|
||||
_, err := conn.Write(r)
|
||||
if err != nil {
|
||||
t.Errorf("did not expect error: %v\n", err)
|
||||
}
|
||||
}
|
||||
c.Stop()
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
NAME
|
||||
parse.go
|
||||
|
||||
DESCRIPTION
|
||||
parse.go contains functionality for parsing RTCP packets.
|
||||
|
||||
AUTHORS
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Timestamp describes an NTP timestamp, see https://tools.ietf.org/html/rfc1305
|
||||
type Timestamp struct {
|
||||
Seconds uint32
|
||||
Fraction uint32
|
||||
}
|
||||
|
||||
// Timestamp gets the timestamp from a receiver report and returns it as the most
|
||||
// significant word, and the least significant word. If the given bytes do not
|
||||
// represent a valid receiver report, an error is returned.
|
||||
func ParseTimestamp(buf []byte) (Timestamp, error) {
|
||||
if len(buf) < 4 {
|
||||
return Timestamp{}, errors.New("bad RTCP packet, not of sufficient length")
|
||||
}
|
||||
if (buf[0]&0xc0)>>6 != rtcpVer {
|
||||
return Timestamp{}, errors.New("incompatible RTCP version")
|
||||
}
|
||||
|
||||
if buf[1] != typeSenderReport {
|
||||
return Timestamp{}, errors.New("RTCP packet is not of sender report type")
|
||||
}
|
||||
|
||||
return Timestamp{
|
||||
Seconds: binary.BigEndian.Uint32(buf[8:]),
|
||||
Fraction: binary.BigEndian.Uint32(buf[12:]),
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
NAME
|
||||
parse_test.go
|
||||
|
||||
DESCRIPTION
|
||||
parse_test.go provides testing utilities for functionality found in parse.go.
|
||||
|
||||
AUTHORS
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestTimestamp checks that Timestamp correctly returns the most signicicant
|
||||
// word, and least signiciant word, of a receiver report timestamp.
|
||||
func TestTimestamp(t *testing.T) {
|
||||
const expectedMSW = 2209003992
|
||||
const expectedLSW = 1956821460
|
||||
report := []byte{
|
||||
0x80, 0xc8, 0x00, 0x06,
|
||||
0x6f, 0xad, 0x40, 0xc6,
|
||||
0x83, 0xaa, 0xb9, 0xd8, // Most significant word of timestamp (2209003992)
|
||||
0x74, 0xa2, 0xb9, 0xd4, // Least significant word of timestamp (1956821460)
|
||||
0x4b, 0x1c, 0x5a, 0xa5,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x01, 0xc2, 0xc5,
|
||||
}
|
||||
|
||||
ts, err := ParseTimestamp(report)
|
||||
if err != nil {
|
||||
t.Fatalf("did not expect error: %v", err)
|
||||
}
|
||||
|
||||
if ts.Seconds != expectedMSW {
|
||||
t.Errorf("most significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.Seconds, expectedMSW)
|
||||
}
|
||||
|
||||
if ts.Fraction != expectedLSW {
|
||||
t.Errorf("least significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.Fraction, expectedLSW)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
NAME
|
||||
rtcp.go
|
||||
|
||||
DESCRIPTION
|
||||
rtcp.go contains structs to describe RTCP packets, and functionality to form
|
||||
[]bytes of these structs.
|
||||
|
||||
AUTHORS
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
// Package RTCP provides RTCP data structures and a client for communicating
|
||||
// with an RTCP service.
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// RTCP packet types.
|
||||
const (
|
||||
typeSenderReport = 200
|
||||
typeReceiverReport = 201
|
||||
typeDescription = 202
|
||||
)
|
||||
|
||||
// Source Description Item types.
|
||||
const (
|
||||
typeCName = 1
|
||||
)
|
||||
|
||||
const (
|
||||
reportBlockSize = 6
|
||||
senderReportSize = 28
|
||||
)
|
||||
|
||||
// ReceiverReport describes an RTCP receiver report packet.
|
||||
type ReceiverReport struct {
|
||||
Header // Standard RTCP packet header.
|
||||
SenderSSRC uint32 // SSRC of the sender of this report.
|
||||
Blocks []ReportBlock // Report blocks.
|
||||
Extensions [][4]byte // Contains any extensions to the packet.
|
||||
}
|
||||
|
||||
// Bytes returns a []byte of the ReceiverReport r.
|
||||
func (r *ReceiverReport) Bytes(buf []byte) []byte {
|
||||
l := 8 + 4*reportBlockSize*len(r.Blocks) + 4*len(r.Extensions)
|
||||
if buf == nil || cap(buf) < l {
|
||||
buf = make([]byte, l)
|
||||
}
|
||||
buf = buf[:l]
|
||||
l = 1 + reportBlockSize*len(r.Blocks) + len(r.Extensions)
|
||||
r.writeHeader(buf, l)
|
||||
binary.BigEndian.PutUint32(buf[4:], r.SenderSSRC)
|
||||
|
||||
idx := 8
|
||||
for _, b := range r.Blocks {
|
||||
binary.BigEndian.PutUint32(buf[idx:], b.SourceIdentifier)
|
||||
binary.BigEndian.PutUint32(buf[idx+4:], b.PacketsLost)
|
||||
buf[idx+4] = b.FractionLost
|
||||
binary.BigEndian.PutUint32(buf[idx+8:], b.HighestSequence)
|
||||
binary.BigEndian.PutUint32(buf[idx+12:], b.Jitter)
|
||||
binary.BigEndian.PutUint32(buf[idx+16:], b.SenderReportTs)
|
||||
binary.BigEndian.PutUint32(buf[idx+20:], b.SenderReportDelay)
|
||||
idx += 24
|
||||
}
|
||||
|
||||
for _, e := range r.Extensions {
|
||||
copy(buf[idx:], e[:])
|
||||
idx += 4
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// ReportBlock describes an RTCP report block used in Sender/Receiver Reports.
|
||||
type ReportBlock struct {
|
||||
SourceIdentifier uint32 // Source identifier.
|
||||
FractionLost uint8 // Fraction of packets lost.
|
||||
PacketsLost uint32 // Cumulative number of packets lost.
|
||||
HighestSequence uint32 // Extended highest sequence number received.
|
||||
Jitter uint32 // Interarrival jitter.
|
||||
SenderReportTs uint32 // Last sender report timestamp.
|
||||
SenderReportDelay uint32 // Delay since last sender report.
|
||||
}
|
||||
|
||||
// Description describes a source description RTCP packet.
|
||||
type Description struct {
|
||||
Header // Standard RTCP packet header.
|
||||
Chunks []Chunk // Chunks to describe items of each SSRC.
|
||||
}
|
||||
|
||||
// Bytes returns an []byte of the Description d.
|
||||
func (d *Description) Bytes(buf []byte) []byte {
|
||||
bodyLen := d.bodyLen()
|
||||
rem := bodyLen % 4
|
||||
if rem != 0 {
|
||||
bodyLen += 4 - rem
|
||||
}
|
||||
|
||||
l := 4 + bodyLen
|
||||
if buf == nil || cap(buf) < l {
|
||||
buf = make([]byte, l)
|
||||
}
|
||||
buf = buf[:l]
|
||||
|
||||
d.writeHeader(buf, bodyLen/4)
|
||||
idx := 4
|
||||
for _, c := range d.Chunks {
|
||||
binary.BigEndian.PutUint32(buf[idx:], c.SSRC)
|
||||
idx += 4
|
||||
for _, i := range c.Items {
|
||||
buf[idx] = i.Type
|
||||
buf[idx+1] = byte(len(i.Text))
|
||||
idx += 2
|
||||
copy(buf[idx:], i.Text)
|
||||
idx += len(i.Text)
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// bodyLen calculates the body length of a source description packet in bytes.
|
||||
func (d *Description) bodyLen() int {
|
||||
var l int
|
||||
for _, c := range d.Chunks {
|
||||
l += c.len()
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// SenderReport describes an RTCP sender report.
|
||||
type SenderReport struct {
|
||||
Header // Standard RTCP header.
|
||||
SSRC uint32 // SSRC of sender.
|
||||
TimestampMSW uint32 // Most significant word of timestamp.
|
||||
TimestampLSW uint32 // Least significant word of timestamp.
|
||||
RTPTimestamp uint32 // Current RTP timestamp.
|
||||
PacketCount uint32 // Senders packet count.
|
||||
OctetCount uint32 // Senders octet count.
|
||||
|
||||
// Report blocks (unimplemented)
|
||||
// ...
|
||||
}
|
||||
|
||||
// Bytes returns a []byte of the SenderReport.
|
||||
func (r *SenderReport) Bytes() []byte {
|
||||
buf := make([]byte, senderReportSize)
|
||||
r.writeHeader(buf, senderReportSize-1)
|
||||
for i, w := range []uint32{
|
||||
r.SSRC,
|
||||
r.TimestampMSW,
|
||||
r.TimestampLSW,
|
||||
r.RTPTimestamp,
|
||||
r.PacketCount,
|
||||
r.OctetCount,
|
||||
} {
|
||||
binary.BigEndian.PutUint32(buf[i+4:], w)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// Header describes a standard RTCP packet header.
|
||||
type Header struct {
|
||||
Version uint8 // RTCP version.
|
||||
Padding bool // Padding indicator.
|
||||
ReportCount uint8 // Number of reports contained.
|
||||
Type uint8 // Type of RTCP packet.
|
||||
}
|
||||
|
||||
// SDESItem describes a source description item.
|
||||
type SDESItem struct {
|
||||
Type uint8 // Type of item.
|
||||
Text []byte // Item text.
|
||||
}
|
||||
|
||||
// Chunk describes a source description chunk for a given SSRC.
|
||||
type Chunk struct {
|
||||
SSRC uint32 // SSRC of the source being described by the below items.
|
||||
Items []SDESItem // Items describing the source.
|
||||
}
|
||||
|
||||
// len returns the len of a chunk in bytes.
|
||||
func (c *Chunk) len() int {
|
||||
tot := 4
|
||||
for _, i := range c.Items {
|
||||
tot += 2 + len(i.Text)
|
||||
}
|
||||
return tot
|
||||
}
|
||||
|
||||
// writeHeader writes the standard RTCP header given a buffer to write to and l
|
||||
// the RTCP body length that needs to be encoded into the header.
|
||||
func (h Header) writeHeader(buf []byte, l int) {
|
||||
buf[0] = h.Version<<6 | asByte(h.Padding)<<5 | 0x1f&h.ReportCount
|
||||
buf[1] = h.Type
|
||||
binary.BigEndian.PutUint16(buf[2:], uint16(l))
|
||||
}
|
||||
|
||||
func asByte(b bool) byte {
|
||||
if b {
|
||||
return 0x01
|
||||
}
|
||||
return 0x00
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
NAME
|
||||
rtcp_test.go
|
||||
|
||||
DESCRIPTION
|
||||
rtcp_test.go contains testing utilities for functionality provided in rtcp_test.go.
|
||||
|
||||
AUTHORS
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
This is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
It is free software: you can redistribute it and/or modify them
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation, either version 3 of the License, or (at your
|
||||
option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package rtcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestReceiverReportBytes checks that we can correctly obtain a []byte of an
|
||||
// RTCP receiver report from the struct representation.
|
||||
func TestReceiverReportBytes(t *testing.T) {
|
||||
expect := []byte{
|
||||
0x81, 0xc9, 0x00, 0x07,
|
||||
0xd6, 0xe0, 0x98, 0xda,
|
||||
0x6f, 0xad, 0x40, 0xc6,
|
||||
0x00, 0xff, 0xff, 0xff,
|
||||
0x00, 0x01, 0x83, 0x08,
|
||||
0x00, 0x00, 0x00, 0x20,
|
||||
0xb9, 0xe1, 0x25, 0x2a,
|
||||
0x00, 0x00, 0x2b, 0xf9,
|
||||
}
|
||||
|
||||
report := ReceiverReport{
|
||||
Header: Header{
|
||||
Version: 2,
|
||||
Padding: false,
|
||||
ReportCount: 1,
|
||||
Type: typeReceiverReport,
|
||||
},
|
||||
SenderSSRC: 3605043418,
|
||||
Blocks: []ReportBlock{
|
||||
ReportBlock{
|
||||
SourceIdentifier: 1873625286,
|
||||
FractionLost: 0,
|
||||
PacketsLost: math.MaxUint32,
|
||||
HighestSequence: 99080,
|
||||
Jitter: 32,
|
||||
SenderReportTs: 3118540074,
|
||||
SenderReportDelay: 11257,
|
||||
},
|
||||
},
|
||||
Extensions: nil,
|
||||
}
|
||||
|
||||
got := report.Bytes(nil)
|
||||
if !bytes.Equal(got, expect) {
|
||||
t.Errorf("did not get expected result. \nGot: %v\nWant: %v\n", got, expect)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSourceDescriptionBytes checks that we can correctly obtain a []byte of an
|
||||
// RTCP source description from the struct representation.
|
||||
func TestSourceDescriptionBytes(t *testing.T) {
|
||||
expect := []byte{
|
||||
0x81, 0xca, 0x00, 0x04,
|
||||
0xd6, 0xe0, 0x98, 0xda,
|
||||
0x01, 0x08, 0x73, 0x61,
|
||||
0x78, 0x6f, 0x6e, 0x2d,
|
||||
0x70, 0x63, 0x00, 0x00,
|
||||
}
|
||||
|
||||
description := Description{
|
||||
Header: Header{
|
||||
Version: 2,
|
||||
Padding: false,
|
||||
ReportCount: 1,
|
||||
Type: typeDescription,
|
||||
},
|
||||
Chunks: []Chunk{
|
||||
Chunk{
|
||||
SSRC: 3605043418,
|
||||
Items: []SDESItem{
|
||||
SDESItem{
|
||||
Type: typeCName,
|
||||
Text: []byte("saxon-pc"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
got := description.Bytes(nil)
|
||||
if !bytes.Equal(got, expect) {
|
||||
t.Errorf("Did not get expected result.\nGot: %v\n Want: %v\n", got, expect)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue