123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- package lib
- import (
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "io"
- "log"
- "net"
- "regexp"
- "strings"
- "time"
- )
- type Camera struct {
- host string
- port int
- alive time.Duration
- biosUri string
- fullUri string
- seq int
- sock net.Conn
- realm string
- nonce string
- auth string
- session string
- logger *log.Logger
- }
- func (cam *Camera) Init(host string, port int, alive int, logger *log.Logger) {
- cam.logger = logger
- cam.host, cam.port, cam.alive = host, port, time.Duration(alive)
- cam.biosUri = fmt.Sprintf("rtsp://%s:%d", host, port)
- cam.fullUri = fmt.Sprintf("rtsp://%s:%d/cam/realmonitor?channel=1&subtype=0", host, port)
- }
- func (cam *Camera) Run(stream chan []byte) {
- sock, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cam.host, cam.port))
- if err != nil {
- cam.logger.Fatal("camera connect failed")
- }
- cam.seq, cam.sock = 1, sock
- cam.prepare(false)
- go cam.keepAliveThread()
- go cam.transportThread(stream)
- }
- func (cam *Camera) Stop() {
- if cam.sock == nil {
- return
- }
- // TEARDOWN
- request := fmt.Sprintf(
- "TEARDOWN %s RTSP/1.0\r\n"+
- "CSeq: %d\r\n"+
- `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"\r\n`+
- "Session: %s\r\n"+
- "User-Agent: Tinger.HM-HZ\r\n\r\n",
- cam.fullUri, cam.seq, User, cam.realm,
- cam.nonce, cam.fullUri, cam.auth, cam.session)
- cam.send(request)
- // response := cam.receive() // no receive, as dirty data
- // close connection
- _ = cam.sock.Close()
- cam.seq, cam.sock = 1, nil
- }
- func (cam *Camera) show(title string, body string) {
- fmt.Printf("==============< %s> START ==============\n", title)
- fmt.Println(body)
- fmt.Printf("===============< %s> END ===============\n", title)
- }
- func (cam *Camera) hash(raw string) string {
- hash := md5.New()
- hash.Write([]byte(raw))
- hashBytes := hash.Sum(nil)
- return hex.EncodeToString(hashBytes)
- }
- func (cam *Camera) calcAuth(realm string, nonce string, method string) string {
- h1 := cam.hash(fmt.Sprintf("%s:%s:%s", User, realm, Pass))
- h2 := cam.hash(fmt.Sprintf("%s:%s", method, cam.biosUri))
- return cam.hash(fmt.Sprintf("%s:%s:%s", h1, nonce, h2))
- }
- func (cam *Camera) send(msg string) {
- _, err := cam.sock.Write([]byte(msg))
- if err != nil {
- // cam.logger.Print("message send failed")
- return
- }
- cam.seq++
- }
- func (cam *Camera) receive() string {
- buff := make([]byte, BlockSize)
- n, err := cam.sock.Read(buff)
- if err != nil {
- cam.logger.Fatal("camera message receive failed")
- }
- return string(buff[:n])
- }
- func (cam *Camera) prepare(verbose bool) {
- // OPTIONS 1: get realm, nonce
- request := fmt.Sprintf(
- "OPTIONS %s RTSP/1.0\r\n"+
- "CSeq: %d\r\n"+
- "User-Agent: Tinger.HM-HZ\r\n\r\n",
- cam.biosUri, cam.seq)
- cam.send(request)
- response := cam.receive()
- ptn := regexp.MustCompile(`Digest realm="(.+?)",nonce="(.+?)"\r\n`)
- search := ptn.FindStringSubmatch(response)
- if len(search) == 0 {
- cam.logger.Fatal("realm and nonce not matched")
- }
- cam.realm, cam.nonce = search[1], search[2]
- if verbose {
- cam.show("OPTIONS 1", request)
- cam.show("OPTIONS 1 RES", response)
- }
- // OPTIONS 2: auth
- cam.auth = cam.calcAuth(cam.realm, cam.nonce, "OPTIONS")
- request = fmt.Sprintf(
- "OPTIONS %s RTSP/1.0\r\n"+
- "Accept: application/sdp\r\n"+
- "CSeq: %d\r\n"+
- `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"\r\n`+
- "User-Agent: Tinger.HM-HZ\r\n\r\n",
- cam.biosUri, cam.seq, User, cam.realm, cam.nonce, cam.biosUri, cam.auth)
- cam.send(request)
- response = cam.receive()
- if verbose {
- cam.show("OPTIONS 2", request)
- cam.show("OPTIONS 2 RES", response)
- }
- // DESCRIBE
- request = fmt.Sprintf(
- "DESCRIBE %s RTSP/1.0\r\n"+
- "CSeq: %d\r\n"+
- "User-Agent: Tinger.HM-HZ\r\n\r\n",
- cam.fullUri, cam.seq)
- cam.send(request)
- response = cam.receive()
- if verbose {
- cam.show("DESCRIBE", request)
- cam.show("DESCRIBE RES", response)
- }
- // SETUP: get session id
- request = fmt.Sprintf(
- "SETUP %s/trackID=0 RTSP/1.0\r\n"+
- "CSeq: %d\r\n"+
- "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n"+
- "User-Agent: Tinger.HM-HZ\r\n\r\n",
- cam.fullUri, cam.seq)
- cam.send(request)
- response = cam.receive()
- ptn = regexp.MustCompile(`Session: (\d+)`)
- search = ptn.FindStringSubmatch(response)
- if len(search) == 0 {
- cam.logger.Fatal("session not matched")
- }
- cam.session = search[1]
- if verbose {
- cam.show("SETUP", request)
- cam.show("SETUP RES", response)
- }
- // PLAY
- request = fmt.Sprintf(
- "PLAY %s RTSP/1.0\r\n"+
- "CSeq: %d\r\n"+
- "Session: %s\r\n"+
- "Range: npt=0.000-\r\n"+
- "User-Agent: Tigner,HN-HZ\r\n\r\n",
- cam.fullUri, cam.seq, cam.session)
- cam.send(request)
- response = cam.receive()
- if !strings.HasPrefix(response, "RTSP/1.0 200 OK") {
- cam.logger.Fatal("PLAY failed")
- }
- if verbose {
- cam.show("PLAY", request)
- cam.show("PLAY RES", response)
- }
- }
- func (cam *Camera) transportThread(stream chan []byte) {
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * a complicated situation: *
- * The actual reason is that every communication message are put in the same socket, *
- * which leads to <image stream data> and <other message>(mostly keep alive response message) *
- * will be mixed in the only socket message pool. *
- * So header[0] maybe not b"$"(ASCII: 36), in this case, it means that there are another *
- * response message which is not image data, what should we do is just find one by one *
- * until b"$" appears, the bytes data which between the wrong header[0] and b"$" are recognized *
- * as dirty data(are dropped in current process) *
- * *
- * resolution in future: *
- * store all received data in a bytes buffer *
- * data start with b"$" is image steam data *
- * it is easy to read every block of this type of data *
- * data start with b"RTSP 200 OK" is response data *
- * problem: how to locate the end(\r\n\r\n not always the real end of a message) *
- * you can try it, believe yourself! another words: you can you up. *
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
- defer cam.Stop()
- header := make([]byte, 4)
- buffer := make([]byte, BlockSize*2)
- syncer := make([]byte, 1)
- for {
- if count, err := io.ReadFull(cam.sock, header); err != nil || count != 4 {
- // cam.logger.Printf(fmt.Sprintf("header read error:\n%s\n", err))
- return
- }
- if header[0] != 36 {
- rid := 0
- for {
- if count, err := io.ReadFull(cam.sock, syncer); err != nil && count != 1 {
- // cam.logger.Printf("syncer read error:\n%s\n", err)
- return
- } else if syncer[0] == 36 {
- header[0] = 36
- if count, err = io.ReadFull(cam.sock, header[1:]); err != nil && count == 3 {
- // cam.logger.Printf("header remain read error:\n%s\n", err)
- return
- }
- break
- } else {
- rid++
- }
- }
- cam.logger.Printf("get rid of: %d\n", rid)
- }
- payloadLen := int(header[2])<<8 + int(header[3])
- fullLength := payloadLen + 4
- if fullLength > len(buffer) {
- buffer = make([]byte, fullLength)
- }
- copy(buffer, header)
- if count, err := io.ReadFull(cam.sock, buffer[4:fullLength]); err != nil || count != payloadLen {
- // cam.logger.Printf("payload read error:\n%s\n", err)
- return
- } else {
- stream <- buffer[:fullLength]
- }
- }
- }
- func (cam *Camera) keepAliveThread() {
- defer cam.Stop()
- for {
- time.Sleep(time.Second * cam.alive)
- if cam.sock == nil {
- continue
- }
- request := fmt.Sprintf(
- "OPTIONS %s RTSP/1.0\r\n"+
- "Accept: application/sdp\r\n"+
- "CSeq: %d\r\n"+
- `Authorization: Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"\r\n`+
- "User-Agent: Tinger.HM-HZ\r\n\r\n",
- cam.biosUri, cam.seq, User, cam.realm, cam.nonce, cam.biosUri, cam.auth)
- cam.send(request)
- // response := cam.receive() // no receive, as dirty data
- }
- }
|