Browse Source

move rtsp into gortsplib

pull/2/head
aler9 5 years ago
parent
commit
9349146d47
  1. 1
      .travis.yml
  2. 24
      Makefile
  3. 47
      client.go
  4. 4
      go.mod
  5. 2
      go.sum
  6. 1
      main.go
  7. 90
      rtsp/conn.go
  8. 88
      rtsp/request.go
  9. 134
      rtsp/request_test.go
  10. 95
      rtsp/response.go
  11. 105
      rtsp/response_test.go
  12. 161
      rtsp/utils.go

1
.travis.yml

@ -4,7 +4,6 @@ services: @@ -4,7 +4,6 @@ services:
- docker
script:
- make test
- make release
deploy:

24
Makefile

@ -10,7 +10,6 @@ help: @@ -10,7 +10,6 @@ help:
@echo ""
@echo " mod-tidy run go mod tidy"
@echo " format format source files"
@echo " test run available tests"
@echo " run ARGS=args run app"
@echo " release build release assets"
@echo " travis-setup setup travis CI"
@ -24,29 +23,6 @@ format: @@ -24,29 +23,6 @@ format:
docker run --rm -it -v $(PWD):/s $(BASE_IMAGE) \
sh -c "cd /s && find . -type f -name '*.go' | xargs gofmt -l -w -s"
define DOCKERFILE_TEST
FROM $(BASE_IMAGE)
RUN apk add --no-cache make git
WORKDIR /s
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
endef
export DOCKERFILE_TEST
test:
echo "$$DOCKERFILE_TEST" | docker build -q . -f - -t temp
docker run --rm -it \
--name temp \
temp \
make test-nodocker
IMAGES = $(shell echo test-images/*/ | xargs -n1 basename)
test-nodocker:
$(eval export CGO_ENABLED = 0)
go test -v ./rtsp
define DOCKERFILE_RUN
FROM $(BASE_IMAGE)
RUN apk add --no-cache git

47
client.go

@ -9,8 +9,7 @@ import ( @@ -9,8 +9,7 @@ import (
"strconv"
"strings"
"rtsp-server/rtsp"
"github.com/aler9/gortsplib"
"gortc.io/sdp"
)
@ -74,7 +73,7 @@ func (th transportHeader) getClientPorts() (int, int) { @@ -74,7 +73,7 @@ func (th transportHeader) getClientPorts() (int, int) {
type client struct {
p *program
rconn *rtsp.Conn
rconn *gortsplib.Conn
state string
ip net.IP
path string
@ -87,7 +86,7 @@ type client struct { @@ -87,7 +86,7 @@ type client struct {
func newClient(p *program, nconn net.Conn) *client {
c := &client{
p: p,
rconn: rtsp.NewConn(nconn),
rconn: gortsplib.NewConn(nconn),
state: "STARTING",
}
@ -157,15 +156,15 @@ func (c *client) run() { @@ -157,15 +156,15 @@ func (c *client) run() {
}
}
func (c *client) writeRes(res *rtsp.Response) {
func (c *client) writeRes(res *gortsplib.Response) {
c.rconn.WriteResponse(res)
}
func (c *client) writeResError(req *rtsp.Request, err error) {
func (c *client) writeResError(req *gortsplib.Request, err error) {
c.log("ERR: %s", err)
if cseq, ok := req.Headers["CSeq"]; ok {
c.rconn.WriteResponse(&rtsp.Response{
c.rconn.WriteResponse(&gortsplib.Response{
StatusCode: 400,
Status: "Bad Request",
Headers: map[string]string{
@ -173,14 +172,14 @@ func (c *client) writeResError(req *rtsp.Request, err error) { @@ -173,14 +172,14 @@ func (c *client) writeResError(req *rtsp.Request, err error) {
},
})
} else {
c.rconn.WriteResponse(&rtsp.Response{
c.rconn.WriteResponse(&gortsplib.Response{
StatusCode: 400,
Status: "Bad Request",
})
}
}
func (c *client) handleRequest(req *rtsp.Request) bool {
func (c *client) handleRequest(req *gortsplib.Request) bool {
c.log(req.Method)
cseq, ok := req.Headers["CSeq"]
@ -216,7 +215,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -216,7 +215,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
// do not check state, since OPTIONS can be requested
// in any state
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -256,7 +255,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -256,7 +255,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
return false
}
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -316,7 +315,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -316,7 +315,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
if !ok || len(key) != 1 || key[0] != c.p.publishKey {
// reply with 401 and exit
c.log("ERR: publish key wrong or missing")
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 401,
Status: "Unauthorized",
Headers: map[string]string{
@ -348,7 +347,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -348,7 +347,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
return false
}
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -388,7 +387,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -388,7 +387,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
}() {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
c.log("ERR: udp streaming is disabled")
c.rconn.WriteResponse(&rtsp.Response{
c.rconn.WriteResponse(&gortsplib.Response{
StatusCode: 461,
Status: "Unsupported Transport",
Headers: map[string]string{
@ -441,7 +440,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -441,7 +440,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
return false
}
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -461,7 +460,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -461,7 +460,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
} else if _, ok := th["RTP/AVP/TCP"]; ok {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
c.log("ERR: tcp streaming is disabled")
c.rconn.WriteResponse(&rtsp.Response{
c.rconn.WriteResponse(&gortsplib.Response{
StatusCode: 461,
Status: "Unsupported Transport",
Headers: map[string]string{
@ -510,7 +509,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -510,7 +509,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
interleaved := fmt.Sprintf("%d-%d", ((len(c.streamTracks) - 1) * 2), ((len(c.streamTracks)-1)*2)+1)
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -556,7 +555,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -556,7 +555,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
}() {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
c.log("ERR: udp streaming is disabled")
c.rconn.WriteResponse(&rtsp.Response{
c.rconn.WriteResponse(&gortsplib.Response{
StatusCode: 461,
Status: "Unsupported Transport",
Headers: map[string]string{
@ -598,7 +597,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -598,7 +597,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
return false
}
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -618,7 +617,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -618,7 +617,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
} else if _, ok := th["RTP/AVP/TCP"]; ok {
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
c.log("ERR: tcp streaming is disabled")
c.rconn.WriteResponse(&rtsp.Response{
c.rconn.WriteResponse(&gortsplib.Response{
StatusCode: 461,
Status: "Unsupported Transport",
Headers: map[string]string{
@ -665,7 +664,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -665,7 +664,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
return false
}
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -724,7 +723,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -724,7 +723,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
// first write response, then set state
// otherwise, in case of TCP connections, RTP packets could be written
// before the response
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -778,7 +777,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -778,7 +777,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
c.state = "PRE_PLAY"
c.p.mutex.Unlock()
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
@ -814,7 +813,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool { @@ -814,7 +813,7 @@ func (c *client) handleRequest(req *rtsp.Request) bool {
return false
}
c.writeRes(&rtsp.Response{
c.writeRes(&gortsplib.Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{

4
go.mod

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
module rtsp-server
module rtsp-simple-server
go 1.13
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/stretchr/testify v1.4.0
github.com/aler9/gortsplib v0.0.0-20200120091821-97304167de21
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gortc.io/sdp v0.17.0
)

2
go.sum

@ -2,6 +2,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo @@ -2,6 +2,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aler9/gortsplib v0.0.0-20200120091821-97304167de21 h1:5TP+mdMu/PiaGeiKXqmvJRQleee93Skiha3fQXOa9ZQ=
github.com/aler9/gortsplib v0.0.0-20200120091821-97304167de21/go.mod h1:YiIgmmv0ELkWUy11Jj2h5AgfqLCpy8sIX/l9MmS8+uw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=

1
main.go

@ -147,7 +147,6 @@ func main() { @@ -147,7 +147,6 @@ func main() {
"RTSP server."
version := kingpin.Flag("version", "print rtsp-simple-server version").Bool()
protocols := kingpin.Flag("protocols", "supported protocols").Default("udp,tcp").String()
rtspPort := kingpin.Flag("rtsp-port", "port of the RTSP TCP listener").Default("8554").Int()
rtpPort := kingpin.Flag("rtp-port", "port of the RTP UDP listener").Default("8000").Int()

90
rtsp/conn.go

@ -1,90 +0,0 @@ @@ -1,90 +0,0 @@
package rtsp
import (
"encoding/binary"
"fmt"
"io"
"net"
)
type Conn struct {
c net.Conn
writeBuf []byte
}
func NewConn(c net.Conn) *Conn {
return &Conn{
c: c,
writeBuf: make([]byte, 2048),
}
}
func (c *Conn) Close() error {
return c.c.Close()
}
func (c *Conn) RemoteAddr() net.Addr {
return c.c.RemoteAddr()
}
func (c *Conn) ReadRequest() (*Request, error) {
return requestDecode(c.c)
}
func (c *Conn) WriteRequest(req *Request) error {
return requestEncode(c.c, req)
}
func (c *Conn) ReadResponse() (*Response, error) {
return responseDecode(c.c)
}
func (c *Conn) WriteResponse(res *Response) error {
return responseEncode(c.c, res)
}
func (c *Conn) ReadInterleavedFrame(frame []byte) (int, int, error) {
var header [4]byte
_, err := io.ReadFull(c.c, header[:])
if err != nil {
return 0, 0, err
}
// connection terminated
if header[0] == 0x54 {
return 0, 0, io.EOF
}
if header[0] != 0x24 {
return 0, 0, fmt.Errorf("wrong magic byte (0x%.2x)", header[0])
}
framelen := binary.BigEndian.Uint16(header[2:])
if framelen > 2048 {
return 0, 0, fmt.Errorf("frame length greater than 2048")
}
_, err = io.ReadFull(c.c, frame[:framelen])
if err != nil {
return 0, 0, err
}
return int(header[1]), int(framelen), nil
}
func (c *Conn) WriteInterleavedFrame(channel int, frame []byte) error {
c.writeBuf[0] = 0x24
c.writeBuf[1] = byte(channel)
binary.BigEndian.PutUint16(c.writeBuf[2:], uint16(len(frame)))
n := copy(c.writeBuf[4:], frame)
_, err := c.c.Write(c.writeBuf[:4+n])
if err != nil {
return err
}
return nil
}
func (c *Conn) Read(buf []byte) (int, error) {
return c.c.Read(buf)
}

88
rtsp/request.go

@ -1,88 +0,0 @@ @@ -1,88 +0,0 @@
package rtsp
import (
"bufio"
"fmt"
"io"
)
type Request struct {
Method string
Url string
Headers map[string]string
Content []byte
}
func requestDecode(r io.Reader) (*Request, error) {
rb := bufio.NewReader(r)
req := &Request{}
byts, err := readBytesLimited(rb, ' ', 255)
if err != nil {
return nil, err
}
req.Method = string(byts[:len(byts)-1])
if len(req.Method) == 0 {
return nil, fmt.Errorf("empty method")
}
byts, err = readBytesLimited(rb, ' ', 255)
if err != nil {
return nil, err
}
req.Url = string(byts[:len(byts)-1])
if len(req.Url) == 0 {
return nil, fmt.Errorf("empty path")
}
byts, err = readBytesLimited(rb, '\r', 255)
if err != nil {
return nil, err
}
proto := string(byts[:len(byts)-1])
if proto != _RTSP_PROTO {
return nil, fmt.Errorf("expected '%s', got '%s'", _RTSP_PROTO, proto)
}
err = readByteEqual(rb, '\n')
if err != nil {
return nil, err
}
req.Headers, err = readHeaders(rb)
if err != nil {
return nil, err
}
req.Content, err = readContent(rb, req.Headers)
if err != nil {
return nil, err
}
return req, nil
}
func requestEncode(w io.Writer, req *Request) error {
wb := bufio.NewWriter(w)
_, err := wb.Write([]byte(req.Method + " " + req.Url + " " + _RTSP_PROTO + "\r\n"))
if err != nil {
return err
}
err = writeHeaders(wb, req.Headers)
if err != nil {
return err
}
err = writeContent(wb, req.Content)
if err != nil {
return err
}
return wb.Flush()
}

134
rtsp/request_test.go

@ -1,134 +0,0 @@ @@ -1,134 +0,0 @@
package rtsp
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var casesRequest = []struct {
name string
byts []byte
req *Request
}{
{
"options",
[]byte("OPTIONS rtsp://example.com/media.mp4 RTSP/1.0\r\n" +
"CSeq: 1\r\n" +
"Proxy-Require: gzipped-messages\r\n" +
"Require: implicit-play\r\n" +
"\r\n"),
&Request{
Method: "OPTIONS",
Url: "rtsp://example.com/media.mp4",
Headers: map[string]string{
"CSeq": "1",
"Require": "implicit-play",
"Proxy-Require": "gzipped-messages",
},
},
},
{
"describe",
[]byte("DESCRIBE rtsp://example.com/media.mp4 RTSP/1.0\r\n" +
"CSeq: 2\r\n" +
"\r\n"),
&Request{
Method: "DESCRIBE",
Url: "rtsp://example.com/media.mp4",
Headers: map[string]string{
"CSeq": "2",
},
},
},
{
"announce",
[]byte("ANNOUNCE rtsp://example.com/media.mp4 RTSP/1.0\r\n" +
"CSeq: 7\r\n" +
"Content-Length: 306\r\n" +
"Content-Type: application/sdp\r\n" +
"Date: 23 Jan 1997 15:35:06 GMT\r\n" +
"Session: 12345678\r\n" +
"\r\n" +
"v=0\n" +
"o=mhandley 2890844526 2890845468 IN IP4 126.16.64.4\n" +
"s=SDP Seminar\n" +
"i=A Seminar on the session description protocol\n" +
"u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps\n" +
"e=mjh@isi.edu (Mark Handley)\n" +
"c=IN IP4 224.2.17.12/127\n" +
"t=2873397496 2873404696\n" +
"a=recvonly\n" +
"m=audio 3456 RTP/AVP 0\n" +
"m=video 2232 RTP/AVP 31\n"),
&Request{
Method: "ANNOUNCE",
Url: "rtsp://example.com/media.mp4",
Headers: map[string]string{
"CSeq": "7",
"Date": "23 Jan 1997 15:35:06 GMT",
"Session": "12345678",
"Content-Type": "application/sdp",
"Content-Length": "306",
},
Content: []byte("v=0\n" +
"o=mhandley 2890844526 2890845468 IN IP4 126.16.64.4\n" +
"s=SDP Seminar\n" +
"i=A Seminar on the session description protocol\n" +
"u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps\n" +
"e=mjh@isi.edu (Mark Handley)\n" +
"c=IN IP4 224.2.17.12/127\n" +
"t=2873397496 2873404696\n" +
"a=recvonly\n" +
"m=audio 3456 RTP/AVP 0\n" +
"m=video 2232 RTP/AVP 31\n",
),
},
},
{
"get_parameter",
[]byte("GET_PARAMETER rtsp://example.com/media.mp4 RTSP/1.0\r\n" +
"CSeq: 9\r\n" +
"Content-Length: 24\r\n" +
"Content-Type: text/parameters\r\n" +
"Session: 12345678\r\n" +
"\r\n" +
"packets_received\n" +
"jitter\n"),
&Request{
Method: "GET_PARAMETER",
Url: "rtsp://example.com/media.mp4",
Headers: map[string]string{
"CSeq": "9",
"Content-Type": "text/parameters",
"Session": "12345678",
"Content-Length": "24",
},
Content: []byte("packets_received\n" +
"jitter\n",
),
},
},
}
func TestRequestDecode(t *testing.T) {
for _, c := range casesRequest {
t.Run(c.name, func(t *testing.T) {
req, err := requestDecode(bytes.NewBuffer(c.byts))
require.NoError(t, err)
require.Equal(t, c.req, req)
})
}
}
func TestRequestEncode(t *testing.T) {
for _, c := range casesRequest {
t.Run(c.name, func(t *testing.T) {
var buf bytes.Buffer
err := requestEncode(&buf, c.req)
require.NoError(t, err)
require.Equal(t, c.byts, buf.Bytes())
})
}
}

95
rtsp/response.go

@ -1,95 +0,0 @@ @@ -1,95 +0,0 @@
package rtsp
import (
"bufio"
"fmt"
"io"
"strconv"
)
type Response struct {
StatusCode int
Status string
Headers map[string]string
Content []byte
}
func responseDecode(r io.Reader) (*Response, error) {
rb := bufio.NewReader(r)
res := &Response{}
byts, err := readBytesLimited(rb, ' ', 255)
if err != nil {
return nil, err
}
proto := string(byts[:len(byts)-1])
if proto != _RTSP_PROTO {
return nil, fmt.Errorf("expected '%s', got '%s'", _RTSP_PROTO, proto)
}
byts, err = readBytesLimited(rb, ' ', 4)
if err != nil {
return nil, err
}
statusCodeStr := string(byts[:len(byts)-1])
statusCode64, err := strconv.ParseInt(statusCodeStr, 10, 32)
res.StatusCode = int(statusCode64)
if err != nil {
return nil, fmt.Errorf("unable to parse status code")
}
byts, err = readBytesLimited(rb, '\r', 255)
if err != nil {
return nil, err
}
res.Status = string(byts[:len(byts)-1])
if len(res.Status) == 0 {
return nil, fmt.Errorf("empty status")
}
err = readByteEqual(rb, '\n')
if err != nil {
return nil, err
}
res.Headers, err = readHeaders(rb)
if err != nil {
return nil, err
}
res.Content, err = readContent(rb, res.Headers)
if err != nil {
return nil, err
}
return res, nil
}
func responseEncode(w io.Writer, res *Response) error {
wb := bufio.NewWriter(w)
_, err := wb.Write([]byte(_RTSP_PROTO + " " + strconv.FormatInt(int64(res.StatusCode), 10) + " " + res.Status + "\r\n"))
if err != nil {
return err
}
if len(res.Content) != 0 {
res.Headers["Content-Length"] = strconv.FormatInt(int64(len(res.Content)), 10)
}
err = writeHeaders(wb, res.Headers)
if err != nil {
return err
}
err = writeContent(wb, res.Content)
if err != nil {
return err
}
return wb.Flush()
}

105
rtsp/response_test.go

@ -1,105 +0,0 @@ @@ -1,105 +0,0 @@
package rtsp
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var casesResponse = []struct {
name string
byts []byte
res *Response
}{
{
"ok",
[]byte("RTSP/1.0 200 OK\r\n" +
"CSeq: 1\r\n" +
"Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n" +
"\r\n",
),
&Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
"CSeq": "1",
"Public": "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE",
},
},
},
{
"ok with content",
[]byte("RTSP/1.0 200 OK\r\n" +
"CSeq: 2\r\n" +
"Content-Base: rtsp://example.com/media.mp4\r\n" +
"Content-Length: 444\r\n" +
"Content-Type: application/sdp\r\n" +
"\r\n" +
"m=video 0 RTP/AVP 96\n" +
"a=control:streamid=0\n" +
"a=range:npt=0-7.741000\n" +
"a=length:npt=7.741000\n" +
"a=rtpmap:96 MP4V-ES/5544\n" +
"a=mimetype:string;\"video/MP4V-ES\"\n" +
"a=AvgBitRate:integer;304018\n" +
"a=StreamName:string;\"hinted video track\"\n" +
"m=audio 0 RTP/AVP 97\n" +
"a=control:streamid=1\n" +
"a=range:npt=0-7.712000\n" +
"a=length:npt=7.712000\n" +
"a=rtpmap:97 mpeg4-generic/32000/2\n" +
"a=mimetype:string;\"audio/mpeg4-generic\"\n" +
"a=AvgBitRate:integer;65790\n" +
"a=StreamName:string;\"hinted audio track\"\n",
),
&Response{
StatusCode: 200,
Status: "OK",
Headers: map[string]string{
"Content-Base": "rtsp://example.com/media.mp4",
"Content-Length": "444",
"Content-Type": "application/sdp",
"CSeq": "2",
},
Content: []byte("m=video 0 RTP/AVP 96\n" +
"a=control:streamid=0\n" +
"a=range:npt=0-7.741000\n" +
"a=length:npt=7.741000\n" +
"a=rtpmap:96 MP4V-ES/5544\n" +
"a=mimetype:string;\"video/MP4V-ES\"\n" +
"a=AvgBitRate:integer;304018\n" +
"a=StreamName:string;\"hinted video track\"\n" +
"m=audio 0 RTP/AVP 97\n" +
"a=control:streamid=1\n" +
"a=range:npt=0-7.712000\n" +
"a=length:npt=7.712000\n" +
"a=rtpmap:97 mpeg4-generic/32000/2\n" +
"a=mimetype:string;\"audio/mpeg4-generic\"\n" +
"a=AvgBitRate:integer;65790\n" +
"a=StreamName:string;\"hinted audio track\"\n",
),
},
},
}
func TestResponseDecode(t *testing.T) {
for _, c := range casesResponse {
t.Run(c.name, func(t *testing.T) {
res, err := responseDecode(bytes.NewBuffer(c.byts))
require.NoError(t, err)
require.Equal(t, c.res, res)
})
}
}
func TestResponseEncode(t *testing.T) {
for _, c := range casesResponse {
t.Run(c.name, func(t *testing.T) {
var buf bytes.Buffer
err := responseEncode(&buf, c.res)
require.NoError(t, err)
require.Equal(t, c.byts, buf.Bytes())
})
}
}

161
rtsp/utils.go

@ -1,161 +0,0 @@ @@ -1,161 +0,0 @@
package rtsp
import (
"bufio"
"fmt"
"io"
"sort"
"strconv"
)
const (
_RTSP_PROTO = "RTSP/1.0"
_MAX_HEADER_COUNT = 255
_MAX_HEADER_KEY_LENGTH = 255
_MAX_HEADER_VALUE_LENGTH = 255
_MAX_CONTENT_LENGTH = 4096
)
func readBytesLimited(rb *bufio.Reader, delim byte, n int) ([]byte, error) {
for i := 1; i <= n; i++ {
byts, err := rb.Peek(i)
if err != nil {
return nil, err
}
if byts[len(byts)-1] == delim {
rb.Discard(len(byts))
return byts, nil
}
}
return nil, fmt.Errorf("buffer length exceeds %d", n)
}
func readByteEqual(rb *bufio.Reader, cmp byte) error {
byt, err := rb.ReadByte()
if err != nil {
return err
}
if byt != cmp {
return fmt.Errorf("expected '%c', got '%c'", cmp, byt)
}
return nil
}
func readHeaders(rb *bufio.Reader) (map[string]string, error) {
ret := make(map[string]string)
for {
byt, err := rb.ReadByte()
if err != nil {
return nil, err
}
if byt == '\r' {
err := readByteEqual(rb, '\n')
if err != nil {
return nil, err
}
break
}
if len(ret) >= _MAX_HEADER_COUNT {
return nil, fmt.Errorf("headers count exceeds %d", _MAX_HEADER_COUNT)
}
key := string([]byte{byt})
byts, err := readBytesLimited(rb, ':', _MAX_HEADER_KEY_LENGTH-1)
if err != nil {
return nil, err
}
key += string(byts[:len(byts)-1])
err = readByteEqual(rb, ' ')
if err != nil {
return nil, err
}
byts, err = readBytesLimited(rb, '\r', _MAX_HEADER_VALUE_LENGTH)
if err != nil {
return nil, err
}
val := string(byts[:len(byts)-1])
if len(val) == 0 {
return nil, fmt.Errorf("empty header value")
}
err = readByteEqual(rb, '\n')
if err != nil {
return nil, err
}
ret[key] = val
}
return ret, nil
}
func writeHeaders(wb *bufio.Writer, headers map[string]string) error {
// sort headers by key
// in order to obtain deterministic results
var keys []string
for key := range headers {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
_, err := wb.Write([]byte(key + ": " + headers[key] + "\r\n"))
if err != nil {
return err
}
}
_, err := wb.Write([]byte("\r\n"))
if err != nil {
return err
}
return nil
}
func readContent(rb *bufio.Reader, headers map[string]string) ([]byte, error) {
cls, ok := headers["Content-Length"]
if !ok {
return nil, nil
}
cl, err := strconv.ParseInt(cls, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid Content-Length")
}
if cl > _MAX_CONTENT_LENGTH {
return nil, fmt.Errorf("Content-Length exceeds %d", _MAX_CONTENT_LENGTH)
}
ret := make([]byte, cl)
n, err := io.ReadFull(rb, ret)
if err != nil && n != len(ret) {
return nil, err
}
return ret, nil
}
func writeContent(wb *bufio.Writer, content []byte) error {
if len(content) == 0 {
return nil
}
_, err := wb.Write(content)
if err != nil {
return err
}
return nil
}
Loading…
Cancel
Save