From 83122d0ac5c3ab3c9ca6a6e9172c78082748f206 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 9 Apr 2019 12:32:12 +0930 Subject: [PATCH 01/47] protocol/rtsp: adding beatgammit's rtsp pkg to our codebase Much of the functionality we require for rtsp input is here, but some modification will be required to fix some bugs and get it functional; which will come in later PRs. --- protocol/rtsp/LICENSE.BSD | 12 + protocol/rtsp/README.md | 11 + protocol/rtsp/rtcp/rtcp.go | 11 + protocol/rtsp/rtp/rtp.go | 150 ++++++++++++ protocol/rtsp/rtp/rtp_test.go | 21 ++ protocol/rtsp/rtsp-util/main.go | 82 +++++++ protocol/rtsp/rtsp.go | 422 ++++++++++++++++++++++++++++++++ protocol/rtsp/sdp.go | 73 ++++++ 8 files changed, 782 insertions(+) create mode 100644 protocol/rtsp/LICENSE.BSD create mode 100644 protocol/rtsp/README.md create mode 100644 protocol/rtsp/rtcp/rtcp.go create mode 100644 protocol/rtsp/rtp/rtp.go create mode 100644 protocol/rtsp/rtp/rtp_test.go create mode 100644 protocol/rtsp/rtsp-util/main.go create mode 100644 protocol/rtsp/rtsp.go create mode 100644 protocol/rtsp/sdp.go diff --git a/protocol/rtsp/LICENSE.BSD b/protocol/rtsp/LICENSE.BSD new file mode 100644 index 00000000..1098d8aa --- /dev/null +++ b/protocol/rtsp/LICENSE.BSD @@ -0,0 +1,12 @@ +Copyright (c) 2015, T. Jameson Little +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/protocol/rtsp/README.md b/protocol/rtsp/README.md new file mode 100644 index 00000000..ec9c6164 --- /dev/null +++ b/protocol/rtsp/README.md @@ -0,0 +1,11 @@ +rtsp +==== + +`rtsp` implements RTSP in Go. The development focus is for video streaming from security cameras, but the library is developed such that it should be useful for any type of stream. + +Currently, `rtp` and `rtcp` are implemented as sub-packages, but this will likely change once the library matures. + +License +======= + +`rtsp` is licensed under the BSD 3-clause license. See LICENSE.BSD for details. diff --git a/protocol/rtsp/rtcp/rtcp.go b/protocol/rtsp/rtcp/rtcp.go new file mode 100644 index 00000000..c45766b8 --- /dev/null +++ b/protocol/rtsp/rtcp/rtcp.go @@ -0,0 +1,11 @@ +package rtcp + +import ( + "io" + "io/ioutil" +) + +func Handle(r io.Reader) error { + io.Copy(ioutil.Discard, r) + return nil +} diff --git a/protocol/rtsp/rtp/rtp.go b/protocol/rtsp/rtp/rtp.go new file mode 100644 index 00000000..8d2712c2 --- /dev/null +++ b/protocol/rtsp/rtp/rtp.go @@ -0,0 +1,150 @@ +package rtp + +import ( + "fmt" + "net" +) + +const ( + RTP_VERSION = 2 +) + +const ( + hasRtpPadding = 1 << 2 + hasRtpExt = 1 << 3 +) + +// Packet as per https://tools.ietf.org/html/rfc1889#section-5.1 +// +// 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 | +// | .... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type RtpPacket struct { + Version byte + Padding bool + Ext bool + Marker bool + PayloadType byte + SequenceNumber uint + Timestamp uint + SyncSource uint + + CSRC []uint + + ExtHeader uint + ExtData []byte + + Payload []byte +} + +type Session struct { + Rtp net.PacketConn + Rtcp net.PacketConn + + RtpChan <-chan RtpPacket + RtcpChan <-chan []byte + + rtpChan chan<- RtpPacket + rtcpChan chan<- []byte +} + +func New(rtp, rtcp net.PacketConn) *Session { + rtpChan := make(chan RtpPacket, 10) + rtcpChan := make(chan []byte, 10) + s := &Session{ + Rtp: rtp, + Rtcp: rtcp, + RtpChan: rtpChan, + RtcpChan: rtcpChan, + rtpChan: rtpChan, + rtcpChan: rtcpChan, + } + go s.HandleRtpConn(rtp) + go s.HandleRtcpConn(rtcp) + return s +} + +func toUint(arr []byte) (ret uint) { + for i, b := range arr { + ret |= uint(b) << (8 * uint(len(arr)-i-1)) + } + return ret +} + +func (s *Session) HandleRtpConn(conn net.PacketConn) { + buf := make([]byte, 4096) + for { + n, _, err := conn.ReadFrom(buf) + if err != nil { + panic(err) + } + + cpy := make([]byte, n) + copy(cpy, buf) + go s.handleRtp(cpy) + } +} + +func (s *Session) HandleRtcpConn(conn net.PacketConn) { + buf := make([]byte, 4096) + for { + n, _, err := conn.ReadFrom(buf) + if err != nil { + panic(err) + } + cpy := make([]byte, n) + copy(cpy, buf) + go s.handleRtcp(cpy) + } +} + +func (s *Session) handleRtp(buf []byte) { + packet := RtpPacket{ + Version: buf[0] & 0x03, + Padding: buf[0]&hasRtpPadding != 0, + Ext: buf[0]&hasRtpExt != 0, + CSRC: make([]uint, buf[0]>>4), + Marker: buf[1]&1 != 0, + PayloadType: buf[1] >> 1, + SequenceNumber: toUint(buf[2:4]), + Timestamp: toUint(buf[4:8]), + SyncSource: toUint(buf[8:12]), + } + if packet.Version != RTP_VERSION { + fmt.Printf("version: %v\n", packet.Version) + } + + i := 12 + + for j := range packet.CSRC { + packet.CSRC[j] = toUint(buf[i : i+4]) + i += 4 + } + + if packet.Ext { + packet.ExtHeader = toUint(buf[i : i+2]) + length := toUint(buf[i+2 : i+4]) + i += 4 + if length > 0 { + packet.ExtData = buf[i : i+int(length)*4] + i += int(length) * 4 + } + } + + packet.Payload = buf[i:] + + s.rtpChan <- packet +} + +func (s *Session) handleRtcp(buf []byte) { + // TODO: implement rtcp +} diff --git a/protocol/rtsp/rtp/rtp_test.go b/protocol/rtsp/rtp/rtp_test.go new file mode 100644 index 00000000..da54d96c --- /dev/null +++ b/protocol/rtsp/rtp/rtp_test.go @@ -0,0 +1,21 @@ +package rtp + +import ( + "testing" +) + +func TestToUint(t *testing.T) { + tests := []struct { + arr []byte + exp uint + }{ + {[]byte{1, 2}, 0x102}, + {[]byte{3, 2, 1, 0}, 0x3020100}, + } + for _, tst := range tests { + val := toUint(tst.arr) + if val != tst.exp { + t.Errorf("%d != %d for % x", val, tst.exp, tst.arr) + } + } +} diff --git a/protocol/rtsp/rtsp-util/main.go b/protocol/rtsp/rtsp-util/main.go new file mode 100644 index 00000000..74c92e45 --- /dev/null +++ b/protocol/rtsp/rtsp-util/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io" + "log" + + "rtsp" +) + +func init() { + flag.Parse() +} + +const sampleRequest = `OPTIONS rtsp://example.com/media.mp4 RTSP/1.0 +CSeq: 1 +Require: implicit-play +Proxy-Require: gzipped-messages + +` + +const sampleResponse = `RTSP/1.0 200 OK +CSeq: 1 +Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE + +` + +func main() { + if len(flag.Args()) >= 1 { + rtspUrl := flag.Args()[0] + + sess := rtsp.NewSession() + res, err := sess.Options(rtspUrl) + if err != nil { + log.Fatalln(err) + } + fmt.Println("Options:") + fmt.Println(res) + + res, err = sess.Describe(rtspUrl) + if err != nil { + log.Fatalln(err) + } + fmt.Println("Describe:") + fmt.Println(res) + + p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength}) + if err != nil { + log.Fatalln(err) + } + log.Printf("%+v", p) + + rtpPort, rtcpPort := 8000, 8001 + res, err = sess.Setup(rtspUrl, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort)) + if err != nil { + log.Fatalln(err) + } + log.Println(res) + + res, err = sess.Play(rtspUrl, res.Header.Get("Session")) + if err != nil { + log.Fatalln(err) + } + log.Println(res) + } else { + r, err := rtsp.ReadRequest(bytes.NewBufferString(sampleRequest)) + if err != nil { + fmt.Println(err) + } else { + fmt.Println(r) + } + + res, err := rtsp.ReadResponse(bytes.NewBufferString(sampleResponse)) + if err != nil { + fmt.Println(err) + } else { + fmt.Println(res) + } + } +} diff --git a/protocol/rtsp/rtsp.go b/protocol/rtsp/rtsp.go new file mode 100644 index 00000000..2e8427d7 --- /dev/null +++ b/protocol/rtsp/rtsp.go @@ -0,0 +1,422 @@ +package rtsp + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "strconv" + "strings" +) + +const ( + // Client to server for presentation and stream objects; recommended + DESCRIBE = "DESCRIBE" + // Bidirectional for client and stream objects; optional + ANNOUNCE = "ANNOUNCE" + // Bidirectional for client and stream objects; optional + GET_PARAMETER = "GET_PARAMETER" + // Bidirectional for client and stream objects; required for Client to server, optional for server to client + OPTIONS = "OPTIONS" + // Client to server for presentation and stream objects; recommended + PAUSE = "PAUSE" + // Client to server for presentation and stream objects; required + PLAY = "PLAY" + // Client to server for presentation and stream objects; optional + RECORD = "RECORD" + // Server to client for presentation and stream objects; optional + REDIRECT = "REDIRECT" + // Client to server for stream objects; required + SETUP = "SETUP" + // Bidirectional for presentation and stream objects; optional + SET_PARAMETER = "SET_PARAMETER" + // Client to server for presentation and stream objects; required + TEARDOWN = "TEARDOWN" +) + +const ( + // all requests + Continue = 100 + + // all requests + OK = 200 + // RECORD + Created = 201 + // RECORD + LowOnStorageSpace = 250 + + // all requests + MultipleChoices = 300 + // all requests + MovedPermanently = 301 + // all requests + MovedTemporarily = 302 + // all requests + SeeOther = 303 + // all requests + UseProxy = 305 + + // all requests + BadRequest = 400 + // all requests + Unauthorized = 401 + // all requests + PaymentRequired = 402 + // all requests + Forbidden = 403 + // all requests + NotFound = 404 + // all requests + MethodNotAllowed = 405 + // all requests + NotAcceptable = 406 + // all requests + ProxyAuthenticationRequired = 407 + // all requests + RequestTimeout = 408 + // all requests + Gone = 410 + // all requests + LengthRequired = 411 + // DESCRIBE, SETUP + PreconditionFailed = 412 + // all requests + RequestEntityTooLarge = 413 + // all requests + RequestURITooLong = 414 + // all requests + UnsupportedMediaType = 415 + // SETUP + Invalidparameter = 451 + // SETUP + IllegalConferenceIdentifier = 452 + // SETUP + NotEnoughBandwidth = 453 + // all requests + SessionNotFound = 454 + // all requests + MethodNotValidInThisState = 455 + // all requests + HeaderFieldNotValid = 456 + // PLAY + InvalidRange = 457 + // SET_PARAMETER + ParameterIsReadOnly = 458 + // all requests + AggregateOperationNotAllowed = 459 + // all requests + OnlyAggregateOperationAllowed = 460 + // all requests + UnsupportedTransport = 461 + // all requests + DestinationUnreachable = 462 + + // all requests + InternalServerError = 500 + // all requests + NotImplemented = 501 + // all requests + BadGateway = 502 + // all requests + ServiceUnavailable = 503 + // all requests + GatewayTimeout = 504 + // all requests + RTSPVersionNotSupported = 505 + // all requests + OptionNotsupport = 551 +) + +type ResponseWriter interface { + http.ResponseWriter +} + +type Request struct { + Method string + URL *url.URL + Proto string + ProtoMajor int + ProtoMinor int + Header http.Header + ContentLength int + Body io.ReadCloser +} + +func (r Request) String() string { + s := fmt.Sprintf("%s %s %s/%d.%d\r\n", r.Method, r.URL, r.Proto, r.ProtoMajor, r.ProtoMinor) + for k, v := range r.Header { + for _, v := range v { + s += fmt.Sprintf("%s: %s\r\n", k, v) + } + } + s += "\r\n" + if r.Body != nil { + str, _ := ioutil.ReadAll(r.Body) + s += string(str) + } + return s +} + +func NewRequest(method, urlStr, cSeq string, body io.ReadCloser) (*Request, error) { + u, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + req := &Request{ + Method: method, + URL: u, + Proto: "RTSP", + ProtoMajor: 1, + ProtoMinor: 0, + Header: map[string][]string{"CSeq": []string{cSeq}}, + Body: body, + } + return req, nil +} + +type Session struct { + cSeq int + conn net.Conn + session string +} + +func NewSession() *Session { + return &Session{} +} + +func (s *Session) nextCSeq() string { + s.cSeq++ + return strconv.Itoa(s.cSeq) +} + +func (s *Session) Describe(urlStr string) (*Response, error) { + req, err := NewRequest(DESCRIBE, urlStr, s.nextCSeq(), nil) + if err != nil { + panic(err) + } + + req.Header.Add("Accept", "application/sdp") + + if s.conn == nil { + s.conn, err = net.Dial("tcp", req.URL.Host) + if err != nil { + return nil, err + } + } + + _, err = io.WriteString(s.conn, req.String()) + if err != nil { + return nil, err + } + return ReadResponse(s.conn) +} + +func (s *Session) Options(urlStr string) (*Response, error) { + req, err := NewRequest(OPTIONS, urlStr, s.nextCSeq(), nil) + if err != nil { + panic(err) + } + + if s.conn == nil { + s.conn, err = net.Dial("tcp", req.URL.Host) + if err != nil { + return nil, err + } + } + + _, err = io.WriteString(s.conn, req.String()) + if err != nil { + return nil, err + } + return ReadResponse(s.conn) +} + +func (s *Session) Setup(urlStr, transport string) (*Response, error) { + req, err := NewRequest(SETUP, urlStr+"/track1", s.nextCSeq(), nil) + if err != nil { + panic(err) + } + + req.Header.Add("Transport", transport) + + if s.conn == nil { + s.conn, err = net.Dial("tcp", req.URL.Host) + if err != nil { + return nil, err + } + } + + _, err = io.WriteString(s.conn, req.String()) + if err != nil { + return nil, err + } + resp, err := ReadResponse(s.conn) + s.session = resp.Header.Get("Session") + return resp, err +} + +func (s *Session) Play(urlStr, sessionId string) (*Response, error) { + req, err := NewRequest(PLAY, urlStr, s.nextCSeq(), nil) + if err != nil { + panic(err) + } + + req.Header.Add("Session", sessionId) + + if s.conn == nil { + s.conn, err = net.Dial("tcp", req.URL.Host) + if err != nil { + return nil, err + } + } + + _, err = io.WriteString(s.conn, req.String()) + if err != nil { + return nil, err + } + return ReadResponse(s.conn) +} + +type closer struct { + *bufio.Reader + r io.Reader +} + +func (c closer) Close() error { + if c.Reader == nil { + return nil + } + defer func() { + c.Reader = nil + c.r = nil + }() + if r, ok := c.r.(io.ReadCloser); ok { + return r.Close() + } + return nil +} + +func ParseRTSPVersion(s string) (proto string, major int, minor int, err error) { + parts := strings.SplitN(s, "/", 2) + proto = parts[0] + parts = strings.SplitN(parts[1], ".", 2) + if major, err = strconv.Atoi(parts[0]); err != nil { + return + } + if minor, err = strconv.Atoi(parts[0]); err != nil { + return + } + return +} + +// super simple RTSP parser; would be nice if net/http would allow more general parsing +func ReadRequest(r io.Reader) (req *Request, err error) { + req = new(Request) + req.Header = make(map[string][]string) + + b := bufio.NewReader(r) + var s string + + // TODO: allow CR, LF, or CRLF + if s, err = b.ReadString('\n'); err != nil { + return + } + + parts := strings.SplitN(s, " ", 3) + req.Method = parts[0] + if req.URL, err = url.Parse(parts[1]); err != nil { + return + } + + req.Proto, req.ProtoMajor, req.ProtoMinor, err = ParseRTSPVersion(parts[2]) + if err != nil { + return + } + + // read headers + for { + if s, err = b.ReadString('\n'); err != nil { + return + } else if s = strings.TrimRight(s, "\r\n"); s == "" { + break + } + + parts := strings.SplitN(s, ":", 2) + req.Header.Add(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) + } + + req.ContentLength, _ = strconv.Atoi(req.Header.Get("Content-Length")) + fmt.Println("Content Length:", req.ContentLength) + req.Body = closer{b, r} + return +} + +type Response struct { + Proto string + ProtoMajor int + ProtoMinor int + + StatusCode int + Status string + + ContentLength int64 + + Header http.Header + Body io.ReadCloser +} + +func (res Response) String() string { + s := fmt.Sprintf("%s/%d.%d %d %s\n", res.Proto, res.ProtoMajor, res.ProtoMinor, res.StatusCode, res.Status) + for k, v := range res.Header { + for _, v := range v { + s += fmt.Sprintf("%s: %s\n", k, v) + } + } + return s +} + +func ReadResponse(r io.Reader) (res *Response, err error) { + res = new(Response) + res.Header = make(map[string][]string) + + b := bufio.NewReader(r) + var s string + + // TODO: allow CR, LF, or CRLF + if s, err = b.ReadString('\n'); err != nil { + return + } + + parts := strings.SplitN(s, " ", 3) + res.Proto, res.ProtoMajor, res.ProtoMinor, err = ParseRTSPVersion(parts[0]) + if err != nil { + return + } + + if res.StatusCode, err = strconv.Atoi(parts[1]); err != nil { + return + } + + res.Status = strings.TrimSpace(parts[2]) + + // read headers + for { + if s, err = b.ReadString('\n'); err != nil { + return + } else if s = strings.TrimRight(s, "\r\n"); s == "" { + break + } + + parts := strings.SplitN(s, ":", 2) + res.Header.Add(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) + } + + res.ContentLength, _ = strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64) + + res.Body = closer{b, r} + return +} diff --git a/protocol/rtsp/sdp.go b/protocol/rtsp/sdp.go new file mode 100644 index 00000000..7bb081ad --- /dev/null +++ b/protocol/rtsp/sdp.go @@ -0,0 +1,73 @@ +package rtsp + +import ( + "bufio" + "errors" + "io" + "strconv" + "strings" +) + +type SessionSection struct { + Version int + Originator string + SessionName string + SessionInformation string + URI string + Email string + Phone string + ConnectionInformation string + BandwidthInformation string +} + +func ParseSdp(r io.Reader) (SessionSection, error) { + var packet SessionSection + s := bufio.NewScanner(r) + for s.Scan() { + parts := strings.SplitN(s.Text(), "=", 2) + if len(parts) == 2 { + if len(parts[0]) != 1 { + return packet, errors.New("SDP only allows 1-character variables") + } + + switch parts[0] { + // version + case "v": + ver, err := strconv.Atoi(parts[1]) + if err != nil { + return packet, err + } + packet.Version = ver + // owner/creator and session identifier + case "o": + // o=
+ // TODO: parse this + packet.Originator = parts[1] + // session name + case "s": + packet.SessionName = parts[1] + // session information + case "i": + packet.SessionInformation = parts[1] + // URI of description + case "u": + packet.URI = parts[1] + // email address + case "e": + packet.Email = parts[1] + // phone number + case "p": + packet.Phone = parts[1] + // connection information - not required if included in all media + case "c": + // TODO: parse this + packet.ConnectionInformation = parts[1] + // bandwidth information + case "b": + // TODO: parse this + packet.BandwidthInformation = parts[1] + } + } + } + return packet, nil +} From 6b9f5526103fa37a6a017270deba4ba817b0308a Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 13:12:31 +0930 Subject: [PATCH 02/47] protocol/rtsp: removed rtsp-util/main.go and create cmd/stream/main.go cmd/stream/main.go will be a simple client that sets up an rtsp connection and asks for the server to stream. --- protocol/rtsp/cmd/stream/main.go | 54 +++++++++++++++++++++ protocol/rtsp/rtsp-util/main.go | 82 -------------------------------- 2 files changed, 54 insertions(+), 82 deletions(-) create mode 100644 protocol/rtsp/cmd/stream/main.go delete mode 100644 protocol/rtsp/rtsp-util/main.go diff --git a/protocol/rtsp/cmd/stream/main.go b/protocol/rtsp/cmd/stream/main.go new file mode 100644 index 00000000..7a9a5d87 --- /dev/null +++ b/protocol/rtsp/cmd/stream/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + + "bitbucket.org/ausocean/av/protocol/rtsp" +) + +func main() { + rtspServerPtr := flag.String("rtsp-server", "", "The RTSP server we would like to get video from") + clientPortPtr := flag.Uint("port", 6870, "The port on the client we would like to receive RTP on") + trackPtr := flag.String("track", "track1", "The track that we would like to receive media from") + flag.Parse() + + sess := rtsp.NewSession() + res, err := sess.Options(*rtspServerPtr) + if err != nil { + log.Fatalln(err) + } + fmt.Println("Options:") + fmt.Println(res) + + res, err = sess.Describe(*rtspServerPtr) + if err != nil { + log.Fatalln(err) + } + fmt.Println("Describe:") + fmt.Println(res) + + p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength}) + if err != nil { + log.Fatalln(err) + } + log.Printf("%+v", p) + + res, err = sess.Setup(*rtspServerPtr+"/"+*trackPtr, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", *clientPortPtr, *clientPortPtr+1)) + if err != nil { + log.Fatalln(err) + } + log.Println(res) + + res, err = sess.Play(*rtspServerPtr, res.Header.Get("Session")) + if err != nil { + log.Fatalln(err) + } + log.Println(res) + + // Send back rtcp + for { + } +} diff --git a/protocol/rtsp/rtsp-util/main.go b/protocol/rtsp/rtsp-util/main.go deleted file mode 100644 index 74c92e45..00000000 --- a/protocol/rtsp/rtsp-util/main.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "bytes" - "flag" - "fmt" - "io" - "log" - - "rtsp" -) - -func init() { - flag.Parse() -} - -const sampleRequest = `OPTIONS rtsp://example.com/media.mp4 RTSP/1.0 -CSeq: 1 -Require: implicit-play -Proxy-Require: gzipped-messages - -` - -const sampleResponse = `RTSP/1.0 200 OK -CSeq: 1 -Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE - -` - -func main() { - if len(flag.Args()) >= 1 { - rtspUrl := flag.Args()[0] - - sess := rtsp.NewSession() - res, err := sess.Options(rtspUrl) - if err != nil { - log.Fatalln(err) - } - fmt.Println("Options:") - fmt.Println(res) - - res, err = sess.Describe(rtspUrl) - if err != nil { - log.Fatalln(err) - } - fmt.Println("Describe:") - fmt.Println(res) - - p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength}) - if err != nil { - log.Fatalln(err) - } - log.Printf("%+v", p) - - rtpPort, rtcpPort := 8000, 8001 - res, err = sess.Setup(rtspUrl, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort)) - if err != nil { - log.Fatalln(err) - } - log.Println(res) - - res, err = sess.Play(rtspUrl, res.Header.Get("Session")) - if err != nil { - log.Fatalln(err) - } - log.Println(res) - } else { - r, err := rtsp.ReadRequest(bytes.NewBufferString(sampleRequest)) - if err != nil { - fmt.Println(err) - } else { - fmt.Println(r) - } - - res, err := rtsp.ReadResponse(bytes.NewBufferString(sampleResponse)) - if err != nil { - fmt.Println(err) - } else { - fmt.Println(res) - } - } -} From 1160985b2a0560b09b1cbd6134a06b49665be408 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 14:01:13 +0930 Subject: [PATCH 03/47] protocol/rtsp: fixing url usage --- protocol/rtsp/rtsp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/rtsp/rtsp.go b/protocol/rtsp/rtsp.go index 2e8427d7..8aae8a9c 100644 --- a/protocol/rtsp/rtsp.go +++ b/protocol/rtsp/rtsp.go @@ -236,7 +236,7 @@ func (s *Session) Options(urlStr string) (*Response, error) { } func (s *Session) Setup(urlStr, transport string) (*Response, error) { - req, err := NewRequest(SETUP, urlStr+"/track1", s.nextCSeq(), nil) + req, err := NewRequest(SETUP, urlStr, s.nextCSeq(), nil) if err != nil { panic(err) } @@ -260,7 +260,7 @@ func (s *Session) Setup(urlStr, transport string) (*Response, error) { } func (s *Session) Play(urlStr, sessionId string) (*Response, error) { - req, err := NewRequest(PLAY, urlStr, s.nextCSeq(), nil) + req, err := NewRequest(PLAY, urlStr+"/", s.nextCSeq(), nil) if err != nil { panic(err) } From 32d232908a5029ac1ea22d3c2bcb9ac27c37aa0e Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 15:19:45 +0930 Subject: [PATCH 04/47] protocol/rtsp/rtcp: start writing rtcp structs Wrote structure for Header, ReceiverReport, ReportBlocks, Chunks and Source Description. Wrote Bytes function for ReceiverReport. --- protocol/rtsp/rtcp/rtcp.go | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/protocol/rtsp/rtcp/rtcp.go b/protocol/rtsp/rtcp/rtcp.go index c45766b8..deca94fb 100644 --- a/protocol/rtsp/rtcp/rtcp.go +++ b/protocol/rtsp/rtcp/rtcp.go @@ -1,10 +1,98 @@ package rtcp import ( + "encoding/binary" "io" "io/ioutil" ) +const ( + reportBlockSize = 6 +) + +type Header struct { + Version uint8 // RTCP version. + Padding bool // Padding indicator. + ReportCount uint8 // Number of reports contained. + Type uint8 // Type of RTCP packet. + SSRC uint32 // Source identifier. +} + +type ReportBlock struct { + SSRC 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. + LSR uint32 // Last sender report timestamp. + DLSR uint32 // Delay since last sender report. +} + +type ReceiverReport struct { + Header + + Blocks []ReportBlock + Extensions [][4]byte +} + +func (r *ReceiverReport) Bytes() []byte { + l := 8 + 4*reportBlockSize*len(r.Blocks) + 4*len(r.Extensions) + buf := make([]byte, l) + + buf[0] = r.Version<<6 | asByte(r.Padding)<<5 | 0x1f&r.ReportCount + buf[1] = r.Type + + l = 1 + reportBlockSize*len(r.Blocks) + len(r.Extensions) + binary.BigEndian.PutUint16(buf[2:], uint16(l)) + + buf[4] = byte(r.SSRC) + + idx := 8 + for _, b := range r.Blocks { + binary.BigEndian.PutUint32(buf[idx:], b.SSRC) + idx += 4 + binary.BigEndian.PutUint32(buf[idx:], b.PacketsLost) + buf[idx] = b.FractionLost + idx += 4 + binary.BigEndian.PutUint32(buf[idx:], b.HighestSequence) + idx += 4 + binary.BigEndian.PutUint32(buf[idx:], b.Jitter) + idx += 4 + binary.BigEndian.PutUint32(buf[idx:], b.LSR) + idx += 4 + binary.BigEndian.PutUint32(buf[idx:], b.DLSR) + idx += 4 + } + + for _, e := range r.Extensions { + binary.BigEndian.PutUint32(buf[idx:], binary.BigEndian.Uint32(e[:])) + idx += 4 + } + + return buf +} + +type Chunk struct { + Type uint8 + Test []byte +} + +type SourceDescription struct { + Header + + Chunks []Chunk +} + +func (d *SourceDescription) Bytes() []byte { + +} + +func asByte(b bool) byte { + if b { + return 0x01 + } + return 0x00 +} func Handle(r io.Reader) error { io.Copy(ioutil.Discard, r) return nil From 5bdea4a09fb0802a3aa2a8066ebdc1998a99937b Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 15:22:38 +0930 Subject: [PATCH 05/47] protocol: moved rtcp protocol stuff to under protocol rather than under rtsp. --- protocol/{rtsp => }/rtcp/rtcp.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename protocol/{rtsp => }/rtcp/rtcp.go (100%) diff --git a/protocol/rtsp/rtcp/rtcp.go b/protocol/rtcp/rtcp.go similarity index 100% rename from protocol/rtsp/rtcp/rtcp.go rename to protocol/rtcp/rtcp.go From ddabd9afbf862c5cac2cc847950cd80924cd8322 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 16:29:40 +0930 Subject: [PATCH 06/47] protocol/rtcp: wrote Bytes for for SourceDescription type --- protocol/rtcp/rtcp.go | 100 +++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index deca94fb..bf82ab80 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -10,24 +10,6 @@ const ( reportBlockSize = 6 ) -type Header struct { - Version uint8 // RTCP version. - Padding bool // Padding indicator. - ReportCount uint8 // Number of reports contained. - Type uint8 // Type of RTCP packet. - SSRC uint32 // Source identifier. -} - -type ReportBlock struct { - SSRC 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. - LSR uint32 // Last sender report timestamp. - DLSR uint32 // Delay since last sender report. -} - type ReceiverReport struct { Header @@ -39,13 +21,8 @@ func (r *ReceiverReport) Bytes() []byte { l := 8 + 4*reportBlockSize*len(r.Blocks) + 4*len(r.Extensions) buf := make([]byte, l) - buf[0] = r.Version<<6 | asByte(r.Padding)<<5 | 0x1f&r.ReportCount - buf[1] = r.Type - l = 1 + reportBlockSize*len(r.Blocks) + len(r.Extensions) - binary.BigEndian.PutUint16(buf[2:], uint16(l)) - - buf[4] = byte(r.SSRC) + r.writeHeader(buf, l) idx := 8 for _, b := range r.Blocks { @@ -65,18 +42,13 @@ func (r *ReceiverReport) Bytes() []byte { } for _, e := range r.Extensions { - binary.BigEndian.PutUint32(buf[idx:], binary.BigEndian.Uint32(e[:])) + copy(buf[idx:], e[:]) idx += 4 } return buf } -type Chunk struct { - Type uint8 - Test []byte -} - type SourceDescription struct { Header @@ -84,7 +56,75 @@ type SourceDescription struct { } func (d *SourceDescription) Bytes() []byte { + bodyLen := d.bodyLen() + l := 8 + bodyLen + buf := make([]byte, l) + d.writeHeader(buf, bodyLen/4) + idx := 8 + for _, c := range d.Chunks { + binary.BigEndian.PutUint32(buf[idx:], c.SSRC) + idx += 4 + for _, i := range c.Items { + buf[idx] = i.Type + idx++ + buf[idx] = byte(len(i.Text)) + idx++ + copy(buf[idx:], i.Text) + idx += len(i.Text) + } + } + return buf +} +func (d *SourceDescription) bodyLen() int { + l := 0 + for _, c := range d.Chunks { + l += c.len() + } + return l +} + +type Header struct { + Version uint8 // RTCP version. + Padding bool // Padding indicator. + ReportCount uint8 // Number of reports contained. + Type uint8 // Type of RTCP packet. + SSRC uint32 // Source identifier. +} + +type ReportBlock struct { + SSRC 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. + LSR uint32 // Last sender report timestamp. + DLSR uint32 // Delay since last sender report. +} + +type SDESItem struct { + Type uint8 + Text []byte +} + +type Chunk struct { + SSRC uint32 + Items []SDESItem +} + +func (c *Chunk) len() int { + tot := 4 + for _, i := range c.Items { + tot += 2 + len(i.Text) + } + return tot +} + +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)) + buf[4] = byte(h.SSRC) } func asByte(b bool) byte { From 139d4b92aba16e533d9261ac397c0abe7063c04e Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 16:33:12 +0930 Subject: [PATCH 07/47] protocol/rtcp: added rtcp_test.go file with signatures for some likely tests --- protocol/rtcp/rtcp_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 protocol/rtcp/rtcp_test.go diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go new file mode 100644 index 00000000..dea8f93d --- /dev/null +++ b/protocol/rtcp/rtcp_test.go @@ -0,0 +1,11 @@ +package rtcp + +import "testing" + +func TestReceiverReportBytes(t *testing.T) { + +} + +func TestSourceDescriptionBytes(t *testing.T) { + +} From 956110f0ef36712d563d83e36e78e0306887cc5d Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 16:54:01 +0930 Subject: [PATCH 08/47] protocol/rtcp: wrote test for ReceiverReport.Bytes() This uses data from a "good" packet that vlc used during a stream from a geovision. I have filled the fields of a receiver report with the same data, and am checking that the result from Bytes() is the same as the good data. --- protocol/rtcp/rtcp.go | 9 ++++++-- protocol/rtcp/rtcp_test.go | 42 +++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index bf82ab80..aedb57e7 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -6,6 +6,11 @@ import ( "io/ioutil" ) +// RTCP packet types. +const ( + typeReceiverReport = 201 +) + const ( reportBlockSize = 6 ) @@ -89,7 +94,7 @@ type Header struct { Padding bool // Padding indicator. ReportCount uint8 // Number of reports contained. Type uint8 // Type of RTCP packet. - SSRC uint32 // Source identifier. + SenderSSRC uint32 // Source identifier. } type ReportBlock struct { @@ -124,7 +129,7 @@ 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)) - buf[4] = byte(h.SSRC) + buf[4] = byte(h.SenderSSRC) } func asByte(b bool) byte { diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index dea8f93d..17ed8c5a 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -1,9 +1,49 @@ package rtcp -import "testing" +import ( + "bytes" + "math" + "testing" +) 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{ + SSRC: 1873625286, + FractionLost: 0, + PacketsLost: math.MaxUint32, + HighestSequence: 99080, + Jitter: 32, + LSR: 3118540074, + DLSR: 11257, + }, + }, + Extensions: nil, + } + + got := report.Bytes() + if !bytes.Equal(got, expect) { + t.Errorf("did not get expected result. \nGot: %v\nWant: %v\n", got, expect) + } } func TestSourceDescriptionBytes(t *testing.T) { From 22d71f8a57780f9026c381ad29af53db78c1a13d Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 10 Apr 2019 17:12:00 +0930 Subject: [PATCH 09/47] protocol/rtcp: fixed writing of SenderSSRC in ReceiverReport header, now test passing. --- protocol/rtcp/rtcp.go | 2 +- protocol/rtcp/rtcp_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index aedb57e7..1c9892bb 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -129,7 +129,7 @@ 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)) - buf[4] = byte(h.SenderSSRC) + binary.BigEndian.PutUint32(buf[4:], h.SenderSSRC) } func asByte(b bool) byte { diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index 17ed8c5a..31588e53 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -41,6 +41,8 @@ func TestReceiverReportBytes(t *testing.T) { } got := report.Bytes() + t.Logf("Got: %v\n", got) + t.Logf("Want: %v\n", expect) if !bytes.Equal(got, expect) { t.Errorf("did not get expected result. \nGot: %v\nWant: %v\n", got, expect) } From db81547962a2bc4469117e81642f59a6a98d5168 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 12 Apr 2019 11:14:07 +0930 Subject: [PATCH 10/47] protocol/rtcp: wrote test for SourceDescription.Bytes() and made modifiations to make it pass. --- protocol/rtcp/rtcp.go | 28 +++++++++++++++++++--------- protocol/rtcp/rtcp_test.go | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 1c9892bb..0b6c9970 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -8,7 +8,13 @@ import ( // RTCP packet types. const ( - typeReceiverReport = 201 + typeReceiverReport = 201 + typeSourceDescription = 202 +) + +// SDES Item types. +const ( + typeCName = 1 ) const ( @@ -18,6 +24,7 @@ const ( type ReceiverReport struct { Header + SenderSSRC uint32 Blocks []ReportBlock Extensions [][4]byte } @@ -28,6 +35,7 @@ func (r *ReceiverReport) Bytes() []byte { 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 { @@ -62,10 +70,14 @@ type SourceDescription struct { func (d *SourceDescription) Bytes() []byte { bodyLen := d.bodyLen() - l := 8 + bodyLen + rem := bodyLen % 4 + if rem != 0 { + bodyLen += 4 - rem + } + l := 4 + bodyLen buf := make([]byte, l) d.writeHeader(buf, bodyLen/4) - idx := 8 + idx := 4 for _, c := range d.Chunks { binary.BigEndian.PutUint32(buf[idx:], c.SSRC) idx += 4 @@ -90,11 +102,10 @@ func (d *SourceDescription) bodyLen() int { } type Header struct { - Version uint8 // RTCP version. - Padding bool // Padding indicator. - ReportCount uint8 // Number of reports contained. - Type uint8 // Type of RTCP packet. - SenderSSRC uint32 // Source identifier. + Version uint8 // RTCP version. + Padding bool // Padding indicator. + ReportCount uint8 // Number of reports contained. + Type uint8 // Type of RTCP packet. } type ReportBlock struct { @@ -129,7 +140,6 @@ 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)) - binary.BigEndian.PutUint32(buf[4:], h.SenderSSRC) } func asByte(b bool) byte { diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index 31588e53..bafa59eb 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -24,8 +24,8 @@ func TestReceiverReportBytes(t *testing.T) { Padding: false, ReportCount: 1, Type: typeReceiverReport, - SenderSSRC: 3605043418, }, + SenderSSRC: 3605043418, Blocks: []ReportBlock{ ReportBlock{ SSRC: 1873625286, @@ -49,5 +49,37 @@ func TestReceiverReportBytes(t *testing.T) { } 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 := SourceDescription{ + Header: Header{ + Version: 2, + Padding: false, + ReportCount: 1, + Type: typeSourceDescription, + }, + Chunks: []Chunk{ + Chunk{ + SSRC: 3605043418, + Items: []SDESItem{ + SDESItem{ + Type: typeCName, + Text: []byte("saxon-pc"), + }, + }, + }, + }, + } + got := description.Bytes() + t.Logf("Got: %v\n", got) + t.Logf("Expect: %v\n", expect) + if !bytes.Equal(got, expect) { + t.Errorf("Did not get expected result.\nGot: %v\n Want: %v\n", got, expect) + } } From 81048d1613f72bb566ccf5e9205e8d37b73450ec Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 12 Apr 2019 16:29:35 +0930 Subject: [PATCH 11/47] protocol/rtcp/client.go: writing rtcp client Added client.go file which contains an rtcp client abstraction. This will listen for incoming sender reports and send out receiver reports. --- protocol/rtcp/client.go | 207 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 protocol/rtcp/client.go diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go new file mode 100644 index 00000000..6717540f --- /dev/null +++ b/protocol/rtcp/client.go @@ -0,0 +1,207 @@ +package rtcp + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "net" + "sync" + "time" +) + +const ( + senderSSRC = 3605043418 + defaultClientName = "client" + delayUnit = 1.0 / 65536.0 +) + +// client is an rtcp client that will hadle receiving SenderReports from a server +// and sending out ReceiverReports. +type client struct { + ErrChan chan error + cAddr *net.UDPAddr + sAddr *net.UDPAddr + name string + sourceSSRC uint32 + mu sync.Mutex + sequence uint32 + senderTs [64]byte + interval time.Duration + receiveTime time.Time +} + +// NewClient returns a pointer to a new client. +func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32) (*client, error) { + if name == "" { + name = defaultClientName + } + + c := &client{ + name: name, + ErrChan: make(chan error), + interval: sendInterval, + sourceSSRC: rtpSSRC, + } + + 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", 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", err)) + } + + return c, nil +} + +// Start starts the listen and send routines. This will start the process of +// receiving and parsing SenderReports, and the process of sending receiver +// reports to the server. +func (c *client) Start() { + go c.listen() + go c.send() +} + +// Listen reads from the UDP connection and parses SenderReports. +func (c *client) listen() { + conn, err := net.ListenUDP("udp", c.cAddr) + if err != nil { + c.ErrChan <- err + } + buf := make([]byte, 4096) + for { + n, _, _ := conn.ReadFromUDP(buf) + c.parse(buf[:n]) + } +} + +// send write sender reports to the server. +func (c *client) send() { + conn, err := net.DialUDP("udp", c.cAddr, c.sAddr) + if err != nil { + c.ErrChan <- err + } + for { + time.Sleep(c.interval) + report := ReceiverReport{ + Header: Header{ + Version: 2, + Padding: false, + ReportCount: 1, + Type: typeReceiverReport, + }, + SenderSSRC: senderSSRC, + Blocks: []ReportBlock{ + ReportBlock{ + SSRC: c.sourceSSRC, + FractionLost: 0, + PacketsLost: math.MaxUint32, + HighestSequence: c.highestSequence(), + Jitter: c.jitter(), + LSR: c.lastSenderTs(), + DLSR: c.delay(), + }, + }, + Extensions: nil, + } + + description := SourceDescription{ + Header: Header{ + Version: 2, + Padding: false, + ReportCount: 1, + Type: typeSourceDescription, + }, + Chunks: []Chunk{ + Chunk{ + SSRC: senderSSRC, + Items: []SDESItem{ + SDESItem{ + Type: typeCName, + Text: []byte(c.name), + }, + }, + }, + }, + } + + reportBytes := report.Bytes() + reportLen := len(reportBytes) + descriptionBytes := description.Bytes() + totalLength := reportLen + len(descriptionBytes) + bytes := make([]byte, totalLength) + copy(bytes, reportBytes) + copy(bytes[reportLen:], descriptionBytes) + + _, err := conn.Write(bytes) + if err != nil { + c.ErrChan <- err + } + } +} + +// parse will read important statistics from sender reports. +func (c *client) parse(buf []byte) { + c.received() + msw, err := TimestampMSW(buf) + if err != nil { + c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp msw from sender report, failed with error: %v", err)) + } + lsw, err := TimestampLSW(buf) + if err != nil { + c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp lsw from sender report, failed with error: %v", err)) + } + c.setSenderTs(msw, lsw) +} + +func (c *client) UpdateSequence(s uint32) { + c.mu.Lock() + c.sequence = s + c.mu.Unlock() +} + +func (c *client) highestSequence() uint32 { + var s uint32 + c.mu.Lock() + s = c.sequence + c.mu.Unlock() + return s +} + +func (c *client) jitter() uint32 { + return 0 +} + +func (c *client) setSenderTs(msw, lsw uint32) { + c.mu.Lock() + binary.BigEndian.PutUint32(c.senderTs[:], msw) + binary.BigEndian.PutUint32(c.senderTs[4:], lsw) + c.mu.Unlock() +} + +func (c *client) lastSenderTs() uint32 { + var ts uint32 + c.mu.Lock() + ts = binary.BigEndian.Uint32(c.senderTs[2:]) + c.mu.Unlock() + return ts +} + +func (c *client) delay() uint32 { + var receiveTime time.Time + c.mu.Lock() + receiveTime = c.receiveTime + c.mu.Unlock() + now := time.Now() + return uint32(now.Sub(receiveTime).Seconds() / delayUnit) +} + +func (c *client) received() { + c.mu.Lock() + c.receiveTime = time.Now() + c.mu.Unlock() +} From 8f452e1155aebf5d587a9eaa951d4d61a9a2df59 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 12 Apr 2019 16:40:52 +0930 Subject: [PATCH 12/47] protocol/rtcp/rtcp.go: added placeholder functions for getting sender report timestamp words --- protocol/rtcp/rtcp.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 0b6c9970..3dcc2d1e 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -2,8 +2,6 @@ package rtcp import ( "encoding/binary" - "io" - "io/ioutil" ) // RTCP packet types. @@ -148,7 +146,11 @@ func asByte(b bool) byte { } return 0x00 } -func Handle(r io.Reader) error { - io.Copy(ioutil.Discard, r) - return nil + +func TimestampMSW(buf []byte) (uint32, error) { + return 0, nil +} + +func TimestampLSW(buf []byte) (uint32, error) { + return 0, nil } From 757564a2ed16abdbcb3b5b166908aa3e9a36f404 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 12 Apr 2019 18:02:27 +0930 Subject: [PATCH 13/47] protocol/rtcp: wrote body for Timestamp and added testing. --- protocol/rtcp/parse.go | 26 ++++++++++++++++++++++++++ protocol/rtcp/parse_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 protocol/rtcp/parse.go create mode 100644 protocol/rtcp/parse_test.go diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go new file mode 100644 index 00000000..9795bbf6 --- /dev/null +++ b/protocol/rtcp/parse.go @@ -0,0 +1,26 @@ +package rtcp + +import ( + "encoding/binary" + "errors" +) + +// Timestamp gets the timestamp from a receiver report and returns as the most +// significant word, and the least significant word. If the given bytes do not +// represent a valid receiver report, and error is returned. +func Timestamp(buf []byte) (msw, lsw uint32, err error) { + // First check version of rtcp + if (buf[0] & 0xc0 >> 6) != 2 { + return 0, 0, errors.New("incompatible RTCP version") + } + + // Check type of packet + if buf[1] != typeSenderReport { + return 0, 0, errors.New("rtcp packet is not of sender report type") + } + + msw = binary.BigEndian.Uint32(buf[8:]) + lsw = binary.BigEndian.Uint32(buf[12:]) + + return +} diff --git a/protocol/rtcp/parse_test.go b/protocol/rtcp/parse_test.go new file mode 100644 index 00000000..8692f73c --- /dev/null +++ b/protocol/rtcp/parse_test.go @@ -0,0 +1,34 @@ +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, + } + + msw, lsw, err := Timestamp(report) + if err != nil { + t.Fatalf("did not expect error: %v", err) + } + + if msw != expectedMSW { + t.Errorf("most significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", msw, expectedMSW) + } + + if lsw != expectedLSW { + t.Errorf("least significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", lsw, expectedLSW) + } +} From 5fa0969530e79dbb1b6f7ec848c5aaea242e92cd Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 13 Apr 2019 19:48:20 +0930 Subject: [PATCH 14/47] protocol/rtcp: changed Timestamp func so that it returns msw and lsw --- protocol/rtcp/client.go | 8 ++------ protocol/rtcp/rtcp.go | 29 +++++++++++------------------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 6717540f..534636cd 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -147,13 +147,9 @@ func (c *client) send() { // parse will read important statistics from sender reports. func (c *client) parse(buf []byte) { c.received() - msw, err := TimestampMSW(buf) + msw, lsw, err := Timestamp(buf) if err != nil { - c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp msw from sender report, failed with error: %v", err)) - } - lsw, err := TimestampLSW(buf) - if err != nil { - c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp lsw from sender report, failed with error: %v", err)) + c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp from sender report, failed with error: %v", err)) } c.setSenderTs(msw, lsw) } diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 3dcc2d1e..f1da3409 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -6,6 +6,7 @@ import ( // RTCP packet types. const ( + typeSenderReport = 200 typeReceiverReport = 201 typeSourceDescription = 202 ) @@ -60,6 +61,16 @@ func (r *ReceiverReport) Bytes() []byte { return buf } +type ReportBlock struct { + SSRC 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. + LSR uint32 // Last sender report timestamp. + DLSR uint32 // Delay since last sender report. +} + type SourceDescription struct { Header @@ -106,16 +117,6 @@ type Header struct { Type uint8 // Type of RTCP packet. } -type ReportBlock struct { - SSRC 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. - LSR uint32 // Last sender report timestamp. - DLSR uint32 // Delay since last sender report. -} - type SDESItem struct { Type uint8 Text []byte @@ -146,11 +147,3 @@ func asByte(b bool) byte { } return 0x00 } - -func TimestampMSW(buf []byte) (uint32, error) { - return 0, nil -} - -func TimestampLSW(buf []byte) (uint32, error) { - return 0, nil -} From af664b0661b9db48cb96ccea3baeebb41591f268 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 15 Apr 2019 14:01:38 +0930 Subject: [PATCH 15/47] protocol/rtsp: more commenting and started writing client_test.go to check the behaviour of the rtcp client --- protocol/rtcp/client.go | 16 +++++++++++++--- protocol/rtcp/client_test.go | 30 ++++++++++++++++++++++++++++++ protocol/rtcp/parse.go | 4 +--- protocol/rtcp/rtcp.go | 35 +++++++++++++++++++++++------------ protocol/rtcp/rtcp_test.go | 4 ++++ 5 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 protocol/rtcp/client_test.go diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 534636cd..0e083d94 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -59,14 +59,14 @@ func NewClient(clientAddress, serverAddress, name string, sendInterval time.Dura } // Start starts the listen and send routines. This will start the process of -// receiving and parsing SenderReports, and the process of sending receiver +// receiving and parsing sender reports, and the process of sending receiver // reports to the server. func (c *client) Start() { go c.listen() go c.send() } -// Listen reads from the UDP connection and parses SenderReports. +// listen reads from the UDP connection and parses SenderReports. func (c *client) listen() { conn, err := net.ListenUDP("udp", c.cAddr) if err != nil { @@ -79,7 +79,7 @@ func (c *client) listen() { } } -// send write sender reports to the server. +// send writes receiver reports to the server. func (c *client) send() { conn, err := net.DialUDP("udp", c.cAddr, c.sAddr) if err != nil { @@ -154,12 +154,15 @@ func (c *client) parse(buf []byte) { c.setSenderTs(msw, lsw) } +// UpdateSequence will allow updating of the highest sequence number received +// through an rtp stream. func (c *client) UpdateSequence(s uint32) { c.mu.Lock() c.sequence = s c.mu.Unlock() } +// highestSequence will return the highest sequence number received through rtp. func (c *client) highestSequence() uint32 { var s uint32 c.mu.Lock() @@ -168,10 +171,13 @@ func (c *client) highestSequence() uint32 { return s } +// jitter returns the interarrival jitter as described by RTCP specifications: +// https://tools.ietf.org/html/rfc3550 func (c *client) jitter() uint32 { return 0 } +// setSenderTs allows us to safely set the current sender report timestamp. func (c *client) setSenderTs(msw, lsw uint32) { c.mu.Lock() binary.BigEndian.PutUint32(c.senderTs[:], msw) @@ -179,6 +185,7 @@ func (c *client) setSenderTs(msw, lsw uint32) { c.mu.Unlock() } +// lastSenderTs returns the timestamp of the most recent sender report. func (c *client) lastSenderTs() uint32 { var ts uint32 c.mu.Lock() @@ -187,6 +194,8 @@ func (c *client) lastSenderTs() uint32 { return ts } +// 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 { var receiveTime time.Time c.mu.Lock() @@ -196,6 +205,7 @@ func (c *client) delay() uint32 { return uint32(now.Sub(receiveTime).Seconds() / delayUnit) } +// received is called when a sender report is received to mark the receive time. func (c *client) received() { c.mu.Lock() c.receiveTime = time.Now() diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go new file mode 100644 index 00000000..26b54307 --- /dev/null +++ b/protocol/rtcp/client_test.go @@ -0,0 +1,30 @@ +package rtcp + +import ( + "net" + "testing" +) + +func TestReceiveAndSend(t *testing.T) { + quit := make(chan struct{}) + go testServer(quit) +} + +func testServer(quit chan struct{}, t *testing.T) { + const testServerAddr = "localhost:8000" + sAddr, err := net.ResolveUDPAddr("udp", testServerAddr) + if err != nil { + t.Fatalf("could not resolve test server address, failed with error: %v", err) + } + + conn, err := net.DialUDP("udp", nil, sAddr) + if err != nil { + t.Fatalf("could not dial, failed with error: %v", err) + } + + select { + case <-quit: + return + default: + } +} diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index 9795bbf6..1a7b6541 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -7,14 +7,12 @@ import ( // Timestamp gets the timestamp from a receiver report and returns as the most // significant word, and the least significant word. If the given bytes do not -// represent a valid receiver report, and error is returned. +// represent a valid receiver report, an error is returned. func Timestamp(buf []byte) (msw, lsw uint32, err error) { - // First check version of rtcp if (buf[0] & 0xc0 >> 6) != 2 { return 0, 0, errors.New("incompatible RTCP version") } - // Check type of packet if buf[1] != typeSenderReport { return 0, 0, errors.New("rtcp packet is not of sender report type") } diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index f1da3409..adb6eeb8 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -16,18 +16,20 @@ const ( typeCName = 1 ) +// MISC. const ( reportBlockSize = 6 ) +// ReceiverReport describes an RTCP receiver report packet. type ReceiverReport struct { - Header - - SenderSSRC uint32 - Blocks []ReportBlock - Extensions [][4]byte + 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() []byte { l := 8 + 4*reportBlockSize*len(r.Blocks) + 4*len(r.Extensions) buf := make([]byte, l) @@ -61,6 +63,7 @@ func (r *ReceiverReport) Bytes() []byte { return buf } +// ReportBlock describes an RTCP report block used in Sender/Receiver Reports. type ReportBlock struct { SSRC uint32 // Source identifier. FractionLost uint8 // Fraction of packets lost. @@ -71,12 +74,13 @@ type ReportBlock struct { DLSR uint32 // Delay since last sender report. } +// SourceDescription describes a source description RTCP packet. type SourceDescription struct { - Header - - Chunks []Chunk + Header // Standard RTCP packet header. + Chunks []Chunk // Chunks to describe items of each SSRC. } +// Bytes returns an []byte of the SourceDescription d. func (d *SourceDescription) Bytes() []byte { bodyLen := d.bodyLen() rem := bodyLen % 4 @@ -102,6 +106,7 @@ func (d *SourceDescription) Bytes() []byte { return buf } +// bodyLen calculates the body length of a source description packet in bytes. func (d *SourceDescription) bodyLen() int { l := 0 for _, c := range d.Chunks { @@ -110,6 +115,7 @@ func (d *SourceDescription) bodyLen() int { return l } +// Header describes a standard RTCP packet header. type Header struct { Version uint8 // RTCP version. Padding bool // Padding indicator. @@ -117,16 +123,19 @@ type Header struct { Type uint8 // Type of RTCP packet. } +// SDESItem describes a source description item. type SDESItem struct { - Type uint8 - Text []byte + Type uint8 // Type of item. + Text []byte // Item text. } +// Chunk describes a source description chunk for a given SSRC. type Chunk struct { - SSRC uint32 - Items []SDESItem + 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 { @@ -135,6 +144,8 @@ func (c *Chunk) len() int { 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 diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index bafa59eb..6c0220dc 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -6,6 +6,8 @@ import ( "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, @@ -48,6 +50,8 @@ func TestReceiverReportBytes(t *testing.T) { } } +// 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, From 2669862ced4cab87e81f964a1a31e83be307d798 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 15 Apr 2019 15:53:06 +0930 Subject: [PATCH 16/47] protocol/rtcp: wrote struct for RTCP sender report --- protocol/rtcp/rtcp.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index adb6eeb8..7e11e8cc 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -18,7 +18,8 @@ const ( // MISC. const ( - reportBlockSize = 6 + reportBlockSize = 6 + senderReportSize = 28 ) // ReceiverReport describes an RTCP receiver report packet. @@ -115,6 +116,37 @@ func (d *SourceDescription) bodyLen() int { 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+1:], w) + } + return buf +} + // Header describes a standard RTCP packet header. type Header struct { Version uint8 // RTCP version. From dca007a5ba7e839365a80ab58f1c447ca2dee893 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 15 Apr 2019 16:56:52 +0930 Subject: [PATCH 17/47] rtcp/protocol: tried to make Bytes funcs and client formation of payload more efficient --- protocol/rtcp/client.go | 22 +++++++++++++--------- protocol/rtcp/client_test.go | 7 ++----- protocol/rtcp/rtcp.go | 17 ++++++++++++----- protocol/rtcp/rtcp_test.go | 4 ++-- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 0e083d94..6659f349 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -29,6 +29,7 @@ type client struct { senderTs [64]byte interval time.Duration receiveTime time.Time + buf [200]byte } // NewClient returns a pointer to a new client. @@ -87,6 +88,7 @@ func (c *client) send() { } for { time.Sleep(c.interval) + report := ReceiverReport{ Header: Header{ Version: 2, @@ -129,21 +131,23 @@ func (c *client) send() { }, } - reportBytes := report.Bytes() - reportLen := len(reportBytes) - descriptionBytes := description.Bytes() - totalLength := reportLen + len(descriptionBytes) - bytes := make([]byte, totalLength) - copy(bytes, reportBytes) - copy(bytes[reportLen:], descriptionBytes) - - _, err := conn.Write(bytes) + _, err := conn.Write(c.formPayload(&report, &description)) if err != nil { c.ErrChan <- err } } } +func (c *client) formPayload(r *ReceiverReport, d *SourceDescription) []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.received() diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index 26b54307..e566aa7c 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -1,10 +1,6 @@ package rtcp -import ( - "net" - "testing" -) - +/* func TestReceiveAndSend(t *testing.T) { quit := make(chan struct{}) go testServer(quit) @@ -28,3 +24,4 @@ func testServer(quit chan struct{}, t *testing.T) { default: } } +*/ diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 7e11e8cc..bc9291a8 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -31,10 +31,12 @@ type ReceiverReport struct { } // Bytes returns a []byte of the ReceiverReport r. -func (r *ReceiverReport) Bytes() []byte { +func (r *ReceiverReport) Bytes(buf []byte) []byte { l := 8 + 4*reportBlockSize*len(r.Blocks) + 4*len(r.Extensions) - buf := make([]byte, l) - + 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) @@ -82,14 +84,19 @@ type SourceDescription struct { } // Bytes returns an []byte of the SourceDescription d. -func (d *SourceDescription) Bytes() []byte { +func (d *SourceDescription) Bytes(buf []byte) []byte { bodyLen := d.bodyLen() rem := bodyLen % 4 if rem != 0 { bodyLen += 4 - rem } + l := 4 + bodyLen - buf := make([]byte, l) + 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 { diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index 6c0220dc..b3ba15c2 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -42,7 +42,7 @@ func TestReceiverReportBytes(t *testing.T) { Extensions: nil, } - got := report.Bytes() + got := report.Bytes(nil) t.Logf("Got: %v\n", got) t.Logf("Want: %v\n", expect) if !bytes.Equal(got, expect) { @@ -80,7 +80,7 @@ func TestSourceDescriptionBytes(t *testing.T) { }, }, } - got := description.Bytes() + got := description.Bytes(nil) t.Logf("Got: %v\n", got) t.Logf("Expect: %v\n", expect) if !bytes.Equal(got, expect) { From aa947d112cc7ab24b33bab8e7ff0208d7cd14cc1 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 15 Apr 2019 17:35:36 +0930 Subject: [PATCH 18/47] protocol/rtcp: wrote test for Client.formPayload --- protocol/rtcp/client.go | 3 ++ protocol/rtcp/client_test.go | 81 ++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 6659f349..654f56ae 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -138,6 +138,9 @@ func (c *client) send() { } } +// 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 *SourceDescription) []byte { rl := len(r.Bytes(c.buf[:])) dl := len(d.Bytes(c.buf[rl:])) diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index e566aa7c..cc10d4b9 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -1,5 +1,85 @@ package rtcp +import ( + "bytes" + "fmt" + "math" + "testing" +) + +func TestFormPayload(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, + 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{ + SSRC: 1873625286, + FractionLost: 0, + PacketsLost: math.MaxUint32, + HighestSequence: 99080, + Jitter: 32, + LSR: 3118540074, + DLSR: 11257, + }, + }, + Extensions: nil, + } + + description := SourceDescription{ + Header: Header{ + Version: 2, + Padding: false, + ReportCount: 1, + Type: typeSourceDescription, + }, + 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) + } +} + /* func TestReceiveAndSend(t *testing.T) { quit := make(chan struct{}) @@ -24,4 +104,5 @@ func testServer(quit chan struct{}, t *testing.T) { default: } } + */ From 1a19412223f001300b0f8469a829836c5c44d9a3 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 16 Apr 2019 12:33:58 +0930 Subject: [PATCH 19/47] protocol/rtcp: finished client_test.go improved usability or client Finished writing the client_test.go file and through the process fixed some bugs in the client. Also increased usability by providing a Stop() method so that the send and recv routines, and also the connection can be terminated. Also created a sender report struct in rtcp.go - this helped with testing. --- protocol/rtcp/client.go | 154 ++++++++++++++++++++++------------- protocol/rtcp/client_test.go | 124 ++++++++++++++++++++++++---- protocol/rtcp/parse.go | 5 +- protocol/rtcp/rtcp.go | 2 +- 4 files changed, 212 insertions(+), 73 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 654f56ae..f2bef73e 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -8,14 +8,19 @@ import ( "net" "sync" "time" + + "bitbucket.org/ausocean/utils/logger" ) const ( senderSSRC = 3605043418 defaultClientName = "client" delayUnit = 1.0 / 65536.0 + pkg = "rtcp: " ) +type log func(lvl int8, msg string, args ...interface{}) + // client is an rtcp client that will hadle receiving SenderReports from a server // and sending out ReceiverReports. type client struct { @@ -30,32 +35,44 @@ type client struct { interval time.Duration receiveTime time.Time buf [200]byte + conn *net.UDPConn + wg sync.WaitGroup + quitSend chan struct{} + quitRecv chan struct{} + log } // NewClient returns a pointer to a new client. -func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32) (*client, error) { +func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32, l log) (*client, error) { if name == "" { name = defaultClientName } c := &client{ name: name, - ErrChan: make(chan error), + ErrChan: make(chan error, 2), + quitSend: make(chan struct{}), + quitRecv: 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", err)) + 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", err)) + 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 } @@ -63,77 +80,102 @@ func NewClient(clientAddress, serverAddress, name string, sendInterval time.Dura // receiving and parsing sender reports, and the process of sending receiver // reports to the server. func (c *client) Start() { - go c.listen() + c.log(logger.Debug, pkg+"client is starting") + c.wg.Add(1) + go c.recv() + c.wg.Add(1) 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.quitSend) + close(c.quitRecv) + c.conn.Close() + c.wg.Wait() +} + // listen reads from the UDP connection and parses SenderReports. -func (c *client) listen() { - conn, err := net.ListenUDP("udp", c.cAddr) - if err != nil { - c.ErrChan <- err - } +func (c *client) recv() { + defer c.wg.Done() + c.log(logger.Debug, pkg+"client is receiving") buf := make([]byte, 4096) for { - n, _, _ := conn.ReadFromUDP(buf) - c.parse(buf[:n]) + select { + case <-c.quitRecv: + return + default: + n, _, err := c.conn.ReadFromUDP(buf) + if err != nil { + c.ErrChan <- 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() { - conn, err := net.DialUDP("udp", c.cAddr, c.sAddr) - if err != nil { - c.ErrChan <- err - } + defer c.wg.Done() + c.log(logger.Debug, pkg+"client is sending") for { - time.Sleep(c.interval) + select { + case <-c.quitSend: + return + default: + time.Sleep(c.interval) - report := ReceiverReport{ - Header: Header{ - Version: 2, - Padding: false, - ReportCount: 1, - Type: typeReceiverReport, - }, - SenderSSRC: senderSSRC, - Blocks: []ReportBlock{ - ReportBlock{ - SSRC: c.sourceSSRC, - FractionLost: 0, - PacketsLost: math.MaxUint32, - HighestSequence: c.highestSequence(), - Jitter: c.jitter(), - LSR: c.lastSenderTs(), - DLSR: c.delay(), + report := ReceiverReport{ + Header: Header{ + Version: 2, + Padding: false, + ReportCount: 1, + Type: typeReceiverReport, }, - }, - Extensions: nil, - } + SenderSSRC: senderSSRC, + Blocks: []ReportBlock{ + ReportBlock{ + SSRC: c.sourceSSRC, + FractionLost: 0, + PacketsLost: math.MaxUint32, + HighestSequence: c.highestSequence(), + Jitter: c.jitter(), + LSR: c.lastSenderTs(), + DLSR: c.delay(), + }, + }, + Extensions: nil, + } - description := SourceDescription{ - Header: Header{ - Version: 2, - Padding: false, - ReportCount: 1, - Type: typeSourceDescription, - }, - Chunks: []Chunk{ - Chunk{ - SSRC: senderSSRC, - Items: []SDESItem{ - SDESItem{ - Type: typeCName, - Text: []byte(c.name), + description := SourceDescription{ + Header: Header{ + Version: 2, + Padding: false, + ReportCount: 1, + Type: typeSourceDescription, + }, + Chunks: []Chunk{ + Chunk{ + SSRC: senderSSRC, + Items: []SDESItem{ + SDESItem{ + Type: typeCName, + Text: []byte(c.name), + }, }, }, }, - }, - } + } - _, err := conn.Write(c.formPayload(&report, &description)) - if err != nil { - c.ErrChan <- err + c.log(logger.Debug, pkg+"sending receiver report") + _, err := c.conn.Write(c.formPayload(&report, &description)) + if err != nil { + c.ErrChan <- err + } } } } diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index cc10d4b9..1cce8c28 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -2,11 +2,18 @@ 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) { expect := []byte{ 0x81, 0xc9, 0x00, 0x07, @@ -80,29 +87,116 @@ func TestFormPayload(t *testing.T) { } } -/* -func TestReceiveAndSend(t *testing.T) { - quit := make(chan struct{}) - go testServer(quit) +// 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) } -func testServer(quit chan struct{}, t *testing.T) { - const testServerAddr = "localhost:8000" - sAddr, err := net.ResolveUDPAddr("udp", testServerAddr) +// 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.ErrChan: + 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) } - conn, err := net.DialUDP("udp", nil, sAddr) + cAddr, err := net.ResolveUDPAddr("udp", clientAddr) if err != nil { - t.Fatalf("could not dial, failed with error: %v", err) + t.Fatalf("could not resolve client address, failed with error: %v", err) } - select { - case <-quit: - return - default: + 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.UpdateSequence(uint32(i)) + + now := time.Now().Second() + var time [8]byte + binary.BigEndian.PutUint64(time[:], uint64(now)) + msw := binary.BigEndian.Uint32(time[:]) + lsw := binary.BigEndian.Uint32(time[4:]) + + report := SenderReport{ + Header: Header{ + Version: 2, + 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() } - -*/ diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index 1a7b6541..4bf5e566 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -9,12 +9,15 @@ import ( // significant word, and the least significant word. If the given bytes do not // represent a valid receiver report, an error is returned. func Timestamp(buf []byte) (msw, lsw uint32, err error) { + if len(buf) < 4 { + return 0, 0, errors.New("bad RTCP packet, not of sufficient length") + } if (buf[0] & 0xc0 >> 6) != 2 { return 0, 0, errors.New("incompatible RTCP version") } if buf[1] != typeSenderReport { - return 0, 0, errors.New("rtcp packet is not of sender report type") + return 0, 0, errors.New("RTCP packet is not of sender report type") } msw = binary.BigEndian.Uint32(buf[8:]) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index bc9291a8..7c76b587 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -149,7 +149,7 @@ func (r *SenderReport) Bytes() []byte { r.PacketCount, r.OctetCount, } { - binary.BigEndian.PutUint32(buf[i+1:], w) + binary.BigEndian.PutUint32(buf[i+4:], w) } return buf } From 9e55feafe7c037528b41a574c320b56763409b40 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 16 Apr 2019 12:38:14 +0930 Subject: [PATCH 20/47] protocol: removed rtsp stuff For this branch we will only include efforts regarding rtcp. --- protocol/rtsp/LICENSE.BSD | 12 - protocol/rtsp/README.md | 11 - protocol/rtsp/cmd/stream/main.go | 54 ---- protocol/rtsp/rtp/rtp.go | 150 ----------- protocol/rtsp/rtp/rtp_test.go | 21 -- protocol/rtsp/rtsp.go | 422 ------------------------------- protocol/rtsp/sdp.go | 73 ------ 7 files changed, 743 deletions(-) delete mode 100644 protocol/rtsp/LICENSE.BSD delete mode 100644 protocol/rtsp/README.md delete mode 100644 protocol/rtsp/cmd/stream/main.go delete mode 100644 protocol/rtsp/rtp/rtp.go delete mode 100644 protocol/rtsp/rtp/rtp_test.go delete mode 100644 protocol/rtsp/rtsp.go delete mode 100644 protocol/rtsp/sdp.go diff --git a/protocol/rtsp/LICENSE.BSD b/protocol/rtsp/LICENSE.BSD deleted file mode 100644 index 1098d8aa..00000000 --- a/protocol/rtsp/LICENSE.BSD +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2015, T. Jameson Little -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/protocol/rtsp/README.md b/protocol/rtsp/README.md deleted file mode 100644 index ec9c6164..00000000 --- a/protocol/rtsp/README.md +++ /dev/null @@ -1,11 +0,0 @@ -rtsp -==== - -`rtsp` implements RTSP in Go. The development focus is for video streaming from security cameras, but the library is developed such that it should be useful for any type of stream. - -Currently, `rtp` and `rtcp` are implemented as sub-packages, but this will likely change once the library matures. - -License -======= - -`rtsp` is licensed under the BSD 3-clause license. See LICENSE.BSD for details. diff --git a/protocol/rtsp/cmd/stream/main.go b/protocol/rtsp/cmd/stream/main.go deleted file mode 100644 index 7a9a5d87..00000000 --- a/protocol/rtsp/cmd/stream/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io" - "log" - - "bitbucket.org/ausocean/av/protocol/rtsp" -) - -func main() { - rtspServerPtr := flag.String("rtsp-server", "", "The RTSP server we would like to get video from") - clientPortPtr := flag.Uint("port", 6870, "The port on the client we would like to receive RTP on") - trackPtr := flag.String("track", "track1", "The track that we would like to receive media from") - flag.Parse() - - sess := rtsp.NewSession() - res, err := sess.Options(*rtspServerPtr) - if err != nil { - log.Fatalln(err) - } - fmt.Println("Options:") - fmt.Println(res) - - res, err = sess.Describe(*rtspServerPtr) - if err != nil { - log.Fatalln(err) - } - fmt.Println("Describe:") - fmt.Println(res) - - p, err := rtsp.ParseSdp(&io.LimitedReader{R: res.Body, N: res.ContentLength}) - if err != nil { - log.Fatalln(err) - } - log.Printf("%+v", p) - - res, err = sess.Setup(*rtspServerPtr+"/"+*trackPtr, fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", *clientPortPtr, *clientPortPtr+1)) - if err != nil { - log.Fatalln(err) - } - log.Println(res) - - res, err = sess.Play(*rtspServerPtr, res.Header.Get("Session")) - if err != nil { - log.Fatalln(err) - } - log.Println(res) - - // Send back rtcp - for { - } -} diff --git a/protocol/rtsp/rtp/rtp.go b/protocol/rtsp/rtp/rtp.go deleted file mode 100644 index 8d2712c2..00000000 --- a/protocol/rtsp/rtp/rtp.go +++ /dev/null @@ -1,150 +0,0 @@ -package rtp - -import ( - "fmt" - "net" -) - -const ( - RTP_VERSION = 2 -) - -const ( - hasRtpPadding = 1 << 2 - hasRtpExt = 1 << 3 -) - -// Packet as per https://tools.ietf.org/html/rfc1889#section-5.1 -// -// 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 | -// | .... | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -type RtpPacket struct { - Version byte - Padding bool - Ext bool - Marker bool - PayloadType byte - SequenceNumber uint - Timestamp uint - SyncSource uint - - CSRC []uint - - ExtHeader uint - ExtData []byte - - Payload []byte -} - -type Session struct { - Rtp net.PacketConn - Rtcp net.PacketConn - - RtpChan <-chan RtpPacket - RtcpChan <-chan []byte - - rtpChan chan<- RtpPacket - rtcpChan chan<- []byte -} - -func New(rtp, rtcp net.PacketConn) *Session { - rtpChan := make(chan RtpPacket, 10) - rtcpChan := make(chan []byte, 10) - s := &Session{ - Rtp: rtp, - Rtcp: rtcp, - RtpChan: rtpChan, - RtcpChan: rtcpChan, - rtpChan: rtpChan, - rtcpChan: rtcpChan, - } - go s.HandleRtpConn(rtp) - go s.HandleRtcpConn(rtcp) - return s -} - -func toUint(arr []byte) (ret uint) { - for i, b := range arr { - ret |= uint(b) << (8 * uint(len(arr)-i-1)) - } - return ret -} - -func (s *Session) HandleRtpConn(conn net.PacketConn) { - buf := make([]byte, 4096) - for { - n, _, err := conn.ReadFrom(buf) - if err != nil { - panic(err) - } - - cpy := make([]byte, n) - copy(cpy, buf) - go s.handleRtp(cpy) - } -} - -func (s *Session) HandleRtcpConn(conn net.PacketConn) { - buf := make([]byte, 4096) - for { - n, _, err := conn.ReadFrom(buf) - if err != nil { - panic(err) - } - cpy := make([]byte, n) - copy(cpy, buf) - go s.handleRtcp(cpy) - } -} - -func (s *Session) handleRtp(buf []byte) { - packet := RtpPacket{ - Version: buf[0] & 0x03, - Padding: buf[0]&hasRtpPadding != 0, - Ext: buf[0]&hasRtpExt != 0, - CSRC: make([]uint, buf[0]>>4), - Marker: buf[1]&1 != 0, - PayloadType: buf[1] >> 1, - SequenceNumber: toUint(buf[2:4]), - Timestamp: toUint(buf[4:8]), - SyncSource: toUint(buf[8:12]), - } - if packet.Version != RTP_VERSION { - fmt.Printf("version: %v\n", packet.Version) - } - - i := 12 - - for j := range packet.CSRC { - packet.CSRC[j] = toUint(buf[i : i+4]) - i += 4 - } - - if packet.Ext { - packet.ExtHeader = toUint(buf[i : i+2]) - length := toUint(buf[i+2 : i+4]) - i += 4 - if length > 0 { - packet.ExtData = buf[i : i+int(length)*4] - i += int(length) * 4 - } - } - - packet.Payload = buf[i:] - - s.rtpChan <- packet -} - -func (s *Session) handleRtcp(buf []byte) { - // TODO: implement rtcp -} diff --git a/protocol/rtsp/rtp/rtp_test.go b/protocol/rtsp/rtp/rtp_test.go deleted file mode 100644 index da54d96c..00000000 --- a/protocol/rtsp/rtp/rtp_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package rtp - -import ( - "testing" -) - -func TestToUint(t *testing.T) { - tests := []struct { - arr []byte - exp uint - }{ - {[]byte{1, 2}, 0x102}, - {[]byte{3, 2, 1, 0}, 0x3020100}, - } - for _, tst := range tests { - val := toUint(tst.arr) - if val != tst.exp { - t.Errorf("%d != %d for % x", val, tst.exp, tst.arr) - } - } -} diff --git a/protocol/rtsp/rtsp.go b/protocol/rtsp/rtsp.go deleted file mode 100644 index 8aae8a9c..00000000 --- a/protocol/rtsp/rtsp.go +++ /dev/null @@ -1,422 +0,0 @@ -package rtsp - -import ( - "bufio" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/url" - "strconv" - "strings" -) - -const ( - // Client to server for presentation and stream objects; recommended - DESCRIBE = "DESCRIBE" - // Bidirectional for client and stream objects; optional - ANNOUNCE = "ANNOUNCE" - // Bidirectional for client and stream objects; optional - GET_PARAMETER = "GET_PARAMETER" - // Bidirectional for client and stream objects; required for Client to server, optional for server to client - OPTIONS = "OPTIONS" - // Client to server for presentation and stream objects; recommended - PAUSE = "PAUSE" - // Client to server for presentation and stream objects; required - PLAY = "PLAY" - // Client to server for presentation and stream objects; optional - RECORD = "RECORD" - // Server to client for presentation and stream objects; optional - REDIRECT = "REDIRECT" - // Client to server for stream objects; required - SETUP = "SETUP" - // Bidirectional for presentation and stream objects; optional - SET_PARAMETER = "SET_PARAMETER" - // Client to server for presentation and stream objects; required - TEARDOWN = "TEARDOWN" -) - -const ( - // all requests - Continue = 100 - - // all requests - OK = 200 - // RECORD - Created = 201 - // RECORD - LowOnStorageSpace = 250 - - // all requests - MultipleChoices = 300 - // all requests - MovedPermanently = 301 - // all requests - MovedTemporarily = 302 - // all requests - SeeOther = 303 - // all requests - UseProxy = 305 - - // all requests - BadRequest = 400 - // all requests - Unauthorized = 401 - // all requests - PaymentRequired = 402 - // all requests - Forbidden = 403 - // all requests - NotFound = 404 - // all requests - MethodNotAllowed = 405 - // all requests - NotAcceptable = 406 - // all requests - ProxyAuthenticationRequired = 407 - // all requests - RequestTimeout = 408 - // all requests - Gone = 410 - // all requests - LengthRequired = 411 - // DESCRIBE, SETUP - PreconditionFailed = 412 - // all requests - RequestEntityTooLarge = 413 - // all requests - RequestURITooLong = 414 - // all requests - UnsupportedMediaType = 415 - // SETUP - Invalidparameter = 451 - // SETUP - IllegalConferenceIdentifier = 452 - // SETUP - NotEnoughBandwidth = 453 - // all requests - SessionNotFound = 454 - // all requests - MethodNotValidInThisState = 455 - // all requests - HeaderFieldNotValid = 456 - // PLAY - InvalidRange = 457 - // SET_PARAMETER - ParameterIsReadOnly = 458 - // all requests - AggregateOperationNotAllowed = 459 - // all requests - OnlyAggregateOperationAllowed = 460 - // all requests - UnsupportedTransport = 461 - // all requests - DestinationUnreachable = 462 - - // all requests - InternalServerError = 500 - // all requests - NotImplemented = 501 - // all requests - BadGateway = 502 - // all requests - ServiceUnavailable = 503 - // all requests - GatewayTimeout = 504 - // all requests - RTSPVersionNotSupported = 505 - // all requests - OptionNotsupport = 551 -) - -type ResponseWriter interface { - http.ResponseWriter -} - -type Request struct { - Method string - URL *url.URL - Proto string - ProtoMajor int - ProtoMinor int - Header http.Header - ContentLength int - Body io.ReadCloser -} - -func (r Request) String() string { - s := fmt.Sprintf("%s %s %s/%d.%d\r\n", r.Method, r.URL, r.Proto, r.ProtoMajor, r.ProtoMinor) - for k, v := range r.Header { - for _, v := range v { - s += fmt.Sprintf("%s: %s\r\n", k, v) - } - } - s += "\r\n" - if r.Body != nil { - str, _ := ioutil.ReadAll(r.Body) - s += string(str) - } - return s -} - -func NewRequest(method, urlStr, cSeq string, body io.ReadCloser) (*Request, error) { - u, err := url.Parse(urlStr) - if err != nil { - return nil, err - } - - req := &Request{ - Method: method, - URL: u, - Proto: "RTSP", - ProtoMajor: 1, - ProtoMinor: 0, - Header: map[string][]string{"CSeq": []string{cSeq}}, - Body: body, - } - return req, nil -} - -type Session struct { - cSeq int - conn net.Conn - session string -} - -func NewSession() *Session { - return &Session{} -} - -func (s *Session) nextCSeq() string { - s.cSeq++ - return strconv.Itoa(s.cSeq) -} - -func (s *Session) Describe(urlStr string) (*Response, error) { - req, err := NewRequest(DESCRIBE, urlStr, s.nextCSeq(), nil) - if err != nil { - panic(err) - } - - req.Header.Add("Accept", "application/sdp") - - if s.conn == nil { - s.conn, err = net.Dial("tcp", req.URL.Host) - if err != nil { - return nil, err - } - } - - _, err = io.WriteString(s.conn, req.String()) - if err != nil { - return nil, err - } - return ReadResponse(s.conn) -} - -func (s *Session) Options(urlStr string) (*Response, error) { - req, err := NewRequest(OPTIONS, urlStr, s.nextCSeq(), nil) - if err != nil { - panic(err) - } - - if s.conn == nil { - s.conn, err = net.Dial("tcp", req.URL.Host) - if err != nil { - return nil, err - } - } - - _, err = io.WriteString(s.conn, req.String()) - if err != nil { - return nil, err - } - return ReadResponse(s.conn) -} - -func (s *Session) Setup(urlStr, transport string) (*Response, error) { - req, err := NewRequest(SETUP, urlStr, s.nextCSeq(), nil) - if err != nil { - panic(err) - } - - req.Header.Add("Transport", transport) - - if s.conn == nil { - s.conn, err = net.Dial("tcp", req.URL.Host) - if err != nil { - return nil, err - } - } - - _, err = io.WriteString(s.conn, req.String()) - if err != nil { - return nil, err - } - resp, err := ReadResponse(s.conn) - s.session = resp.Header.Get("Session") - return resp, err -} - -func (s *Session) Play(urlStr, sessionId string) (*Response, error) { - req, err := NewRequest(PLAY, urlStr+"/", s.nextCSeq(), nil) - if err != nil { - panic(err) - } - - req.Header.Add("Session", sessionId) - - if s.conn == nil { - s.conn, err = net.Dial("tcp", req.URL.Host) - if err != nil { - return nil, err - } - } - - _, err = io.WriteString(s.conn, req.String()) - if err != nil { - return nil, err - } - return ReadResponse(s.conn) -} - -type closer struct { - *bufio.Reader - r io.Reader -} - -func (c closer) Close() error { - if c.Reader == nil { - return nil - } - defer func() { - c.Reader = nil - c.r = nil - }() - if r, ok := c.r.(io.ReadCloser); ok { - return r.Close() - } - return nil -} - -func ParseRTSPVersion(s string) (proto string, major int, minor int, err error) { - parts := strings.SplitN(s, "/", 2) - proto = parts[0] - parts = strings.SplitN(parts[1], ".", 2) - if major, err = strconv.Atoi(parts[0]); err != nil { - return - } - if minor, err = strconv.Atoi(parts[0]); err != nil { - return - } - return -} - -// super simple RTSP parser; would be nice if net/http would allow more general parsing -func ReadRequest(r io.Reader) (req *Request, err error) { - req = new(Request) - req.Header = make(map[string][]string) - - b := bufio.NewReader(r) - var s string - - // TODO: allow CR, LF, or CRLF - if s, err = b.ReadString('\n'); err != nil { - return - } - - parts := strings.SplitN(s, " ", 3) - req.Method = parts[0] - if req.URL, err = url.Parse(parts[1]); err != nil { - return - } - - req.Proto, req.ProtoMajor, req.ProtoMinor, err = ParseRTSPVersion(parts[2]) - if err != nil { - return - } - - // read headers - for { - if s, err = b.ReadString('\n'); err != nil { - return - } else if s = strings.TrimRight(s, "\r\n"); s == "" { - break - } - - parts := strings.SplitN(s, ":", 2) - req.Header.Add(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) - } - - req.ContentLength, _ = strconv.Atoi(req.Header.Get("Content-Length")) - fmt.Println("Content Length:", req.ContentLength) - req.Body = closer{b, r} - return -} - -type Response struct { - Proto string - ProtoMajor int - ProtoMinor int - - StatusCode int - Status string - - ContentLength int64 - - Header http.Header - Body io.ReadCloser -} - -func (res Response) String() string { - s := fmt.Sprintf("%s/%d.%d %d %s\n", res.Proto, res.ProtoMajor, res.ProtoMinor, res.StatusCode, res.Status) - for k, v := range res.Header { - for _, v := range v { - s += fmt.Sprintf("%s: %s\n", k, v) - } - } - return s -} - -func ReadResponse(r io.Reader) (res *Response, err error) { - res = new(Response) - res.Header = make(map[string][]string) - - b := bufio.NewReader(r) - var s string - - // TODO: allow CR, LF, or CRLF - if s, err = b.ReadString('\n'); err != nil { - return - } - - parts := strings.SplitN(s, " ", 3) - res.Proto, res.ProtoMajor, res.ProtoMinor, err = ParseRTSPVersion(parts[0]) - if err != nil { - return - } - - if res.StatusCode, err = strconv.Atoi(parts[1]); err != nil { - return - } - - res.Status = strings.TrimSpace(parts[2]) - - // read headers - for { - if s, err = b.ReadString('\n'); err != nil { - return - } else if s = strings.TrimRight(s, "\r\n"); s == "" { - break - } - - parts := strings.SplitN(s, ":", 2) - res.Header.Add(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) - } - - res.ContentLength, _ = strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64) - - res.Body = closer{b, r} - return -} diff --git a/protocol/rtsp/sdp.go b/protocol/rtsp/sdp.go deleted file mode 100644 index 7bb081ad..00000000 --- a/protocol/rtsp/sdp.go +++ /dev/null @@ -1,73 +0,0 @@ -package rtsp - -import ( - "bufio" - "errors" - "io" - "strconv" - "strings" -) - -type SessionSection struct { - Version int - Originator string - SessionName string - SessionInformation string - URI string - Email string - Phone string - ConnectionInformation string - BandwidthInformation string -} - -func ParseSdp(r io.Reader) (SessionSection, error) { - var packet SessionSection - s := bufio.NewScanner(r) - for s.Scan() { - parts := strings.SplitN(s.Text(), "=", 2) - if len(parts) == 2 { - if len(parts[0]) != 1 { - return packet, errors.New("SDP only allows 1-character variables") - } - - switch parts[0] { - // version - case "v": - ver, err := strconv.Atoi(parts[1]) - if err != nil { - return packet, err - } - packet.Version = ver - // owner/creator and session identifier - case "o": - // o=
- // TODO: parse this - packet.Originator = parts[1] - // session name - case "s": - packet.SessionName = parts[1] - // session information - case "i": - packet.SessionInformation = parts[1] - // URI of description - case "u": - packet.URI = parts[1] - // email address - case "e": - packet.Email = parts[1] - // phone number - case "p": - packet.Phone = parts[1] - // connection information - not required if included in all media - case "c": - // TODO: parse this - packet.ConnectionInformation = parts[1] - // bandwidth information - case "b": - // TODO: parse this - packet.BandwidthInformation = parts[1] - } - } - } - return packet, nil -} From 5cf39595eb903e7bf31df419e9c69a69c7e244ef Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 16 Apr 2019 16:31:04 +0930 Subject: [PATCH 21/47] protocol/rtcp: fix case of protocols in comments --- protocol/rtcp/client.go | 8 ++++---- protocol/rtcp/client_test.go | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index f2bef73e..674485a6 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -21,7 +21,7 @@ const ( type log func(lvl int8, msg string, args ...interface{}) -// client is an rtcp client that will hadle receiving SenderReports from a server +// client is an RTCP client that will hadle receiving SenderReports from a server // and sending out ReceiverReports. type client struct { ErrChan chan error @@ -88,7 +88,7 @@ func (c *client) Start() { } // Stop sends a quit signal to the send and receive routines and closes the -// udp connection. It will wait until both routines have returned. +// UDP connection. It will wait until both routines have returned. func (c *client) Stop() { c.log(logger.Debug, pkg+"client is stopping") close(c.quitSend) @@ -204,14 +204,14 @@ func (c *client) parse(buf []byte) { } // UpdateSequence will allow updating of the highest sequence number received -// through an rtp stream. +// through an RTP stream. func (c *client) UpdateSequence(s uint32) { c.mu.Lock() c.sequence = s c.mu.Unlock() } -// highestSequence will return the highest sequence number received through rtp. +// highestSequence will return the highest sequence number received through RTP. func (c *client) highestSequence() uint32 { var s uint32 c.mu.Lock() diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index 1cce8c28..441bb758 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -15,6 +15,7 @@ import ( // 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, From f54dd139598e85239cdea16884f9b4edd8d5b8f3 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 16 Apr 2019 16:45:44 +0930 Subject: [PATCH 22/47] protocol/rtcp: added file headers --- protocol/rtcp/client.go | 28 ++++++++++++++++++++++++++++ protocol/rtcp/client_test.go | 27 +++++++++++++++++++++++++++ protocol/rtcp/parse.go | 27 +++++++++++++++++++++++++++ protocol/rtcp/parse_test.go | 27 +++++++++++++++++++++++++++ protocol/rtcp/rtcp.go | 28 ++++++++++++++++++++++++++++ protocol/rtcp/rtcp_test.go | 27 +++++++++++++++++++++++++++ 6 files changed, 164 insertions(+) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 674485a6..6d293648 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -1,3 +1,31 @@ +/* +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 + +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 ( diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index 441bb758..b7d61572 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -1,3 +1,30 @@ +/* +NAME + client_test.go + +DESCRIPTION + client_test.go contains testing utilities for functionality provided in client.go. + +AUTHORS + Saxon A. Nelson-Milton + +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 ( diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index 4bf5e566..b033cb74 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -1,3 +1,30 @@ +/* +NAME + parse.go + +DESCRIPTION + parse.go contains functionality for parsing RTCP packets. + +AUTHORS + Saxon A. Nelson-Milton + +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 ( diff --git a/protocol/rtcp/parse_test.go b/protocol/rtcp/parse_test.go index 8692f73c..0d040578 100644 --- a/protocol/rtcp/parse_test.go +++ b/protocol/rtcp/parse_test.go @@ -1,3 +1,30 @@ +/* +NAME + parse_test.go + +DESCRIPTION + parse_test.go provides testing utilities for functionality found in parse.go. + +AUTHORS + Saxon A. Nelson-Milton + +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 ( diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 7c76b587..098438ea 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -1,3 +1,31 @@ +/* +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 + +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 ( diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index b3ba15c2..e53d384f 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -1,3 +1,30 @@ +/* +NAME + rtcp_test.go + +DESCRIPTION + rtcp_test.go contains testing utilities for functionality provided in rtcp_test.go. + +AUTHORS + Saxon A. Nelson-Milton + +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 ( From 51478ee0642e6541ff14cb67f7c5ea5074de2823 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 16 Apr 2019 17:16:13 +0930 Subject: [PATCH 23/47] protocol/rtcp: addressing of PR feedback --- protocol/rtcp/client.go | 10 ++++++---- protocol/rtcp/client_test.go | 4 ++-- protocol/rtcp/parse.go | 2 +- protocol/rtcp/rtcp.go | 24 +++++++++--------------- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 6d293648..da2fddde 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -45,6 +45,7 @@ const ( defaultClientName = "client" delayUnit = 1.0 / 65536.0 pkg = "rtcp: " + rtcpVer = 2 ) type log func(lvl int8, msg string, args ...interface{}) @@ -52,7 +53,8 @@ type log func(lvl int8, msg string, args ...interface{}) // client is an RTCP client that will hadle receiving SenderReports from a server // and sending out ReceiverReports. type client struct { - ErrChan chan error + ErrChan chan error + cAddr *net.UDPAddr sAddr *net.UDPAddr name string @@ -125,7 +127,7 @@ func (c *client) Stop() { c.wg.Wait() } -// listen reads from the UDP connection and parses SenderReports. +// 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") @@ -159,7 +161,7 @@ func (c *client) send() { report := ReceiverReport{ Header: Header{ - Version: 2, + Version: rtcpVer, Padding: false, ReportCount: 1, Type: typeReceiverReport, @@ -181,7 +183,7 @@ func (c *client) send() { description := SourceDescription{ Header: Header{ - Version: 2, + Version: rtcpVer, Padding: false, ReportCount: 1, Type: typeSourceDescription, diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index b7d61572..a05822fe 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -202,12 +202,12 @@ func TestReceiveAndSend(t *testing.T) { now := time.Now().Second() var time [8]byte binary.BigEndian.PutUint64(time[:], uint64(now)) - msw := binary.BigEndian.Uint32(time[:]) + msw := binary.BigEndian.Uint32(time[:4]) lsw := binary.BigEndian.Uint32(time[4:]) report := SenderReport{ Header: Header{ - Version: 2, + Version: rtcpVer, Padding: false, ReportCount: 0, Type: typeSenderReport, diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index b033cb74..d56af390 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -39,7 +39,7 @@ func Timestamp(buf []byte) (msw, lsw uint32, err error) { if len(buf) < 4 { return 0, 0, errors.New("bad RTCP packet, not of sufficient length") } - if (buf[0] & 0xc0 >> 6) != 2 { + if (buf[0]&0xc0)>>6 != 2 { return 0, 0, errors.New("incompatible RTCP version") } diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 098438ea..1fd3cf3f 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -72,18 +72,13 @@ func (r *ReceiverReport) Bytes(buf []byte) []byte { idx := 8 for _, b := range r.Blocks { binary.BigEndian.PutUint32(buf[idx:], b.SSRC) - idx += 4 - binary.BigEndian.PutUint32(buf[idx:], b.PacketsLost) - buf[idx] = b.FractionLost - idx += 4 - binary.BigEndian.PutUint32(buf[idx:], b.HighestSequence) - idx += 4 - binary.BigEndian.PutUint32(buf[idx:], b.Jitter) - idx += 4 - binary.BigEndian.PutUint32(buf[idx:], b.LSR) - idx += 4 - binary.BigEndian.PutUint32(buf[idx:], b.DLSR) - idx += 4 + 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.LSR) + binary.BigEndian.PutUint32(buf[idx+20:], b.DLSR) + idx += 24 } for _, e := range r.Extensions { @@ -132,9 +127,8 @@ func (d *SourceDescription) Bytes(buf []byte) []byte { idx += 4 for _, i := range c.Items { buf[idx] = i.Type - idx++ - buf[idx] = byte(len(i.Text)) - idx++ + buf[idx+1] = byte(len(i.Text)) + idx += 2 copy(buf[idx:], i.Text) idx += len(i.Text) } From f66a94543af03678330485b7921b0aa353ded18f Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 16 Apr 2019 17:17:48 +0930 Subject: [PATCH 24/47] protocol/rtcp: fixed some further feedback --- protocol/rtcp/parse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index d56af390..35bc1b2a 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -32,14 +32,14 @@ import ( "errors" ) -// Timestamp gets the timestamp from a receiver report and returns as the most +// 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 Timestamp(buf []byte) (msw, lsw uint32, err error) { if len(buf) < 4 { return 0, 0, errors.New("bad RTCP packet, not of sufficient length") } - if (buf[0]&0xc0)>>6 != 2 { + if (buf[0]&0xc0)>>6 != rtpVer { return 0, 0, errors.New("incompatible RTCP version") } From 881ddc3d38e167ea27f66d95fef777994a92a5fb Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 16 Apr 2019 22:01:38 +0930 Subject: [PATCH 25/47] protocol/rtcp: using defer where I can --- protocol/rtcp/client.go | 22 ++++++++-------------- protocol/rtcp/parse.go | 2 +- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index da2fddde..6e9fb750 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -243,11 +243,9 @@ func (c *client) UpdateSequence(s uint32) { // highestSequence will return the highest sequence number received through RTP. func (c *client) highestSequence() uint32 { - var s uint32 c.mu.Lock() - s = c.sequence - c.mu.Unlock() - return s + defer c.mu.Unlock() + return c.sequence } // jitter returns the interarrival jitter as described by RTCP specifications: @@ -259,34 +257,30 @@ func (c *client) jitter() uint32 { // setSenderTs allows us to safely set the current sender report timestamp. func (c *client) setSenderTs(msw, lsw uint32) { c.mu.Lock() + defer c.mu.Unlock() binary.BigEndian.PutUint32(c.senderTs[:], msw) binary.BigEndian.PutUint32(c.senderTs[4:], lsw) - c.mu.Unlock() } // lastSenderTs returns the timestamp of the most recent sender report. func (c *client) lastSenderTs() uint32 { - var ts uint32 c.mu.Lock() - ts = binary.BigEndian.Uint32(c.senderTs[2:]) - c.mu.Unlock() - return ts + defer c.mu.Unlock() + return binary.BigEndian.Uint32(c.senderTs[2:]) } // 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 { - var receiveTime time.Time c.mu.Lock() - receiveTime = c.receiveTime - c.mu.Unlock() + defer c.mu.Unlock() now := time.Now() - return uint32(now.Sub(receiveTime).Seconds() / delayUnit) + return uint32(now.Sub(c.receiveTime).Seconds() / delayUnit) } // received is called when a sender report is received to mark the receive time. func (c *client) received() { c.mu.Lock() + defer c.mu.Unlock() c.receiveTime = time.Now() - c.mu.Unlock() } diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index 35bc1b2a..6e1b1fe4 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -39,7 +39,7 @@ func Timestamp(buf []byte) (msw, lsw uint32, err error) { if len(buf) < 4 { return 0, 0, errors.New("bad RTCP packet, not of sufficient length") } - if (buf[0]&0xc0)>>6 != rtpVer { + if (buf[0]&0xc0)>>6 != rtcpVer { return 0, 0, errors.New("incompatible RTCP version") } From d34eabcd34b7f62e15db8d8929adbfc52806d5b3 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 17 Apr 2019 07:41:31 +0930 Subject: [PATCH 26/47] protocol/rtcp/client.go: not using defer for simple setters/getters --- protocol/rtcp/client.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 6e9fb750..796e4489 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -244,8 +244,9 @@ func (c *client) UpdateSequence(s uint32) { // highestSequence will return the highest sequence number received through RTP. func (c *client) highestSequence() uint32 { c.mu.Lock() - defer c.mu.Unlock() - return c.sequence + s := c.sequence + c.mu.Unlock() + return s } // jitter returns the interarrival jitter as described by RTCP specifications: @@ -257,30 +258,31 @@ func (c *client) jitter() uint32 { // setSenderTs allows us to safely set the current sender report timestamp. func (c *client) setSenderTs(msw, lsw uint32) { c.mu.Lock() - defer c.mu.Unlock() binary.BigEndian.PutUint32(c.senderTs[:], msw) binary.BigEndian.PutUint32(c.senderTs[4:], lsw) + c.mu.Unlock() } // lastSenderTs returns the timestamp of the most recent sender report. func (c *client) lastSenderTs() uint32 { c.mu.Lock() - defer c.mu.Unlock() - return binary.BigEndian.Uint32(c.senderTs[2:]) + 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() - defer c.mu.Unlock() - now := time.Now() - return uint32(now.Sub(c.receiveTime).Seconds() / delayUnit) + t := c.receiveTime + c.mu.Unlock() + return uint32(time.Now().Sub(t).Seconds() / delayUnit) } // received is called when a sender report is received to mark the receive time. func (c *client) received() { c.mu.Lock() - defer c.mu.Unlock() c.receiveTime = time.Now() + c.mu.Unlock() } From a8e56311c25659c37dad6898fc9502b47fa014f5 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Apr 2019 14:15:43 +0930 Subject: [PATCH 27/47] protocol/rtcp: addressing PR feedback --- protocol/rtcp/client.go | 111 +++++++++++++++++++---------------- protocol/rtcp/client_test.go | 4 +- protocol/rtcp/rtcp.go | 2 +- 3 files changed, 62 insertions(+), 55 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 796e4489..715b1089 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -1,9 +1,9 @@ /* NAME - client.go + Client.go DESCRIPTION - client.go provides an implemntation of a basic RTCP client that will send + Client.go provides an implemntation of a basic RTCP Client that will send receiver reports, and receive sender reports to parse relevant statistics. AUTHORS @@ -41,44 +41,45 @@ import ( ) const ( - senderSSRC = 3605043418 - defaultClientName = "client" + senderSSRC = 1 // Any non-zero value will do. + defaultClientName = "Client" delayUnit = 1.0 / 65536.0 pkg = "rtcp: " rtcpVer = 2 + receiverBufSize = 200 ) type log func(lvl int8, msg string, args ...interface{}) -// client is an RTCP client that will hadle receiving SenderReports from a server +// Client is an RTCP Client that will handle receiving SenderReports from a server // and sending out ReceiverReports. -type client struct { - ErrChan chan error +type Client struct { + ErrChan chan error // Client will send any errors through this chan. - cAddr *net.UDPAddr - sAddr *net.UDPAddr - name string - sourceSSRC uint32 - mu sync.Mutex - sequence uint32 - senderTs [64]byte - interval time.Duration - receiveTime time.Time - buf [200]byte - conn *net.UDPConn - wg sync.WaitGroup - quitSend chan struct{} - quitRecv chan struct{} - log + 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. + sequence 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. + quitSend chan struct{} // Channel used to communicate quit signal to send routine. + quitRecv chan struct{} // Channel used to communicate quit signal to recv routine. + log // Used to log any messages. } -// NewClient returns a pointer to a new client. -func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32, l log) (*client, error) { +// 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{ + c := &Client{ name: name, ErrChan: make(chan error, 2), quitSend: make(chan struct{}), @@ -91,7 +92,7 @@ func NewClient(clientAddress, serverAddress, name string, sendInterval time.Dura 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)) + return nil, errors.New(fmt.Sprintf("can't resolve Client address, failed with error: %v\n", err)) } c.sAddr, err = net.ResolveUDPAddr("udp", serverAddress) @@ -109,18 +110,17 @@ func NewClient(clientAddress, serverAddress, name string, sendInterval time.Dura // 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(1) +func (c *Client) Start() { + c.log(logger.Debug, pkg+"Client is starting") + c.wg.Add(2) go c.recv() - c.wg.Add(1) 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") +func (c *Client) Stop() { + c.log(logger.Debug, pkg+"Client is stopping") close(c.quitSend) close(c.quitRecv) c.conn.Close() @@ -128,9 +128,9 @@ func (c *client) Stop() { } // recv reads from the UDP connection and parses SenderReports. -func (c *client) recv() { +func (c *Client) recv() { defer c.wg.Done() - c.log(logger.Debug, pkg+"client is receiving") + c.log(logger.Debug, pkg+"Client is receiving") buf := make([]byte, 4096) for { select { @@ -149,9 +149,9 @@ func (c *client) recv() { } // send writes receiver reports to the server. -func (c *client) send() { +func (c *Client) send() { defer c.wg.Done() - c.log(logger.Debug, pkg+"client is sending") + c.log(logger.Debug, pkg+"Client is sending") for { select { case <-c.quitSend: @@ -211,38 +211,45 @@ func (c *client) send() { } // formPayload takes a pointer to a ReceiverReport and a pointer to a -// Source Description and calls Bytes on both, writing to the underlying client +// 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 *SourceDescription) []byte { +func (c *Client) formPayload(r *ReceiverReport, d *SourceDescription) []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") + 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.received() +func (c *Client) parse(buf []byte) { + c.markReceivedTime() msw, lsw, err := Timestamp(buf) if err != nil { c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp from sender report, failed with error: %v", err)) } - c.setSenderTs(msw, lsw) + c.setSenderTs( + struct { + msw uint32 + lsw uint32 + }{ + msw, + lsw, + }) } -// UpdateSequence will allow updating of the highest sequence number received +// SetSequence will allow updating of the highest sequence number received // through an RTP stream. -func (c *client) UpdateSequence(s uint32) { +func (c *Client) SetSequence(s uint32) { c.mu.Lock() c.sequence = s c.mu.Unlock() } // highestSequence will return the highest sequence number received through RTP. -func (c *client) highestSequence() uint32 { +func (c *Client) highestSequence() uint32 { c.mu.Lock() s := c.sequence c.mu.Unlock() @@ -251,20 +258,20 @@ func (c *client) highestSequence() uint32 { // jitter returns the interarrival jitter as described by RTCP specifications: // https://tools.ietf.org/html/rfc3550 -func (c *client) jitter() uint32 { +func (c *Client) jitter() uint32 { return 0 } // setSenderTs allows us to safely set the current sender report timestamp. -func (c *client) setSenderTs(msw, lsw uint32) { +func (c *Client) setSenderTs(t struct{ msw, lsw uint32 }) { c.mu.Lock() - binary.BigEndian.PutUint32(c.senderTs[:], msw) - binary.BigEndian.PutUint32(c.senderTs[4:], lsw) + binary.BigEndian.PutUint32(c.senderTs[:], t.msw) + binary.BigEndian.PutUint32(c.senderTs[4:], t.lsw) c.mu.Unlock() } // lastSenderTs returns the timestamp of the most recent sender report. -func (c *client) lastSenderTs() uint32 { +func (c *Client) lastSenderTs() uint32 { c.mu.Lock() t := binary.BigEndian.Uint32(c.senderTs[2:]) c.mu.Unlock() @@ -273,7 +280,7 @@ func (c *client) lastSenderTs() uint32 { // 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 { +func (c *Client) delay() uint32 { c.mu.Lock() t := c.receiveTime c.mu.Unlock() @@ -281,7 +288,7 @@ func (c *client) delay() uint32 { } // received is called when a sender report is received to mark the receive time. -func (c *client) received() { +func (c *Client) markReceivedTime() { c.mu.Lock() c.receiveTime = time.Now() c.mu.Unlock() diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index a05822fe..62491bf9 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -101,7 +101,7 @@ func TestFormPayload(t *testing.T) { }, } - c := &client{} + c := &Client{} p := c.formPayload(&report, &description) if !bytes.Equal(p, expect) { @@ -197,7 +197,7 @@ func TestReceiveAndSend(t *testing.T) { n, _, _ := conn.ReadFromUDP(buf) t.Logf("SERVER: receiver report received: \n%v\n", buf[:n]) - c.UpdateSequence(uint32(i)) + c.SetSequence(uint32(i)) now := time.Now().Second() var time [8]byte diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 1fd3cf3f..109e129e 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -138,7 +138,7 @@ func (d *SourceDescription) Bytes(buf []byte) []byte { // bodyLen calculates the body length of a source description packet in bytes. func (d *SourceDescription) bodyLen() int { - l := 0 + var l int for _, c := range d.Chunks { l += c.len() } From a43ef566180abba2f191cf3ca64dc92f6fb04b4e Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 22 Apr 2019 00:34:03 +0930 Subject: [PATCH 28/47] protocol/rtcp: addressing PR feedback --- protocol/rtcp/client.go | 21 +++++++-------------- protocol/rtcp/parse.go | 24 +++++++++++++++--------- protocol/rtcp/parse_test.go | 12 ++++++------ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 715b1089..1aebc18d 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -4,7 +4,7 @@ NAME DESCRIPTION Client.go provides an implemntation of a basic RTCP Client that will send - receiver reports, and receive sender reports to parse relevant statistics. + receiver reports, and receive sender reports to parse relevant statistics. AUTHORS Saxon A. Nelson-Milton @@ -20,7 +20,7 @@ LICENSE 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. + 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. @@ -226,18 +226,11 @@ func (c *Client) formPayload(r *ReceiverReport, d *SourceDescription) []byte { // parse will read important statistics from sender reports. func (c *Client) parse(buf []byte) { c.markReceivedTime() - msw, lsw, err := Timestamp(buf) + t, err := Timestamp(buf) if err != nil { c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp from sender report, failed with error: %v", err)) } - c.setSenderTs( - struct { - msw uint32 - lsw uint32 - }{ - msw, - lsw, - }) + c.setSenderTs(t) } // SetSequence will allow updating of the highest sequence number received @@ -263,10 +256,10 @@ func (c *Client) jitter() uint32 { } // setSenderTs allows us to safely set the current sender report timestamp. -func (c *Client) setSenderTs(t struct{ msw, lsw uint32 }) { +func (c *Client) setSenderTs(t NTPTimestamp) { c.mu.Lock() - binary.BigEndian.PutUint32(c.senderTs[:], t.msw) - binary.BigEndian.PutUint32(c.senderTs[4:], t.lsw) + binary.BigEndian.PutUint32(c.senderTs[:], t.MSW) + binary.BigEndian.PutUint32(c.senderTs[4:], t.LSW) c.mu.Unlock() } diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index 6e1b1fe4..159d739c 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -19,7 +19,7 @@ LICENSE 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. + 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. @@ -32,23 +32,29 @@ import ( "errors" ) +// NTPTimestamp describes the NTP timestamp format (http://www.beaglesoft.com/Manual/page53.htm) +type NTPTimestamp struct { + MSW uint32 + LSW 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 Timestamp(buf []byte) (msw, lsw uint32, err error) { +func Timestamp(buf []byte) (NTPTimestamp, error) { if len(buf) < 4 { - return 0, 0, errors.New("bad RTCP packet, not of sufficient length") + return NTPTimestamp{}, errors.New("bad RTCP packet, not of sufficient length") } if (buf[0]&0xc0)>>6 != rtcpVer { - return 0, 0, errors.New("incompatible RTCP version") + return NTPTimestamp{}, errors.New("incompatible RTCP version") } if buf[1] != typeSenderReport { - return 0, 0, errors.New("RTCP packet is not of sender report type") + return NTPTimestamp{}, errors.New("RTCP packet is not of sender report type") } - msw = binary.BigEndian.Uint32(buf[8:]) - lsw = binary.BigEndian.Uint32(buf[12:]) - - return + return NTPTimestamp{ + MSW: binary.BigEndian.Uint32(buf[8:]), + LSW: binary.BigEndian.Uint32(buf[12:]), + }, nil } diff --git a/protocol/rtcp/parse_test.go b/protocol/rtcp/parse_test.go index 0d040578..2777bddb 100644 --- a/protocol/rtcp/parse_test.go +++ b/protocol/rtcp/parse_test.go @@ -19,7 +19,7 @@ LICENSE 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. + 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. @@ -46,16 +46,16 @@ func TestTimestamp(t *testing.T) { 0x00, 0x01, 0xc2, 0xc5, } - msw, lsw, err := Timestamp(report) + ts, err := Timestamp(report) if err != nil { t.Fatalf("did not expect error: %v", err) } - if msw != expectedMSW { - t.Errorf("most significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", msw, expectedMSW) + if ts.MSW != expectedMSW { + t.Errorf("most significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.MSW, expectedMSW) } - if lsw != expectedLSW { - t.Errorf("least significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", lsw, expectedLSW) + if ts.LSW != expectedLSW { + t.Errorf("least significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.LSW, expectedLSW) } } From 6b994d0dbacc63ea5b4a7f1b166fecb574d18c8c Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 13:26:56 +0930 Subject: [PATCH 29/47] protocol/rtcp: made Client error channel unexported Renamed Client ErrChan field to err, i.e. made unexported. Wrote Err() accessor that allows user to only read from error channel. --- protocol/rtcp/client.go | 15 ++++++++++----- protocol/rtcp/client_test.go | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 1aebc18d..fda4f866 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -54,7 +54,7 @@ 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 { - ErrChan chan error // Client will send any errors through this chan. + err chan error // Client will send any errors through this chan. Can be accessed by Err(). cAddr *net.UDPAddr // Address of client. sAddr *net.UDPAddr // Address of RTSP server. @@ -81,7 +81,7 @@ func NewClient(clientAddress, serverAddress, name string, sendInterval time.Dura c := &Client{ name: name, - ErrChan: make(chan error, 2), + err: make(chan error), quitSend: make(chan struct{}), quitRecv: make(chan struct{}), interval: sendInterval, @@ -127,6 +127,11 @@ func (c *Client) Stop() { c.wg.Wait() } +// Err provides read access to the Client err channel. +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() @@ -139,7 +144,7 @@ func (c *Client) recv() { default: n, _, err := c.conn.ReadFromUDP(buf) if err != nil { - c.ErrChan <- err + c.err <- err continue } c.log(logger.Debug, pkg+"sender report received", "report", buf[:n]) @@ -204,7 +209,7 @@ func (c *Client) send() { c.log(logger.Debug, pkg+"sending receiver report") _, err := c.conn.Write(c.formPayload(&report, &description)) if err != nil { - c.ErrChan <- err + c.err <- err } } } @@ -228,7 +233,7 @@ func (c *Client) parse(buf []byte) { c.markReceivedTime() t, err := Timestamp(buf) if err != nil { - c.ErrChan <- errors.New(fmt.Sprintf("could not get timestamp from sender report, failed with error: %v", err)) + c.err <- errors.New(fmt.Sprintf("could not get timestamp from sender report, failed with error: %v", err)) } c.setSenderTs(t) } diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index 62491bf9..dd7a6ff4 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -163,7 +163,7 @@ func TestReceiveAndSend(t *testing.T) { go func() { for { select { - case err := <-c.ErrChan: + 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) From b43f6f8072e3e8fe38566ec44352827bff58aa65 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 14:16:40 +0930 Subject: [PATCH 30/47] protocol/rtcp: renamed highestSequence() to sequence. --- protocol/rtcp/client.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index fda4f866..06261ef1 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -61,7 +61,7 @@ type Client struct { 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. - sequence uint32 // Last RTP sequence number. + 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. @@ -177,7 +177,7 @@ func (c *Client) send() { SSRC: c.sourceSSRC, FractionLost: 0, PacketsLost: math.MaxUint32, - HighestSequence: c.highestSequence(), + HighestSequence: c.sequence(), Jitter: c.jitter(), LSR: c.lastSenderTs(), DLSR: c.delay(), @@ -242,14 +242,14 @@ func (c *Client) parse(buf []byte) { // through an RTP stream. func (c *Client) SetSequence(s uint32) { c.mu.Lock() - c.sequence = s + c.seq = s c.mu.Unlock() } -// highestSequence will return the highest sequence number received through RTP. -func (c *Client) highestSequence() uint32 { +// sequence will return the highest sequence number received through RTP. +func (c *Client) sequence() uint32 { c.mu.Lock() - s := c.sequence + s := c.seq c.mu.Unlock() return s } From 4068aea2073dd85b3d34514545a6c6848018089a Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 14:25:22 +0930 Subject: [PATCH 31/47] protocol/rtp: better comment for NTPTimestamp and renamed fields --- protocol/rtcp/client.go | 6 +++--- protocol/rtcp/parse.go | 20 +++++++++++++++----- protocol/rtcp/parse_test.go | 8 ++++---- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 06261ef1..ce2ad88d 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -263,8 +263,8 @@ func (c *Client) jitter() uint32 { // setSenderTs allows us to safely set the current sender report timestamp. func (c *Client) setSenderTs(t NTPTimestamp) { c.mu.Lock() - binary.BigEndian.PutUint32(c.senderTs[:], t.MSW) - binary.BigEndian.PutUint32(c.senderTs[4:], t.LSW) + binary.BigEndian.PutUint32(c.senderTs[:], t.Seconds) + binary.BigEndian.PutUint32(c.senderTs[4:], t.Fraction) c.mu.Unlock() } @@ -285,7 +285,7 @@ func (c *Client) delay() uint32 { return uint32(time.Now().Sub(t).Seconds() / delayUnit) } -// received is called when a sender report is received to mark the receive time. +// 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() diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index 159d739c..271af761 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -32,10 +32,20 @@ import ( "errors" ) -// NTPTimestamp describes the NTP timestamp format (http://www.beaglesoft.com/Manual/page53.htm) +// NTPTimestamp describes an NTP timestamp. +// +// NTP timestamps are represented as a 64-bit unsigned fixed- +// point number, in seconds relative to 0h on 1 January 1900. The integer +// part is in the first 32 bits and the fraction part in the last 32 bits. +// This format allows convenient multiple-precision arithmetic and +// conversion to Time Protocol representation (seconds), but does +// complicate the conversion to ICMP Timestamp message representation +// (milliseconds). The precision of this representation is about 200 +// picoseconds, which should be adequate for even the most exotic +// requirements. type NTPTimestamp struct { - MSW uint32 - LSW uint32 + Seconds uint32 + Fraction uint32 } // Timestamp gets the timestamp from a receiver report and returns it as the most @@ -54,7 +64,7 @@ func Timestamp(buf []byte) (NTPTimestamp, error) { } return NTPTimestamp{ - MSW: binary.BigEndian.Uint32(buf[8:]), - LSW: binary.BigEndian.Uint32(buf[12:]), + Seconds: binary.BigEndian.Uint32(buf[8:]), + Fraction: binary.BigEndian.Uint32(buf[12:]), }, nil } diff --git a/protocol/rtcp/parse_test.go b/protocol/rtcp/parse_test.go index 2777bddb..66e0f18a 100644 --- a/protocol/rtcp/parse_test.go +++ b/protocol/rtcp/parse_test.go @@ -51,11 +51,11 @@ func TestTimestamp(t *testing.T) { t.Fatalf("did not expect error: %v", err) } - if ts.MSW != expectedMSW { - t.Errorf("most significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.MSW, expectedMSW) + 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.LSW != expectedLSW { - t.Errorf("least significant word of timestamp is not what's expected. \nGot: %v\n Want: %v\n", ts.LSW, expectedLSW) + 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) } } From 889072bde0e305da88a18b3357c238b24e78f443 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 14:30:05 +0930 Subject: [PATCH 32/47] protocol/rtcp: improve ReportBlock field names --- protocol/rtcp/client.go | 14 +++++++------- protocol/rtcp/client_test.go | 14 +++++++------- protocol/rtcp/rtcp.go | 23 +++++++++++------------ protocol/rtcp/rtcp_test.go | 14 +++++++------- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index ce2ad88d..c32696f1 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -174,13 +174,13 @@ func (c *Client) send() { SenderSSRC: senderSSRC, Blocks: []ReportBlock{ ReportBlock{ - SSRC: c.sourceSSRC, - FractionLost: 0, - PacketsLost: math.MaxUint32, - HighestSequence: c.sequence(), - Jitter: c.jitter(), - LSR: c.lastSenderTs(), - DLSR: c.delay(), + SourceIdentifier: c.sourceSSRC, + FractionLost: 0, + PacketsLost: math.MaxUint32, + HighestSequence: c.sequence(), + Jitter: c.jitter(), + SenderReportTs: c.lastSenderTs(), + SenderReportDelay: c.delay(), }, }, Extensions: nil, diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index dd7a6ff4..db515c16 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -69,13 +69,13 @@ func TestFormPayload(t *testing.T) { SenderSSRC: 3605043418, Blocks: []ReportBlock{ ReportBlock{ - SSRC: 1873625286, - FractionLost: 0, - PacketsLost: math.MaxUint32, - HighestSequence: 99080, - Jitter: 32, - LSR: 3118540074, - DLSR: 11257, + SourceIdentifier: 1873625286, + FractionLost: 0, + PacketsLost: math.MaxUint32, + HighestSequence: 99080, + Jitter: 32, + SenderReportTs: 3118540074, + SenderReportDelay: 11257, }, }, Extensions: nil, diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 109e129e..0e74d08f 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -39,12 +39,11 @@ const ( typeSourceDescription = 202 ) -// SDES Item types. +// Source Description Item types. const ( typeCName = 1 ) -// MISC. const ( reportBlockSize = 6 senderReportSize = 28 @@ -71,13 +70,13 @@ func (r *ReceiverReport) Bytes(buf []byte) []byte { idx := 8 for _, b := range r.Blocks { - binary.BigEndian.PutUint32(buf[idx:], b.SSRC) + 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.LSR) - binary.BigEndian.PutUint32(buf[idx+20:], b.DLSR) + binary.BigEndian.PutUint32(buf[idx+16:], b.SenderReportTs) + binary.BigEndian.PutUint32(buf[idx+20:], b.SenderReportDelay) idx += 24 } @@ -91,13 +90,13 @@ func (r *ReceiverReport) Bytes(buf []byte) []byte { // ReportBlock describes an RTCP report block used in Sender/Receiver Reports. type ReportBlock struct { - SSRC 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. - LSR uint32 // Last sender report timestamp. - DLSR uint32 // Delay since last sender report. + 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. } // SourceDescription describes a source description RTCP packet. diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index e53d384f..4eb458ce 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -57,13 +57,13 @@ func TestReceiverReportBytes(t *testing.T) { SenderSSRC: 3605043418, Blocks: []ReportBlock{ ReportBlock{ - SSRC: 1873625286, - FractionLost: 0, - PacketsLost: math.MaxUint32, - HighestSequence: 99080, - Jitter: 32, - LSR: 3118540074, - DLSR: 11257, + SourceIdentifier: 1873625286, + FractionLost: 0, + PacketsLost: math.MaxUint32, + HighestSequence: 99080, + Jitter: 32, + SenderReportTs: 3118540074, + SenderReportDelay: 11257, }, }, Extensions: nil, From 4c2962ba75ff48dd25a7b3fb0c6a76b8fcd9c6e6 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 14:33:27 +0930 Subject: [PATCH 33/47] protocol/rtcp: renamed SourceDescription to Description --- protocol/rtcp/client.go | 6 +++--- protocol/rtcp/client_test.go | 4 ++-- protocol/rtcp/rtcp.go | 16 ++++++++-------- protocol/rtcp/rtcp_test.go | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index c32696f1..74fb9c8d 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -186,12 +186,12 @@ func (c *Client) send() { Extensions: nil, } - description := SourceDescription{ + description := Description{ Header: Header{ Version: rtcpVer, Padding: false, ReportCount: 1, - Type: typeSourceDescription, + Type: typeDescription, }, Chunks: []Chunk{ Chunk{ @@ -218,7 +218,7 @@ func (c *Client) send() { // 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 *SourceDescription) []byte { +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 diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index db515c16..2f8ce5ce 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -81,12 +81,12 @@ func TestFormPayload(t *testing.T) { Extensions: nil, } - description := SourceDescription{ + description := Description{ Header: Header{ Version: 2, Padding: false, ReportCount: 1, - Type: typeSourceDescription, + Type: typeDescription, }, Chunks: []Chunk{ Chunk{ diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 0e74d08f..afde21fc 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -34,9 +34,9 @@ import ( // RTCP packet types. const ( - typeSenderReport = 200 - typeReceiverReport = 201 - typeSourceDescription = 202 + typeSenderReport = 200 + typeReceiverReport = 201 + typeDescription = 202 ) // Source Description Item types. @@ -99,14 +99,14 @@ type ReportBlock struct { SenderReportDelay uint32 // Delay since last sender report. } -// SourceDescription describes a source description RTCP packet. -type SourceDescription struct { +// 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 SourceDescription d. -func (d *SourceDescription) Bytes(buf []byte) []byte { +// Bytes returns an []byte of the Description d. +func (d *Description) Bytes(buf []byte) []byte { bodyLen := d.bodyLen() rem := bodyLen % 4 if rem != 0 { @@ -136,7 +136,7 @@ func (d *SourceDescription) Bytes(buf []byte) []byte { } // bodyLen calculates the body length of a source description packet in bytes. -func (d *SourceDescription) bodyLen() int { +func (d *Description) bodyLen() int { var l int for _, c := range d.Chunks { l += c.len() diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index 4eb458ce..a75a750d 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -88,12 +88,12 @@ func TestSourceDescriptionBytes(t *testing.T) { 0x70, 0x63, 0x00, 0x00, } - description := SourceDescription{ + description := Description{ Header: Header{ Version: 2, Padding: false, ReportCount: 1, - Type: typeSourceDescription, + Type: typeDescription, }, Chunks: []Chunk{ Chunk{ From 017abea667abf7bda42b91195b9260eb5de47ce8 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 14:38:26 +0930 Subject: [PATCH 34/47] protocol/rtcp: fixed indentation in file headers --- protocol/rtcp/client_test.go | 2 +- protocol/rtcp/rtcp.go | 4 ++-- protocol/rtcp/rtcp_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/rtcp/client_test.go b/protocol/rtcp/client_test.go index 2f8ce5ce..64a4d685 100644 --- a/protocol/rtcp/client_test.go +++ b/protocol/rtcp/client_test.go @@ -19,7 +19,7 @@ LICENSE 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. + 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. diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index afde21fc..1de243b9 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -4,7 +4,7 @@ NAME DESCRIPTION rtcp.go contains structs to describe RTCP packets, and functionality to form - []bytes of these structs. + []bytes of these structs. AUTHORS Saxon A. Nelson-Milton @@ -20,7 +20,7 @@ LICENSE 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. + 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. diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index a75a750d..0fe446ba 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -19,7 +19,7 @@ LICENSE 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. + 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. From 63da7dbb596159309b434c10cea5e322eef2a9cc Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 14:45:16 +0930 Subject: [PATCH 35/47] protocol/rtcp: removed unecessary logging in test files --- protocol/rtcp/rtcp_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/protocol/rtcp/rtcp_test.go b/protocol/rtcp/rtcp_test.go index 0fe446ba..9d109ebe 100644 --- a/protocol/rtcp/rtcp_test.go +++ b/protocol/rtcp/rtcp_test.go @@ -70,8 +70,6 @@ func TestReceiverReportBytes(t *testing.T) { } got := report.Bytes(nil) - t.Logf("Got: %v\n", got) - t.Logf("Want: %v\n", expect) if !bytes.Equal(got, expect) { t.Errorf("did not get expected result. \nGot: %v\nWant: %v\n", got, expect) } @@ -108,8 +106,6 @@ func TestSourceDescriptionBytes(t *testing.T) { }, } got := description.Bytes(nil) - t.Logf("Got: %v\n", got) - t.Logf("Expect: %v\n", expect) if !bytes.Equal(got, expect) { t.Errorf("Did not get expected result.\nGot: %v\n Want: %v\n", got, expect) } From e00c959a84fb872cb94e44f001c1754b2230fc30 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Apr 2019 16:40:26 +0930 Subject: [PATCH 36/47] protocol/rtcp: renamed NTPTimestamp to Timestamp Renamed NTPTimestamp and referenced specifications rather than quoting. Renamed Timestamp func to ParseTimestamp. --- protocol/rtcp/client.go | 4 ++-- protocol/rtcp/parse.go | 24 +++++++----------------- protocol/rtcp/parse_test.go | 2 +- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 74fb9c8d..accaceba 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -231,7 +231,7 @@ func (c *Client) formPayload(r *ReceiverReport, d *Description) []byte { // parse will read important statistics from sender reports. func (c *Client) parse(buf []byte) { c.markReceivedTime() - t, err := Timestamp(buf) + t, err := ParseTimestamp(buf) if err != nil { c.err <- errors.New(fmt.Sprintf("could not get timestamp from sender report, failed with error: %v", err)) } @@ -261,7 +261,7 @@ func (c *Client) jitter() uint32 { } // setSenderTs allows us to safely set the current sender report timestamp. -func (c *Client) setSenderTs(t NTPTimestamp) { +func (c *Client) setSenderTs(t Timestamp) { c.mu.Lock() binary.BigEndian.PutUint32(c.senderTs[:], t.Seconds) binary.BigEndian.PutUint32(c.senderTs[4:], t.Fraction) diff --git a/protocol/rtcp/parse.go b/protocol/rtcp/parse.go index 271af761..2f756f4b 100644 --- a/protocol/rtcp/parse.go +++ b/protocol/rtcp/parse.go @@ -32,18 +32,8 @@ import ( "errors" ) -// NTPTimestamp describes an NTP timestamp. -// -// NTP timestamps are represented as a 64-bit unsigned fixed- -// point number, in seconds relative to 0h on 1 January 1900. The integer -// part is in the first 32 bits and the fraction part in the last 32 bits. -// This format allows convenient multiple-precision arithmetic and -// conversion to Time Protocol representation (seconds), but does -// complicate the conversion to ICMP Timestamp message representation -// (milliseconds). The precision of this representation is about 200 -// picoseconds, which should be adequate for even the most exotic -// requirements. -type NTPTimestamp struct { +// Timestamp describes an NTP timestamp, see https://tools.ietf.org/html/rfc1305 +type Timestamp struct { Seconds uint32 Fraction uint32 } @@ -51,19 +41,19 @@ type NTPTimestamp struct { // 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 Timestamp(buf []byte) (NTPTimestamp, error) { +func ParseTimestamp(buf []byte) (Timestamp, error) { if len(buf) < 4 { - return NTPTimestamp{}, errors.New("bad RTCP packet, not of sufficient length") + return Timestamp{}, errors.New("bad RTCP packet, not of sufficient length") } if (buf[0]&0xc0)>>6 != rtcpVer { - return NTPTimestamp{}, errors.New("incompatible RTCP version") + return Timestamp{}, errors.New("incompatible RTCP version") } if buf[1] != typeSenderReport { - return NTPTimestamp{}, errors.New("RTCP packet is not of sender report type") + return Timestamp{}, errors.New("RTCP packet is not of sender report type") } - return NTPTimestamp{ + return Timestamp{ Seconds: binary.BigEndian.Uint32(buf[8:]), Fraction: binary.BigEndian.Uint32(buf[12:]), }, nil diff --git a/protocol/rtcp/parse_test.go b/protocol/rtcp/parse_test.go index 66e0f18a..ec63aac2 100644 --- a/protocol/rtcp/parse_test.go +++ b/protocol/rtcp/parse_test.go @@ -46,7 +46,7 @@ func TestTimestamp(t *testing.T) { 0x00, 0x01, 0xc2, 0xc5, } - ts, err := Timestamp(report) + ts, err := ParseTimestamp(report) if err != nil { t.Fatalf("did not expect error: %v", err) } From 7fe5e7412314d8df2a82122b4f9325874d588219 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 3 May 2019 19:58:50 +0930 Subject: [PATCH 37/47] protocol/rtcp/client.go: added comment for Err() regarding usage. --- protocol/rtcp/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index accaceba..d76a2cbd 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -127,7 +127,8 @@ func (c *Client) Stop() { c.wg.Wait() } -// Err provides read access to the Client err channel. +// 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 } From 9b3523607b130cae55a8c264172606f22aa490fc Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 3 May 2019 20:00:18 +0930 Subject: [PATCH 38/47] protocol/rtcp: added todo comment to jitter() --- protocol/rtcp/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index d76a2cbd..c9ec9b93 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -257,6 +257,7 @@ func (c *Client) sequence() uint32 { // 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 } From 040cd18db384b4e2db33612110da859eb2cd0428 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 8 May 2019 13:48:12 +0930 Subject: [PATCH 39/47] protocol/rtcp/client.go: moved err chan to bottom of fields list. --- protocol/rtcp/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index c9ec9b93..12c2795b 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -54,8 +54,6 @@ 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 { - err chan error // Client will send any errors through this chan. Can be accessed by Err(). - cAddr *net.UDPAddr // Address of client. sAddr *net.UDPAddr // Address of RTSP server. name string // Name of the client for source description purposes. @@ -71,6 +69,8 @@ type Client struct { quitSend chan struct{} // Channel used to communicate quit signal to send routine. quitRecv chan struct{} // Channel used to communicate quit signal to recv routine. 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. From 76612ea8df44d732990bef96a28b2d8d6e361ce9 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 8 May 2019 13:49:09 +0930 Subject: [PATCH 40/47] protocol/rtcp/client.go: not embedding log --- protocol/rtcp/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 12c2795b..f1329b10 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -68,7 +68,7 @@ type Client struct { wg sync.WaitGroup // This is used to wait for send and recv routines to stop when Client is stopped. quitSend chan struct{} // Channel used to communicate quit signal to send routine. quitRecv chan struct{} // Channel used to communicate quit signal to recv routine. - log // Used to log any messages. + log log // Used to log any messages. err chan error // Client will send any errors through this chan. Can be accessed by Err(). } From ea309b295ed639cf2b1d68e3c8f8fe9e1731d79e Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 8 May 2019 13:51:54 +0930 Subject: [PATCH 41/47] protocol/rtcp/client.go: only using one quit chan for both send and recv routines. --- protocol/rtcp/client.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index f1329b10..cf7d2519 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -66,8 +66,7 @@ type Client struct { 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. - quitSend chan struct{} // Channel used to communicate quit signal to send routine. - quitRecv chan struct{} // Channel used to communicate quit signal to recv routine. + 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(). @@ -82,8 +81,7 @@ func NewClient(clientAddress, serverAddress, name string, sendInterval time.Dura c := &Client{ name: name, err: make(chan error), - quitSend: make(chan struct{}), - quitRecv: make(chan struct{}), + quit: make(chan struct{}), interval: sendInterval, sourceSSRC: rtpSSRC, log: l, @@ -121,8 +119,7 @@ func (c *Client) Start() { // UDP connection. It will wait until both routines have returned. func (c *Client) Stop() { c.log(logger.Debug, pkg+"Client is stopping") - close(c.quitSend) - close(c.quitRecv) + close(c.quit) c.conn.Close() c.wg.Wait() } @@ -140,7 +137,7 @@ func (c *Client) recv() { buf := make([]byte, 4096) for { select { - case <-c.quitRecv: + case <-c.quit: return default: n, _, err := c.conn.ReadFromUDP(buf) @@ -160,7 +157,7 @@ func (c *Client) send() { c.log(logger.Debug, pkg+"Client is sending") for { select { - case <-c.quitSend: + case <-c.quit: return default: time.Sleep(c.interval) From f5d38b1bfcb839545689eac5458623b219803c74 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 8 May 2019 13:53:36 +0930 Subject: [PATCH 42/47] protocol/rtcp/client.go: using fmt.Errorf rather than errors.New(fmt.Sprintf(...)) in parse() --- protocol/rtcp/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index cf7d2519..b40aaed2 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -231,7 +231,7 @@ func (c *Client) parse(buf []byte) { c.markReceivedTime() t, err := ParseTimestamp(buf) if err != nil { - c.err <- errors.New(fmt.Sprintf("could not get timestamp from sender report, failed with error: %v", err)) + c.err <- fmt.Errorf("could not get timestamp from sender report, failed with error: %v\n", err) } c.setSenderTs(t) } From a06083ecb7e5042e559bc853be4ea420b19a5f5e Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 8 May 2019 13:54:56 +0930 Subject: [PATCH 43/47] protocol/rtcp/client.go: no newline at the end of error string in parse() --- protocol/rtcp/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index b40aaed2..aa1c5906 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -231,7 +231,7 @@ 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\n", err) + c.err <- fmt.Errorf("could not get timestamp from sender report, failed with error: %v", err) } c.setSenderTs(t) } From 841dccaec8d073c9a44b916b9cd12798d0ec3863 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 9 May 2019 11:35:07 +0930 Subject: [PATCH 44/47] protocol/rtcp/rtcp.go: added package comment --- protocol/rtcp/rtcp.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/rtcp/rtcp.go b/protocol/rtcp/rtcp.go index 1de243b9..7debdf79 100644 --- a/protocol/rtcp/rtcp.go +++ b/protocol/rtcp/rtcp.go @@ -26,6 +26,8 @@ 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 ( From 51160c884995b7628466768eb0b66e011075fe84 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 9 May 2019 11:41:51 +0930 Subject: [PATCH 45/47] protocol/rtcp/client.go: export log type --- protocol/rtcp/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index aa1c5906..3db2ad20 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -49,7 +49,7 @@ const ( receiverBufSize = 200 ) -type log func(lvl int8, msg string, args ...interface{}) +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. @@ -67,13 +67,13 @@ type Client struct { 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. + 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) { +func NewClient(clientAddress, serverAddress, name string, sendInterval time.Duration, rtpSSRC uint32, l Log) (*Client, error) { if name == "" { name = defaultClientName } From 5bd0e31db3a7bb211fb9b461f700152d52350b92 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 9 May 2019 12:24:48 +0930 Subject: [PATCH 46/47] protocol/rtcp/client.go: fixed filename in file header --- protocol/rtcp/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 3db2ad20..4abc4651 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -1,6 +1,6 @@ /* NAME - Client.go + client.go DESCRIPTION Client.go provides an implemntation of a basic RTCP Client that will send From 524dbea0e102812fafe28ad3047fc27f8dd33e9c Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 9 May 2019 12:28:31 +0930 Subject: [PATCH 47/47] protocol/rtcp/client.go: added comment for Log func signature type --- protocol/rtcp/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/rtcp/client.go b/protocol/rtcp/client.go index 4abc4651..7d6c995c 100644 --- a/protocol/rtcp/client.go +++ b/protocol/rtcp/client.go @@ -49,6 +49,8 @@ const ( 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