From be1994a49b6e11b7e733fb1c5998d837c2e725e3 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 4 May 2016 22:28:01 +0800 Subject: [PATCH 01/61] first blood --- client.go | 318 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ stream.go | 20 ++++ 2 files changed, 338 insertions(+) create mode 100644 client.go create mode 100644 stream.go diff --git a/client.go b/client.go new file mode 100644 index 0000000..7a75588 --- /dev/null +++ b/client.go @@ -0,0 +1,318 @@ +package rtsp + +import ( + "fmt" + "net" + "io" + "io/ioutil" + "strings" + "strconv" + "bufio" + "net/textproto" + "net/url" + "encoding/hex" + "crypto/md5" + "github.com/nareix/av" +) + +type Client struct { + DebugConn bool + url *url.URL + conn net.Conn + requestUri string + cseq uint + streams []*Stream + session string + authorization string + body io.Reader +} + +func Connect(uri string) (self *Client, err error) { + var URL *url.URL + if URL, err = url.Parse(uri); err != nil { + return + } + + dailer := net.Dialer{} + var conn net.Conn + if conn, err = dailer.Dial("tcp", URL.Host); err != nil { + return + } + + u2 := *URL + u2.User = nil + + self = &Client{ + conn: conn, + url: URL, + requestUri: u2.String(), + } + return +} + +func (self *Client) writeLine(line string) (err error) { + if self.DebugConn { + fmt.Print(line) + } + _, err = fmt.Fprint(self.conn, line) + return +} + +func (self *Client) WriteRequest(method string, uri string, headers []string) (err error) { + self.cseq++ + headers = append(headers, fmt.Sprintf("CSeq: %d", self.cseq)) + if err = self.writeLine(fmt.Sprintf("%s %s RTSP/1.0\r\n", method, uri)); err != nil { + return + } + for _, header := range headers { + if err = self.writeLine(header+"\r\n"); err != nil { + return + } + } + if err = self.writeLine("\r\n"); err != nil { + return + } + return +} + +func (self *Client) ReadResponse() (statusCode int, body io.Reader, err error) { + br := bufio.NewReader(self.conn) + tp := textproto.NewReader(br) + + var line string + if line, err = tp.ReadLine(); err != nil { + return + } + if self.DebugConn { + fmt.Println(line) + } + + fline := strings.SplitN(line, " ", 3) + if len(fline) < 2 { + err = fmt.Errorf("malformed RTSP response") + return + } + + if statusCode, err = strconv.Atoi(fline[1]); err != nil { + return + } + if statusCode != 200 && statusCode != 401 { + err = fmt.Errorf("statusCode(%d) invalid", statusCode) + return + } + + var header textproto.MIMEHeader + if header, err = tp.ReadMIMEHeader(); err != nil { + return + } + + if statusCode == 401 { + /* + RTSP/1.0 401 Unauthorized + CSeq: 2 + Date: Wed, May 04 2016 10:10:51 GMT + WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="c633aaf8b83127633cbe98fac1d20d87" + */ + authval := header.Get("WWW-Authenticate") + hdrval := strings.SplitN(authval, " ", 2) + var realm, nonce string + + if len(hdrval) == 2 { + for _, field := range strings.Split(hdrval[1], ",") { + field = strings.Trim(field, ", ") + if keyval := strings.Split(field, "="); len(keyval) == 2 { + key := keyval[0] + val := strings.Trim(keyval[1], `"`) + switch key { + case "realm": + realm = val + case "nonce": + nonce = val + } + } + } + + if realm != "" && nonce != "" { + if self.url.User == nil { + err = fmt.Errorf("please provide username and password") + return + } + var username string + var password string + var ok bool + username = self.url.User.Username() + if password, ok = self.url.User.Password(); !ok { + err = fmt.Errorf("please provide password") + return + } + hs1 := md5hash(username+":"+realm+":"+password) + hs2 := md5hash("DESCRIBE:"+self.requestUri) + response := md5hash(hs1+":"+nonce+":"+hs2) + self.authorization = fmt.Sprintf( + `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, + username, realm, nonce, self.requestUri, response) + } + } + } + + if sess := header.Get("Session"); sess != "" && self.session == "" { + if fields := strings.Split(sess, ";"); len(fields) > 0 { + self.session = fields[0] + } + } + + clen, _ := strconv.Atoi(header.Get("Content-Length")) + + if statusCode == 200 { + if clen > 0 { + body = io.LimitReader(br, int64(clen)) + } else { + body = io.MultiReader(io.LimitReader(br, int64(br.Buffered())), self.conn) + } + } + + return +} + +func (self *Client) Setup(streams []int) (err error) { + for _, si := range streams { + reqhdr := []string{fmt.Sprintf("Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d", si*2, si*2+1)} + if self.session != "" { + reqhdr = append(reqhdr, "Session: "+self.session) + } + if err = self.WriteRequest("SETUP", self.requestUri+"/"+self.streams[si].control, reqhdr); err != nil { + return + } + if _, _, err = self.ReadResponse(); err != nil { + return + } + } + return +} + +func md5hash(s string) string { + h := md5.Sum([]byte(s)) + return hex.EncodeToString(h[:]) +} + +func (self *Client) Describe() (streams []*Stream, err error) { + var body io.Reader + var statusCode int + + for i := 0; i < 2; i++ { + reqhdr := []string{} + if self.authorization != "" { + reqhdr = append(reqhdr, self.authorization) + } + if err = self.WriteRequest("DESCRIBE", self.requestUri, reqhdr); err != nil { + return + } + if statusCode, body, err = self.ReadResponse(); err != nil { + return + } + if statusCode == 200 { + break + } + } + if body == nil { + err = fmt.Errorf("Describe failed") + return + } + + br := bufio.NewReader(body) + tp := textproto.NewReader(br) + var stream *Stream + + for { + line, err := tp.ReadLine() + if err != nil { + break + } + + if self.DebugConn { + fmt.Println(line) + } + + typeval := strings.SplitN(line, "=", 2) + if len(typeval) == 2 { + fields := strings.Split(typeval[1], " ") + switch typeval[0] { + case "m": + if len(fields) > 0 { + switch fields[0] { + case "audio", "video": + stream = &Stream{typestr: fields[0]} + self.streams = append(self.streams, stream) + } + } + + case "a": + if stream != nil { + for _, field := range fields { + keyval := strings.Split(field, ":") + if len(keyval) >= 2 { + key := keyval[0] + val := keyval[1] + if key == "control" { + stream.control = val + } + } + } + } + } + } + } + + streams = self.streams + return +} + +func (self *Client) Options() (err error) { + if err = self.WriteRequest("OPTIONS", self.requestUri, []string{}); err != nil { + return + } + if _, _, err = self.ReadResponse(); err != nil { + return + } + return +} + +func (self *Client) readBlock() (err error) { + var h [4]byte + for { + if _, err = io.ReadFull(self.body, h[:]); err != nil { + return + } + if h[0] != 36 { + err = fmt.Errorf("block not start with $") + fmt.Println(h) + return + } + length := int(h[2])<<8+int(h[3]) + + if self.DebugConn { + fmt.Println("packet", length, h[1]) + } + + if _, err = io.CopyN(ioutil.Discard, self.body, int64(length)); err != nil { + return + } + } +} + +func (self *Client) ReadHeader() (streams []av.Stream, err error) { + if err = self.WriteRequest("PLAY", self.requestUri, []string{"Session: "+self.session}); err != nil { + return + } + if _, self.body, err = self.ReadResponse(); err != nil { + return + } + + for { + if err = self.readBlock(); err != nil { + return + } + } + return +} + diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..bd9b1d1 --- /dev/null +++ b/stream.go @@ -0,0 +1,20 @@ +package rtsp + +import ( + "github.com/nareix/av" +) + +type Stream struct { + av.StreamCommon + typestr string + control string +} + +func (self Stream) IsAudio() bool { + return self.typestr == "audio" +} + +func (self Stream) IsVideo() bool { + return self.typestr == "video" +} + From ab6a068d45062abce299d32914f0f3c682589f54 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 19 May 2016 19:40:50 +0800 Subject: [PATCH 02/61] second --- client.go | 510 +++++++++++++++++++++++++++++++++++---------- sdp/parser.go | 94 +++++++++ sdp/parser_test.go | 36 ++++ stream.go | 16 +- 4 files changed, 545 insertions(+), 111 deletions(-) create mode 100644 sdp/parser.go create mode 100644 sdp/parser_test.go diff --git a/client.go b/client.go index 7a75588..1a4576d 100644 --- a/client.go +++ b/client.go @@ -3,22 +3,26 @@ package rtsp import ( "fmt" "net" + "bytes" "io" - "io/ioutil" "strings" "strconv" "bufio" "net/textproto" "net/url" "encoding/hex" + "encoding/binary" "crypto/md5" "github.com/nareix/av" + "github.com/nareix/codec/h264parser" + "github.com/nareix/rtsp/sdp" ) type Client struct { DebugConn bool url *url.URL conn net.Conn + rconn io.Reader requestUri string cseq uint streams []*Stream @@ -27,6 +31,23 @@ type Client struct { body io.Reader } +type Request struct { + Header []string + Uri string + Method string +} + +type Response struct { + BlockLength int + Block []byte + BlockNo int + + StatusCode int + Header textproto.MIMEHeader + ContentLength int + Body []byte +} + func Connect(uri string) (self *Client, err error) { var URL *url.URL if URL, err = url.Parse(uri); err != nil { @@ -44,6 +65,7 @@ func Connect(uri string) (self *Client, err error) { self = &Client{ conn: conn, + rconn: conn, url: URL, requestUri: u2.String(), } @@ -52,20 +74,20 @@ func Connect(uri string) (self *Client, err error) { func (self *Client) writeLine(line string) (err error) { if self.DebugConn { - fmt.Print(line) + fmt.Print("> ", line) } _, err = fmt.Fprint(self.conn, line) return } -func (self *Client) WriteRequest(method string, uri string, headers []string) (err error) { +func (self *Client) WriteRequest(req Request) (err error) { self.cseq++ - headers = append(headers, fmt.Sprintf("CSeq: %d", self.cseq)) - if err = self.writeLine(fmt.Sprintf("%s %s RTSP/1.0\r\n", method, uri)); err != nil { + req.Header = append(req.Header, fmt.Sprintf("CSeq: %d", self.cseq)) + if err = self.writeLine(fmt.Sprintf("%s %s RTSP/1.0\r\n", req.Method, req.Uri)); err != nil { return } - for _, header := range headers { - if err = self.writeLine(header+"\r\n"); err != nil { + for _, v := range req.Header { + if err = self.writeLine(fmt.Sprint(v, "\r\n")); err != nil { return } } @@ -75,8 +97,50 @@ func (self *Client) WriteRequest(method string, uri string, headers []string) (e return } -func (self *Client) ReadResponse() (statusCode int, body io.Reader, err error) { - br := bufio.NewReader(self.conn) +func (self *Client) ReadResponse() (res Response, err error) { + var br *bufio.Reader + + defer func() { + if br != nil { + buf, _ := br.Peek(br.Buffered()) + self.rconn = io.MultiReader(bytes.NewReader(buf), self.rconn) + } + if res.StatusCode == 200 { + if res.ContentLength > 0 { + res.Body = make([]byte, res.ContentLength) + if _, err = io.ReadFull(self.rconn, res.Body); err != nil { + return + } + } + } else if res.BlockLength > 0 { + res.Block = make([]byte, res.BlockLength) + if _, err = io.ReadFull(self.rconn, res.Block); err != nil { + return + } + } + }() + + var h [4]byte + if _, err = io.ReadFull(self.rconn, h[:]); err != nil { + return + } + if h[0] == 36 { + // $ + res.BlockLength = int(h[2])<<8+int(h[3]) + res.BlockNo = int(h[1]) + if self.DebugConn { + fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) + } + return + } else if h[0] == 82 { + // RTSP 200 OK + self.rconn = io.MultiReader(bytes.NewReader(h[:]), self.rconn) + } else { + err = fmt.Errorf("invalid response bytes=%v", h) + return + } + + br = bufio.NewReader(self.rconn) tp := textproto.NewReader(br) var line string @@ -84,29 +148,33 @@ func (self *Client) ReadResponse() (statusCode int, body io.Reader, err error) { return } if self.DebugConn { - fmt.Println(line) + fmt.Println("<", line) } fline := strings.SplitN(line, " ", 3) if len(fline) < 2 { - err = fmt.Errorf("malformed RTSP response") + err = fmt.Errorf("malformed RTSP response line") return } - if statusCode, err = strconv.Atoi(fline[1]); err != nil { + if res.StatusCode, err = strconv.Atoi(fline[1]); err != nil { return } - if statusCode != 200 && statusCode != 401 { - err = fmt.Errorf("statusCode(%d) invalid", statusCode) - return - } - var header textproto.MIMEHeader if header, err = tp.ReadMIMEHeader(); err != nil { return } - if statusCode == 401 { + if self.DebugConn { + fmt.Println("<", header) + } + + if res.StatusCode != 200 && res.StatusCode != 401 { + err = fmt.Errorf("StatusCode=%d invalid", res.StatusCode) + return + } + + if res.StatusCode == 401 { /* RTSP/1.0 401 Unauthorized CSeq: 2 @@ -149,7 +217,7 @@ func (self *Client) ReadResponse() (statusCode int, body io.Reader, err error) { hs2 := md5hash("DESCRIBE:"+self.requestUri) response := md5hash(hs1+":"+nonce+":"+hs2) self.authorization = fmt.Sprintf( - `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, + `Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, username, realm, nonce, self.requestUri, response) } } @@ -161,29 +229,25 @@ func (self *Client) ReadResponse() (statusCode int, body io.Reader, err error) { } } - clen, _ := strconv.Atoi(header.Get("Content-Length")) - - if statusCode == 200 { - if clen > 0 { - body = io.LimitReader(br, int64(clen)) - } else { - body = io.MultiReader(io.LimitReader(br, int64(br.Buffered())), self.conn) - } - } + res.ContentLength, _ = strconv.Atoi(header.Get("Content-Length")) return } func (self *Client) Setup(streams []int) (err error) { for _, si := range streams { - reqhdr := []string{fmt.Sprintf("Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d", si*2, si*2+1)} - if self.session != "" { - reqhdr = append(reqhdr, "Session: "+self.session) + req := Request{ + Method: "SETUP", + Uri: self.requestUri+"/"+self.streams[si].Sdp.Control, } - if err = self.WriteRequest("SETUP", self.requestUri+"/"+self.streams[si].control, reqhdr); err != nil { + req.Header = append(req.Header, fmt.Sprintf("Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d", si*2, si*2+1)) + if self.session != "" { + req.Header = append(req.Header, "Session: "+self.session) + } + if err = self.WriteRequest(req); err != nil { return } - if _, _, err = self.ReadResponse(); err != nil { + if _, err = self.ReadResponse(); err != nil { return } } @@ -195,124 +259,356 @@ func md5hash(s string) string { return hex.EncodeToString(h[:]) } -func (self *Client) Describe() (streams []*Stream, err error) { - var body io.Reader - var statusCode int +func (self *Client) Describe() (streams []av.Stream, err error) { + var res Response for i := 0; i < 2; i++ { - reqhdr := []string{} + req := Request{ + Method: "DESCRIBE", + Uri: self.requestUri, + } if self.authorization != "" { - reqhdr = append(reqhdr, self.authorization) + req.Header = append(req.Header, "Authorization: "+self.authorization) } - if err = self.WriteRequest("DESCRIBE", self.requestUri, reqhdr); err != nil { + if err = self.WriteRequest(req); err != nil { return } - if statusCode, body, err = self.ReadResponse(); err != nil { + if res, err = self.ReadResponse(); err != nil { return } - if statusCode == 200 { + if res.StatusCode == 200 { break } } - if body == nil { + if res.ContentLength == 0 { err = fmt.Errorf("Describe failed") return } - br := bufio.NewReader(body) - tp := textproto.NewReader(br) - var stream *Stream + body := string(res.Body) - for { - line, err := tp.ReadLine() - if err != nil { - break - } + if self.DebugConn { + fmt.Println("<", body) + } - if self.DebugConn { - fmt.Println(line) - } + self.streams = []*Stream{} + for _, info := range sdp.Decode(body) { + stream := &Stream{Sdp: info} + stream.SetType(info.Type) - typeval := strings.SplitN(line, "=", 2) - if len(typeval) == 2 { - fields := strings.Split(typeval[1], " ") - switch typeval[0] { - case "m": - if len(fields) > 0 { - switch fields[0] { - case "audio", "video": - stream = &Stream{typestr: fields[0]} - self.streams = append(self.streams, stream) - } - } - - case "a": - if stream != nil { - for _, field := range fields { - keyval := strings.Split(field, ":") - if len(keyval) >= 2 { - key := keyval[0] - val := keyval[1] - if key == "control" { - stream.control = val - } - } + switch info.Type { + case av.H264: + var sps, pps []byte + for _, nalu := range info.SpropParameterSets { + if len(nalu) > 0 { + switch nalu[0]&0x1f { + case 7: + sps = nalu + case 8: + pps = nalu } } } + if len(sps) > 0 && len(pps) > 0 { + codecData, _ := h264parser.CreateCodecDataBySPSAndPPS(sps, pps) + if err = stream.SetCodecData(codecData); err != nil { + err = fmt.Errorf("h264 sps/pps invalid: %s", err) + return + } + } else { + err = fmt.Errorf("h264 sdp sprop-parameter-sets invalid: missing sps or pps") + return + } + + case av.AAC: + if len(info.Config) == 0 { + err = fmt.Errorf("aac sdp config missing") + return + } + if err = stream.SetCodecData(info.Config); err != nil { + err = fmt.Errorf("aac sdp config invalid: %s", err) + return + } } + + self.streams = append(self.streams, stream) } - streams = self.streams + for _, stream := range self.streams { + streams = append(streams, stream) + } return } func (self *Client) Options() (err error) { - if err = self.WriteRequest("OPTIONS", self.requestUri, []string{}); err != nil { + if err = self.WriteRequest(Request{ + Method: "OPTIONS", + Uri: self.requestUri, + }); err != nil { return } - if _, _, err = self.ReadResponse(); err != nil { + if _, err = self.ReadResponse(); err != nil { return } return } -func (self *Client) readBlock() (err error) { - var h [4]byte - for { - if _, err = io.ReadFull(self.body, h[:]); err != nil { - return - } - if h[0] != 36 { - err = fmt.Errorf("block not start with $") - fmt.Println(h) - return - } - length := int(h[2])<<8+int(h[3]) +func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet []byte) (err error) { + /* + Table 7-1 – NAL unit type codes + 1 Coded slice of a non-IDR picture + 5 Coded slice of an IDR picture + 6 Supplemental enhancement information (SEI) + 7 Sequence parameter set + 8 Picture parameter set + */ + switch naluType { + case 7,8: + // sps/pps - if self.DebugConn { - fmt.Println("packet", length, h[1]) - } - - if _, err = io.CopyN(ioutil.Discard, self.body, int64(length)); err != nil { - return + default: + if naluType == 5 { + self.pkt.IsKeyFrame = true } + self.gotpkt = true + self.pkt.Data = packet } + + return } -func (self *Client) ReadHeader() (streams []av.Stream, err error) { - if err = self.WriteRequest("PLAY", self.requestUri, []string{"Session: "+self.session}); err != nil { - return - } - if _, self.body, err = self.ReadResponse(); err != nil { - return - } +func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { + switch self.Type() { + case av.AAC: - for { - if err = self.readBlock(); err != nil { + case av.H264: + /* + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + */ + naluType := packet[0]&0x1f + + /* + NAL Unit Packet Packet Type Name Section + Type Type + ------------------------------------------------------------- + 0 reserved - + 1-23 NAL unit Single NAL unit packet 5.6 + 24 STAP-A Single-time aggregation packet 5.7.1 + 25 STAP-B Single-time aggregation packet 5.7.1 + 26 MTAP16 Multi-time aggregation packet 5.7.2 + 27 MTAP24 Multi-time aggregation packet 5.7.2 + 28 FU-A Fragmentation unit 5.8 + 29 FU-B Fragmentation unit 5.8 + 30-31 reserved - + */ + + switch { + case naluType >= 1 && naluType <= 23: + if err = self.handleH264Payload(naluType, timestamp, packet); err != nil { + return + } + + case naluType == 28: + /* + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | FU indicator | FU header | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + | | + | FU payload | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Figure 14. RTP payload format for FU-A + + The FU indicator octet has the following format: + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + + + The FU header has the following format: + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |S|E|R| Type | + +---------------+ + + S: 1 bit + When set to one, the Start bit indicates the start of a fragmented + NAL unit. When the following FU payload is not the start of a + fragmented NAL unit payload, the Start bit is set to zero. + + E: 1 bit + When set to one, the End bit indicates the end of a fragmented NAL + unit, i.e., the last byte of the payload is also the last byte of + the fragmented NAL unit. When the following FU payload is not the + last fragment of a fragmented NAL unit, the End bit is set to + zero. + + R: 1 bit + The Reserved bit MUST be equal to 0 and MUST be ignored by the + receiver. + + Type: 5 bits + The NAL unit payload type as defined in table 7-1 of [1]. + */ + fuIndicator := packet[0] + fuHeader := packet[1] + isStart := fuHeader&0x80!=0 + isEnd := fuHeader&0x40!=0 + naluType := fuHeader&0x1f + if isStart { + self.fuBuffer = []byte{fuIndicator&0xe0|fuHeader&0x1f} + } + self.fuBuffer = append(self.fuBuffer, packet[2:]...) + if isEnd { + if err = self.handleH264Payload(naluType, timestamp, self.fuBuffer); err != nil { + return + } + } + + default: + err = fmt.Errorf("unsupported H264 naluType=%d", naluType) return } } return } +func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err error) { + if blockNo % 2 != 0 { + // rtcp block + return + } + + /* + 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 | + | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + if len(packet) < 8 { + err = fmt.Errorf("rtp packet too short") + return + } + payloadOffset := 12+int(packet[0]&0xf)*4 + if payloadOffset+2 > len(packet) { + err = fmt.Errorf("rtp packet too short") + return + } + + timestamp := binary.BigEndian.Uint32(packet[4:8]) + payload := packet[payloadOffset:] + + /* + PT Encoding Name Audio/Video (A/V) Clock Rate (Hz) Channels Reference + 0 PCMU A 8000 1 [RFC3551] + 1 Reserved + 2 Reserved + 3 GSM A 8000 1 [RFC3551] + 4 G723 A 8000 1 [Vineet_Kumar][RFC3551] + 5 DVI4 A 8000 1 [RFC3551] + 6 DVI4 A 16000 1 [RFC3551] + 7 LPC A 8000 1 [RFC3551] + 8 PCMA A 8000 1 [RFC3551] + 9 G722 A 8000 1 [RFC3551] + 10 L16 A 44100 2 [RFC3551] + 11 L16 A 44100 1 [RFC3551] + 12 QCELP A 8000 1 [RFC3551] + 13 CN A 8000 1 [RFC3389] + 14 MPA A 90000 [RFC3551][RFC2250] + 15 G728 A 8000 1 [RFC3551] + 16 DVI4 A 11025 1 [Joseph_Di_Pol] + 17 DVI4 A 22050 1 [Joseph_Di_Pol] + 18 G729 A 8000 1 [RFC3551] + 19 Reserved A + 20 Unassigned A + 21 Unassigned A + 22 Unassigned A + 23 Unassigned A + 24 Unassigned V + 25 CelB V 90000 [RFC2029] + 26 JPEG V 90000 [RFC2435] + 27 Unassigned V + 28 nv V 90000 [RFC3551] + 29 Unassigned V + 30 Unassigned V + 31 H261 V 90000 [RFC4587] + 32 MPV V 90000 [RFC2250] + 33 MP2T AV 90000 [RFC2250] + 34 H263 V 90000 [Chunrong_Zhu] + 35-71 Unassigned ? + 72-76 Reserved for RTCP conflict avoidance [RFC3551] + 77-95 Unassigned ? + 96-127 dynamic ? [RFC3551] + */ + //payloadType := packet[1]&0x7f + + streamIndex = blockNo/2 + stream := self.streams[streamIndex] + + if self.DebugConn { + //fmt.Println("packet:", stream.Type(), "offset", payloadOffset, "pt", payloadType) + if len(packet)>24 { + fmt.Println(hex.Dump(packet[:24])) + } + } + + if err = stream.handlePacket(timestamp, payload); err != nil { + return + } + + return +} + +func (self *Client) Play() (err error) { + req := Request{ + Method: "PLAY", + Uri: self.requestUri, + } + req.Header = append(req.Header, "Session: "+self.session) + if err = self.WriteRequest(req); err != nil { + return + } + return +} + +func (self *Client) ReadPacket() (streamIndex int, pkt av.Packet, err error) { + for { + var res Response + if res, err = self.ReadResponse(); err != nil { + return + } + if res.BlockLength > 0 { + if streamIndex, err = self.parseBlock(res.BlockNo, res.Block); err != nil { + return + } + stream := self.streams[streamIndex] + if stream.gotpkt { + pkt = stream.pkt + stream.gotpkt = false + return + } + } + } + return +} + diff --git a/sdp/parser.go b/sdp/parser.go new file mode 100644 index 0000000..b149f03 --- /dev/null +++ b/sdp/parser.go @@ -0,0 +1,94 @@ +package sdp + +import ( + "strings" + "strconv" + "encoding/hex" + "encoding/base64" + "github.com/nareix/av" +) + +type Info struct { + AVType string + Type av.CodecType + TimeScale int + Control string + Rtpmap int + Config []byte + SpropParameterSets [][]byte +} + +func Decode(content string) (infos []Info) { + var info *Info + + for _, line := range strings.Split(content, "\n") { + line = strings.Trim(line, "\r") + typeval := strings.SplitN(line, "=", 2) + if len(typeval) == 2 { + fields := strings.Split(typeval[1], " ") + + switch typeval[0] { + case "m": + if len(fields) > 0 { + switch fields[0] { + case "audio", "video": + infos = append(infos, Info{AVType: fields[0]}) + info = &infos[len(infos)-1] + } + } + + case "a": + if info != nil { + for _, field := range fields { + keyval := strings.Split(field, ":") + if len(keyval) >= 2 { + key := keyval[0] + val := keyval[1] + switch key { + case "control": + info.Control = val + case "rtpmap": + info.Rtpmap, _ = strconv.Atoi(val) + } + } + keyval = strings.Split(field, "/") + if len(keyval) >= 2 { + key := keyval[0] + switch key { + case "MPEG4-GENERIC": + info.Type = av.AAC + info.TimeScale, _ = strconv.Atoi(keyval[1]) + case "H264": + info.Type = av.H264 + info.TimeScale, _ = strconv.Atoi(keyval[1]) + } + } + keyval = strings.Split(field, ";") + if len(keyval) > 1 { + for _, field := range keyval { + keyval := strings.SplitN(field, "=", 2) + if len(keyval) == 2 { + key := keyval[0] + val := keyval[1] + switch key { + case "config": + info.Config, _ = hex.DecodeString(val) + case "sprop-parameter-sets": + fields := strings.Split(val, ",") + for _, field := range fields { + val, _ := base64.StdEncoding.DecodeString(field) + info.SpropParameterSets = append(info.SpropParameterSets, val) + } + } + } + } + } + } + } + + } + } + } + return +} + diff --git a/sdp/parser_test.go b/sdp/parser_test.go new file mode 100644 index 0000000..c62d360 --- /dev/null +++ b/sdp/parser_test.go @@ -0,0 +1,36 @@ +package sdp + +import ( + "testing" +) + +func TestParse(t *testing.T) { + infos := Decode(` +v=0 +o=- 1459325504777324 1 IN IP4 192.168.0.123 +s=RTSP/RTP stream from Network Video Server +i=mpeg4cif +t=0 0 +a=tool:LIVE555 Streaming Media v2009.09.28 +a=type:broadcast +a=control:* +a=range:npt=0- +a=x-qt-text-nam:RTSP/RTP stream from Network Video Server +a=x-qt-text-inf:mpeg4cif +m=video 0 RTP/AVP 96 +c=IN IP4 0.0.0.0 +b=AS:300 +a=rtpmap:96 H264/90000 +a=fmtp:96 packetization-mode=1;profile-level-id=640028;sprop-parameter-sets=Z2QAKK2EBUViuKxUdCAqKxXFYqOhAVFYrisVHQgKisVxWKjoQFRWK4rFR0ICorFcVio6ECSFITk8nyfk/k/J8nm5s00IEkKQnJ5Pk/J/J+T5PNzZprQFoe0qQAAAHgAABDgYEABJPAAUmW974XhEI1A=,aO48sA==;config=0000000167640028ad84054562b8ac5474202a2b15c562a3a1015158ae2b151d080a8ac57158a8e84054562b8ac5474202a2b15c562a3a10248521393c9f27e4fe4fc9f279b9b34d081242909c9e4f93f27f27e4f93cdcd9a6b405a1ed2a4000001e00000438181000493c0014996f7be1784423500000000168ee3cb0 +a=x-dimensions: 720, 480 +a=x-framerate: 15 +a=control:track1 +m=audio 0 RTP/AVP 96 +c=IN IP4 0.0.0.0 +b=AS:256 +a=rtpmap:96 MPEG4-GENERIC/16000/2 +a=fmtp:96 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408 +a=control:track2 +`) + t.Logf("%v", infos) +} diff --git a/stream.go b/stream.go index bd9b1d1..178cf48 100644 --- a/stream.go +++ b/stream.go @@ -2,19 +2,27 @@ package rtsp import ( "github.com/nareix/av" + "github.com/nareix/rtsp/sdp" ) type Stream struct { av.StreamCommon - typestr string - control string + Sdp sdp.Info + + // h264 + fuBuffer []byte + sps []byte + pps []byte + + gotpkt bool + pkt av.Packet } func (self Stream) IsAudio() bool { - return self.typestr == "audio" + return self.Sdp.AVType == "audio" } func (self Stream) IsVideo() bool { - return self.typestr == "video" + return self.Sdp.AVType == "video" } From fed739021e0187aa08743390b3a9f40ad9e0a6a3 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 6 Jun 2016 16:45:29 +0800 Subject: [PATCH 03/61] bugfix Describe(), use pktqueue, add Open(), add Streams(), add relocate block start $ sign --- client.go | 176 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 135 insertions(+), 41 deletions(-) diff --git a/client.go b/client.go index 1a4576d..435a085 100644 --- a/client.go +++ b/client.go @@ -15,7 +15,9 @@ import ( "crypto/md5" "github.com/nareix/av" "github.com/nareix/codec/h264parser" + "github.com/nareix/codec/aacparser" "github.com/nareix/rtsp/sdp" + "github.com/nareix/av/pktqueue" ) type Client struct { @@ -29,6 +31,7 @@ type Client struct { session string authorization string body io.Reader + pktque *pktqueue.Queue } type Request struct { @@ -72,6 +75,13 @@ func Connect(uri string) (self *Client, err error) { return } +func (self *Client) Streams() (streams []av.CodecData) { + for _, stream := range self.streams { + streams = append(streams, stream.CodecData) + } + return +} + func (self *Client) writeLine(line string) (err error) { if self.DebugConn { fmt.Print("> ", line) @@ -124,6 +134,7 @@ func (self *Client) ReadResponse() (res Response, err error) { if _, err = io.ReadFull(self.rconn, h[:]); err != nil { return } + if h[0] == 36 { // $ res.BlockLength = int(h[2])<<8+int(h[3]) @@ -132,11 +143,35 @@ func (self *Client) ReadResponse() (res Response, err error) { fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) } return - } else if h[0] == 82 { + } else if h[0] == 82 && h[1] == 84 && h[2] == 83 && h[3] == 80 { // RTSP 200 OK self.rconn = io.MultiReader(bytes.NewReader(h[:]), self.rconn) } else { - err = fmt.Errorf("invalid response bytes=%v", h) + for { + for { + var b [1]byte + if _, err = self.rconn.Read(b[:]); err != nil { + return + } + if b[0] == 36 { + break + } + } + if self.DebugConn { + fmt.Println("block: relocate") + } + if _, err = io.ReadFull(self.rconn, h[1:4]); err != nil { + return + } + res.BlockLength = int(h[2])<<8+int(h[3]) + res.BlockNo = int(h[1]) + if res.BlockNo/2 < len(self.streams) { + break + } + } + if self.DebugConn { + fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) + } return } @@ -236,10 +271,14 @@ func (self *Client) ReadResponse() (res Response, err error) { func (self *Client) Setup(streams []int) (err error) { for _, si := range streams { - req := Request{ - Method: "SETUP", - Uri: self.requestUri+"/"+self.streams[si].Sdp.Control, + uri := "" + control := self.streams[si].Sdp.Control + if strings.HasPrefix(control, "rtsp://") { + uri = control + } else { + uri = self.requestUri+"/"+control } + req := Request{Method: "SETUP", Uri: uri} req.Header = append(req.Header, fmt.Sprintf("Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d", si*2, si*2+1)) if self.session != "" { req.Header = append(req.Header, "Session: "+self.session) @@ -259,7 +298,7 @@ func md5hash(s string) string { return hex.EncodeToString(h[:]) } -func (self *Client) Describe() (streams []av.Stream, err error) { +func (self *Client) Describe() (streams []av.CodecData, err error) { var res Response for i := 0; i < 2; i++ { @@ -294,39 +333,48 @@ func (self *Client) Describe() (streams []av.Stream, err error) { self.streams = []*Stream{} for _, info := range sdp.Decode(body) { stream := &Stream{Sdp: info} - stream.SetType(info.Type) - switch info.Type { - case av.H264: - var sps, pps []byte - for _, nalu := range info.SpropParameterSets { - if len(nalu) > 0 { - switch nalu[0]&0x1f { - case 7: - sps = nalu - case 8: - pps = nalu + if info.PayloadType >= 96 && info.PayloadType <= 127 { + switch info.Type { + case av.H264: + var sps, pps []byte + for _, nalu := range info.SpropParameterSets { + if len(nalu) > 0 { + switch nalu[0]&0x1f { + case 7: + sps = nalu + case 8: + pps = nalu + } } } - } - if len(sps) > 0 && len(pps) > 0 { - codecData, _ := h264parser.CreateCodecDataBySPSAndPPS(sps, pps) - if err = stream.SetCodecData(codecData); err != nil { - err = fmt.Errorf("h264 sps/pps invalid: %s", err) + if len(sps) > 0 && len(pps) > 0 { + if stream.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil { + err = fmt.Errorf("h264 sps/pps invalid: %s", err) + return + } + } else { + err = fmt.Errorf("h264 sdp sprop-parameter-sets invalid: missing sps or pps") return } - } else { - err = fmt.Errorf("h264 sdp sprop-parameter-sets invalid: missing sps or pps") - return - } - case av.AAC: - if len(info.Config) == 0 { - err = fmt.Errorf("aac sdp config missing") - return + case av.AAC: + if len(info.Config) == 0 { + err = fmt.Errorf("aac sdp config missing") + return + } + if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(info.Config); err != nil { + err = fmt.Errorf("aac sdp config invalid: %s", err) + return + } } - if err = stream.SetCodecData(info.Config); err != nil { - err = fmt.Errorf("aac sdp config invalid: %s", err) + } else { + switch info.PayloadType { + case 0: + stream.CodecData = av.NewPCMMulawCodecData() + + default: + err = fmt.Errorf("PayloadType=%d unsupported", info.PayloadType) return } } @@ -337,6 +385,11 @@ func (self *Client) Describe() (streams []av.Stream, err error) { for _, stream := range self.streams { streams = append(streams, stream) } + self.pktque = &pktqueue.Queue{ + Poll: self.poll, + } + self.pktque.Alloc(streams) + return } @@ -372,6 +425,7 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] } self.gotpkt = true self.pkt.Data = packet + self.timestamp = timestamp } return @@ -379,8 +433,6 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { switch self.Type() { - case av.AAC: - case av.H264: /* +---------------+ @@ -480,6 +532,11 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { err = fmt.Errorf("unsupported H264 naluType=%d", naluType) return } + + default: + self.gotpkt = true + self.pkt.Data = packet + self.timestamp = timestamp } return } @@ -490,6 +547,9 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err return } + streamIndex = blockNo/2 + stream := self.streams[streamIndex] + /* 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 @@ -562,9 +622,6 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err */ //payloadType := packet[1]&0x7f - streamIndex = blockNo/2 - stream := self.streams[streamIndex] - if self.DebugConn { //fmt.Println("packet:", stream.Type(), "offset", payloadOffset, "pt", payloadType) if len(packet)>24 { @@ -591,19 +648,22 @@ func (self *Client) Play() (err error) { return } -func (self *Client) ReadPacket() (streamIndex int, pkt av.Packet, err error) { +func (self *Client) poll() (err error) { for { var res Response if res, err = self.ReadResponse(); err != nil { return } if res.BlockLength > 0 { - if streamIndex, err = self.parseBlock(res.BlockNo, res.Block); err != nil { + var i int + if i, err = self.parseBlock(res.BlockNo, res.Block); err != nil { return } - stream := self.streams[streamIndex] + stream := self.streams[i] if stream.gotpkt { - pkt = stream.pkt + time := float64(stream.timestamp)/float64(stream.Sdp.TimeScale) + self.pktque.WriteTimePacket(i, time, stream.pkt) + stream.pkt = av.Packet{} stream.gotpkt = false return } @@ -612,3 +672,37 @@ func (self *Client) ReadPacket() (streamIndex int, pkt av.Packet, err error) { return } +func (self *Client) ReadPacket() (i int, pkt av.Packet, err error) { + return self.pktque.ReadPacket() +} + +func Open(uri string) (cli *Client, err error) { + _cli, err := Connect(uri) + if err != nil { + return + } + + streams, err := _cli.Describe() + if err != nil { + return + } + + setup := []int{} + for i := range streams { + setup = append(setup, i) + } + + err = _cli.Setup(setup) + if err != nil { + return + } + + err = _cli.Play() + if err != nil { + return + } + + cli = _cli + return +} + From 9af997cd0f41c7df2f755875f4349f90264807a2 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 6 Jun 2016 16:47:35 +0800 Subject: [PATCH 04/61] add sdp field PayloadType, bugfix --- sdp/parser.go | 18 +++++++++++------- sdp/parser_test.go | 10 +++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/sdp/parser.go b/sdp/parser.go index b149f03..66d463d 100644 --- a/sdp/parser.go +++ b/sdp/parser.go @@ -10,22 +10,23 @@ import ( type Info struct { AVType string - Type av.CodecType + Type int TimeScale int Control string Rtpmap int Config []byte SpropParameterSets [][]byte + PayloadType int } func Decode(content string) (infos []Info) { var info *Info for _, line := range strings.Split(content, "\n") { - line = strings.Trim(line, "\r") + line = strings.TrimSpace(line) typeval := strings.SplitN(line, "=", 2) if len(typeval) == 2 { - fields := strings.Split(typeval[1], " ") + fields := strings.SplitN(typeval[1], " ", 2) switch typeval[0] { case "m": @@ -34,13 +35,17 @@ func Decode(content string) (infos []Info) { case "audio", "video": infos = append(infos, Info{AVType: fields[0]}) info = &infos[len(infos)-1] + mfields := strings.Split(fields[1], " ") + if len(mfields) >= 3 { + info.PayloadType, _ = strconv.Atoi(mfields[2]) + } } } case "a": if info != nil { for _, field := range fields { - keyval := strings.Split(field, ":") + keyval := strings.SplitN(field, ":", 2) if len(keyval) >= 2 { key := keyval[0] val := keyval[1] @@ -54,13 +59,12 @@ func Decode(content string) (infos []Info) { keyval = strings.Split(field, "/") if len(keyval) >= 2 { key := keyval[0] + info.TimeScale, _ = strconv.Atoi(keyval[1]) switch key { case "MPEG4-GENERIC": info.Type = av.AAC - info.TimeScale, _ = strconv.Atoi(keyval[1]) case "H264": info.Type = av.H264 - info.TimeScale, _ = strconv.Atoi(keyval[1]) } } keyval = strings.Split(field, ";") @@ -68,7 +72,7 @@ func Decode(content string) (infos []Info) { for _, field := range keyval { keyval := strings.SplitN(field, "=", 2) if len(keyval) == 2 { - key := keyval[0] + key := strings.TrimSpace(keyval[0]) val := keyval[1] switch key { case "config": diff --git a/sdp/parser_test.go b/sdp/parser_test.go index c62d360..fb26e69 100644 --- a/sdp/parser_test.go +++ b/sdp/parser_test.go @@ -21,7 +21,7 @@ m=video 0 RTP/AVP 96 c=IN IP4 0.0.0.0 b=AS:300 a=rtpmap:96 H264/90000 -a=fmtp:96 packetization-mode=1;profile-level-id=640028;sprop-parameter-sets=Z2QAKK2EBUViuKxUdCAqKxXFYqOhAVFYrisVHQgKisVxWKjoQFRWK4rFR0ICorFcVio6ECSFITk8nyfk/k/J8nm5s00IEkKQnJ5Pk/J/J+T5PNzZprQFoe0qQAAAHgAABDgYEABJPAAUmW974XhEI1A=,aO48sA==;config=0000000167640028ad84054562b8ac5474202a2b15c562a3a1015158ae2b151d080a8ac57158a8e84054562b8ac5474202a2b15c562a3a10248521393c9f27e4fe4fc9f279b9b34d081242909c9e4f93f27f27e4f93cdcd9a6b405a1ed2a4000001e00000438181000493c0014996f7be1784423500000000168ee3cb0 +a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AHpWoKA9k,aO48gA== a=x-dimensions: 720, 480 a=x-framerate: 15 a=control:track1 @@ -31,6 +31,14 @@ b=AS:256 a=rtpmap:96 MPEG4-GENERIC/16000/2 a=fmtp:96 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408 a=control:track2 +m=audio 0 RTP/AVP 0 +c=IN IP4 0.0.0.0 +b=AS:50 +a=recvonly +a=control:rtsp://109.195.127.207:554/mpeg4cif/trackID=2 +a=rtpmap:0 PCMU/8000 +a=Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000; +a=appversion:1.0 `) t.Logf("%v", infos) } From 966db88bc3d4b6795df09363204fa699b9126583 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 6 Jun 2016 16:48:00 +0800 Subject: [PATCH 05/61] use av.CodecData --- stream.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stream.go b/stream.go index 178cf48..0654958 100644 --- a/stream.go +++ b/stream.go @@ -6,7 +6,7 @@ import ( ) type Stream struct { - av.StreamCommon + av.CodecData Sdp sdp.Info // h264 @@ -16,6 +16,7 @@ type Stream struct { gotpkt bool pkt av.Packet + timestamp uint32 } func (self Stream) IsAudio() bool { From 9bb11c6fc48ed0d389a7decfb6f5831e81dfa964 Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 7 Jun 2016 19:33:11 +0800 Subject: [PATCH 06/61] add packet handler for av.AAC --- client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client.go b/client.go index 435a085..38ab3d4 100644 --- a/client.go +++ b/client.go @@ -533,6 +533,11 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { return } + case av.AAC: + self.gotpkt = true + self.pkt.Data = packet[4:] + self.timestamp = timestamp + default: self.gotpkt = true self.pkt.Data = packet From 4a48b5df35e604e2f7f2485728023bfca05f4d95 Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 7 Jun 2016 19:33:26 +0800 Subject: [PATCH 07/61] add SizeLength and IndexLength --- sdp/parser.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdp/parser.go b/sdp/parser.go index 66d463d..e3db4d3 100644 --- a/sdp/parser.go +++ b/sdp/parser.go @@ -17,6 +17,8 @@ type Info struct { Config []byte SpropParameterSets [][]byte PayloadType int + SizeLength int + IndexLength int } func Decode(content string) (infos []Info) { @@ -77,6 +79,10 @@ func Decode(content string) (infos []Info) { switch key { case "config": info.Config, _ = hex.DecodeString(val) + case "sizelength": + info.SizeLength, _ = strconv.Atoi(val) + case "indexlength": + info.IndexLength, _ = strconv.Atoi(val) case "sprop-parameter-sets": fields := strings.Split(val, ",") for _, field := range fields { From d5075b55b2b99d6f4e539ae2df74d91fa603f2f7 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 8 Jun 2016 15:29:20 +0800 Subject: [PATCH 08/61] fix info.TimeScale assign bug --- sdp/parser.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sdp/parser.go b/sdp/parser.go index e3db4d3..6b83b46 100644 --- a/sdp/parser.go +++ b/sdp/parser.go @@ -2,6 +2,7 @@ package sdp import ( "strings" + "fmt" "strconv" "encoding/hex" "encoding/base64" @@ -61,13 +62,18 @@ func Decode(content string) (infos []Info) { keyval = strings.Split(field, "/") if len(keyval) >= 2 { key := keyval[0] - info.TimeScale, _ = strconv.Atoi(keyval[1]) switch key { case "MPEG4-GENERIC": info.Type = av.AAC case "H264": info.Type = av.H264 } + if i, err := strconv.Atoi(keyval[1]); err == nil { + info.TimeScale = i + } + if false { + fmt.Println("sdp:", keyval[1], info.TimeScale) + } } keyval = strings.Split(field, ";") if len(keyval) > 1 { From e9a38285727105344e9a31680946096a8097e26f Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 8 Jun 2016 15:29:49 +0800 Subject: [PATCH 09/61] fix github.com/nareix/codec api change --- client.go | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/client.go b/client.go index 38ab3d4..03dbaa9 100644 --- a/client.go +++ b/client.go @@ -16,6 +16,7 @@ import ( "github.com/nareix/av" "github.com/nareix/codec/h264parser" "github.com/nareix/codec/aacparser" + "github.com/nareix/codec" "github.com/nareix/rtsp/sdp" "github.com/nareix/av/pktqueue" ) @@ -205,7 +206,7 @@ func (self *Client) ReadResponse() (res Response, err error) { } if res.StatusCode != 200 && res.StatusCode != 401 { - err = fmt.Errorf("StatusCode=%d invalid", res.StatusCode) + err = fmt.Errorf("rtsp: StatusCode=%d invalid", res.StatusCode) return } @@ -237,7 +238,7 @@ func (self *Client) ReadResponse() (res Response, err error) { if realm != "" && nonce != "" { if self.url.User == nil { - err = fmt.Errorf("please provide username and password") + err = fmt.Errorf("rtsp: please provide username and password") return } var username string @@ -245,7 +246,7 @@ func (self *Client) ReadResponse() (res Response, err error) { var ok bool username = self.url.User.Username() if password, ok = self.url.User.Password(); !ok { - err = fmt.Errorf("please provide password") + err = fmt.Errorf("rtsp: please provide password") return } hs1 := md5hash(username+":"+realm+":"+password) @@ -320,7 +321,7 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { } } if res.ContentLength == 0 { - err = fmt.Errorf("Describe failed") + err = fmt.Errorf("rtsp: Describe failed, StatusCode=%d", res.StatusCode) return } @@ -334,6 +335,10 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { for _, info := range sdp.Decode(body) { stream := &Stream{Sdp: info} + if false { + fmt.Println("sdp:", info.TimeScale) + } + if info.PayloadType >= 96 && info.PayloadType <= 127 { switch info.Type { case av.H264: @@ -350,31 +355,31 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { } if len(sps) > 0 && len(pps) > 0 { if stream.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil { - err = fmt.Errorf("h264 sps/pps invalid: %s", err) + err = fmt.Errorf("rtsp: h264 sps/pps invalid: %s", err) return } } else { - err = fmt.Errorf("h264 sdp sprop-parameter-sets invalid: missing sps or pps") + err = fmt.Errorf("rtsp: h264 sdp sprop-parameter-sets invalid: missing sps or pps") return } case av.AAC: if len(info.Config) == 0 { - err = fmt.Errorf("aac sdp config missing") + err = fmt.Errorf("rtsp: aac sdp config missing") return } if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(info.Config); err != nil { - err = fmt.Errorf("aac sdp config invalid: %s", err) + err = fmt.Errorf("rtsp: aac sdp config invalid: %s", err) return } } } else { switch info.PayloadType { case 0: - stream.CodecData = av.NewPCMMulawCodecData() + stream.CodecData = codec.NewPCMMulawCodecData() default: - err = fmt.Errorf("PayloadType=%d unsupported", info.PayloadType) + err = fmt.Errorf("rtsp: PayloadType=%d unsupported", info.PayloadType) return } } @@ -464,7 +469,7 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { return } - case naluType == 28: + case naluType == 28: // FU-A /* 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 @@ -528,8 +533,12 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { } } + case naluType == 24: + err = fmt.Errorf("rtsp: unsupported H264 STAP-A") + return + default: - err = fmt.Errorf("unsupported H264 naluType=%d", naluType) + err = fmt.Errorf("rtsp: unsupported H264 naluType=%d", naluType) return } @@ -667,6 +676,9 @@ func (self *Client) poll() (err error) { stream := self.streams[i] if stream.gotpkt { time := float64(stream.timestamp)/float64(stream.Sdp.TimeScale) + if false { + fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, stream.Sdp.TimeScale, len(stream.pkt.Data)) + } self.pktque.WriteTimePacket(i, time, stream.pkt) stream.pkt = av.Packet{} stream.gotpkt = false From f4d9ad60aca3878cf4fb3eff171e83e4097516ee Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 09:27:19 +0800 Subject: [PATCH 10/61] check stream index when parse block --- client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client.go b/client.go index 03dbaa9..463171d 100644 --- a/client.go +++ b/client.go @@ -562,6 +562,10 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err } streamIndex = blockNo/2 + if streamIndex >= len(self.streams) { + err = fmt.Errorf("rtsp: parseBlock: streamIndex=%d invalid", streamIndex) + return + } stream := self.streams[streamIndex] /* From b6f30f9f55ca262baaaf43169ee5f06467699107 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 17:07:53 +0800 Subject: [PATCH 11/61] fix rtsp url maybe not have port need default 554 --- client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client.go b/client.go index 463171d..9029bb9 100644 --- a/client.go +++ b/client.go @@ -58,6 +58,10 @@ func Connect(uri string) (self *Client, err error) { return } + if strings.IndexByte(URL.Host, ':') == -1 { + URL.Host = URL.Host + ":554" + } + dailer := net.Dialer{} var conn net.Conn if conn, err = dailer.Dial("tcp", URL.Host); err != nil { From 0bfb3c6cf0f91faaa902819bd0e800d6444bd9ef Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 19:14:53 +0800 Subject: [PATCH 12/61] add basic authorization and Client.Headers option --- client.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index 9029bb9..dc60ac9 100644 --- a/client.go +++ b/client.go @@ -8,10 +8,12 @@ import ( "strings" "strconv" "bufio" + "html" "net/textproto" "net/url" "encoding/hex" "encoding/binary" + "encoding/base64" "crypto/md5" "github.com/nareix/av" "github.com/nareix/codec/h264parser" @@ -23,6 +25,7 @@ import ( type Client struct { DebugConn bool + Headers []string url *url.URL conn net.Conn rconn io.Reader @@ -30,7 +33,7 @@ type Client struct { cseq uint streams []*Stream session string - authorization string + authorization []string body io.Reader pktque *pktqueue.Queue } @@ -54,7 +57,7 @@ type Response struct { func Connect(uri string) (self *Client, err error) { var URL *url.URL - if URL, err = url.Parse(uri); err != nil { + if URL, err = url.Parse(html.UnescapeString(uri)); err != nil { return } @@ -97,7 +100,11 @@ func (self *Client) writeLine(line string) (err error) { func (self *Client) WriteRequest(req Request) (err error) { self.cseq++ + req.Header = append(req.Header, self.Headers...) req.Header = append(req.Header, fmt.Sprintf("CSeq: %d", self.cseq)) + for _, s := range self.authorization { + req.Header = append(req.Header, "Authorization: "+s) + } if err = self.writeLine(fmt.Sprintf("%s %s RTSP/1.0\r\n", req.Method, req.Uri)); err != nil { return } @@ -256,9 +263,11 @@ func (self *Client) ReadResponse() (res Response, err error) { hs1 := md5hash(username+":"+realm+":"+password) hs2 := md5hash("DESCRIBE:"+self.requestUri) response := md5hash(hs1+":"+nonce+":"+hs2) - self.authorization = fmt.Sprintf( - `Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, - username, realm, nonce, self.requestUri, response) + self.authorization = []string{ + fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, + username, realm, nonce, self.requestUri, response), + fmt.Sprintf(`Basic %s`, base64.StdEncoding.EncodeToString([]byte(username+":"+password))), + } } } } @@ -311,9 +320,6 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { Method: "DESCRIBE", Uri: self.requestUri, } - if self.authorization != "" { - req.Header = append(req.Header, "Authorization: "+self.authorization) - } if err = self.WriteRequest(req); err != nil { return } From 2f1caf91516f538bcd895521a6c2e412e2f1c5ff Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 19:46:43 +0800 Subject: [PATCH 13/61] improve sdp --- client.go | 28 +++++++++++++++++----------- sdp/parser.go | 41 ++++++++++++++++++++++++----------------- stream.go | 2 +- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/client.go b/client.go index dc60ac9..f4d81d8 100644 --- a/client.go +++ b/client.go @@ -61,7 +61,7 @@ func Connect(uri string) (self *Client, err error) { return } - if strings.IndexByte(URL.Host, ':') == -1 { + if _, _, err := net.SplitHostPort(URL.Host); err != nil { URL.Host = URL.Host + ":554" } @@ -342,18 +342,24 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { } self.streams = []*Stream{} - for _, info := range sdp.Decode(body) { - stream := &Stream{Sdp: info} + sess, medias := sdp.Parse(body) + + if sess.Uri != "" { + self.requestUri = sess.Uri + } + + for _, media := range medias { + stream := &Stream{Sdp: media} if false { - fmt.Println("sdp:", info.TimeScale) + fmt.Println("sdp:", media.TimeScale) } - if info.PayloadType >= 96 && info.PayloadType <= 127 { - switch info.Type { + if media.PayloadType >= 96 && media.PayloadType <= 127 { + switch media.Type { case av.H264: var sps, pps []byte - for _, nalu := range info.SpropParameterSets { + for _, nalu := range media.SpropParameterSets { if len(nalu) > 0 { switch nalu[0]&0x1f { case 7: @@ -374,22 +380,22 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { } case av.AAC: - if len(info.Config) == 0 { + if len(media.Config) == 0 { err = fmt.Errorf("rtsp: aac sdp config missing") return } - if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(info.Config); err != nil { + if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(media.Config); err != nil { err = fmt.Errorf("rtsp: aac sdp config invalid: %s", err) return } } } else { - switch info.PayloadType { + switch media.PayloadType { case 0: stream.CodecData = codec.NewPCMMulawCodecData() default: - err = fmt.Errorf("rtsp: PayloadType=%d unsupported", info.PayloadType) + err = fmt.Errorf("rtsp: PayloadType=%d unsupported", media.PayloadType) return } } diff --git a/sdp/parser.go b/sdp/parser.go index 6b83b46..f171d80 100644 --- a/sdp/parser.go +++ b/sdp/parser.go @@ -9,7 +9,11 @@ import ( "github.com/nareix/av" ) -type Info struct { +type Session struct { + Uri string +} + +type Media struct { AVType string Type int TimeScale int @@ -22,8 +26,8 @@ type Info struct { IndexLength int } -func Decode(content string) (infos []Info) { - var info *Info +func Parse(content string) (sess Session, medias []Media) { + var media *Media for _, line := range strings.Split(content, "\n") { line = strings.TrimSpace(line) @@ -36,17 +40,20 @@ func Decode(content string) (infos []Info) { if len(fields) > 0 { switch fields[0] { case "audio", "video": - infos = append(infos, Info{AVType: fields[0]}) - info = &infos[len(infos)-1] + medias = append(medias, Media{AVType: fields[0]}) + media = &medias[len(medias)-1] mfields := strings.Split(fields[1], " ") if len(mfields) >= 3 { - info.PayloadType, _ = strconv.Atoi(mfields[2]) + media.PayloadType, _ = strconv.Atoi(mfields[2]) } } } + case "u": + sess.Uri = typeval[1] + case "a": - if info != nil { + if media != nil { for _, field := range fields { keyval := strings.SplitN(field, ":", 2) if len(keyval) >= 2 { @@ -54,9 +61,9 @@ func Decode(content string) (infos []Info) { val := keyval[1] switch key { case "control": - info.Control = val + media.Control = val case "rtpmap": - info.Rtpmap, _ = strconv.Atoi(val) + media.Rtpmap, _ = strconv.Atoi(val) } } keyval = strings.Split(field, "/") @@ -64,15 +71,15 @@ func Decode(content string) (infos []Info) { key := keyval[0] switch key { case "MPEG4-GENERIC": - info.Type = av.AAC + media.Type = av.AAC case "H264": - info.Type = av.H264 + media.Type = av.H264 } if i, err := strconv.Atoi(keyval[1]); err == nil { - info.TimeScale = i + media.TimeScale = i } if false { - fmt.Println("sdp:", keyval[1], info.TimeScale) + fmt.Println("sdp:", keyval[1], media.TimeScale) } } keyval = strings.Split(field, ";") @@ -84,16 +91,16 @@ func Decode(content string) (infos []Info) { val := keyval[1] switch key { case "config": - info.Config, _ = hex.DecodeString(val) + media.Config, _ = hex.DecodeString(val) case "sizelength": - info.SizeLength, _ = strconv.Atoi(val) + media.SizeLength, _ = strconv.Atoi(val) case "indexlength": - info.IndexLength, _ = strconv.Atoi(val) + media.IndexLength, _ = strconv.Atoi(val) case "sprop-parameter-sets": fields := strings.Split(val, ",") for _, field := range fields { val, _ := base64.StdEncoding.DecodeString(field) - info.SpropParameterSets = append(info.SpropParameterSets, val) + media.SpropParameterSets = append(media.SpropParameterSets, val) } } } diff --git a/stream.go b/stream.go index 0654958..c983e78 100644 --- a/stream.go +++ b/stream.go @@ -7,7 +7,7 @@ import ( type Stream struct { av.CodecData - Sdp sdp.Info + Sdp sdp.Media // h264 fuBuffer []byte From 39b65aacd61f1b7a4108f139f7ceeb9143a4a2dd Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 20:18:57 +0800 Subject: [PATCH 14/61] add setupCalled/playCalled --- client.go | 49 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index f4d81d8..2a0c7e5 100644 --- a/client.go +++ b/client.go @@ -26,6 +26,10 @@ import ( type Client struct { DebugConn bool Headers []string + + setupCalled bool + playCalled bool + url *url.URL conn net.Conn rconn io.Reader @@ -283,6 +287,14 @@ func (self *Client) ReadResponse() (res Response, err error) { return } +func (self *Client) setupAll() (err error) { + idx := []int{} + for i := range self.streams { + idx = append(idx, i) + } + return self.Setup(idx) +} + func (self *Client) Setup(streams []int) (err error) { for _, si := range streams { uri := "" @@ -304,6 +316,7 @@ func (self *Client) Setup(streams []int) (err error) { return } } + self.setupCalled = true return } @@ -679,6 +692,7 @@ func (self *Client) Play() (err error) { if err = self.WriteRequest(req); err != nil { return } + self.playCalled = true return } @@ -710,32 +724,41 @@ func (self *Client) poll() (err error) { } func (self *Client) ReadPacket() (i int, pkt av.Packet, err error) { + if !self.setupCalled { + if err = self.setupAll(); err != nil { + return + } + } + if !self.playCalled { + if err = self.Play(); err != nil { + return + } + } return self.pktque.ReadPacket() } +func (self *Client) ReadHeader() (err error) { + if _, err = self.Describe(); err != nil { + return + } + return +} + func Open(uri string) (cli *Client, err error) { - _cli, err := Connect(uri) - if err != nil { + var _cli *Client + if _cli, err = Connect(uri); err != nil { return } - streams, err := _cli.Describe() - if err != nil { + if _, err = _cli.Describe(); err != nil { return } - setup := []int{} - for i := range streams { - setup = append(setup, i) - } - - err = _cli.Setup(setup) - if err != nil { + if err = _cli.setupAll(); err != nil { return } - err = _cli.Play() - if err != nil { + if err = _cli.Play(); err != nil { return } From 5bf2c76bff73a54fbe5909f4ca11e83b1d234e74 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 20:29:21 +0800 Subject: [PATCH 15/61] add Client Timeout fields --- client.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client.go b/client.go index 2a0c7e5..39670cd 100644 --- a/client.go +++ b/client.go @@ -27,6 +27,12 @@ type Client struct { DebugConn bool Headers []string + DialTimeout time.Duration + RtspTimeout time.Duration + RtpFirstReadTimeout time.Duration + RtpReadTimeout time.Duration + RtpKeepAliveTimeout time.Duration + setupCalled bool playCalled bool From 32e3debf2efa0f0aac86f9e3f6fd619706d17a07 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 20:31:41 +0800 Subject: [PATCH 16/61] rename Connect to Dial and add DialTimeout --- client.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client.go b/client.go index 39670cd..ae6d663 100644 --- a/client.go +++ b/client.go @@ -1,6 +1,7 @@ package rtsp import ( + "time" "fmt" "net" "bytes" @@ -27,7 +28,6 @@ type Client struct { DebugConn bool Headers []string - DialTimeout time.Duration RtspTimeout time.Duration RtpFirstReadTimeout time.Duration RtpReadTimeout time.Duration @@ -65,7 +65,7 @@ type Response struct { Body []byte } -func Connect(uri string) (self *Client, err error) { +func DialTimeout(uri string, timeout time.Duration) (self *Client, err error) { var URL *url.URL if URL, err = url.Parse(html.UnescapeString(uri)); err != nil { return @@ -75,7 +75,7 @@ func Connect(uri string) (self *Client, err error) { URL.Host = URL.Host + ":554" } - dailer := net.Dialer{} + dailer := net.Dialer{Timeout: timeout} var conn net.Conn if conn, err = dailer.Dial("tcp", URL.Host); err != nil { return @@ -93,6 +93,10 @@ func Connect(uri string) (self *Client, err error) { return } +func Dial(uri string) (self *Client, err error) { + return DialTimeout(uri, 0) +} + func (self *Client) Streams() (streams []av.CodecData) { for _, stream := range self.streams { streams = append(streams, stream.CodecData) @@ -752,7 +756,7 @@ func (self *Client) ReadHeader() (err error) { func Open(uri string) (cli *Client, err error) { var _cli *Client - if _cli, err = Connect(uri); err != nil { + if _cli, err = Dial(uri); err != nil { return } From 3443f71d5423b18b82587cb04b8a0ea4202f174e Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 22:34:15 +0800 Subject: [PATCH 17/61] add timeout control --- client.go | 36 +++++++++++++++++++++++++++++++----- conn.go | 26 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 conn.go diff --git a/client.go b/client.go index ae6d663..b5c8509 100644 --- a/client.go +++ b/client.go @@ -29,15 +29,15 @@ type Client struct { Headers []string RtspTimeout time.Duration - RtpFirstReadTimeout time.Duration - RtpReadTimeout time.Duration + RtpTimeout time.Duration RtpKeepAliveTimeout time.Duration + rtpKeepaliveTimer time.Time setupCalled bool playCalled bool url *url.URL - conn net.Conn + conn *connWithTimeout rconn io.Reader requestUri string cseq uint @@ -84,9 +84,11 @@ func DialTimeout(uri string, timeout time.Duration) (self *Client, err error) { u2 := *URL u2.User = nil + connt := &connWithTimeout{Conn: conn} + self = &Client{ - conn: conn, - rconn: conn, + conn: connt, + rconn: connt, url: URL, requestUri: u2.String(), } @@ -112,7 +114,21 @@ func (self *Client) writeLine(line string) (err error) { return } +func (self *Client) sendRtpKeepalive() (err error) { + if self.RtpKeepAliveTimeout > 0 { + if !self.rtpKeepaliveTimer.IsZero() && time.Now().Sub(self.rtpKeepaliveTimer) > self.RtpKeepAliveTimeout { + if err = self.Options(); err != nil { + return + } + } + self.rtpKeepaliveTimer = time.Now() + } + return +} + func (self *Client) WriteRequest(req Request) (err error) { + self.conn.Timeout = self.RtspTimeout + self.cseq++ req.Header = append(req.Header, self.Headers...) req.Header = append(req.Header, fmt.Sprintf("CSeq: %d", self.cseq)) @@ -142,6 +158,8 @@ func (self *Client) ReadResponse() (res Response, err error) { self.rconn = io.MultiReader(bytes.NewReader(buf), self.rconn) } if res.StatusCode == 200 { + self.conn.Timeout = self.RtspTimeout + if res.ContentLength > 0 { res.Body = make([]byte, res.ContentLength) if _, err = io.ReadFull(self.rconn, res.Body); err != nil { @@ -149,6 +167,10 @@ func (self *Client) ReadResponse() (res Response, err error) { } } } else if res.BlockLength > 0 { + if err = self.sendRtpKeepalive(); err != nil { + return + } + self.conn.Timeout = self.RtpTimeout res.Block = make([]byte, res.BlockLength) if _, err = io.ReadFull(self.rconn, res.Block); err != nil { return @@ -156,6 +178,8 @@ func (self *Client) ReadResponse() (res Response, err error) { } }() + self.conn.Timeout = self.RtspTimeout + var h [4]byte if _, err = io.ReadFull(self.rconn, h[:]); err != nil { return @@ -173,6 +197,8 @@ func (self *Client) ReadResponse() (res Response, err error) { // RTSP 200 OK self.rconn = io.MultiReader(bytes.NewReader(h[:]), self.rconn) } else { + self.conn.Timeout = self.RtpTimeout + for { for { var b [1]byte diff --git a/conn.go b/conn.go new file mode 100644 index 0000000..621895d --- /dev/null +++ b/conn.go @@ -0,0 +1,26 @@ +package rtsp + +import ( + "net" + "time" +) + +type connWithTimeout struct { + Timeout time.Duration + net.Conn +} + +func (self connWithTimeout) Read(p []byte) (n int, err error) { + if self.Timeout > 0 { + self.Conn.SetReadDeadline(time.Now().Add(self.Timeout)) + } + return self.Conn.Read(p) +} + +func (self connWithTimeout) Write(p []byte) (n int, err error) { + if self.Timeout > 0 { + self.Conn.SetWriteDeadline(time.Now().Add(self.Timeout)) + } + return self.Conn.Write(p) +} + From 273f9e635d584f57e82d069ef2afb1fd99b014c6 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 23:03:31 +0800 Subject: [PATCH 18/61] change debug output --- client.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index b5c8509..0cd88dd 100644 --- a/client.go +++ b/client.go @@ -200,6 +200,10 @@ func (self *Client) ReadResponse() (res Response, err error) { self.conn.Timeout = self.RtpTimeout for { + if self.DebugConn { + fmt.Println("block: relocate try") + } + for { var b [1]byte if _, err = self.rconn.Read(b[:]); err != nil { @@ -209,19 +213,19 @@ func (self *Client) ReadResponse() (res Response, err error) { break } } - if self.DebugConn { - fmt.Println("block: relocate") - } if _, err = io.ReadFull(self.rconn, h[1:4]); err != nil { return } + res.BlockLength = int(h[2])<<8+int(h[3]) res.BlockNo = int(h[1]) if res.BlockNo/2 < len(self.streams) { break } } + if self.DebugConn { + fmt.Println("block: relocate done") fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) } return From 468aeaa1ae1aef23f72a23351793fa5857b4f2a1 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 11 Jun 2016 23:32:27 +0800 Subject: [PATCH 19/61] add PCMA --- client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client.go b/client.go index 0cd88dd..91eaed4 100644 --- a/client.go +++ b/client.go @@ -447,6 +447,9 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { case 0: stream.CodecData = codec.NewPCMMulawCodecData() + case 8: + stream.CodecData = codec.NewPCMAlawCodecData() + default: err = fmt.Errorf("rtsp: PayloadType=%d unsupported", media.PayloadType) return From 769d03cb9fa9fe2d26ceb5ddbc3bbf2356c06c10 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 08:52:44 +0800 Subject: [PATCH 20/61] fix streams bug --- client.go | 56 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/client.go b/client.go index 91eaed4..49889ee 100644 --- a/client.go +++ b/client.go @@ -34,6 +34,8 @@ type Client struct { rtpKeepaliveTimer time.Time setupCalled bool + setupIdx []int + setupMap []int playCalled bool url *url.URL @@ -99,9 +101,14 @@ func Dial(uri string) (self *Client, err error) { return DialTimeout(uri, 0) } -func (self *Client) Streams() (streams []av.CodecData) { - for _, stream := range self.streams { - streams = append(streams, stream.CodecData) +func (self *Client) Streams() (streams []av.CodecData, err error) { + if self.setupCalled { + for _, i := range self.setupIdx { + streams = append(streams, self.streams[i].CodecData) + } + } else { + err = fmt.Errorf("rtsp: no streams") + return } return } @@ -335,8 +342,26 @@ func (self *Client) setupAll() (err error) { return self.Setup(idx) } -func (self *Client) Setup(streams []int) (err error) { - for _, si := range streams { +func (self *Client) Setup(idx []int) (err error) { + if self.setupCalled { + err = fmt.Errorf("rtsp: Setup() called twice") + return + } + + if len(self.streams) == 0 { + err = fmt.Errorf("rtsp: no streams, please call Describe() first") + return + } + + self.setupMap = make([]int, len(self.streams)) + for i := range self.setupMap { + self.setupMap[i] = -1 + } + self.setupIdx = idx + + for i, si := range idx { + self.setupMap[si] = i + uri := "" control := self.streams[si].Sdp.Control if strings.HasPrefix(control, "rtsp://") { @@ -356,6 +381,7 @@ func (self *Client) Setup(streams []int) (err error) { return } } + self.setupCalled = true return } @@ -756,7 +782,7 @@ func (self *Client) poll() (err error) { if false { fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, stream.Sdp.TimeScale, len(stream.pkt.Data)) } - self.pktque.WriteTimePacket(i, time, stream.pkt) + self.pktque.WriteTimePacket(self.setupMap[i], time, stream.pkt) stream.pkt = av.Packet{} stream.gotpkt = false return @@ -784,6 +810,12 @@ func (self *Client) ReadHeader() (err error) { if _, err = self.Describe(); err != nil { return } + if err = self.setupAll(); err != nil { + return + } + if err = self.Play(); err != nil { + return + } return } @@ -792,19 +824,9 @@ func Open(uri string) (cli *Client, err error) { if _cli, err = Dial(uri); err != nil { return } - - if _, err = _cli.Describe(); err != nil { + if err = _cli.ReadHeader(); err != nil { return } - - if err = _cli.setupAll(); err != nil { - return - } - - if err = _cli.Play(); err != nil { - return - } - cli = _cli return } From b9fb9fc3608ba1349d3e9b4d1123d23211848c54 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 09:04:45 +0800 Subject: [PATCH 21/61] change error msg --- client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 49889ee..be4886e 100644 --- a/client.go +++ b/client.go @@ -251,7 +251,7 @@ func (self *Client) ReadResponse() (res Response, err error) { fline := strings.SplitN(line, " ", 3) if len(fline) < 2 { - err = fmt.Errorf("malformed RTSP response line") + err = fmt.Errorf("rtsp: malformed response line") return } @@ -682,12 +682,12 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err */ if len(packet) < 8 { - err = fmt.Errorf("rtp packet too short") + err = fmt.Errorf("rtp: packet too short") return } payloadOffset := 12+int(packet[0]&0xf)*4 if payloadOffset+2 > len(packet) { - err = fmt.Errorf("rtp packet too short") + err = fmt.Errorf("rtp: packet too short") return } From f2dca8e3697bed705411343485e5b25fd9914ac0 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 10:00:40 +0800 Subject: [PATCH 22/61] fix rtp keep alive bug --- client.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index be4886e..0c26075 100644 --- a/client.go +++ b/client.go @@ -123,12 +123,17 @@ func (self *Client) writeLine(line string) (err error) { func (self *Client) sendRtpKeepalive() (err error) { if self.RtpKeepAliveTimeout > 0 { - if !self.rtpKeepaliveTimer.IsZero() && time.Now().Sub(self.rtpKeepaliveTimer) > self.RtpKeepAliveTimeout { + if self.rtpKeepaliveTimer.IsZero() { + self.rtpKeepaliveTimer = time.Now() + } else if time.Now().Sub(self.rtpKeepaliveTimer) > self.RtpKeepAliveTimeout { + self.rtpKeepaliveTimer = time.Now() + if self.DebugConn { + fmt.Println("rtp: keep alive") + } if err = self.Options(); err != nil { return } } - self.rtpKeepaliveTimer = time.Now() } return } @@ -174,19 +179,18 @@ func (self *Client) ReadResponse() (res Response, err error) { } } } else if res.BlockLength > 0 { - if err = self.sendRtpKeepalive(); err != nil { - return - } self.conn.Timeout = self.RtpTimeout res.Block = make([]byte, res.BlockLength) if _, err = io.ReadFull(self.rconn, res.Block); err != nil { return } + if err = self.sendRtpKeepalive(); err != nil { + return + } } }() self.conn.Timeout = self.RtspTimeout - var h [4]byte if _, err = io.ReadFull(self.rconn, h[:]); err != nil { return From eecd22115926822a93fe5f634295034d0e2fa655 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 10:29:18 +0800 Subject: [PATCH 23/61] adjust header --- client.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index 0c26075..60add71 100644 --- a/client.go +++ b/client.go @@ -142,11 +142,11 @@ func (self *Client) WriteRequest(req Request) (err error) { self.conn.Timeout = self.RtspTimeout self.cseq++ - req.Header = append(req.Header, self.Headers...) req.Header = append(req.Header, fmt.Sprintf("CSeq: %d", self.cseq)) for _, s := range self.authorization { req.Header = append(req.Header, "Authorization: "+s) } + req.Header = append(req.Header, self.Headers...) if err = self.writeLine(fmt.Sprintf("%s %s RTSP/1.0\r\n", req.Method, req.Uri)); err != nil { return } @@ -402,6 +402,7 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { req := Request{ Method: "DESCRIBE", Uri: self.requestUri, + Header: []string{"Accept: application/sdp"}, } if err = self.WriteRequest(req); err != nil { return @@ -425,11 +426,7 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { } self.streams = []*Stream{} - sess, medias := sdp.Parse(body) - - if sess.Uri != "" { - self.requestUri = sess.Uri - } + _, medias := sdp.Parse(body) for _, media := range medias { stream := &Stream{Sdp: media} From acccd0067489c729aee4e437279c9ffe32bdc744 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 14:48:41 +0800 Subject: [PATCH 24/61] fix authHeaders bug, and make WriteRequest conn.Write only once to avoid camera bug --- client.go | 74 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/client.go b/client.go index 60add71..7b98380 100644 --- a/client.go +++ b/client.go @@ -38,6 +38,8 @@ type Client struct { setupMap []int playCalled bool + authHeaders func(method string) []string + url *url.URL conn *connWithTimeout rconn io.Reader @@ -45,7 +47,6 @@ type Client struct { cseq uint streams []*Stream session string - authorization []string body io.Reader pktque *pktqueue.Queue } @@ -113,14 +114,6 @@ func (self *Client) Streams() (streams []av.CodecData, err error) { return } -func (self *Client) writeLine(line string) (err error) { - if self.DebugConn { - fmt.Print("> ", line) - } - _, err = fmt.Fprint(self.conn, line) - return -} - func (self *Client) sendRtpKeepalive() (err error) { if self.RtpKeepAliveTimeout > 0 { if self.rtpKeepaliveTimer.IsZero() { @@ -140,24 +133,40 @@ func (self *Client) sendRtpKeepalive() (err error) { func (self *Client) WriteRequest(req Request) (err error) { self.conn.Timeout = self.RtspTimeout - self.cseq++ - req.Header = append(req.Header, fmt.Sprintf("CSeq: %d", self.cseq)) - for _, s := range self.authorization { - req.Header = append(req.Header, "Authorization: "+s) - } - req.Header = append(req.Header, self.Headers...) - if err = self.writeLine(fmt.Sprintf("%s %s RTSP/1.0\r\n", req.Method, req.Uri)); err != nil { - return - } - for _, v := range req.Header { - if err = self.writeLine(fmt.Sprint(v, "\r\n")); err != nil { - return + + buf := &bytes.Buffer{} + + fmt.Fprintf(buf, "%s %s RTSP/1.0\r\n", req.Method, req.Uri) + fmt.Fprintf(buf, "CSeq: %d\r\n", self.cseq) + + if self.authHeaders != nil { + headers := self.authHeaders(req.Method) + for _, s := range headers { + io.WriteString(buf, s) + io.WriteString(buf, "\r\n") } } - if err = self.writeLine("\r\n"); err != nil { + for _, s := range req.Header { + io.WriteString(buf, s) + io.WriteString(buf, "\r\n") + } + for _, s := range self.Headers { + io.WriteString(buf, s) + io.WriteString(buf, "\r\n") + } + io.WriteString(buf, "\r\n") + + bufout := buf.Bytes() + + if self.DebugConn { + fmt.Print("> ", string(bufout)) + } + + if _, err = self.conn.Write(bufout); err != nil { return } + return } @@ -268,7 +277,10 @@ func (self *Client) ReadResponse() (res Response, err error) { } if self.DebugConn { - fmt.Println("<", header) + for k, s := range header { + fmt.Println(k, s) + } + fmt.Println() } if res.StatusCode != 200 && res.StatusCode != 401 { @@ -316,12 +328,15 @@ func (self *Client) ReadResponse() (res Response, err error) { return } hs1 := md5hash(username+":"+realm+":"+password) - hs2 := md5hash("DESCRIBE:"+self.requestUri) - response := md5hash(hs1+":"+nonce+":"+hs2) - self.authorization = []string{ - fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, - username, realm, nonce, self.requestUri, response), - fmt.Sprintf(`Basic %s`, base64.StdEncoding.EncodeToString([]byte(username+":"+password))), + + self.authHeaders = func(method string) []string { + hs2 := md5hash(method+":"+self.requestUri) + response := md5hash(hs1+":"+nonce+":"+hs2) + return []string{ + fmt.Sprintf(`Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, + username, realm, nonce, self.requestUri, response), + fmt.Sprintf(`Authorization: Basic %s`, base64.StdEncoding.EncodeToString([]byte(username+":"+password))), + } } } } @@ -651,6 +666,7 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { self.pkt.Data = packet self.timestamp = timestamp } + return } From be73d07f24f381c125da462cd9263646b11dfe7b Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 22:41:34 +0800 Subject: [PATCH 25/61] skip naluType==6 --- client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client.go b/client.go index 7b98380..6b68d01 100644 --- a/client.go +++ b/client.go @@ -578,6 +578,9 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { */ switch { + case naluType == 6: + // skip naluType == 6 + case naluType >= 1 && naluType <= 23: if err = self.handleH264Payload(naluType, timestamp, packet); err != nil { return From f299d6709a0f631b5768c499b4c8afd61c536052 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 13 Jun 2016 15:31:46 +0800 Subject: [PATCH 26/61] add HandleCodecDataChange() --- client.go | 184 +++++++++++++++++++++++++++++++++++++----------------- stream.go | 3 + 2 files changed, 130 insertions(+), 57 deletions(-) diff --git a/client.go b/client.go index 6b68d01..95fcac9 100644 --- a/client.go +++ b/client.go @@ -24,6 +24,8 @@ import ( "github.com/nareix/av/pktqueue" ) +var ErrCodecDataChange = fmt.Errorf("rtsp: codec data change, please call HandleCodecDataChange()") + type Client struct { DebugConn bool Headers []string @@ -444,60 +446,10 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { _, medias := sdp.Parse(body) for _, media := range medias { - stream := &Stream{Sdp: media} - - if false { - fmt.Println("sdp:", media.TimeScale) + stream := &Stream{Sdp: media, client: self} + if err = stream.makeCodecData(); err != nil { + return } - - if media.PayloadType >= 96 && media.PayloadType <= 127 { - switch media.Type { - case av.H264: - var sps, pps []byte - for _, nalu := range media.SpropParameterSets { - if len(nalu) > 0 { - switch nalu[0]&0x1f { - case 7: - sps = nalu - case 8: - pps = nalu - } - } - } - if len(sps) > 0 && len(pps) > 0 { - if stream.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil { - err = fmt.Errorf("rtsp: h264 sps/pps invalid: %s", err) - return - } - } else { - err = fmt.Errorf("rtsp: h264 sdp sprop-parameter-sets invalid: missing sps or pps") - return - } - - case av.AAC: - if len(media.Config) == 0 { - err = fmt.Errorf("rtsp: aac sdp config missing") - return - } - if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(media.Config); err != nil { - err = fmt.Errorf("rtsp: aac sdp config invalid: %s", err) - return - } - } - } else { - switch media.PayloadType { - case 0: - stream.CodecData = codec.NewPCMMulawCodecData() - - case 8: - stream.CodecData = codec.NewPCMAlawCodecData() - - default: - err = fmt.Errorf("rtsp: PayloadType=%d unsupported", media.PayloadType) - return - } - } - self.streams = append(self.streams, stream) } @@ -525,6 +477,98 @@ func (self *Client) Options() (err error) { return } +func (self *Client) HandleCodecDataChange() (_newcli *Client, err error) { + newcli := &Client{} + *newcli = *self + + newcli.streams = []*Stream{} + for _, stream := range self.streams { + newstream := &Stream{} + *newstream = *stream + newstream.client = newcli + + if newstream.isCodecDataChange() { + if err = newstream.makeCodecData(); err != nil { + return + } + newstream.clearCodecDataChange() + } + newcli.streams = append(newcli.streams, newstream) + } + + _newcli = newcli + return +} + +func (self *Stream) clearCodecDataChange() { + self.spsChanged = false + self.ppsChanged = false +} + +func (self *Stream) isCodecDataChange() bool { + if self.spsChanged && self.ppsChanged { + return true + } + return false +} + +func (self *Stream) makeCodecData() (err error) { + media := self.Sdp + + if media.PayloadType >= 96 && media.PayloadType <= 127 { + switch media.Type { + case av.H264: + for _, nalu := range media.SpropParameterSets { + if len(nalu) > 0 { + switch nalu[0]&0x1f { + case 7: + if len(self.sps) == 0 { + self.sps = nalu + } + case 8: + if len(self.pps) == 0 { + self.pps = nalu + } + } + } + } + if len(self.sps) > 0 && len(self.pps) > 0 { + if self.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(self.sps, self.pps); err != nil { + err = fmt.Errorf("rtsp: h264 sps/pps invalid: %s", err) + return + } + } else { + err = fmt.Errorf("rtsp: missing h264 sps or pps") + return + } + + case av.AAC: + if len(media.Config) == 0 { + err = fmt.Errorf("rtsp: aac sdp config missing") + return + } + if self.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(media.Config); err != nil { + err = fmt.Errorf("rtsp: aac sdp config invalid: %s", err) + return + } + } + } else { + switch media.PayloadType { + case 0: + self.CodecData = codec.NewPCMMulawCodecData() + + case 8: + self.CodecData = codec.NewPCMAlawCodecData() + + default: + err = fmt.Errorf("rtsp: PayloadType=%d unsupported", media.PayloadType) + return + } + } + + return +} + func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet []byte) (err error) { /* Table 7-1 – NAL unit type codes @@ -535,8 +579,31 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] 8 Picture parameter set */ switch naluType { - case 7,8: - // sps/pps + case 6: // SEI ignored + + case 7: // sps + if self.client != nil && self.client.DebugConn { + fmt.Println("rtsp: got sps") + } + if bytes.Compare(self.sps, packet) != 0 { + self.spsChanged = true + self.sps = packet + if self.client != nil && self.client.DebugConn { + fmt.Println("rtsp: sps changed") + } + } + + case 8: // pps + if self.client != nil && self.client.DebugConn { + fmt.Println("rtsp: got pps") + } + if bytes.Compare(self.pps, packet) != 0 { + self.ppsChanged = true + self.pps = packet + if self.client != nil && self.client.DebugConn { + fmt.Println("rtsp: pps changed") + } + } default: if naluType == 5 { @@ -551,6 +618,11 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] } func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { + if self.isCodecDataChange() { + err = ErrCodecDataChange + return + } + switch self.Type() { case av.H264: /* @@ -578,8 +650,6 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { */ switch { - case naluType == 6: - // skip naluType == 6 case naluType >= 1 && naluType <= 23: if err = self.handleH264Payload(naluType, timestamp, packet); err != nil { diff --git a/stream.go b/stream.go index c983e78..3dd2375 100644 --- a/stream.go +++ b/stream.go @@ -8,11 +8,14 @@ import ( type Stream struct { av.CodecData Sdp sdp.Media + client *Client // h264 fuBuffer []byte sps []byte pps []byte + spsChanged bool + ppsChanged bool gotpkt bool pkt av.Packet From 374779c3752dcc400aa710e34c6c92fb2d9a0d47 Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 14 Jun 2016 10:25:57 +0800 Subject: [PATCH 27/61] add default 20 sec rtp keepalive timer --- client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client.go b/client.go index 95fcac9..ff98468 100644 --- a/client.go +++ b/client.go @@ -917,6 +917,8 @@ func Open(uri string) (cli *Client, err error) { if err = _cli.ReadHeader(); err != nil { return } + _cli.rtpKeepaliveTimer = time.Now() + _cli.RtpKeepAliveTimeout = 20*time.Second cli = _cli return } From 39fcf2150f5a925f56ef57923857fd90ce36d748 Mon Sep 17 00:00:00 2001 From: Andrey Semochkin Date: Tue, 14 Jun 2016 06:42:35 +0400 Subject: [PATCH 28/61] more camera check Session keep-alive fix keep-alive add Session --- client.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 95fcac9..b12f035 100644 --- a/client.go +++ b/client.go @@ -465,10 +465,14 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { } func (self *Client) Options() (err error) { - if err = self.WriteRequest(Request{ + req := Request{ Method: "OPTIONS", - Uri: self.requestUri, - }); err != nil { + Uri: self.requestUri, + } + if self.session != "" { + req.Header = append(req.Header, "Session: "+self.session) + } + if err = self.WriteRequest(req); err != nil { return } if _, err = self.ReadResponse(); err != nil { From 4fb78f9e74c6d290bbd7b3de176f6c2bf457783b Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 14 Jun 2016 11:55:44 +0800 Subject: [PATCH 29/61] add rtpKeepaliveEnterCnt to fix keepalive reenter problem --- client.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 3032d86..c3a78a7 100644 --- a/client.go +++ b/client.go @@ -34,6 +34,7 @@ type Client struct { RtpTimeout time.Duration RtpKeepAliveTimeout time.Duration rtpKeepaliveTimer time.Time + rtpKeepaliveEnterCnt int setupCalled bool setupIdx []int @@ -117,7 +118,11 @@ func (self *Client) Streams() (streams []av.CodecData, err error) { } func (self *Client) sendRtpKeepalive() (err error) { - if self.RtpKeepAliveTimeout > 0 { + if self.RtpKeepAliveTimeout > 0 && self.rtpKeepaliveEnterCnt == 0 { + self.rtpKeepaliveEnterCnt++ + defer func() { + self.rtpKeepaliveEnterCnt-- + }() if self.rtpKeepaliveTimer.IsZero() { self.rtpKeepaliveTimer = time.Now() } else if time.Now().Sub(self.rtpKeepaliveTimer) > self.RtpKeepAliveTimeout { From cd8f5fd6735af4c0470c3e5e20ba408d808ee07a Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 06:52:04 +0800 Subject: [PATCH 30/61] run gofmt --- client.go | 361 +++++++++++++++++++++++++------------------------- conn.go | 1 - sdp/parser.go | 27 ++-- stream.go | 13 +- 4 files changed, 199 insertions(+), 203 deletions(-) diff --git a/client.go b/client.go index c3a78a7..cf29408 100644 --- a/client.go +++ b/client.go @@ -1,74 +1,74 @@ package rtsp import ( - "time" - "fmt" - "net" - "bytes" - "io" - "strings" - "strconv" "bufio" + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "fmt" + "github.com/nareix/av" + "github.com/nareix/av/pktqueue" + "github.com/nareix/codec" + "github.com/nareix/codec/aacparser" + "github.com/nareix/codec/h264parser" + "github.com/nareix/rtsp/sdp" "html" + "io" + "net" "net/textproto" "net/url" - "encoding/hex" - "encoding/binary" - "encoding/base64" - "crypto/md5" - "github.com/nareix/av" - "github.com/nareix/codec/h264parser" - "github.com/nareix/codec/aacparser" - "github.com/nareix/codec" - "github.com/nareix/rtsp/sdp" - "github.com/nareix/av/pktqueue" + "strconv" + "strings" + "time" ) var ErrCodecDataChange = fmt.Errorf("rtsp: codec data change, please call HandleCodecDataChange()") type Client struct { DebugConn bool - Headers []string + Headers []string - RtspTimeout time.Duration - RtpTimeout time.Duration - RtpKeepAliveTimeout time.Duration - rtpKeepaliveTimer time.Time + RtspTimeout time.Duration + RtpTimeout time.Duration + RtpKeepAliveTimeout time.Duration + rtpKeepaliveTimer time.Time rtpKeepaliveEnterCnt int setupCalled bool - setupIdx []int - setupMap []int - playCalled bool + setupIdx []int + setupMap []int + playCalled bool authHeaders func(method string) []string - url *url.URL - conn *connWithTimeout - rconn io.Reader + url *url.URL + conn *connWithTimeout + rconn io.Reader requestUri string - cseq uint - streams []*Stream - session string - body io.Reader - pktque *pktqueue.Queue + cseq uint + streams []*Stream + session string + body io.Reader + pktque *pktqueue.Queue } type Request struct { Header []string - Uri string + Uri string Method string } type Response struct { BlockLength int - Block []byte - BlockNo int + Block []byte + BlockNo int - StatusCode int - Header textproto.MIMEHeader + StatusCode int + Header textproto.MIMEHeader ContentLength int - Body []byte + Body []byte } func DialTimeout(uri string, timeout time.Duration) (self *Client, err error) { @@ -93,9 +93,9 @@ func DialTimeout(uri string, timeout time.Duration) (self *Client, err error) { connt := &connWithTimeout{Conn: conn} self = &Client{ - conn: connt, - rconn: connt, - url: URL, + conn: connt, + rconn: connt, + url: URL, requestUri: u2.String(), } return @@ -214,7 +214,7 @@ func (self *Client) ReadResponse() (res Response, err error) { if h[0] == 36 { // $ - res.BlockLength = int(h[2])<<8+int(h[3]) + res.BlockLength = int(h[2])<<8 + int(h[3]) res.BlockNo = int(h[1]) if self.DebugConn { fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) @@ -244,7 +244,7 @@ func (self *Client) ReadResponse() (res Response, err error) { return } - res.BlockLength = int(h[2])<<8+int(h[3]) + res.BlockLength = int(h[2])<<8 + int(h[3]) res.BlockNo = int(h[1]) if res.BlockNo/2 < len(self.streams) { break @@ -297,10 +297,10 @@ func (self *Client) ReadResponse() (res Response, err error) { if res.StatusCode == 401 { /* - RTSP/1.0 401 Unauthorized - CSeq: 2 - Date: Wed, May 04 2016 10:10:51 GMT - WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="c633aaf8b83127633cbe98fac1d20d87" + RTSP/1.0 401 Unauthorized + CSeq: 2 + Date: Wed, May 04 2016 10:10:51 GMT + WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="c633aaf8b83127633cbe98fac1d20d87" */ authval := header.Get("WWW-Authenticate") hdrval := strings.SplitN(authval, " ", 2) @@ -334,11 +334,11 @@ func (self *Client) ReadResponse() (res Response, err error) { err = fmt.Errorf("rtsp: please provide password") return } - hs1 := md5hash(username+":"+realm+":"+password) + hs1 := md5hash(username + ":" + realm + ":" + password) self.authHeaders = func(method string) []string { - hs2 := md5hash(method+":"+self.requestUri) - response := md5hash(hs1+":"+nonce+":"+hs2) + hs2 := md5hash(method + ":" + self.requestUri) + response := md5hash(hs1 + ":" + nonce + ":" + hs2) return []string{ fmt.Sprintf(`Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, username, realm, nonce, self.requestUri, response), @@ -393,7 +393,7 @@ func (self *Client) Setup(idx []int) (err error) { if strings.HasPrefix(control, "rtsp://") { uri = control } else { - uri = self.requestUri+"/"+control + uri = self.requestUri + "/" + control } req := Request{Method: "SETUP", Uri: uri} req.Header = append(req.Header, fmt.Sprintf("Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d", si*2, si*2+1)) @@ -423,7 +423,7 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { for i := 0; i < 2; i++ { req := Request{ Method: "DESCRIBE", - Uri: self.requestUri, + Uri: self.requestUri, Header: []string{"Accept: application/sdp"}, } if err = self.WriteRequest(req); err != nil { @@ -529,7 +529,7 @@ func (self *Stream) makeCodecData() (err error) { case av.H264: for _, nalu := range media.SpropParameterSets { if len(nalu) > 0 { - switch nalu[0]&0x1f { + switch nalu[0] & 0x1f { case 7: if len(self.sps) == 0 { self.sps = nalu @@ -580,12 +580,12 @@ func (self *Stream) makeCodecData() (err error) { func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet []byte) (err error) { /* - Table 7-1 – NAL unit type codes - 1 Coded slice of a non-IDR picture - 5 Coded slice of an IDR picture - 6 Supplemental enhancement information (SEI) - 7 Sequence parameter set - 8 Picture parameter set + Table 7-1 – NAL unit type codes + 1 Coded slice of a non-IDR picture + 5 Coded slice of an IDR picture + 6 Supplemental enhancement information (SEI) + 7 Sequence parameter set + 8 Picture parameter set */ switch naluType { case 6: // SEI ignored @@ -635,27 +635,27 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { switch self.Type() { case av.H264: /* - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |F|NRI| Type | - +---------------+ + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ */ - naluType := packet[0]&0x1f + naluType := packet[0] & 0x1f /* - NAL Unit Packet Packet Type Name Section - Type Type - ------------------------------------------------------------- - 0 reserved - - 1-23 NAL unit Single NAL unit packet 5.6 - 24 STAP-A Single-time aggregation packet 5.7.1 - 25 STAP-B Single-time aggregation packet 5.7.1 - 26 MTAP16 Multi-time aggregation packet 5.7.2 - 27 MTAP24 Multi-time aggregation packet 5.7.2 - 28 FU-A Fragmentation unit 5.8 - 29 FU-B Fragmentation unit 5.8 - 30-31 reserved - + NAL Unit Packet Packet Type Name Section + Type Type + ------------------------------------------------------------- + 0 reserved - + 1-23 NAL unit Single NAL unit packet 5.6 + 24 STAP-A Single-time aggregation packet 5.7.1 + 25 STAP-B Single-time aggregation packet 5.7.1 + 26 MTAP16 Multi-time aggregation packet 5.7.2 + 27 MTAP24 Multi-time aggregation packet 5.7.2 + 28 FU-A Fragmentation unit 5.8 + 29 FU-B Fragmentation unit 5.8 + 30-31 reserved - */ switch { @@ -667,60 +667,60 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { case naluType == 28: // FU-A /* - 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 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | FU indicator | FU header | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | - | | - | FU payload | - | | - | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | :...OPTIONAL RTP padding | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - Figure 14. RTP payload format for FU-A + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | FU indicator | FU header | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + | | + | FU payload | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Figure 14. RTP payload format for FU-A - The FU indicator octet has the following format: - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |F|NRI| Type | - +---------------+ + The FU indicator octet has the following format: + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ - The FU header has the following format: - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |S|E|R| Type | - +---------------+ + The FU header has the following format: + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |S|E|R| Type | + +---------------+ - S: 1 bit - When set to one, the Start bit indicates the start of a fragmented - NAL unit. When the following FU payload is not the start of a - fragmented NAL unit payload, the Start bit is set to zero. + S: 1 bit + When set to one, the Start bit indicates the start of a fragmented + NAL unit. When the following FU payload is not the start of a + fragmented NAL unit payload, the Start bit is set to zero. - E: 1 bit - When set to one, the End bit indicates the end of a fragmented NAL - unit, i.e., the last byte of the payload is also the last byte of - the fragmented NAL unit. When the following FU payload is not the - last fragment of a fragmented NAL unit, the End bit is set to - zero. + E: 1 bit + When set to one, the End bit indicates the end of a fragmented NAL + unit, i.e., the last byte of the payload is also the last byte of + the fragmented NAL unit. When the following FU payload is not the + last fragment of a fragmented NAL unit, the End bit is set to + zero. - R: 1 bit - The Reserved bit MUST be equal to 0 and MUST be ignored by the - receiver. + R: 1 bit + The Reserved bit MUST be equal to 0 and MUST be ignored by the + receiver. - Type: 5 bits - The NAL unit payload type as defined in table 7-1 of [1]. + Type: 5 bits + The NAL unit payload type as defined in table 7-1 of [1]. */ fuIndicator := packet[0] fuHeader := packet[1] - isStart := fuHeader&0x80!=0 - isEnd := fuHeader&0x40!=0 - naluType := fuHeader&0x1f + isStart := fuHeader&0x80 != 0 + isEnd := fuHeader&0x40 != 0 + naluType := fuHeader & 0x1f if isStart { - self.fuBuffer = []byte{fuIndicator&0xe0|fuHeader&0x1f} + self.fuBuffer = []byte{fuIndicator&0xe0 | fuHeader&0x1f} } self.fuBuffer = append(self.fuBuffer, packet[2:]...) if isEnd { @@ -753,12 +753,12 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { } func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err error) { - if blockNo % 2 != 0 { + if blockNo%2 != 0 { // rtcp block return } - streamIndex = blockNo/2 + streamIndex = blockNo / 2 if streamIndex >= len(self.streams) { err = fmt.Errorf("rtsp: parseBlock: streamIndex=%d invalid", streamIndex) return @@ -766,25 +766,25 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err stream := self.streams[streamIndex] /* - 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 | - | .... | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 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 | + | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ if len(packet) < 8 { err = fmt.Errorf("rtp: packet too short") return } - payloadOffset := 12+int(packet[0]&0xf)*4 + payloadOffset := 12 + int(packet[0]&0xf)*4 if payloadOffset+2 > len(packet) { err = fmt.Errorf("rtp: packet too short") return @@ -794,52 +794,52 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err payload := packet[payloadOffset:] /* - PT Encoding Name Audio/Video (A/V) Clock Rate (Hz) Channels Reference - 0 PCMU A 8000 1 [RFC3551] - 1 Reserved - 2 Reserved - 3 GSM A 8000 1 [RFC3551] - 4 G723 A 8000 1 [Vineet_Kumar][RFC3551] - 5 DVI4 A 8000 1 [RFC3551] - 6 DVI4 A 16000 1 [RFC3551] - 7 LPC A 8000 1 [RFC3551] - 8 PCMA A 8000 1 [RFC3551] - 9 G722 A 8000 1 [RFC3551] - 10 L16 A 44100 2 [RFC3551] - 11 L16 A 44100 1 [RFC3551] - 12 QCELP A 8000 1 [RFC3551] - 13 CN A 8000 1 [RFC3389] - 14 MPA A 90000 [RFC3551][RFC2250] - 15 G728 A 8000 1 [RFC3551] - 16 DVI4 A 11025 1 [Joseph_Di_Pol] - 17 DVI4 A 22050 1 [Joseph_Di_Pol] - 18 G729 A 8000 1 [RFC3551] - 19 Reserved A - 20 Unassigned A - 21 Unassigned A - 22 Unassigned A - 23 Unassigned A - 24 Unassigned V - 25 CelB V 90000 [RFC2029] - 26 JPEG V 90000 [RFC2435] - 27 Unassigned V - 28 nv V 90000 [RFC3551] - 29 Unassigned V - 30 Unassigned V - 31 H261 V 90000 [RFC4587] - 32 MPV V 90000 [RFC2250] - 33 MP2T AV 90000 [RFC2250] - 34 H263 V 90000 [Chunrong_Zhu] - 35-71 Unassigned ? - 72-76 Reserved for RTCP conflict avoidance [RFC3551] - 77-95 Unassigned ? - 96-127 dynamic ? [RFC3551] + PT Encoding Name Audio/Video (A/V) Clock Rate (Hz) Channels Reference + 0 PCMU A 8000 1 [RFC3551] + 1 Reserved + 2 Reserved + 3 GSM A 8000 1 [RFC3551] + 4 G723 A 8000 1 [Vineet_Kumar][RFC3551] + 5 DVI4 A 8000 1 [RFC3551] + 6 DVI4 A 16000 1 [RFC3551] + 7 LPC A 8000 1 [RFC3551] + 8 PCMA A 8000 1 [RFC3551] + 9 G722 A 8000 1 [RFC3551] + 10 L16 A 44100 2 [RFC3551] + 11 L16 A 44100 1 [RFC3551] + 12 QCELP A 8000 1 [RFC3551] + 13 CN A 8000 1 [RFC3389] + 14 MPA A 90000 [RFC3551][RFC2250] + 15 G728 A 8000 1 [RFC3551] + 16 DVI4 A 11025 1 [Joseph_Di_Pol] + 17 DVI4 A 22050 1 [Joseph_Di_Pol] + 18 G729 A 8000 1 [RFC3551] + 19 Reserved A + 20 Unassigned A + 21 Unassigned A + 22 Unassigned A + 23 Unassigned A + 24 Unassigned V + 25 CelB V 90000 [RFC2029] + 26 JPEG V 90000 [RFC2435] + 27 Unassigned V + 28 nv V 90000 [RFC3551] + 29 Unassigned V + 30 Unassigned V + 31 H261 V 90000 [RFC4587] + 32 MPV V 90000 [RFC2250] + 33 MP2T AV 90000 [RFC2250] + 34 H263 V 90000 [Chunrong_Zhu] + 35-71 Unassigned ? + 72-76 Reserved for RTCP conflict avoidance [RFC3551] + 77-95 Unassigned ? + 96-127 dynamic ? [RFC3551] */ //payloadType := packet[1]&0x7f if self.DebugConn { //fmt.Println("packet:", stream.Type(), "offset", payloadOffset, "pt", payloadType) - if len(packet)>24 { + if len(packet) > 24 { fmt.Println(hex.Dump(packet[:24])) } } @@ -854,7 +854,7 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err func (self *Client) Play() (err error) { req := Request{ Method: "PLAY", - Uri: self.requestUri, + Uri: self.requestUri, } req.Header = append(req.Header, "Session: "+self.session) if err = self.WriteRequest(req); err != nil { @@ -877,7 +877,7 @@ func (self *Client) poll() (err error) { } stream := self.streams[i] if stream.gotpkt { - time := float64(stream.timestamp)/float64(stream.Sdp.TimeScale) + time := float64(stream.timestamp) / float64(stream.Sdp.TimeScale) if false { fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, stream.Sdp.TimeScale, len(stream.pkt.Data)) } @@ -927,8 +927,7 @@ func Open(uri string) (cli *Client, err error) { return } _cli.rtpKeepaliveTimer = time.Now() - _cli.RtpKeepAliveTimeout = 20*time.Second + _cli.RtpKeepAliveTimeout = 20 * time.Second cli = _cli return } - diff --git a/conn.go b/conn.go index 621895d..20775e1 100644 --- a/conn.go +++ b/conn.go @@ -23,4 +23,3 @@ func (self connWithTimeout) Write(p []byte) (n int, err error) { } return self.Conn.Write(p) } - diff --git a/sdp/parser.go b/sdp/parser.go index f171d80..3dc587d 100644 --- a/sdp/parser.go +++ b/sdp/parser.go @@ -1,12 +1,12 @@ package sdp import ( - "strings" - "fmt" - "strconv" - "encoding/hex" "encoding/base64" + "encoding/hex" + "fmt" "github.com/nareix/av" + "strconv" + "strings" ) type Session struct { @@ -14,16 +14,16 @@ type Session struct { } type Media struct { - AVType string - Type int - TimeScale int - Control string - Rtpmap int - Config []byte + AVType string + Type int + TimeScale int + Control string + Rtpmap int + Config []byte SpropParameterSets [][]byte - PayloadType int - SizeLength int - IndexLength int + PayloadType int + SizeLength int + IndexLength int } func Parse(content string) (sess Session, medias []Media) { @@ -114,4 +114,3 @@ func Parse(content string) (sess Session, medias []Media) { } return } - diff --git a/stream.go b/stream.go index 3dd2375..6537dc0 100644 --- a/stream.go +++ b/stream.go @@ -7,18 +7,18 @@ import ( type Stream struct { av.CodecData - Sdp sdp.Media + Sdp sdp.Media client *Client // h264 - fuBuffer []byte - sps []byte - pps []byte + fuBuffer []byte + sps []byte + pps []byte spsChanged bool ppsChanged bool - gotpkt bool - pkt av.Packet + gotpkt bool + pkt av.Packet timestamp uint32 } @@ -29,4 +29,3 @@ func (self Stream) IsAudio() bool { func (self Stream) IsVideo() bool { return self.Sdp.AVType == "video" } - From d2ad170d67802f7ee90b42d7f9a76e8cb0356044 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 06:53:04 +0800 Subject: [PATCH 31/61] change DebugConn to DebugRtsp, add DebugRtp --- client.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/client.go b/client.go index cf29408..f3f0bda 100644 --- a/client.go +++ b/client.go @@ -27,7 +27,8 @@ import ( var ErrCodecDataChange = fmt.Errorf("rtsp: codec data change, please call HandleCodecDataChange()") type Client struct { - DebugConn bool + DebugRtsp bool + DebugRtp bool Headers []string RtspTimeout time.Duration @@ -127,7 +128,7 @@ func (self *Client) sendRtpKeepalive() (err error) { self.rtpKeepaliveTimer = time.Now() } else if time.Now().Sub(self.rtpKeepaliveTimer) > self.RtpKeepAliveTimeout { self.rtpKeepaliveTimer = time.Now() - if self.DebugConn { + if self.DebugRtsp { fmt.Println("rtp: keep alive") } if err = self.Options(); err != nil { @@ -166,7 +167,7 @@ func (self *Client) WriteRequest(req Request) (err error) { bufout := buf.Bytes() - if self.DebugConn { + if self.DebugRtsp { fmt.Print("> ", string(bufout)) } @@ -216,7 +217,7 @@ func (self *Client) ReadResponse() (res Response, err error) { // $ res.BlockLength = int(h[2])<<8 + int(h[3]) res.BlockNo = int(h[1]) - if self.DebugConn { + if self.DebugRtsp { fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) } return @@ -227,7 +228,7 @@ func (self *Client) ReadResponse() (res Response, err error) { self.conn.Timeout = self.RtpTimeout for { - if self.DebugConn { + if self.DebugRtsp { fmt.Println("block: relocate try") } @@ -251,7 +252,7 @@ func (self *Client) ReadResponse() (res Response, err error) { } } - if self.DebugConn { + if self.DebugRtsp { fmt.Println("block: relocate done") fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) } @@ -265,7 +266,7 @@ func (self *Client) ReadResponse() (res Response, err error) { if line, err = tp.ReadLine(); err != nil { return } - if self.DebugConn { + if self.DebugRtsp { fmt.Println("<", line) } @@ -283,7 +284,7 @@ func (self *Client) ReadResponse() (res Response, err error) { return } - if self.DebugConn { + if self.DebugRtsp { for k, s := range header { fmt.Println(k, s) } @@ -443,7 +444,7 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { body := string(res.Body) - if self.DebugConn { + if self.DebugRtsp { fmt.Println("<", body) } @@ -591,25 +592,25 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] case 6: // SEI ignored case 7: // sps - if self.client != nil && self.client.DebugConn { + if self.client != nil && self.client.DebugRtp { fmt.Println("rtsp: got sps") } if bytes.Compare(self.sps, packet) != 0 { self.spsChanged = true self.sps = packet - if self.client != nil && self.client.DebugConn { + if self.client != nil && self.client.DebugRtp { fmt.Println("rtsp: sps changed") } } case 8: // pps - if self.client != nil && self.client.DebugConn { + if self.client != nil && self.client.DebugRtp { fmt.Println("rtsp: got pps") } if bytes.Compare(self.pps, packet) != 0 { self.ppsChanged = true self.pps = packet - if self.client != nil && self.client.DebugConn { + if self.client != nil && self.client.DebugRtp { fmt.Println("rtsp: pps changed") } } @@ -837,7 +838,7 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err */ //payloadType := packet[1]&0x7f - if self.DebugConn { + if self.DebugRtsp { //fmt.Println("packet:", stream.Type(), "offset", payloadOffset, "pt", payloadType) if len(packet) > 24 { fmt.Println(hex.Dump(packet[:24])) From b16f72faae25feed976b7d1a7a7ef34e9c59c7ba Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 06:58:07 +0800 Subject: [PATCH 32/61] do Options() first --- client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client.go b/client.go index f3f0bda..e929357 100644 --- a/client.go +++ b/client.go @@ -907,6 +907,9 @@ func (self *Client) ReadPacket() (i int, pkt av.Packet, err error) { } func (self *Client) ReadHeader() (err error) { + if _, err = self.Options(); err != nil { + return + } if _, err = self.Describe(); err != nil { return } From 227ce8b0738358f5e9321e4222c05d46ae87acb3 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 07:28:05 +0800 Subject: [PATCH 33/61] send Authorization: Digest only when nonce exists --- client.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/client.go b/client.go index e929357..6bfc1b8 100644 --- a/client.go +++ b/client.go @@ -322,29 +322,30 @@ func (self *Client) ReadResponse() (res Response, err error) { } } - if realm != "" && nonce != "" { - if self.url.User == nil { - err = fmt.Errorf("rtsp: please provide username and password") - return - } + if realm != "" { var username string var password string - var ok bool - username = self.url.User.Username() - if password, ok = self.url.User.Password(); !ok { - err = fmt.Errorf("rtsp: please provide password") + + if self.url.User == nil { + err = fmt.Errorf("rtsp: no username") return } - hs1 := md5hash(username + ":" + realm + ":" + password) + username = self.url.User.Username() + password, _ = self.url.User.Password() self.authHeaders = func(method string) []string { - hs2 := md5hash(method + ":" + self.requestUri) - response := md5hash(hs1 + ":" + nonce + ":" + hs2) - return []string{ - fmt.Sprintf(`Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, - username, realm, nonce, self.requestUri, response), + headers := []string{ fmt.Sprintf(`Authorization: Basic %s`, base64.StdEncoding.EncodeToString([]byte(username+":"+password))), } + if nonce != "" { + hs1 := md5hash(username + ":" + realm + ":" + password) + hs2 := md5hash(method + ":" + self.requestUri) + response := md5hash(hs1 + ":" + nonce + ":" + hs2) + headers = append(headers, fmt.Sprintf( + `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, + username, realm, nonce, self.requestUri, response)) + } + return headers } } } @@ -907,7 +908,7 @@ func (self *Client) ReadPacket() (i int, pkt av.Packet, err error) { } func (self *Client) ReadHeader() (err error) { - if _, err = self.Options(); err != nil { + if err = self.Options(); err != nil { return } if _, err = self.Describe(); err != nil { From 0e64595992b02e3ecd5ccc4072083ba6897fc2c8 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 07:28:56 +0800 Subject: [PATCH 34/61] print block log when DebugRtp on --- client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 6bfc1b8..6cc7d36 100644 --- a/client.go +++ b/client.go @@ -217,7 +217,7 @@ func (self *Client) ReadResponse() (res Response, err error) { // $ res.BlockLength = int(h[2])<<8 + int(h[3]) res.BlockNo = int(h[1]) - if self.DebugRtsp { + if self.DebugRtp { fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) } return @@ -228,7 +228,7 @@ func (self *Client) ReadResponse() (res Response, err error) { self.conn.Timeout = self.RtpTimeout for { - if self.DebugRtsp { + if self.DebugRtp { fmt.Println("block: relocate try") } @@ -252,7 +252,7 @@ func (self *Client) ReadResponse() (res Response, err error) { } } - if self.DebugRtsp { + if self.DebugRtp { fmt.Println("block: relocate done") fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) } From c490b69c0e3c9c1501e11488057b1ecfdc7f84d4 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 07:31:02 +0800 Subject: [PATCH 35/61] remove default headers; print packet hex when DebugRtp on --- client.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client.go b/client.go index 6cc7d36..9dcc49c 100644 --- a/client.go +++ b/client.go @@ -839,7 +839,7 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err */ //payloadType := packet[1]&0x7f - if self.DebugRtsp { + if self.DebugRtp { //fmt.Println("packet:", stream.Type(), "offset", payloadOffset, "pt", payloadType) if len(packet) > 24 { fmt.Println(hex.Dump(packet[:24])) @@ -931,8 +931,6 @@ func Open(uri string) (cli *Client, err error) { if err = _cli.ReadHeader(); err != nil { return } - _cli.rtpKeepaliveTimer = time.Now() - _cli.RtpKeepAliveTimeout = 20 * time.Second cli = _cli return } From 0837c385a6477ebde0f5aa867cd41b4d12360d70 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 07:34:45 +0800 Subject: [PATCH 36/61] make send rtp keepalive public --- client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 9dcc49c..f420fc0 100644 --- a/client.go +++ b/client.go @@ -118,7 +118,7 @@ func (self *Client) Streams() (streams []av.CodecData, err error) { return } -func (self *Client) sendRtpKeepalive() (err error) { +func (self *Client) SendRtpKeepalive() (err error) { if self.RtpKeepAliveTimeout > 0 && self.rtpKeepaliveEnterCnt == 0 { self.rtpKeepaliveEnterCnt++ defer func() { @@ -201,7 +201,7 @@ func (self *Client) ReadResponse() (res Response, err error) { if _, err = io.ReadFull(self.rconn, res.Block); err != nil { return } - if err = self.sendRtpKeepalive(); err != nil { + if err = self.SendRtpKeepalive(); err != nil { return } } From e6b1c2561e10c32ec2d00d9ca508f9fd35512ae3 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 09:47:20 +0800 Subject: [PATCH 37/61] parse sps/pps from media.Config --- client.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/client.go b/client.go index f420fc0..715e155 100644 --- a/client.go +++ b/client.go @@ -543,6 +543,26 @@ func (self *Stream) makeCodecData() (err error) { } } } + + if len(self.sps) == 0 || len(self.pps) == 0 { + if nalus, ok := h264parser.SplitNALUs(media.Config); ok { + for _, nalu := range nalus { + if len(nalu) > 0 { + switch nalu[0] & 0x1f { + case 7: + if len(self.sps) == 0 { + self.sps = nalu + } + case 8: + if len(self.pps) == 0 { + self.pps = nalu + } + } + } + } + } + } + if len(self.sps) > 0 && len(self.pps) > 0 { if self.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(self.sps, self.pps); err != nil { err = fmt.Errorf("rtsp: h264 sps/pps invalid: %s", err) From b0ccdad10b43f2e7e6daf781811b89bb83507235 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 10:28:34 +0800 Subject: [PATCH 38/61] rewrite handleH264Payload, support BuggyCameraHasAnnexbH264Packet --- client.go | 261 ++++++++++++++++++++++++++---------------------------- 1 file changed, 127 insertions(+), 134 deletions(-) diff --git a/client.go b/client.go index 715e155..5755de9 100644 --- a/client.go +++ b/client.go @@ -531,16 +531,7 @@ func (self *Stream) makeCodecData() (err error) { case av.H264: for _, nalu := range media.SpropParameterSets { if len(nalu) > 0 { - switch nalu[0] & 0x1f { - case 7: - if len(self.sps) == 0 { - self.sps = nalu - } - case 8: - if len(self.pps) == 0 { - self.pps = nalu - } - } + self.handleH264Payload(0, nalu) } } @@ -548,16 +539,7 @@ func (self *Stream) makeCodecData() (err error) { if nalus, ok := h264parser.SplitNALUs(media.Config); ok { for _, nalu := range nalus { if len(nalu) > 0 { - switch nalu[0] & 0x1f { - case 7: - if len(self.sps) == 0 { - self.sps = nalu - } - case 8: - if len(self.pps) == 0 { - self.pps = nalu - } - } + self.handleH264Payload(0, nalu) } } } @@ -600,7 +582,30 @@ func (self *Stream) makeCodecData() (err error) { return } -func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet []byte) (err error) { +func (self *Stream) handleBuggyCameraHasAnnexbH264Packet(timestamp uint32, packet []byte) (isBuggy bool, err error) { + if len(packet) >= 4 && packet[0] == 0 && packet[1] == 0 && packet[2] == 0 && packet[3] == 1 { + isBuggy = true + if nalus, ok := h264parser.SplitNALUs(packet); ok { + for _, nalu := range nalus { + if len(nalu) > 0 { + if err = self.handleH264Payload(timestamp, nalu); err != nil { + return + } + } + } + } + } + return +} + +func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err error) { + var isBuggy bool + if isBuggy, err = self.handleBuggyCameraHasAnnexbH264Packet(timestamp, packet); isBuggy { + return + } + + naluType := packet[0]&0x1f + /* Table 7-1 – NAL unit type codes 1 Coded slice of a non-IDR picture @@ -608,15 +613,39 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] 6 Supplemental enhancement information (SEI) 7 Sequence parameter set 8 Picture parameter set + 1-23 NAL unit Single NAL unit packet 5.6 + 24 STAP-A Single-time aggregation packet 5.7.1 + 25 STAP-B Single-time aggregation packet 5.7.1 + 26 MTAP16 Multi-time aggregation packet 5.7.2 + 27 MTAP24 Multi-time aggregation packet 5.7.2 + 28 FU-A Fragmentation unit 5.8 + 29 FU-B Fragmentation unit 5.8 + 30-31 reserved - */ - switch naluType { - case 6: // SEI ignored - case 7: // sps + switch { + default: + if naluType >= 1 && naluType <= 23 { + if naluType == 5 { + self.pkt.IsKeyFrame = true + } + self.gotpkt = true + self.pkt.Data = packet + self.timestamp = timestamp + } else { + err = fmt.Errorf("rtsp: unsupported H264 naluType=%d", naluType) + return + } + + case naluType == 6: // SEI ignored + + case naluType == 7: // sps if self.client != nil && self.client.DebugRtp { fmt.Println("rtsp: got sps") } - if bytes.Compare(self.sps, packet) != 0 { + if len(self.sps) == 0 { + self.sps = packet + } else if bytes.Compare(self.sps, packet) != 0 { self.spsChanged = true self.sps = packet if self.client != nil && self.client.DebugRtp { @@ -624,11 +653,13 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] } } - case 8: // pps + case naluType == 8: // pps if self.client != nil && self.client.DebugRtp { fmt.Println("rtsp: got pps") } - if bytes.Compare(self.pps, packet) != 0 { + if len(self.pps) == 0 { + self.pps = packet + } else if bytes.Compare(self.pps, packet) != 0 { self.ppsChanged = true self.pps = packet if self.client != nil && self.client.DebugRtp { @@ -636,13 +667,72 @@ func (self *Stream) handleH264Payload(naluType byte, timestamp uint32, packet [] } } - default: - if naluType == 5 { - self.pkt.IsKeyFrame = true + case naluType == 28: // FU-A + /* + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | FU indicator | FU header | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + | | + | FU payload | + | | + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Figure 14. RTP payload format for FU-A + + The FU indicator octet has the following format: + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + + + The FU header has the following format: + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |S|E|R| Type | + +---------------+ + + S: 1 bit + When set to one, the Start bit indicates the start of a fragmented + NAL unit. When the following FU payload is not the start of a + fragmented NAL unit payload, the Start bit is set to zero. + + E: 1 bit + When set to one, the End bit indicates the end of a fragmented NAL + unit, i.e., the last byte of the payload is also the last byte of + the fragmented NAL unit. When the following FU payload is not the + last fragment of a fragmented NAL unit, the End bit is set to + zero. + + R: 1 bit + The Reserved bit MUST be equal to 0 and MUST be ignored by the + receiver. + + Type: 5 bits + The NAL unit payload type as defined in table 7-1 of [1]. + */ + fuIndicator := packet[0] + fuHeader := packet[1] + isStart := fuHeader&0x80 != 0 + isEnd := fuHeader&0x40 != 0 + if isStart { + self.fuBuffer = []byte{fuIndicator&0xe0 | fuHeader&0x1f} } - self.gotpkt = true - self.pkt.Data = packet - self.timestamp = timestamp + self.fuBuffer = append(self.fuBuffer, packet[2:]...) + if isEnd { + if err = self.handleH264Payload(timestamp, self.fuBuffer); err != nil { + return + } + } + + case naluType == 24: + err = fmt.Errorf("rtsp: unsupported H264 STAP-A") + return } return @@ -656,107 +746,10 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { switch self.Type() { case av.H264: - /* - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |F|NRI| Type | - +---------------+ - */ - naluType := packet[0] & 0x1f - - /* - NAL Unit Packet Packet Type Name Section - Type Type - ------------------------------------------------------------- - 0 reserved - - 1-23 NAL unit Single NAL unit packet 5.6 - 24 STAP-A Single-time aggregation packet 5.7.1 - 25 STAP-B Single-time aggregation packet 5.7.1 - 26 MTAP16 Multi-time aggregation packet 5.7.2 - 27 MTAP24 Multi-time aggregation packet 5.7.2 - 28 FU-A Fragmentation unit 5.8 - 29 FU-B Fragmentation unit 5.8 - 30-31 reserved - - */ - - switch { - - case naluType >= 1 && naluType <= 23: - if err = self.handleH264Payload(naluType, timestamp, packet); err != nil { - return - } - - case naluType == 28: // FU-A - /* - 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 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | FU indicator | FU header | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | - | | - | FU payload | - | | - | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | :...OPTIONAL RTP padding | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - Figure 14. RTP payload format for FU-A - - The FU indicator octet has the following format: - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |F|NRI| Type | - +---------------+ - - - The FU header has the following format: - +---------------+ - |0|1|2|3|4|5|6|7| - +-+-+-+-+-+-+-+-+ - |S|E|R| Type | - +---------------+ - - S: 1 bit - When set to one, the Start bit indicates the start of a fragmented - NAL unit. When the following FU payload is not the start of a - fragmented NAL unit payload, the Start bit is set to zero. - - E: 1 bit - When set to one, the End bit indicates the end of a fragmented NAL - unit, i.e., the last byte of the payload is also the last byte of - the fragmented NAL unit. When the following FU payload is not the - last fragment of a fragmented NAL unit, the End bit is set to - zero. - - R: 1 bit - The Reserved bit MUST be equal to 0 and MUST be ignored by the - receiver. - - Type: 5 bits - The NAL unit payload type as defined in table 7-1 of [1]. - */ - fuIndicator := packet[0] - fuHeader := packet[1] - isStart := fuHeader&0x80 != 0 - isEnd := fuHeader&0x40 != 0 - naluType := fuHeader & 0x1f - if isStart { - self.fuBuffer = []byte{fuIndicator&0xe0 | fuHeader&0x1f} - } - self.fuBuffer = append(self.fuBuffer, packet[2:]...) - if isEnd { - if err = self.handleH264Payload(naluType, timestamp, self.fuBuffer); err != nil { - return - } - } - - case naluType == 24: - err = fmt.Errorf("rtsp: unsupported H264 STAP-A") - return - - default: - err = fmt.Errorf("rtsp: unsupported H264 naluType=%d", naluType) + if self.client != nil && self.client.DebugRtp { + fmt.Printf("rtsp: h264 data=%x\n", packet) + } + if err = self.handleH264Payload(timestamp, packet); err != nil { return } From 06147bce3590ee19965f80e76a6faeb03bc72656 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 10:30:15 +0800 Subject: [PATCH 39/61] remove log --- client.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/client.go b/client.go index 5755de9..5cfc049 100644 --- a/client.go +++ b/client.go @@ -746,9 +746,6 @@ func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { switch self.Type() { case av.H264: - if self.client != nil && self.client.DebugRtp { - fmt.Printf("rtsp: h264 data=%x\n", packet) - } if err = self.handleH264Payload(timestamp, packet); err != nil { return } From a2b07cc407b6b872e897703648db0048419f9d80 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 11:11:33 +0800 Subject: [PATCH 40/61] add TEARDOWN --- client.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 5cfc049..d40a464 100644 --- a/client.go +++ b/client.go @@ -622,7 +622,6 @@ func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err erro 29 FU-B Fragmentation unit 5.8 30-31 reserved - */ - switch { default: if naluType >= 1 && naluType <= 23 { @@ -876,6 +875,18 @@ func (self *Client) Play() (err error) { return } +func (self *Client) Teardown() (err error) { + req := Request{ + Method: "TEARDOWN", + Uri: self.requestUri, + } + req.Header = append(req.Header, "Session: "+self.session) + if err = self.WriteRequest(req); err != nil { + return + } + return +} + func (self *Client) poll() (err error) { for { var res Response From 74f68bcc5527ab2c112af685f874e3ec1e6949d6 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 12:28:39 +0800 Subject: [PATCH 41/61] add Close() --- client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client.go b/client.go index d40a464..ec1a345 100644 --- a/client.go +++ b/client.go @@ -887,6 +887,10 @@ func (self *Client) Teardown() (err error) { return } +func (self *Client) Close() (err error) { + return self.conn.Conn.Close() +} + func (self *Client) poll() (err error) { for { var res Response From 5dcc996ee4ec306ce093d1d049b02a9844a9c0ab Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 13:23:10 +0800 Subject: [PATCH 42/61] support STAP-A --- client.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index ec1a345..ce65885 100644 --- a/client.go +++ b/client.go @@ -729,8 +729,40 @@ func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err erro } } - case naluType == 24: - err = fmt.Errorf("rtsp: unsupported H264 STAP-A") + case naluType == 24: // STAP-A + /* + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RTP Header | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NALU 1 Data | + : : + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | NALU 2 Size | NALU 2 HDR | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NALU 2 Data | + : : + | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | :...OPTIONAL RTP padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 7. An example of an RTP packet including an STAP-A + containing two single-time aggregation units + */ + packet = packet[1:] + for len(packet) >= 2 { + size := int(packet[0])<<8|int(packet[1]) + if size == 0 || size+2 > len(packet) { + break + } + if err = self.handleH264Payload(timestamp, packet[2:size+2]); err != nil { + return + } + packet = packet[size+2:] + } return } From eb1fd52ef4f6cbea8836f78f27337ed877bf5b24 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 15 Jun 2016 13:26:34 +0800 Subject: [PATCH 43/61] STAP-A fix --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index ce65885..e63a81b 100644 --- a/client.go +++ b/client.go @@ -755,7 +755,7 @@ func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err erro packet = packet[1:] for len(packet) >= 2 { size := int(packet[0])<<8|int(packet[1]) - if size == 0 || size+2 > len(packet) { + if size+2 > len(packet) { break } if err = self.handleH264Payload(timestamp, packet[2:size+2]); err != nil { From b7dc09db2f9d59c1cddc6a6c1e03f434ae6eb40e Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 18 Jun 2016 10:17:29 +0800 Subject: [PATCH 44/61] switch to time.Duration --- client.go | 4 ++-- sdp/parser.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index e63a81b..93d5473 100644 --- a/client.go +++ b/client.go @@ -936,11 +936,11 @@ func (self *Client) poll() (err error) { } stream := self.streams[i] if stream.gotpkt { - time := float64(stream.timestamp) / float64(stream.Sdp.TimeScale) + tm := time.Duration(stream.timestamp)*time.Second / time.Duration(stream.Sdp.TimeScale) if false { fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, stream.Sdp.TimeScale, len(stream.pkt.Data)) } - self.pktque.WriteTimePacket(self.setupMap[i], time, stream.pkt) + self.pktque.WriteTimePacket(self.setupMap[i], tm, stream.pkt) stream.pkt = av.Packet{} stream.gotpkt = false return diff --git a/sdp/parser.go b/sdp/parser.go index 3dc587d..925d9eb 100644 --- a/sdp/parser.go +++ b/sdp/parser.go @@ -15,7 +15,7 @@ type Session struct { type Media struct { AVType string - Type int + Type av.CodecType TimeScale int Control string Rtpmap int From b3684fb382fe248964b9aa2c540a0828a14acee7 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 18 Jun 2016 11:15:20 +0800 Subject: [PATCH 45/61] fix g711 timestamp --- client.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 93d5473..ac0707c 100644 --- a/client.go +++ b/client.go @@ -936,9 +936,19 @@ func (self *Client) poll() (err error) { } stream := self.streams[i] if stream.gotpkt { - tm := time.Duration(stream.timestamp)*time.Second / time.Duration(stream.Sdp.TimeScale) + timeScale := stream.Sdp.TimeScale + + if timeScale == 0 { + /* + https://tools.ietf.org/html/rfc5391 + The RTP timestamp clock frequency is the same as the default sampling frequency: 16 kHz. + */ + timeScale = 16000 + } + + tm := time.Duration(stream.timestamp)*time.Second / time.Duration(timeScale) if false { - fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, stream.Sdp.TimeScale, len(stream.pkt.Data)) + fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, timeScale, len(stream.pkt.Data)) } self.pktque.WriteTimePacket(self.setupMap[i], tm, stream.pkt) stream.pkt = av.Packet{} From 9d464e5c67c78eaec81486d41a3311602a05fee7 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 18 Jun 2016 15:56:49 +0800 Subject: [PATCH 46/61] rename func --- client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index ac0707c..00a385a 100644 --- a/client.go +++ b/client.go @@ -582,7 +582,7 @@ func (self *Stream) makeCodecData() (err error) { return } -func (self *Stream) handleBuggyCameraHasAnnexbH264Packet(timestamp uint32, packet []byte) (isBuggy bool, err error) { +func (self *Stream) handleBuggyAnnexbH264Packet(timestamp uint32, packet []byte) (isBuggy bool, err error) { if len(packet) >= 4 && packet[0] == 0 && packet[1] == 0 && packet[2] == 0 && packet[3] == 1 { isBuggy = true if nalus, ok := h264parser.SplitNALUs(packet); ok { @@ -600,7 +600,7 @@ func (self *Stream) handleBuggyCameraHasAnnexbH264Packet(timestamp uint32, packe func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err error) { var isBuggy bool - if isBuggy, err = self.handleBuggyCameraHasAnnexbH264Packet(timestamp, packet); isBuggy { + if isBuggy, err = self.handleBuggyAnnexbH264Packet(timestamp, packet); isBuggy { return } From ded940976a98d4620de546294d4ce9ad5383a2f3 Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 21 Jun 2016 00:45:26 +0800 Subject: [PATCH 47/61] use new time corrector --- client.go | 63 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/client.go b/client.go index 00a385a..581ec23 100644 --- a/client.go +++ b/client.go @@ -9,7 +9,7 @@ import ( "encoding/hex" "fmt" "github.com/nareix/av" - "github.com/nareix/av/pktqueue" + "github.com/nareix/av/pktque" "github.com/nareix/codec" "github.com/nareix/codec/aacparser" "github.com/nareix/codec/h264parser" @@ -50,9 +50,10 @@ type Client struct { requestUri string cseq uint streams []*Stream + streamsintf []av.CodecData session string body io.Reader - pktque *pktqueue.Queue + corrector *pktque.TimeCorrector } type Request struct { @@ -108,9 +109,7 @@ func Dial(uri string) (self *Client, err error) { func (self *Client) Streams() (streams []av.CodecData, err error) { if self.setupCalled { - for _, i := range self.setupIdx { - streams = append(streams, self.streams[i].CodecData) - } + streams = self.streamsintf } else { err = fmt.Errorf("rtsp: no streams") return @@ -409,6 +408,7 @@ func (self *Client) Setup(idx []int) (err error) { return } } + self.initstructs() self.setupCalled = true return @@ -419,6 +419,14 @@ func md5hash(s string) string { return hex.EncodeToString(h[:]) } +func (self *Client) initstructs() { + self.streamsintf = make([]av.CodecData, len(self.setupIdx)) + for i := range self.setupIdx { + self.streamsintf[i] = self.streams[self.setupIdx[i]].CodecData + } + self.corrector = pktque.NewTimeCorrector(self.streamsintf) +} + func (self *Client) Describe() (streams []av.CodecData, err error) { var res Response @@ -449,9 +457,9 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { fmt.Println("<", body) } - self.streams = []*Stream{} _, medias := sdp.Parse(body) + self.streams = []*Stream{} for _, media := range medias { stream := &Stream{Sdp: media, client: self} if err = stream.makeCodecData(); err != nil { @@ -459,14 +467,9 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { } self.streams = append(self.streams, stream) } - for _, stream := range self.streams { streams = append(streams, stream) } - self.pktque = &pktqueue.Queue{ - Poll: self.poll, - } - self.pktque.Alloc(streams) return } @@ -506,6 +509,7 @@ func (self *Client) HandleCodecDataChange() (_newcli *Client, err error) { } newcli.streams = append(newcli.streams, newstream) } + newcli.initstructs() _newcli = newcli return @@ -923,7 +927,18 @@ func (self *Client) Close() (err error) { return self.conn.Conn.Close() } -func (self *Client) poll() (err error) { +func (self *Client) ReadPacket() (pkt av.Packet, err error) { + if !self.setupCalled { + if err = self.setupAll(); err != nil { + return + } + } + if !self.playCalled { + if err = self.Play(); err != nil { + return + } + } + for { var res Response if res, err = self.ReadResponse(); err != nil { @@ -946,32 +961,22 @@ func (self *Client) poll() (err error) { timeScale = 16000 } - tm := time.Duration(stream.timestamp)*time.Second / time.Duration(timeScale) + pkt = stream.pkt + pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(timeScale) if false { - fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, timeScale, len(stream.pkt.Data)) + fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, timeScale, len(pkt.Data)) } - self.pktque.WriteTimePacket(self.setupMap[i], tm, stream.pkt) + pkt.Idx = int8(self.setupMap[i]) + self.corrector.Correct(&pkt) + stream.pkt = av.Packet{} stream.gotpkt = false return } } } - return -} -func (self *Client) ReadPacket() (i int, pkt av.Packet, err error) { - if !self.setupCalled { - if err = self.setupAll(); err != nil { - return - } - } - if !self.playCalled { - if err = self.Play(); err != nil { - return - } - } - return self.pktque.ReadPacket() + return } func (self *Client) ReadHeader() (err error) { From d62f2cef96d9ab858ba61fc79309e2162ecf0729 Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 21 Jun 2016 15:55:20 +0800 Subject: [PATCH 48/61] add firsttimestamp --- client.go | 29 ++++++++++++++++++++++------- stream.go | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index 581ec23..12a98ea 100644 --- a/client.go +++ b/client.go @@ -217,8 +217,9 @@ func (self *Client) ReadResponse() (res Response, err error) { res.BlockLength = int(h[2])<<8 + int(h[3]) res.BlockNo = int(h[1]) if self.DebugRtp { - fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) + fmt.Println("rtp: block: len", res.BlockLength, "no", res.BlockNo) } + // TODO: if invalid need relocate also return } else if h[0] == 82 && h[1] == 84 && h[2] == 83 && h[3] == 80 { // RTSP 200 OK @@ -228,7 +229,7 @@ func (self *Client) ReadResponse() (res Response, err error) { for { if self.DebugRtp { - fmt.Println("block: relocate try") + fmt.Println("rtp: block: relocate try") } for { @@ -252,8 +253,8 @@ func (self *Client) ReadResponse() (res Response, err error) { } if self.DebugRtp { - fmt.Println("block: relocate done") - fmt.Println("block: len", res.BlockLength, "no", res.BlockNo) + fmt.Println("rtp: block: relocate done") + fmt.Println("rtp: block: len", res.BlockLength, "no", res.BlockNo) } return } @@ -953,6 +954,16 @@ func (self *Client) ReadPacket() (pkt av.Packet, err error) { if stream.gotpkt { timeScale := stream.Sdp.TimeScale + /* + TODO: https://tools.ietf.org/html/rfc3550 + A receiver can then synchronize presentation of the audio and video packets by relating + their RTP timestamps using the timestamp pairs in RTCP SR packets. + */ + if stream.firsttimestamp == 0 { + stream.firsttimestamp = stream.timestamp + } + stream.timestamp -= stream.firsttimestamp + if timeScale == 0 { /* https://tools.ietf.org/html/rfc5391 @@ -963,11 +974,15 @@ func (self *Client) ReadPacket() (pkt av.Packet, err error) { pkt = stream.pkt pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(timeScale) - if false { - fmt.Printf("rtsp: #%d %d/%d %d\n", i, stream.timestamp, timeScale, len(pkt.Data)) - } pkt.Idx = int8(self.setupMap[i]) + + if self.DebugRtp { + fmt.Println("rtp: pktin", pkt.Idx, pkt.Time, len(pkt.Data)) + } self.corrector.Correct(&pkt) + if self.DebugRtp { + fmt.Println("rtp: pktout", pkt.Idx, pkt.Time, len(pkt.Data)) + } stream.pkt = av.Packet{} stream.gotpkt = false diff --git a/stream.go b/stream.go index 6537dc0..5715a24 100644 --- a/stream.go +++ b/stream.go @@ -20,6 +20,7 @@ type Stream struct { gotpkt bool pkt av.Packet timestamp uint32 + firsttimestamp uint32 } func (self Stream) IsAudio() bool { From 6ba0534fb9893b035d47c0cab102a612671ec0fa Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 21 Jun 2016 16:59:43 +0800 Subject: [PATCH 49/61] rewrite ugly ReadResponse --- client.go | 269 ++++++++++++++++++++++++++++-------------------------- stream.go | 7 -- 2 files changed, 140 insertions(+), 136 deletions(-) diff --git a/client.go b/client.go index 12a98ea..0f804e6 100644 --- a/client.go +++ b/client.go @@ -9,7 +9,6 @@ import ( "encoding/hex" "fmt" "github.com/nareix/av" - "github.com/nareix/av/pktque" "github.com/nareix/codec" "github.com/nareix/codec/aacparser" "github.com/nareix/codec/h264parser" @@ -53,7 +52,6 @@ type Client struct { streamsintf []av.CodecData session string body io.Reader - corrector *pktque.TimeCorrector } type Request struct { @@ -177,89 +175,142 @@ func (self *Client) WriteRequest(req Request) (err error) { return } -func (self *Client) ReadResponse() (res Response, err error) { - var br *bufio.Reader - - defer func() { - if br != nil { - buf, _ := br.Peek(br.Buffered()) - self.rconn = io.MultiReader(bytes.NewReader(buf), self.rconn) - } - if res.StatusCode == 200 { - self.conn.Timeout = self.RtspTimeout - - if res.ContentLength > 0 { - res.Body = make([]byte, res.ContentLength) - if _, err = io.ReadFull(self.rconn, res.Body); err != nil { - return - } - } - } else if res.BlockLength > 0 { - self.conn.Timeout = self.RtpTimeout - res.Block = make([]byte, res.BlockLength) - if _, err = io.ReadFull(self.rconn, res.Block); err != nil { - return - } - if err = self.SendRtpKeepalive(); err != nil { - return - } - } - }() - - self.conn.Timeout = self.RtspTimeout - var h [4]byte - if _, err = io.ReadFull(self.rconn, h[:]); err != nil { +func (self *Client) probeBlockHeader(h []byte) (length int, no int, valid bool) { + length = int(h[2])<<8 + int(h[3]) + no = int(h[1]) + if no/2 >= len(self.streams) { return } - - if h[0] == 36 { - // $ - res.BlockLength = int(h[2])<<8 + int(h[3]) - res.BlockNo = int(h[1]) - if self.DebugRtp { - fmt.Println("rtp: block: len", res.BlockLength, "no", res.BlockNo) - } - // TODO: if invalid need relocate also + if no%2 != 0 { return - } else if h[0] == 82 && h[1] == 84 && h[2] == 83 && h[3] == 80 { - // RTSP 200 OK - self.rconn = io.MultiReader(bytes.NewReader(h[:]), self.rconn) - } else { - self.conn.Timeout = self.RtpTimeout + } + if length < 8 { + return + } + stream := self.streams[no/2] + if int(h[5]&0x7f) != stream.Sdp.PayloadType { + return + } + valid = true + return +} + +func (self *Client) readBlock(h []byte) (res Response, err error) { + self.conn.Timeout = self.RtpTimeout + + for { + var valid bool + if res.BlockLength, res.BlockNo, valid = self.probeBlockHeader(h); valid { + break + } + if self.DebugRtp { + fmt.Println("rtp: block: relocate try") + } for { - if self.DebugRtp { - fmt.Println("rtp: block: relocate try") - } - - for { - var b [1]byte - if _, err = self.rconn.Read(b[:]); err != nil { - return - } - if b[0] == 36 { - break - } - } - if _, err = io.ReadFull(self.rconn, h[1:4]); err != nil { + if _, err = self.rconn.Read(h[:1]); err != nil { return } - - res.BlockLength = int(h[2])<<8 + int(h[3]) - res.BlockNo = int(h[1]) - if res.BlockNo/2 < len(self.streams) { + if h[0] == 36 { break } } - if self.DebugRtp { - fmt.Println("rtp: block: relocate done") - fmt.Println("rtp: block: len", res.BlockLength, "no", res.BlockNo) + if _, err = io.ReadFull(self.rconn, h[1:]); err != nil { + return } + } + + if self.DebugRtp { + fmt.Println("rtp: block: len", res.BlockLength, "no", res.BlockNo) + } + + res.Block = make([]byte, res.BlockLength) + copy(res.Block[:len(h)-4], h[4:]) + if _, err = io.ReadFull(self.rconn, res.Block[len(h)-4:]); err != nil { return } - br = bufio.NewReader(self.rconn) + return +} + +func (self *Client) handle401(header textproto.MIMEHeader) (err error) { + /* + RTSP/1.0 401 Unauthorized + CSeq: 2 + Date: Wed, May 04 2016 10:10:51 GMT + WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="c633aaf8b83127633cbe98fac1d20d87" + */ + authval := header.Get("WWW-Authenticate") + hdrval := strings.SplitN(authval, " ", 2) + var realm, nonce string + + if len(hdrval) == 2 { + for _, field := range strings.Split(hdrval[1], ",") { + field = strings.Trim(field, ", ") + if keyval := strings.Split(field, "="); len(keyval) == 2 { + key := keyval[0] + val := strings.Trim(keyval[1], `"`) + switch key { + case "realm": + realm = val + case "nonce": + nonce = val + } + } + } + + if realm != "" { + var username string + var password string + + if self.url.User == nil { + err = fmt.Errorf("rtsp: no username") + return + } + username = self.url.User.Username() + password, _ = self.url.User.Password() + + self.authHeaders = func(method string) []string { + headers := []string{ + fmt.Sprintf(`Authorization: Basic %s`, base64.StdEncoding.EncodeToString([]byte(username+":"+password))), + } + if nonce != "" { + hs1 := md5hash(username + ":" + realm + ":" + password) + hs2 := md5hash(method + ":" + self.requestUri) + response := md5hash(hs1 + ":" + nonce + ":" + hs2) + headers = append(headers, fmt.Sprintf( + `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, + username, realm, nonce, self.requestUri, response)) + } + return headers + } + } + } + + return +} + +func (self *Client) ReadResponse() (res Response, err error) { + if err = self.SendRtpKeepalive(); err != nil { + return + } + + self.conn.Timeout = self.RtspTimeout + + h := make([]byte, 16) + if _, err = io.ReadFull(self.rconn, h); err != nil { + return + } + + if h[0] == 82 && h[1] == 84 && h[2] == 83 && h[3] == 80 { + // RTSP 200 OK + self.rconn = io.MultiReader(bytes.NewReader(h), self.rconn) + } else { + return self.readBlock(h) + } + + br := bufio.NewReader(self.rconn) tp := textproto.NewReader(br) var line string @@ -291,73 +342,35 @@ func (self *Client) ReadResponse() (res Response, err error) { fmt.Println() } - if res.StatusCode != 200 && res.StatusCode != 401 { + switch res.StatusCode { + case 401: + if err = self.handle401(header); err != nil { + return + } + + case 200: + + default: err = fmt.Errorf("rtsp: StatusCode=%d invalid", res.StatusCode) return } - if res.StatusCode == 401 { - /* - RTSP/1.0 401 Unauthorized - CSeq: 2 - Date: Wed, May 04 2016 10:10:51 GMT - WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="c633aaf8b83127633cbe98fac1d20d87" - */ - authval := header.Get("WWW-Authenticate") - hdrval := strings.SplitN(authval, " ", 2) - var realm, nonce string - - if len(hdrval) == 2 { - for _, field := range strings.Split(hdrval[1], ",") { - field = strings.Trim(field, ", ") - if keyval := strings.Split(field, "="); len(keyval) == 2 { - key := keyval[0] - val := strings.Trim(keyval[1], `"`) - switch key { - case "realm": - realm = val - case "nonce": - nonce = val - } - } - } - - if realm != "" { - var username string - var password string - - if self.url.User == nil { - err = fmt.Errorf("rtsp: no username") - return - } - username = self.url.User.Username() - password, _ = self.url.User.Password() - - self.authHeaders = func(method string) []string { - headers := []string{ - fmt.Sprintf(`Authorization: Basic %s`, base64.StdEncoding.EncodeToString([]byte(username+":"+password))), - } - if nonce != "" { - hs1 := md5hash(username + ":" + realm + ":" + password) - hs2 := md5hash(method + ":" + self.requestUri) - response := md5hash(hs1 + ":" + nonce + ":" + hs2) - headers = append(headers, fmt.Sprintf( - `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`, - username, realm, nonce, self.requestUri, response)) - } - return headers - } - } - } - } - + res.ContentLength, _ = strconv.Atoi(header.Get("Content-Length")) if sess := header.Get("Session"); sess != "" && self.session == "" { if fields := strings.Split(sess, ";"); len(fields) > 0 { self.session = fields[0] } } - res.ContentLength, _ = strconv.Atoi(header.Get("Content-Length")) + buf, _ := br.Peek(br.Buffered()) + self.rconn = io.MultiReader(bytes.NewReader(buf), self.rconn) + + if res.ContentLength > 0 { + res.Body = make([]byte, res.ContentLength) + if _, err = io.ReadFull(self.rconn, res.Body); err != nil { + return + } + } return } @@ -425,7 +438,6 @@ func (self *Client) initstructs() { for i := range self.setupIdx { self.streamsintf[i] = self.streams[self.setupIdx[i]].CodecData } - self.corrector = pktque.NewTimeCorrector(self.streamsintf) } func (self *Client) Describe() (streams []av.CodecData, err error) { @@ -979,7 +991,6 @@ func (self *Client) ReadPacket() (pkt av.Packet, err error) { if self.DebugRtp { fmt.Println("rtp: pktin", pkt.Idx, pkt.Time, len(pkt.Data)) } - self.corrector.Correct(&pkt) if self.DebugRtp { fmt.Println("rtp: pktout", pkt.Idx, pkt.Time, len(pkt.Data)) } diff --git a/stream.go b/stream.go index 5715a24..44f8cc6 100644 --- a/stream.go +++ b/stream.go @@ -23,10 +23,3 @@ type Stream struct { firsttimestamp uint32 } -func (self Stream) IsAudio() bool { - return self.Sdp.AVType == "audio" -} - -func (self Stream) IsVideo() bool { - return self.Sdp.AVType == "video" -} From bced7bd91538a56a8fdf729952b1b2a74c9e068a Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 22 Jun 2016 12:56:28 +0800 Subject: [PATCH 50/61] change block search mechanism again, improve relocate --- client.go | 505 +++++++++++++++++++++++++++++++++--------------------- stream.go | 3 + 2 files changed, 312 insertions(+), 196 deletions(-) diff --git a/client.go b/client.go index 0f804e6..bc4a3aa 100644 --- a/client.go +++ b/client.go @@ -45,7 +45,7 @@ type Client struct { url *url.URL conn *connWithTimeout - rconn io.Reader + brconn *bufio.Reader requestUri string cseq uint streams []*Stream @@ -61,14 +61,12 @@ type Request struct { } type Response struct { - BlockLength int - Block []byte - BlockNo int - StatusCode int - Header textproto.MIMEHeader + Headers textproto.MIMEHeader ContentLength int Body []byte + + Block []byte } func DialTimeout(uri string, timeout time.Duration) (self *Client, err error) { @@ -94,7 +92,7 @@ func DialTimeout(uri string, timeout time.Duration) (self *Client, err error) { self = &Client{ conn: connt, - rconn: connt, + brconn: bufio.NewReaderSize(connt, 256), url: URL, requestUri: u2.String(), } @@ -116,11 +114,7 @@ func (self *Client) Streams() (streams []av.CodecData, err error) { } func (self *Client) SendRtpKeepalive() (err error) { - if self.RtpKeepAliveTimeout > 0 && self.rtpKeepaliveEnterCnt == 0 { - self.rtpKeepaliveEnterCnt++ - defer func() { - self.rtpKeepaliveEnterCnt-- - }() + if self.RtpKeepAliveTimeout > 0 { if self.rtpKeepaliveTimer.IsZero() { self.rtpKeepaliveTimer = time.Now() } else if time.Now().Sub(self.rtpKeepaliveTimer) > self.RtpKeepAliveTimeout { @@ -128,7 +122,11 @@ func (self *Client) SendRtpKeepalive() (err error) { if self.DebugRtsp { fmt.Println("rtp: keep alive") } - if err = self.Options(); err != nil { + req := Request{ + Method: "OPTIONS", + Uri: self.requestUri, + } + if err = self.WriteRequest(req); err != nil { return } } @@ -175,15 +173,12 @@ func (self *Client) WriteRequest(req Request) (err error) { return } -func (self *Client) probeBlockHeader(h []byte) (length int, no int, valid bool) { +func (self *Client) parseBlockHeader(h []byte) (length int, no int, valid bool) { length = int(h[2])<<8 + int(h[3]) no = int(h[1]) if no/2 >= len(self.streams) { return } - if no%2 != 0 { - return - } if length < 8 { return } @@ -195,53 +190,50 @@ func (self *Client) probeBlockHeader(h []byte) (length int, no int, valid bool) return } -func (self *Client) readBlock(h []byte) (res Response, err error) { - self.conn.Timeout = self.RtpTimeout +func (self *Client) parseHeaders(b []byte) (statusCode int, headers textproto.MIMEHeader, err error) { + var line string + r := textproto.NewReader(bufio.NewReader(bytes.NewReader(b))) + if line, err = r.ReadLine(); err != nil { + err = fmt.Errorf("rtsp: header invalid") + return + } - for { - var valid bool - if res.BlockLength, res.BlockNo, valid = self.probeBlockHeader(h); valid { - break - } - if self.DebugRtp { - fmt.Println("rtp: block: relocate try") - } - - for { - if _, err = self.rconn.Read(h[:1]); err != nil { - return - } - if h[0] == 36 { - break - } - } - - if _, err = io.ReadFull(self.rconn, h[1:]); err != nil { + if codes := strings.Split(line, " "); len(codes) >= 2 { + if statusCode, err = strconv.Atoi(codes[1]); err != nil { + err = fmt.Errorf("rtsp: header invalid: %s", err) return } } - if self.DebugRtp { - fmt.Println("rtp: block: len", res.BlockLength, "no", res.BlockNo) - } - - res.Block = make([]byte, res.BlockLength) - copy(res.Block[:len(h)-4], h[4:]) - if _, err = io.ReadFull(self.rconn, res.Block[len(h)-4:]); err != nil { + if headers, err = r.ReadMIMEHeader(); err != nil { return } return } -func (self *Client) handle401(header textproto.MIMEHeader) (err error) { +func (self *Client) handleResp(res *Response) (err error) { + if sess := res.Headers.Get("Session"); sess != "" && self.session == "" { + if fields := strings.Split(sess, ";"); len(fields) > 0 { + self.session = fields[0] + } + } + if res.StatusCode == 401 { + if err = self.handle401(res); err != nil { + return + } + } + return +} + +func (self *Client) handle401(res *Response) (err error) { /* RTSP/1.0 401 Unauthorized CSeq: 2 Date: Wed, May 04 2016 10:10:51 GMT WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="c633aaf8b83127633cbe98fac1d20d87" */ - authval := header.Get("WWW-Authenticate") + authval := res.Headers.Get("WWW-Authenticate") hdrval := strings.SplitN(authval, " ", 2) var realm, nonce string @@ -291,87 +283,187 @@ func (self *Client) handle401(header textproto.MIMEHeader) (err error) { return } -func (self *Client) ReadResponse() (res Response, err error) { - if err = self.SendRtpKeepalive(); err != nil { - return - } +func (self *Client) findRTSP() (block []byte, data []byte, err error) { + const ( + R = iota+1 + T + S + Header + Dollar + ) + var _peek [8]byte + peek := _peek[0:0] + stat := 0 - self.conn.Timeout = self.RtspTimeout - - h := make([]byte, 16) - if _, err = io.ReadFull(self.rconn, h); err != nil { - return - } - - if h[0] == 82 && h[1] == 84 && h[2] == 83 && h[3] == 80 { - // RTSP 200 OK - self.rconn = io.MultiReader(bytes.NewReader(h), self.rconn) - } else { - return self.readBlock(h) - } - - br := bufio.NewReader(self.rconn) - tp := textproto.NewReader(br) - - var line string - if line, err = tp.ReadLine(); err != nil { - return - } - if self.DebugRtsp { - fmt.Println("<", line) - } - - fline := strings.SplitN(line, " ", 3) - if len(fline) < 2 { - err = fmt.Errorf("rtsp: malformed response line") - return - } - - if res.StatusCode, err = strconv.Atoi(fline[1]); err != nil { - return - } - var header textproto.MIMEHeader - if header, err = tp.ReadMIMEHeader(); err != nil { - return - } - - if self.DebugRtsp { - for k, s := range header { - fmt.Println(k, s) + for { + var b byte + if b, err = self.brconn.ReadByte(); err != nil { + return + } + switch b { + case 'R': + if stat == 0 { + stat = R + } + case 'T': + if stat == R { + stat = T + } + case 'S': + if stat == T { + stat = S + } + case 'P': + if stat == S { + stat = Header + } + case '$': + stat = Dollar + peek = _peek[0:0] + default: + if stat != Dollar { + stat = 0 + peek = _peek[0:0] + } } - fmt.Println() - } - switch res.StatusCode { - case 401: - if err = self.handle401(header); err != nil { + if stat != 0 { + peek = append(peek, b) + } + if stat == Header { + data = peek return } - case 200: - - default: - err = fmt.Errorf("rtsp: StatusCode=%d invalid", res.StatusCode) - return - } - - res.ContentLength, _ = strconv.Atoi(header.Get("Content-Length")) - if sess := header.Get("Session"); sess != "" && self.session == "" { - if fields := strings.Split(sess, ";"); len(fields) > 0 { - self.session = fields[0] + if stat == Dollar && len(peek) >= 8 { + if blocklen, _, ok := self.parseBlockHeader(peek); ok { + left := blocklen+4-len(peek) + block = append(peek, make([]byte, left)...) + if _, err = io.ReadFull(self.brconn, block[len(peek):]); err != nil { + return + } + return + } + stat = 0 + peek = _peek[0:0] } } - buf, _ := br.Peek(br.Buffered()) - self.rconn = io.MultiReader(bytes.NewReader(buf), self.rconn) + return +} +func (self *Client) readLFLF() (block []byte, data []byte, err error) { + const ( + LF = iota+1 + LFLF + ) + peek := []byte{} + stat := 0 + dollarpos := -1 + lpos := 0 + pos := 0 + + for { + var b byte + if b, err = self.brconn.ReadByte(); err != nil { + return + } + switch b { + case '\n': + if stat == 0 { + stat = LF + lpos = pos + } else if stat == LF { + if pos - lpos <= 2 { + stat = LFLF + } else { + lpos = pos + } + } + case '$': + dollarpos = pos + } + peek = append(peek, b) + + if stat == LFLF { + data = peek + return + } else if dollarpos != -1 && dollarpos - pos >= 8 { + hdrlen := dollarpos-pos + start := len(peek)-hdrlen + if blocklen, _, ok := self.parseBlockHeader(peek[start:]); ok { + block = append(peek[start:], make([]byte, blocklen+4-hdrlen)...) + if _, err = io.ReadFull(self.brconn, block[hdrlen:]); err != nil { + return + } + return + } + dollarpos = -1 + } + + pos++ + } + + return +} + +func (self *Client) readResp(b []byte) (res Response, err error) { + if res.StatusCode, res.Headers, err = self.parseHeaders(b); err != nil { + return + } + res.ContentLength, _ = strconv.Atoi(res.Headers.Get("Content-Length")) if res.ContentLength > 0 { res.Body = make([]byte, res.ContentLength) - if _, err = io.ReadFull(self.rconn, res.Body); err != nil { + if _, err = io.ReadFull(self.brconn, res.Body); err != nil { return } } + if err = self.handleResp(&res); err != nil { + return + } + return +} +func (self *Client) poll() (res Response, err error) { + var block []byte + var rtsp []byte + var headers []byte + + self.conn.Timeout = self.RtspTimeout + for { + if block, rtsp, err = self.findRTSP(); err != nil { + return + } + if len(block) > 0 { + res.Block = block + return + } else { + if block, headers, err = self.readLFLF(); err != nil { + return + } + if len(block) > 0 { + res.Block = block + return + } + if res, err = self.readResp(append(rtsp, headers...)); err != nil { + return + } + } + return + } + + return +} + +func (self *Client) ReadResponse() (res Response, err error) { + for { + if res, err = self.poll(); err != nil { + return + } + if res.StatusCode > 0 { + return + } + } return } @@ -616,6 +708,11 @@ func (self *Stream) handleBuggyAnnexbH264Packet(timestamp uint32, packet []byte) } func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err error) { + if len(packet) < 2 { + err = fmt.Errorf("rtp: h264 packet too short") + return + } + var isBuggy bool if isBuggy, err = self.handleBuggyAnnexbH264Packet(timestamp, packet); isBuggy { return @@ -786,45 +883,21 @@ func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err erro return } -func (self *Stream) handlePacket(timestamp uint32, packet []byte) (err error) { +func (self *Stream) handleRtpPacket(packet []byte) (err error) { if self.isCodecDataChange() { err = ErrCodecDataChange return } - switch self.Type() { - case av.H264: - if err = self.handleH264Payload(timestamp, packet); err != nil { - return + if self.client != nil && self.client.DebugRtp { + fmt.Println("rtp: packet len", len(packet)) + dumpsize := len(packet) + if dumpsize > 32 { + dumpsize = 32 } - - case av.AAC: - self.gotpkt = true - self.pkt.Data = packet[4:] - self.timestamp = timestamp - - default: - self.gotpkt = true - self.pkt.Data = packet - self.timestamp = timestamp + fmt.Print(hex.Dump(packet[:dumpsize])) } - return -} - -func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err error) { - if blockNo%2 != 0 { - // rtcp block - return - } - - streamIndex = blockNo / 2 - if streamIndex >= len(self.streams) { - err = fmt.Errorf("rtsp: parseBlock: streamIndex=%d invalid", streamIndex) - return - } - stream := self.streams[streamIndex] - /* 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 @@ -839,17 +912,15 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ - if len(packet) < 8 { err = fmt.Errorf("rtp: packet too short") return } payloadOffset := 12 + int(packet[0]&0xf)*4 - if payloadOffset+2 > len(packet) { + if payloadOffset > len(packet) { err = fmt.Errorf("rtp: packet too short") return } - timestamp := binary.BigEndian.Uint32(packet[4:8]) payload := packet[payloadOffset:] @@ -897,15 +968,21 @@ func (self *Client) parseBlock(blockNo int, packet []byte) (streamIndex int, err */ //payloadType := packet[1]&0x7f - if self.DebugRtp { - //fmt.Println("packet:", stream.Type(), "offset", payloadOffset, "pt", payloadType) - if len(packet) > 24 { - fmt.Println(hex.Dump(packet[:24])) + switch self.Type() { + case av.H264: + if err = self.handleH264Payload(timestamp, payload); err != nil { + return } - } - if err = stream.handlePacket(timestamp, payload); err != nil { - return + case av.AAC: + self.gotpkt = true + self.pkt.Data = payload[4:] // TODO: remove this hack + self.timestamp = timestamp + + default: + self.gotpkt = true + self.pkt.Data = packet + self.timestamp = timestamp } return @@ -940,6 +1017,68 @@ func (self *Client) Close() (err error) { return self.conn.Conn.Close() } +func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error) { + _, blockno, _ := self.parseBlockHeader(block) + if blockno%2 != 0 { + return + } + + i := blockno/2 + if i >= len(self.streams) { + err = fmt.Errorf("rtsp: block no=%d invalid", blockno) + return + } + stream := self.streams[i] + + if err = stream.handleRtpPacket(block[4:]); err != nil { + return + } + + if stream.gotpkt { + timeScale := stream.Sdp.TimeScale + + /* + TODO: https://tools.ietf.org/html/rfc3550 + A receiver can then synchronize presentation of the audio and video packets by relating + their RTP timestamps using the timestamp pairs in RTCP SR packets. + */ + if stream.firsttimestamp == 0 { + stream.firsttimestamp = stream.timestamp + } + stream.timestamp -= stream.firsttimestamp + + if timeScale == 0 { + /* + https://tools.ietf.org/html/rfc5391 + The RTP timestamp clock frequency is the same as the default sampling frequency: 16 kHz. + */ + timeScale = 16000 + } + + ok = true + pkt = stream.pkt + pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(timeScale) + pkt.Idx = int8(self.setupMap[i]) + + if pkt.Time < stream.lasttime { + err = fmt.Errorf("rtp: stream#%d time=%v < lasttime=%v", pkt.Time, stream.lasttime) + } + stream.lasttime = pkt.Time + + if self.DebugRtp { + fmt.Println("rtp: pktin", pkt.Idx, pkt.Time, len(pkt.Data)) + } + if self.DebugRtp { + fmt.Println("rtp: pktout", pkt.Idx, pkt.Time, len(pkt.Data)) + } + + stream.pkt = av.Packet{} + stream.gotpkt = false + } + + return +} + func (self *Client) ReadPacket() (pkt av.Packet, err error) { if !self.setupCalled { if err = self.setupAll(); err != nil { @@ -952,53 +1091,27 @@ func (self *Client) ReadPacket() (pkt av.Packet, err error) { } } + if err = self.SendRtpKeepalive(); err != nil { + return + } + for { var res Response - if res, err = self.ReadResponse(); err != nil { + for { + if res, err = self.poll(); err != nil { + return + } + if len(res.Block) > 0 { + break + } + } + + var ok bool + if pkt, ok, err = self.handleBlock(res.Block); err != nil { return } - if res.BlockLength > 0 { - var i int - if i, err = self.parseBlock(res.BlockNo, res.Block); err != nil { - return - } - stream := self.streams[i] - if stream.gotpkt { - timeScale := stream.Sdp.TimeScale - - /* - TODO: https://tools.ietf.org/html/rfc3550 - A receiver can then synchronize presentation of the audio and video packets by relating - their RTP timestamps using the timestamp pairs in RTCP SR packets. - */ - if stream.firsttimestamp == 0 { - stream.firsttimestamp = stream.timestamp - } - stream.timestamp -= stream.firsttimestamp - - if timeScale == 0 { - /* - https://tools.ietf.org/html/rfc5391 - The RTP timestamp clock frequency is the same as the default sampling frequency: 16 kHz. - */ - timeScale = 16000 - } - - pkt = stream.pkt - pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(timeScale) - pkt.Idx = int8(self.setupMap[i]) - - if self.DebugRtp { - fmt.Println("rtp: pktin", pkt.Idx, pkt.Time, len(pkt.Data)) - } - if self.DebugRtp { - fmt.Println("rtp: pktout", pkt.Idx, pkt.Time, len(pkt.Data)) - } - - stream.pkt = av.Packet{} - stream.gotpkt = false - return - } + if ok { + return } } diff --git a/stream.go b/stream.go index 44f8cc6..4f43d7a 100644 --- a/stream.go +++ b/stream.go @@ -3,6 +3,7 @@ package rtsp import ( "github.com/nareix/av" "github.com/nareix/rtsp/sdp" + "time" ) type Stream struct { @@ -21,5 +22,7 @@ type Stream struct { pkt av.Packet timestamp uint32 firsttimestamp uint32 + + lasttime time.Duration } From 0f5d9834623d07f5b811552d5320546a3061df46 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 22 Jun 2016 14:07:08 +0800 Subject: [PATCH 51/61] add comments --- client.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index bc4a3aa..38e9ea8 100644 --- a/client.go +++ b/client.go @@ -975,6 +975,10 @@ func (self *Stream) handleRtpPacket(packet []byte) (err error) { } case av.AAC: + if len(payload) < 4 { + err = fmt.Errorf("rtp: aac packet too short") + return + } self.gotpkt = true self.pkt.Data = payload[4:] // TODO: remove this hack self.timestamp = timestamp @@ -1038,7 +1042,8 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error timeScale := stream.Sdp.TimeScale /* - TODO: https://tools.ietf.org/html/rfc3550 + TODO: sync AV by rtcp NTP timestamp + https://tools.ietf.org/html/rfc3550 A receiver can then synchronize presentation of the audio and video packets by relating their RTP timestamps using the timestamp pairs in RTCP SR packets. */ From 4d441f030ba3270123a56cbb9358495232039db5 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 22 Jun 2016 20:51:57 +0800 Subject: [PATCH 52/61] add fuStarted flag --- client.go | 17 +++++++++-------- stream.go | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index 38e9ea8..edaef4b 100644 --- a/client.go +++ b/client.go @@ -205,7 +205,7 @@ func (self *Client) parseHeaders(b []byte) (statusCode int, headers textproto.MI } } - if headers, err = r.ReadMIMEHeader(); err != nil { + if headers, _ = r.ReadMIMEHeader(); err != nil { return } @@ -834,12 +834,16 @@ func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err erro isStart := fuHeader&0x80 != 0 isEnd := fuHeader&0x40 != 0 if isStart { + self.fuStarted = true self.fuBuffer = []byte{fuIndicator&0xe0 | fuHeader&0x1f} } - self.fuBuffer = append(self.fuBuffer, packet[2:]...) - if isEnd { - if err = self.handleH264Payload(timestamp, self.fuBuffer); err != nil { - return + if self.fuStarted { + self.fuBuffer = append(self.fuBuffer, packet[2:]...) + if isEnd { + self.fuStarted = false + if err = self.handleH264Payload(timestamp, self.fuBuffer); err != nil { + return + } } } @@ -1070,9 +1074,6 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error } stream.lasttime = pkt.Time - if self.DebugRtp { - fmt.Println("rtp: pktin", pkt.Idx, pkt.Time, len(pkt.Data)) - } if self.DebugRtp { fmt.Println("rtp: pktout", pkt.Idx, pkt.Time, len(pkt.Data)) } diff --git a/stream.go b/stream.go index 4f43d7a..b4d3e31 100644 --- a/stream.go +++ b/stream.go @@ -12,6 +12,7 @@ type Stream struct { client *Client // h264 + fuStarted bool fuBuffer []byte sps []byte pps []byte From 06583889af947d1f4669a4ca39633b4daad86b5b Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 22 Jun 2016 22:27:04 +0800 Subject: [PATCH 53/61] bugfix handleRtpPacket change packet to payload solve PCMU noise problem --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index edaef4b..5f39b05 100644 --- a/client.go +++ b/client.go @@ -989,7 +989,7 @@ func (self *Stream) handleRtpPacket(packet []byte) (err error) { default: self.gotpkt = true - self.pkt.Data = packet + self.pkt.Data = payload self.timestamp = timestamp } From a2be4cf878b1aae388460403846947b8c09c67a6 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 23 Jun 2016 07:08:26 +0800 Subject: [PATCH 54/61] fix default timescale --- client.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index 5f39b05..d09f62b 100644 --- a/client.go +++ b/client.go @@ -1057,11 +1057,8 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error stream.timestamp -= stream.firsttimestamp if timeScale == 0 { - /* - https://tools.ietf.org/html/rfc5391 - The RTP timestamp clock frequency is the same as the default sampling frequency: 16 kHz. - */ - timeScale = 16000 + // https://tools.ietf.org/html/rfc5391 + timeScale = 8000 } ok = true From c7d51e38e6eedbc2b466d15b4bea96be7833fd82 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 23 Jun 2016 08:20:04 +0800 Subject: [PATCH 55/61] add SkipErrRtpBlock, improve parseBlockHeader --- client.go | 77 ++++++++++++++++++++++++++++++++++++++----------------- stream.go | 2 ++ 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/client.go b/client.go index d09f62b..ea7323f 100644 --- a/client.go +++ b/client.go @@ -30,6 +30,8 @@ type Client struct { DebugRtp bool Headers []string + SkipErrRtpBlock bool + RtspTimeout time.Duration RtpTimeout time.Duration RtpKeepAliveTimeout time.Duration @@ -179,13 +181,34 @@ func (self *Client) parseBlockHeader(h []byte) (length int, no int, valid bool) if no/2 >= len(self.streams) { return } - if length < 8 { - return - } - stream := self.streams[no/2] - if int(h[5]&0x7f) != stream.Sdp.PayloadType { - return + + if no%2 == 0 { // rtp + if length < 8 { + return + } + + // V=2 + if h[4]&0xc0 != 0x80 { + return + } + + stream := self.streams[no/2] + if int(h[5]&0x7f) != stream.Sdp.PayloadType { + return + } + + timestamp := binary.BigEndian.Uint32(h[8:12]) + if stream.firsttimestamp != 0 { + timestamp -= stream.firsttimestamp + if timestamp < stream.timestamp { + return + } else if timestamp - stream.timestamp > uint32(stream.timeScale*60*60) { + return + } + } + } else { // rtcp } + valid = true return } @@ -205,9 +228,7 @@ func (self *Client) parseHeaders(b []byte) (statusCode int, headers textproto.MI } } - if headers, _ = r.ReadMIMEHeader(); err != nil { - return - } + headers, _ = r.ReadMIMEHeader() return } @@ -298,6 +319,7 @@ func (self *Client) findRTSP() (block []byte, data []byte, err error) { for { var b byte if b, err = self.brconn.ReadByte(); err != nil { + err = fmt.Errorf("rtsp: find rtsp header failed: %v", err) return } switch b { @@ -335,11 +357,12 @@ func (self *Client) findRTSP() (block []byte, data []byte, err error) { return } - if stat == Dollar && len(peek) >= 8 { + if stat == Dollar && len(peek) >= 12 { if blocklen, _, ok := self.parseBlockHeader(peek); ok { left := blocklen+4-len(peek) block = append(peek, make([]byte, left)...) if _, err = io.ReadFull(self.brconn, block[len(peek):]); err != nil { + err = fmt.Errorf("rtsp: read block failed: %v", err) return } return @@ -366,6 +389,7 @@ func (self *Client) readLFLF() (block []byte, data []byte, err error) { for { var b byte if b, err = self.brconn.ReadByte(); err != nil { + err = fmt.Errorf("rtsp: read LFLF failed: %v", err) return } switch b { @@ -388,12 +412,13 @@ func (self *Client) readLFLF() (block []byte, data []byte, err error) { if stat == LFLF { data = peek return - } else if dollarpos != -1 && dollarpos - pos >= 8 { + } else if dollarpos != -1 && dollarpos - pos >= 12 { hdrlen := dollarpos-pos start := len(peek)-hdrlen if blocklen, _, ok := self.parseBlockHeader(peek[start:]); ok { block = append(peek[start:], make([]byte, blocklen+4-hdrlen)...) if _, err = io.ReadFull(self.brconn, block[hdrlen:]); err != nil { + err = fmt.Errorf("rtsp: read block failed: %v", err) return } return @@ -415,6 +440,7 @@ func (self *Client) readResp(b []byte) (res Response, err error) { if res.ContentLength > 0 { res.Body = make([]byte, res.ContentLength) if _, err = io.ReadFull(self.brconn, res.Body); err != nil { + err = fmt.Errorf("rtsp: read body failed: %v", err) return } } @@ -688,6 +714,12 @@ func (self *Stream) makeCodecData() (err error) { } } + self.timeScale = media.TimeScale + if self.timeScale == 0 { + // https://tools.ietf.org/html/rfc5391 + self.timeScale = 8000 + } + return } @@ -1038,15 +1070,18 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error } stream := self.streams[i] - if err = stream.handleRtpPacket(block[4:]); err != nil { - return + herr := stream.handleRtpPacket(block[4:]) + if herr != nil { + if !self.SkipErrRtpBlock { + err = herr + return + } } if stream.gotpkt { - timeScale := stream.Sdp.TimeScale - /* TODO: sync AV by rtcp NTP timestamp + TODO: handle timestamp overflow https://tools.ietf.org/html/rfc3550 A receiver can then synchronize presentation of the audio and video packets by relating their RTP timestamps using the timestamp pairs in RTCP SR packets. @@ -1056,18 +1091,14 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error } stream.timestamp -= stream.firsttimestamp - if timeScale == 0 { - // https://tools.ietf.org/html/rfc5391 - timeScale = 8000 - } - ok = true pkt = stream.pkt - pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(timeScale) + pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(stream.timeScale) pkt.Idx = int8(self.setupMap[i]) - if pkt.Time < stream.lasttime { - err = fmt.Errorf("rtp: stream#%d time=%v < lasttime=%v", pkt.Time, stream.lasttime) + if pkt.Time < stream.lasttime || pkt.Time - stream.lasttime > time.Minute*30 { + err = fmt.Errorf("rtp: time invalid stream#%d time=%v lasttime=%v", pkt.Idx, pkt.Time, stream.lasttime) + return } stream.lasttime = pkt.Time diff --git a/stream.go b/stream.go index b4d3e31..2462295 100644 --- a/stream.go +++ b/stream.go @@ -11,6 +11,8 @@ type Stream struct { Sdp sdp.Media client *Client + timeScale int + // h264 fuStarted bool fuBuffer []byte From 330fcdbf4eeddd844a8f4e979bcb058cf5e71af4 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 29 Jun 2016 10:45:31 +0800 Subject: [PATCH 56/61] add Probe() --- client.go | 91 +++++++++++++++++++++++++++++++++++-------------------- stream.go | 2 -- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/client.go b/client.go index ea7323f..21d5c89 100644 --- a/client.go +++ b/client.go @@ -105,13 +105,44 @@ func Dial(uri string) (self *Client, err error) { return DialTimeout(uri, 0) } +func (self *Client) Probe() (err error) { + for { + ok := true + for _, si:= range self.setupIdx { + stream := self.streams[si] + if stream.CodecData == nil { + ok = false + break + } + } + if ok { + break + } + + if _, err = self.ReadPacket(); err != nil { + return + } + } + + return +} + func (self *Client) Streams() (streams []av.CodecData, err error) { - if self.setupCalled { - streams = self.streamsintf - } else { - err = fmt.Errorf("rtsp: no streams") + if !self.setupCalled { + err = fmt.Errorf("rtsp: no setup") return } + _streams := []av.CodecData{} + for _, si := range self.setupIdx { + stream := self.streams[si] + if stream.CodecData != nil { + _streams = append(_streams, stream.CodecData) + } else { + err = fmt.Errorf("rtsp: codec data not ready, Probe() first") + return + } + } + streams = _streams return } @@ -202,7 +233,7 @@ func (self *Client) parseBlockHeader(h []byte) (length int, no int, valid bool) timestamp -= stream.firsttimestamp if timestamp < stream.timestamp { return - } else if timestamp - stream.timestamp > uint32(stream.timeScale*60*60) { + } else if timestamp - stream.timestamp > uint32(stream.timeScale()*60*60) { return } } @@ -229,7 +260,6 @@ func (self *Client) parseHeaders(b []byte) (statusCode int, headers textproto.MI } headers, _ = r.ReadMIMEHeader() - return } @@ -493,7 +523,7 @@ func (self *Client) ReadResponse() (res Response, err error) { return } -func (self *Client) setupAll() (err error) { +func (self *Client) SetupAll() (err error) { idx := []int{} for i := range self.streams { idx = append(idx, i) @@ -540,7 +570,6 @@ func (self *Client) Setup(idx []int) (err error) { return } } - self.initstructs() self.setupCalled = true return @@ -551,14 +580,7 @@ func md5hash(s string) string { return hex.EncodeToString(h[:]) } -func (self *Client) initstructs() { - self.streamsintf = make([]av.CodecData, len(self.setupIdx)) - for i := range self.setupIdx { - self.streamsintf[i] = self.streams[self.setupIdx[i]].CodecData - } -} - -func (self *Client) Describe() (streams []av.CodecData, err error) { +func (self *Client) Describe() (streams []sdp.Media, err error) { var res Response for i := 0; i < 2; i++ { @@ -593,13 +615,9 @@ func (self *Client) Describe() (streams []av.CodecData, err error) { self.streams = []*Stream{} for _, media := range medias { stream := &Stream{Sdp: media, client: self} - if err = stream.makeCodecData(); err != nil { - return - } + stream.makeCodecData() self.streams = append(self.streams, stream) - } - for _, stream := range self.streams { - streams = append(streams, stream) + streams = append(streams, media) } return @@ -640,7 +658,6 @@ func (self *Client) HandleCodecDataChange() (_newcli *Client, err error) { } newcli.streams = append(newcli.streams, newstream) } - newcli.initstructs() _newcli = newcli return @@ -658,6 +675,15 @@ func (self *Stream) isCodecDataChange() bool { return false } +func (self *Stream) timeScale() int { + t := self.Sdp.TimeScale + if t == 0 { + // https://tools.ietf.org/html/rfc5391 + t = 8000 + } + return t +} + func (self *Stream) makeCodecData() (err error) { media := self.Sdp @@ -714,12 +740,6 @@ func (self *Stream) makeCodecData() (err error) { } } - self.timeScale = media.TimeScale - if self.timeScale == 0 { - // https://tools.ietf.org/html/rfc5391 - self.timeScale = 8000 - } - return } @@ -790,6 +810,7 @@ func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err erro } if len(self.sps) == 0 { self.sps = packet + self.makeCodecData() } else if bytes.Compare(self.sps, packet) != 0 { self.spsChanged = true self.sps = packet @@ -804,6 +825,7 @@ func (self *Stream) handleH264Payload(timestamp uint32, packet []byte) (err erro } if len(self.pps) == 0 { self.pps = packet + self.makeCodecData() } else if bytes.Compare(self.pps, packet) != 0 { self.ppsChanged = true self.pps = packet @@ -1004,7 +1026,7 @@ func (self *Stream) handleRtpPacket(packet []byte) (err error) { */ //payloadType := packet[1]&0x7f - switch self.Type() { + switch self.Sdp.Type { case av.H264: if err = self.handleH264Payload(timestamp, payload); err != nil { return @@ -1093,7 +1115,7 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error ok = true pkt = stream.pkt - pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(stream.timeScale) + pkt.Time = time.Duration(stream.timestamp)*time.Second / time.Duration(stream.timeScale()) pkt.Idx = int8(self.setupMap[i]) if pkt.Time < stream.lasttime || pkt.Time - stream.lasttime > time.Minute*30 { @@ -1115,7 +1137,7 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error func (self *Client) ReadPacket() (pkt av.Packet, err error) { if !self.setupCalled { - if err = self.setupAll(); err != nil { + if err = self.SetupAll(); err != nil { return } } @@ -1159,12 +1181,15 @@ func (self *Client) ReadHeader() (err error) { if _, err = self.Describe(); err != nil { return } - if err = self.setupAll(); err != nil { + if err = self.SetupAll(); err != nil { return } if err = self.Play(); err != nil { return } + if err = self.Probe(); err != nil { + return + } return } diff --git a/stream.go b/stream.go index 2462295..b4d3e31 100644 --- a/stream.go +++ b/stream.go @@ -11,8 +11,6 @@ type Stream struct { Sdp sdp.Media client *Client - timeScale int - // h264 fuStarted bool fuBuffer []byte From b0c16a0e0b828f953e2e437c80bc47be2f33bdd0 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 29 Jun 2016 10:46:06 +0800 Subject: [PATCH 57/61] add toupper --- sdp/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdp/parser.go b/sdp/parser.go index 925d9eb..8704081 100644 --- a/sdp/parser.go +++ b/sdp/parser.go @@ -69,7 +69,7 @@ func Parse(content string) (sess Session, medias []Media) { keyval = strings.Split(field, "/") if len(keyval) >= 2 { key := keyval[0] - switch key { + switch strings.ToUpper(key) { case "MPEG4-GENERIC": media.Type = av.AAC case "H264": From 6464b2f68ca04f59ab3fa7f42e4d3c814d578de0 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 19:21:08 +0800 Subject: [PATCH 58/61] add handler --- client.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/client.go b/client.go index 21d5c89..adae68a 100644 --- a/client.go +++ b/client.go @@ -9,6 +9,7 @@ import ( "encoding/hex" "fmt" "github.com/nareix/av" + "github.com/nareix/av/avutil" "github.com/nareix/codec" "github.com/nareix/codec/aacparser" "github.com/nareix/codec/h264parser" @@ -1204,3 +1205,15 @@ func Open(uri string) (cli *Client, err error) { cli = _cli return } + +func Handler(h *avutil.RegisterHandler) { + h.UrlDemuxer = func(uri string) (ok bool, demuxer av.DemuxCloser, err error) { + if !strings.HasPrefix(uri, "rtsp://") { + return + } + ok = true + demuxer, err = Dial(uri) + return + } +} + From 8ec37155b50d8562c2b34318edba1f8d8b619877 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 22:54:53 +0800 Subject: [PATCH 59/61] remove html.UnescapeString; ReadHeader() -> Streams() --- client.go | 143 ++++++++++++++++++++++++++---------------------------- 1 file changed, 69 insertions(+), 74 deletions(-) diff --git a/client.go b/client.go index adae68a..bd5f383 100644 --- a/client.go +++ b/client.go @@ -14,7 +14,6 @@ import ( "github.com/nareix/codec/aacparser" "github.com/nareix/codec/h264parser" "github.com/nareix/rtsp/sdp" - "html" "io" "net" "net/textproto" @@ -26,6 +25,13 @@ import ( var ErrCodecDataChange = fmt.Errorf("rtsp: codec data change, please call HandleCodecDataChange()") +const ( + stageDescribeDone = iota+1 + stageSetupDone + stageWaitCodecData + stageCodecDataDone +) + type Client struct { DebugRtsp bool DebugRtp bool @@ -39,10 +45,10 @@ type Client struct { rtpKeepaliveTimer time.Time rtpKeepaliveEnterCnt int - setupCalled bool + stage int + setupIdx []int setupMap []int - playCalled bool authHeaders func(method string) []string @@ -74,7 +80,7 @@ type Response struct { func DialTimeout(uri string, timeout time.Duration) (self *Client, err error) { var URL *url.URL - if URL, err = url.Parse(html.UnescapeString(uri)); err != nil { + if URL, err = url.Parse(uri); err != nil { return } @@ -106,44 +112,64 @@ func Dial(uri string) (self *Client, err error) { return DialTimeout(uri, 0) } -func (self *Client) Probe() (err error) { - for { - ok := true - for _, si:= range self.setupIdx { - stream := self.streams[si] - if stream.CodecData == nil { - ok = false - break - } +func (self *Client) allCodecDataReady() bool { + for _, si:= range self.setupIdx { + stream := self.streams[si] + if stream.CodecData == nil { + return false } - if ok { + } + return true +} + +func (self *Client) probe() (err error) { + for { + if self.allCodecDataReady() { break } - - if _, err = self.ReadPacket(); err != nil { + if _, err = self.readPacket(); err != nil { return } } + self.stage = stageCodecDataDone + return +} +func (self *Client) prepare(stage int) (err error) { + for self.stage < stage { + switch self.stage { + case 0: + if _, err = self.Describe(); err != nil { + return + } + + case stageDescribeDone: + if err = self.SetupAll(); err != nil { + return + } + + case stageSetupDone: + if err = self.Play(); err != nil { + return + } + + case stageWaitCodecData: + if err = self.probe(); err != nil { + return + } + } + } return } func (self *Client) Streams() (streams []av.CodecData, err error) { - if !self.setupCalled { - err = fmt.Errorf("rtsp: no setup") + if err = self.prepare(stageCodecDataDone); err != nil { return } - _streams := []av.CodecData{} for _, si := range self.setupIdx { stream := self.streams[si] - if stream.CodecData != nil { - _streams = append(_streams, stream.CodecData) - } else { - err = fmt.Errorf("rtsp: codec data not ready, Probe() first") - return - } + streams = append(streams, stream.CodecData) } - streams = _streams return } @@ -533,13 +559,7 @@ func (self *Client) SetupAll() (err error) { } func (self *Client) Setup(idx []int) (err error) { - if self.setupCalled { - err = fmt.Errorf("rtsp: Setup() called twice") - return - } - - if len(self.streams) == 0 { - err = fmt.Errorf("rtsp: no streams, please call Describe() first") + if err = self.prepare(stageDescribeDone); err != nil { return } @@ -572,7 +592,9 @@ func (self *Client) Setup(idx []int) (err error) { } } - self.setupCalled = true + if self.stage == stageDescribeDone { + self.stage = stageSetupDone + } return } @@ -621,6 +643,9 @@ func (self *Client) Describe() (streams []sdp.Media, err error) { streams = append(streams, media) } + if self.stage == 0 { + self.stage = stageDescribeDone + } return } @@ -1060,7 +1085,12 @@ func (self *Client) Play() (err error) { if err = self.WriteRequest(req); err != nil { return } - self.playCalled = true + + if self.allCodecDataReady() { + self.stage = stageCodecDataDone + } else { + self.stage = stageWaitCodecData + } return } @@ -1136,18 +1166,7 @@ func (self *Client) handleBlock(block []byte) (pkt av.Packet, ok bool, err error return } -func (self *Client) ReadPacket() (pkt av.Packet, err error) { - if !self.setupCalled { - if err = self.SetupAll(); err != nil { - return - } - } - if !self.playCalled { - if err = self.Play(); err != nil { - return - } - } - +func (self *Client) readPacket() (pkt av.Packet, err error) { if err = self.SendRtpKeepalive(); err != nil { return } @@ -1175,35 +1194,11 @@ func (self *Client) ReadPacket() (pkt av.Packet, err error) { return } -func (self *Client) ReadHeader() (err error) { - if err = self.Options(); err != nil { +func (self *Client) ReadPacket() (pkt av.Packet, err error) { + if err = self.prepare(stageCodecDataDone); err != nil { return } - if _, err = self.Describe(); err != nil { - return - } - if err = self.SetupAll(); err != nil { - return - } - if err = self.Play(); err != nil { - return - } - if err = self.Probe(); err != nil { - return - } - return -} - -func Open(uri string) (cli *Client, err error) { - var _cli *Client - if _cli, err = Dial(uri); err != nil { - return - } - if err = _cli.ReadHeader(); err != nil { - return - } - cli = _cli - return + return self.readPacket() } func Handler(h *avutil.RegisterHandler) { From ebb5dcf9e1c767950791a2a971405826f27389a1 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 18:47:31 +0800 Subject: [PATCH 60/61] use new h264parser.SplitNALUs --- client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index bd5f383..0864b3f 100644 --- a/client.go +++ b/client.go @@ -723,7 +723,7 @@ func (self *Stream) makeCodecData() (err error) { } if len(self.sps) == 0 || len(self.pps) == 0 { - if nalus, ok := h264parser.SplitNALUs(media.Config); ok { + if nalus, typ := h264parser.SplitNALUs(media.Config); typ != h264parser.NALU_RAW { for _, nalu := range nalus { if len(nalu) > 0 { self.handleH264Payload(0, nalu) @@ -772,7 +772,7 @@ func (self *Stream) makeCodecData() (err error) { func (self *Stream) handleBuggyAnnexbH264Packet(timestamp uint32, packet []byte) (isBuggy bool, err error) { if len(packet) >= 4 && packet[0] == 0 && packet[1] == 0 && packet[2] == 0 && packet[3] == 1 { isBuggy = true - if nalus, ok := h264parser.SplitNALUs(packet); ok { + if nalus, typ := h264parser.SplitNALUs(packet); typ != h264parser.NALU_RAW { for _, nalu := range nalus { if len(nalu) > 0 { if err = self.handleH264Payload(timestamp, nalu); err != nil { From df0f52cf38a45d842291bb72e1adc24de397d1dd Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 19:48:36 +0800 Subject: [PATCH 61/61] remove aac adts header when input --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index 0864b3f..35dd43e 100644 --- a/client.go +++ b/client.go @@ -1064,7 +1064,7 @@ func (self *Stream) handleRtpPacket(packet []byte) (err error) { return } self.gotpkt = true - self.pkt.Data = payload[4:] // TODO: remove this hack + self.pkt.Data = payload[4+7:] // TODO: remove this hack self.timestamp = timestamp default: