├── README.md ├── example.go └── rtsp_client.go /README.md: -------------------------------------------------------------------------------- 1 | # rtsp_client 2 | 3 | go run *.go 4 | 5 | test 6 | 7 | ffplay test.h264 8 | -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/gob" 5 | "flag" 6 | "log" 7 | "os" 8 | 9 | "github.com/nareix/mp4" 10 | mpegts "github.com/nareix/ts" 11 | ) 12 | 13 | var ( 14 | VideoWidth int 15 | VideoHeight int 16 | ) 17 | 18 | type GobAllSamples struct { 19 | TimeScale int 20 | SPS []byte 21 | PPS []byte 22 | Samples []GobSample 23 | } 24 | 25 | type GobSample struct { 26 | Duration int 27 | Data []byte 28 | Sync bool 29 | } 30 | 31 | func main() { 32 | var saveGob bool 33 | var url string 34 | var maxgop int 35 | 36 | // with aac rtsp://admin:123456@80.254.21.110:554/mpeg4cif 37 | // with aac rtsp://admin:123456@95.31.251.50:5050/mpeg4cif 38 | // 1808p rtsp://admin:123456@171.25.235.18/mpeg4 39 | // 640x360 rtsp://admin:123456@94.242.52.34:5543/mpeg4cif 40 | 41 | flag.BoolVar(&saveGob, "s", false, "save to gob file") 42 | flag.IntVar(&maxgop, "g", 10, "max gop recording") 43 | flag.StringVar(&url, "url", "rtsp://admin:123456@176.99.65.80:558/mpeg4cif", "") 44 | flag.Parse() 45 | 46 | RtspReader := RtspClientNew() 47 | 48 | quit := false 49 | 50 | sps := []byte{} 51 | pps := []byte{} 52 | fuBuffer := []byte{} 53 | syncCount := 0 54 | 55 | // rtp timestamp: 90 kHz clock rate 56 | // 1 sec = timestamp 90000 57 | timeScale := 90000 58 | 59 | type NALU struct { 60 | ts int 61 | data []byte 62 | sync bool 63 | } 64 | var lastNALU *NALU 65 | 66 | var mp4w *mp4.SimpleH264Writer 67 | var tsw *mpegts.SimpleH264Writer 68 | 69 | var allSamples *GobAllSamples 70 | 71 | outfileMp4, _ := os.Create("out.mp4") 72 | outfileTs, _ := os.Create("out.ts") 73 | outfileAAC, _ := os.Create("out.aac") 74 | endWriteNALU := func() { 75 | log.Println("finish write") 76 | if mp4w != nil { 77 | if err := mp4w.Finish(); err != nil { 78 | panic(err) 79 | } 80 | } 81 | outfileTs.Close() 82 | 83 | if saveGob { 84 | file, _ := os.Create("out.gob") 85 | enc := gob.NewEncoder(file) 86 | enc.Encode(allSamples) 87 | file.Close() 88 | } 89 | } 90 | 91 | writeNALU := func(sync bool, ts int, payload []byte) { 92 | if saveGob && allSamples == nil { 93 | allSamples = &GobAllSamples{ 94 | SPS: sps, 95 | PPS: pps, 96 | TimeScale: timeScale, 97 | } 98 | } 99 | if mp4w == nil { 100 | mp4w = &mp4.SimpleH264Writer{ 101 | SPS: sps, 102 | PPS: pps, 103 | TimeScale: timeScale, 104 | W: outfileMp4, 105 | } 106 | //log.Println("SPS:\n"+hex.Dump(sps), "\nPPS:\n"+hex.Dump(pps)) 107 | } 108 | curNALU := &NALU{ 109 | ts: ts, 110 | sync: sync, 111 | data: payload, 112 | } 113 | 114 | if lastNALU != nil { 115 | log.Println("write", lastNALU.sync, len(lastNALU.data)) 116 | 117 | if err := mp4w.WriteNALU(lastNALU.sync, curNALU.ts-lastNALU.ts, lastNALU.data); err != nil { 118 | panic(err) 119 | } 120 | 121 | if tsw == nil { 122 | tsw = &mpegts.SimpleH264Writer{ 123 | TimeScale: timeScale, 124 | W: outfileTs, 125 | SPS: sps, 126 | PPS: pps, 127 | PCR: int64(lastNALU.ts), 128 | PTS: int64(lastNALU.ts), 129 | } 130 | } 131 | if err := tsw.WriteNALU(lastNALU.sync, curNALU.ts-lastNALU.ts, lastNALU.data); err != nil { 132 | panic(err) 133 | } 134 | 135 | if saveGob { 136 | allSamples.Samples = append(allSamples.Samples, GobSample{ 137 | Sync: lastNALU.sync, 138 | Duration: curNALU.ts - lastNALU.ts, 139 | Data: lastNALU.data, 140 | }) 141 | } 142 | } 143 | lastNALU = curNALU 144 | } 145 | 146 | handleNALU := func(nalType byte, payload []byte, ts int64) { 147 | if nalType == 7 { 148 | if len(sps) == 0 { 149 | sps = payload 150 | } 151 | } else if nalType == 8 { 152 | if len(pps) == 0 { 153 | pps = payload 154 | } 155 | } else if nalType == 5 { 156 | // keyframe 157 | syncCount++ 158 | if syncCount == maxgop { 159 | quit = true 160 | } 161 | writeNALU(true, int(ts), payload) 162 | } else { 163 | // non-keyframe 164 | if syncCount > 0 { 165 | writeNALU(false, int(ts), payload) 166 | } 167 | } 168 | } 169 | 170 | if status, message := RtspReader.Client(url); status { 171 | log.Println("connected") 172 | i := 0 173 | for { 174 | i++ 175 | //read 100 frame and exit loop 176 | if quit { 177 | break 178 | } 179 | select { 180 | case data := <-RtspReader.outgoing: 181 | 182 | if true { 183 | log.Printf("packet [0]=%x type=%d\n", data[0], data[1]) 184 | } 185 | 186 | //log.Println("packet recive") 187 | if data[0] == 36 && data[1] == 0 { 188 | cc := data[4] & 0xF 189 | //rtp header 190 | rtphdr := 12 + cc*4 191 | 192 | //packet time 193 | ts := (int64(data[8]) << 24) + (int64(data[9]) << 16) + (int64(data[10]) << 8) + (int64(data[11])) 194 | 195 | //packet number 196 | packno := (int64(data[6]) << 8) + int64(data[7]) 197 | if false { 198 | log.Println("packet num", packno) 199 | } 200 | 201 | nalType := data[4+rtphdr] & 0x1F 202 | 203 | if nalType >= 1 && nalType <= 23 { 204 | handleNALU(nalType, data[4+rtphdr:], ts) 205 | } else if nalType == 28 { 206 | isStart := data[4+rtphdr+1]&0x80 != 0 207 | isEnd := data[4+rtphdr+1]&0x40 != 0 208 | nalType := data[4+rtphdr+1] & 0x1F 209 | //nri := (data[4+rtphdr+1]&0x60)>>5 210 | nal := data[4+rtphdr]&0xE0 | data[4+rtphdr+1]&0x1F 211 | if isStart { 212 | fuBuffer = []byte{0} 213 | } 214 | fuBuffer = append(fuBuffer, data[4+rtphdr+2:]...) 215 | if isEnd { 216 | fuBuffer[0] = nal 217 | handleNALU(nalType, fuBuffer, ts) 218 | } 219 | } 220 | 221 | } else if data[0] == 36 && data[1] == 2 { 222 | // audio 223 | 224 | cc := data[4] & 0xF 225 | rtphdr := 12 + cc*4 226 | //or not payload := data[4+rtphdr:] 227 | payload := data[4+rtphdr+4:] 228 | outfileAAC.Write(payload) 229 | //log.Print("audio payload\n", hex.Dump(payload)) 230 | } 231 | 232 | case <-RtspReader.signals: 233 | log.Println("exit signal by class rtsp") 234 | } 235 | } 236 | } else { 237 | log.Println("error", message) 238 | } 239 | 240 | endWriteNALU() 241 | RtspReader.Close() 242 | } 243 | -------------------------------------------------------------------------------- /rtsp_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | b64 "encoding/base64" 6 | "encoding/hex" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "net/url" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | type RtspClient struct { 18 | socket net.Conn 19 | outgoing chan []byte //out chanel 20 | signals chan bool //signals quit 21 | host string //host 22 | port string //port 23 | uri string //url 24 | auth bool //aut 25 | login string 26 | password string //password 27 | session string //rtsp session 28 | responce string //responce string 29 | bauth string //string b auth 30 | track []string //rtsp track 31 | cseq int //qury number 32 | videow int 33 | videoh int 34 | } 35 | 36 | //вернет пустой инициализированный обьект 37 | func RtspClientNew() *RtspClient { 38 | Obj := &RtspClient{ 39 | cseq: 1, //стартовый номер запроса 40 | signals: make(chan bool, 1), //буферизируемый канал на 1 сообщение 41 | outgoing: make(chan []byte, 100000), //буферизиуемый канал на 100000 байт 42 | } 43 | return Obj 44 | } 45 | 46 | //основная функция работы с rtsp 47 | func (this *RtspClient) Client(rtsp_url string) (bool, string) { 48 | //проверить и отпарсить url 49 | if !this.ParseUrl(rtsp_url) { 50 | return false, "Не верный url" 51 | } 52 | //установить подключение к камере 53 | if !this.Connect() { 54 | return false, "Не возможно подключиться" 55 | } 56 | //фаза 1 OPTIONS первый этап общения с камерой 57 | //отправляем запрос OPTIONS 58 | if !this.Write("OPTIONS " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\n\r\n") { 59 | return false, "Не возможно отправить сообщение OPTIONS" 60 | } 61 | //читаем ответ на запрос OPTIONS 62 | if status, message := this.Read(); !status { 63 | return false, "Не возможно прочитать ответ OPTIONS соединение потеряно" 64 | } else if status && strings.Contains(message, "Digest") { 65 | if !this.AuthDigest("OPTIONS", message) { 66 | return false, "Требуеться авторизация Digest" 67 | } 68 | } else if status && strings.Contains(message, "Basic") { 69 | if !this.AuthBasic("OPTIONS", message) { 70 | return false, "Требуеться авторизация Basic" 71 | } 72 | } else if !strings.Contains(message, "200") { 73 | return false, "Ошибка OPTIONS not status code 200 OK " + message 74 | } 75 | 76 | ////////////PHASE 2 DESCRIBE 77 | log.Println("DESCRIBE " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + this.bauth + "\r\n\r\n") 78 | if !this.Write("DESCRIBE " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + this.bauth + "\r\n\r\n") { 79 | return false, "Не возможно отправть запрос DESCRIBE" 80 | } 81 | if status, message := this.Read(); !status { 82 | return false, "Не возможно прочитать ответ DESCRIBE соединение потеряно ?" 83 | } else if status && strings.Contains(message, "Digest") { 84 | if !this.AuthDigest("DESCRIBE", message) { 85 | return false, "Требуеться авторизация Digest" 86 | } 87 | } else if status && strings.Contains(message, "Basic") { 88 | if !this.AuthBasic("DESCRIBE", message) { 89 | return false, "Требуеться авторизация Basic" 90 | } 91 | } else if !strings.Contains(message, "200") { 92 | return false, "Ошибка DESCRIBE not status code 200 OK " + message 93 | } else { 94 | log.Println(message) 95 | this.track = this.ParseMedia(message) 96 | 97 | } 98 | if len(this.track) == 0 { 99 | return false, "Ошибка track not found " 100 | } 101 | //PHASE 3 SETUP 102 | log.Println("SETUP " + this.uri + "/" + this.track[0] + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nTransport: RTP/AVP/TCP;unicast;interleaved=0-1" + this.bauth + "\r\n\r\n") 103 | if !this.Write("SETUP " + this.uri + "/" + this.track[0] + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nTransport: RTP/AVP/TCP;unicast;interleaved=0-1" + this.bauth + "\r\n\r\n") { 104 | return false, "" 105 | } 106 | if status, message := this.Read(); !status { 107 | return false, "Не возможно прочитать ответ SETUP соединение потеряно" 108 | 109 | } else if !strings.Contains(message, "200") { 110 | if strings.Contains(message, "401") { 111 | str := this.AuthDigest_Only("SETUP", message) 112 | if !this.Write("SETUP " + this.uri + "/" + this.track[0] + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nTransport: RTP/AVP/TCP;unicast;interleaved=0-1" + this.bauth + str + "\r\n\r\n") { 113 | return false, "" 114 | } 115 | if status, message := this.Read(); !status { 116 | return false, "Не возможно прочитать ответ SETUP соединение потеряно" 117 | 118 | } else if !strings.Contains(message, "200") { 119 | 120 | return false, "Ошибка SETUP not status code 200 OK " + message 121 | 122 | } else { 123 | this.session = ParseSession(message) 124 | } 125 | } else { 126 | return false, "Ошибка SETUP not status code 200 OK " + message 127 | } 128 | } else { 129 | log.Println(message) 130 | this.session = ParseSession(message) 131 | log.Println(this.session) 132 | } 133 | if len(this.track) > 1 { 134 | 135 | if !this.Write("SETUP " + this.uri + "/" + this.track[1] + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nTransport: RTP/AVP/TCP;unicast;interleaved=2-3" + "\r\nSession: " + this.session + this.bauth + "\r\n\r\n") { 136 | return false, "" 137 | } 138 | if status, message := this.Read(); !status { 139 | return false, "Не возможно прочитать ответ SETUP Audio соединение потеряно" 140 | 141 | } else if !strings.Contains(message, "200") { 142 | if strings.Contains(message, "401") { 143 | str := this.AuthDigest_Only("SETUP", message) 144 | if !this.Write("SETUP " + this.uri + "/" + this.track[1] + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nTransport: RTP/AVP/TCP;unicast;interleaved=2-3" + this.bauth + str + "\r\n\r\n") { 145 | return false, "" 146 | } 147 | if status, message := this.Read(); !status { 148 | return false, "Не возможно прочитать ответ SETUP Audio соединение потеряно" 149 | 150 | } else if !strings.Contains(message, "200") { 151 | 152 | return false, "Ошибка SETUP not status code 200 OK " + message 153 | 154 | } else { 155 | log.Println(message) 156 | this.session = ParseSession(message) 157 | } 158 | } else { 159 | return false, "Ошибка SETUP not status code 200 OK " + message 160 | } 161 | } else { 162 | log.Println(message) 163 | this.session = ParseSession(message) 164 | } 165 | } 166 | 167 | //PHASE 4 SETUP 168 | log.Println("PLAY " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nSession: " + this.session + this.bauth + "\r\n\r\n") 169 | if !this.Write("PLAY " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nSession: " + this.session + this.bauth + "\r\n\r\n") { 170 | return false, "" 171 | } 172 | if status, message := this.Read(); !status { 173 | return false, "Не возможно прочитать ответ PLAY соединение потеряно" 174 | 175 | } else if !strings.Contains(message, "200") { 176 | //return false, "Ошибка PLAY not status code 200 OK " + message 177 | if strings.Contains(message, "401") { 178 | str := this.AuthDigest_Only("PLAY", message) 179 | if !this.Write("PLAY " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nSession: " + this.session + this.bauth + str + "\r\n\r\n") { 180 | return false, "" 181 | } 182 | if status, message := this.Read(); !status { 183 | return false, "Не возможно прочитать ответ PLAY соединение потеряно" 184 | 185 | } else if !strings.Contains(message, "200") { 186 | 187 | return false, "Ошибка PLAY not status code 200 OK " + message 188 | 189 | } else { 190 | //this.session = ParseSession(message) 191 | log.Print(message) 192 | go this.RtspRtpLoop() 193 | return true, "ok" 194 | } 195 | } else { 196 | return false, "Ошибка PLAY not status code 200 OK " + message 197 | } 198 | } else { 199 | log.Print(message) 200 | go this.RtspRtpLoop() 201 | return true, "ok" 202 | } 203 | return false, "other error" 204 | } 205 | 206 | /* 207 | The RTP header has the following format: 208 | 209 | 0 1 2 3 210 | 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 211 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 212 | |V=2|P|X| CC |M| PT | sequence number | 213 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 214 | | timestamp | 215 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 216 | | synchronization source (SSRC) identifier | 217 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ 218 | | contributing source (CSRC) identifiers | 219 | | .... | 220 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 221 | version (V): 2 bits 222 | This field identifies the version of RTP. The version defined by 223 | this specification is two (2). (The value 1 is used by the first 224 | draft version of RTP and the value 0 is used by the protocol 225 | initially implemented in the "vat" audio tool.) 226 | 227 | padding (P): 1 bit 228 | If the padding bit is set, the packet contains one or more 229 | additional padding octets at the end which are not part of the 230 | payload. The last octet of the padding contains a count of how 231 | many padding octets should be ignored, including itself. Padding 232 | may be needed by some encryption algorithms with fixed block sizes 233 | or for carrying several RTP packets in a lower-layer protocol data 234 | unit. 235 | 236 | extension (X): 1 bit 237 | If the extension bit is set, the fixed header MUST be followed by 238 | exactly one header extension, with a format defined in Section 239 | 5.3.1. 240 | 241 | */ 242 | func (this *RtspClient) RtspRtpLoop() { 243 | defer func() { 244 | this.signals <- true 245 | }() 246 | header := make([]byte, 4) 247 | payload := make([]byte, 4096) 248 | //sync := make([]byte, 256) 249 | sync_b := make([]byte, 1) 250 | timer := time.Now() 251 | for { 252 | if int(time.Now().Sub(timer).Seconds()) > 50 { 253 | if !this.Write("OPTIONS " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + "\r\nSession: " + this.session + this.bauth + "\r\n\r\n") { 254 | return 255 | } 256 | timer = time.Now() 257 | } 258 | this.socket.SetDeadline(time.Now().Add(50 * time.Second)) 259 | //read rtp hdr 4 260 | if n, err := io.ReadFull(this.socket, header); err != nil || n != 4 { 261 | //rtp hdr read error 262 | return 263 | } 264 | //log.Println(header) 265 | if header[0] != 36 { 266 | //log.Println("desync?", this.host) 267 | for { 268 | ///////////////////////////skeep///////////////////////////////////// 269 | if n, err := io.ReadFull(this.socket, sync_b); err != nil && n != 1 { 270 | return 271 | } else if sync_b[0] == 36 { 272 | header[0] = 36 273 | if n, err := io.ReadFull(this.socket, header[1:]); err != nil && n == 3 { 274 | return 275 | } 276 | break 277 | } 278 | } 279 | /* 280 | //вычитываем 256 в попытке отсять мусор обрезать RTSP 281 | if string(header) == "RTSP" { 282 | if n, err := io.ReadFull(this.socket, sync); err != nil && n == 256 { 283 | return 284 | } else { 285 | rtsp_rtp := []byte(strings.Split(string(sync), "\r\n\r\n")[1]) 286 | //отправим все что есть в буфере 287 | this.SendBufer(rtsp_rtp) 288 | continue 289 | } 290 | } else { 291 | log.Println("full desync") 292 | return 293 | } 294 | */ 295 | } 296 | 297 | payloadLen := (int)(header[2])<<8 + (int)(header[3]) 298 | //log.Println("payloadLen", payloadLen) 299 | if payloadLen > 4096 || payloadLen < 12 { 300 | log.Println("desync", this.uri, payloadLen) 301 | return 302 | } 303 | if n, err := io.ReadFull(this.socket, payload[:payloadLen]); err != nil || n != payloadLen { 304 | return 305 | } else { 306 | this.outgoing <- append(header, payload[:n]...) 307 | } 308 | } 309 | 310 | } 311 | 312 | //unsafe! 313 | func (this *RtspClient) SendBufer(bufer []byte) { 314 | //тут надо отправлять все пакеты из буфера send all? 315 | payload := make([]byte, 4096) 316 | for { 317 | if len(bufer) < 4 { 318 | log.Fatal("bufer small") 319 | } 320 | dataLength := (int)(bufer[2])<<8 + (int)(bufer[3]) 321 | if dataLength > len(bufer)+4 { 322 | if n, err := io.ReadFull(this.socket, payload[:dataLength-len(bufer)+4]); err != nil { 323 | return 324 | } else { 325 | this.outgoing <- append(bufer, payload[:n]...) 326 | return 327 | } 328 | 329 | } else { 330 | this.outgoing <- bufer[:dataLength+4] 331 | bufer = bufer[dataLength+4:] 332 | } 333 | } 334 | } 335 | func (this *RtspClient) Connect() bool { 336 | d := &net.Dialer{Timeout: 3 * time.Second} 337 | conn, err := d.Dial("tcp", this.host+":"+this.port) 338 | if err != nil { 339 | return false 340 | } 341 | this.socket = conn 342 | return true 343 | } 344 | func (this *RtspClient) Write(message string) bool { 345 | this.cseq += 1 346 | if _, e := this.socket.Write([]byte(message)); e != nil { 347 | return false 348 | } 349 | return true 350 | } 351 | func (this *RtspClient) Read() (bool, string) { 352 | buffer := make([]byte, 4096) 353 | if nb, err := this.socket.Read(buffer); err != nil || nb <= 0 { 354 | log.Println("socket read failed", err) 355 | return false, "" 356 | } else { 357 | return true, string(buffer[:nb]) 358 | } 359 | } 360 | func (this *RtspClient) AuthBasic(phase string, message string) bool { 361 | this.bauth = "\r\nAuthorization: Basic " + b64.StdEncoding.EncodeToString([]byte(this.login+":"+this.password)) 362 | if !this.Write(phase + " " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + this.bauth + "\r\n\r\n") { 363 | return false 364 | } 365 | if status, message := this.Read(); status && strings.Contains(message, "200") { 366 | this.track = ParseMedia(message) 367 | return true 368 | } 369 | return false 370 | } 371 | func (this *RtspClient) AuthDigest(phase string, message string) bool { 372 | nonce := ParseDirective(message, "nonce") 373 | realm := ParseDirective(message, "realm") 374 | hs1 := GetMD5Hash(this.login + ":" + realm + ":" + this.password) 375 | hs2 := GetMD5Hash(phase + ":" + this.uri) 376 | responce := GetMD5Hash(hs1 + ":" + nonce + ":" + hs2) 377 | dauth := "\r\n" + `Authorization: Digest username="` + this.login + `", realm="` + realm + `", nonce="` + nonce + `", uri="` + this.uri + `", response="` + responce + `"` 378 | if !this.Write(phase + " " + this.uri + " RTSP/1.0\r\nCSeq: " + strconv.Itoa(this.cseq) + dauth + "\r\n\r\n") { 379 | return false 380 | } 381 | if status, message := this.Read(); status && strings.Contains(message, "200") { 382 | this.track = ParseMedia(message) 383 | return true 384 | } 385 | return false 386 | } 387 | func (this *RtspClient) AuthDigest_Only(phase string, message string) string { 388 | nonce := ParseDirective(message, "nonce") 389 | realm := ParseDirective(message, "realm") 390 | hs1 := GetMD5Hash(this.login + ":" + realm + ":" + this.password) 391 | hs2 := GetMD5Hash(phase + ":" + this.uri) 392 | responce := GetMD5Hash(hs1 + ":" + nonce + ":" + hs2) 393 | dauth := "\r\n" + `Authorization: Digest username="` + this.login + `", realm="` + realm + `", nonce="` + nonce + `", uri="` + this.uri + `", response="` + responce + `"` 394 | return dauth 395 | } 396 | func (this *RtspClient) ParseUrl(rtsp_url string) bool { 397 | 398 | u, err := url.Parse(rtsp_url) 399 | if err != nil { 400 | return false 401 | } 402 | phost := strings.Split(u.Host, ":") 403 | this.host = phost[0] 404 | if len(phost) == 2 { 405 | this.port = phost[1] 406 | } else { 407 | this.port = "554" 408 | } 409 | this.login = u.User.Username() 410 | this.password, this.auth = u.User.Password() 411 | if u.RawQuery != "" { 412 | this.uri = "rtsp://" + this.host + ":" + this.port + u.Path + "?" + string(u.RawQuery) 413 | } else { 414 | this.uri = "rtsp://" + this.host + ":" + this.port + u.Path 415 | } 416 | return true 417 | } 418 | func (this *RtspClient) Close() { 419 | if this.socket != nil { 420 | this.socket.Close() 421 | } 422 | } 423 | func ParseDirective(header, name string) string { 424 | index := strings.Index(header, name) 425 | if index == -1 { 426 | return "" 427 | } 428 | start := 1 + index + strings.Index(header[index:], `"`) 429 | end := start + strings.Index(header[start:], `"`) 430 | return strings.TrimSpace(header[start:end]) 431 | } 432 | func ParseSession(header string) string { 433 | mparsed := strings.Split(header, "\r\n") 434 | for _, element := range mparsed { 435 | if strings.Contains(element, "Session:") { 436 | if strings.Contains(element, ";") { 437 | fist := strings.Split(element, ";")[0] 438 | return fist[9:] 439 | } else { 440 | return element[9:] 441 | } 442 | } 443 | } 444 | return "" 445 | } 446 | func ParseMedia(header string) []string { 447 | letters := []string{} 448 | mparsed := strings.Split(header, "\r\n") 449 | paste := "" 450 | 451 | if true { 452 | log.Println("headers", header) 453 | } 454 | 455 | for _, element := range mparsed { 456 | if strings.Contains(element, "a=control:") && !strings.Contains(element, "*") && strings.Contains(element, "tra") { 457 | paste = element[10:] 458 | if strings.Contains(element, "/") { 459 | striped := strings.Split(element, "/") 460 | paste = striped[len(striped)-1] 461 | } 462 | letters = append(letters, paste) 463 | } 464 | 465 | dimensionsPrefix := "a=x-dimensions:" 466 | if strings.HasPrefix(element, dimensionsPrefix) { 467 | dims := []int{} 468 | for _, s := range strings.Split(element[len(dimensionsPrefix):], ",") { 469 | v := 0 470 | fmt.Sscanf(s, "%d", &v) 471 | if v <= 0 { 472 | break 473 | } 474 | dims = append(dims, v) 475 | } 476 | if len(dims) == 2 { 477 | VideoWidth = dims[0] 478 | VideoHeight = dims[1] 479 | } 480 | } 481 | } 482 | return letters 483 | } 484 | func GetMD5Hash(text string) string { 485 | hash := md5.Sum([]byte(text)) 486 | return hex.EncodeToString(hash[:]) 487 | } 488 | func (this *RtspClient) ParseMedia(header string) []string { 489 | letters := []string{} 490 | mparsed := strings.Split(header, "\r\n") 491 | paste := "" 492 | for _, element := range mparsed { 493 | if strings.Contains(element, "a=control:") && !strings.Contains(element, "*") && strings.Contains(element, "tra") { 494 | paste = element[10:] 495 | if strings.Contains(element, "/") { 496 | striped := strings.Split(element, "/") 497 | paste = striped[len(striped)-1] 498 | } 499 | letters = append(letters, paste) 500 | } 501 | 502 | dimensionsPrefix := "a=x-dimensions:" 503 | if strings.HasPrefix(element, dimensionsPrefix) { 504 | dims := []int{} 505 | for _, s := range strings.Split(element[len(dimensionsPrefix):], ",") { 506 | v := 0 507 | fmt.Sscanf(s, "%d", &v) 508 | if v <= 0 { 509 | break 510 | } 511 | dims = append(dims, v) 512 | } 513 | if len(dims) == 2 { 514 | this.videow = dims[0] 515 | this.videoh = dims[1] 516 | } 517 | } 518 | } 519 | return letters 520 | } 521 | --------------------------------------------------------------------------------