Почему видео по RTSP пишет только первых два кадра?

193 views
Skip to first unread message

Олег Войтенко

unread,
Mar 29, 2018, 2:29:59 PM3/29/18
to Golang Russian
Нашел скриптик на GoLangна просторах гитхаба, и допилил его. Видео пишет в файл, все работает без запинок. Но, на этом все заканчивается, во время исполнения видео-файл в формате H264 увеличивается в размере. Открываю записанный файл через VLC, вижу длительность видео 00:00, во время проигрыша видео, показывает только первых два кадра и усё. Растолкуйте, в чем может быть проблема? С Go имею дело недавно. Так что прошу сильно не пинать, а если пинать, то по сути.

Вот собсно, код:
package main

import "fmt"
import "net"
import "bufio"
import "strings"
import "strconv"
import "encoding/base64"
import "os"
import "bytes"
import "time"
import "flag"

var fuaBuffer *bytes.Buffer
var f *os.File

var  server = flag.String("s", "122.12.528.71:554", "server address host")
var  login = flag.String("l", "admin", "login")
var  pass = flag.String("p", "somepass", "password")
var  link = flag.String("link", "/Stream/Cha/101","link")
var  recordMinutes = flag.Int("t", 1, "record time length in minutes")
var  outputFile = flag.String("o", "", "output file")


func readHeader(reader *bufio.Reader) map[string]string {
  result := make(map[string]string)

  for{
    line, _, err := reader.ReadLine();
    if err != nil{
      panic(err)
    }
    if string(line)=="" {
      break
    }

    fmt.Println(string(line))

    keyvalue := strings.SplitN(string(line),":", 2)
    if len(keyvalue) > 1{
      result[strings.ToUpper(keyvalue[0])] = strings.TrimSpace(keyvalue[1])
    }
  }

  fmt.Println()

  return result

}

func readBody(reader *bufio.Reader, contentLength int) (result string){
  if(contentLength < 0){

    buf := make([]byte, 512)
    for{
      read, _ := reader.Read(buf)
      if read == 0 {
        return
      }else{
        result += string(buf)
      }

    }
  }else{
    buf := make([]byte, contentLength)
    start := 0

    for ;start < contentLength; {
      readed, _ := reader.Read(buf[start:])
      result += string(buf)

      start += readed
    }


  }

  return result
}

func request(conn net.Conn, reader *bufio.Reader, action, url string, hs map[string]string, seq int) (map[string]string, string){

  req := action + " " + url + " RTSP/1.0\r\n"
  for k, v := range hs{
    req += k + ": " + v + "\r\n"
  }
  req += fmt.Sprintf("CSeq: %d\r\n", seq)
  req += "\r\n"

  fmt.Print(req)

  conn.Write([]byte(req))

  headers := readHeader(reader)

  contentLength := 0
  value, ok := headers["CONTENT-LENGTH"]
  if ok {
    contentLength, _ = strconv.Atoi(value)
  }


  if contentLength != 0{
    return headers, readBody(reader, contentLength)
  }

  return headers, ""
}

func main(){

  flag.Parse()

  conn, err := net.Dial("tcp", *server);

  if err != nil {
    fmt.Printf("connect server fail...%v\n", err);
    return
  }

  url := "rtsp://" + *server
  
        seq := 1
  fmt.Println(url)

  reader := bufio.NewReader(conn)

  //dobavlennui header
            headers, bod := request(conn, reader, "OPTIONS", url, (map[string]string{
        	"User-Agent" : "GLOBALNET cam service (LIVE555 Streaming Media v2018.07.24)",
            }), seq)
  fmt.Println(bod)
    seq++



  headers, body := request(conn, reader, "DESCRIBE", url, nil, seq)
  seq++

  fmt.Println(body)

  var ppsSps [][]byte

  startPos := strings.Index(body, "sprop-parameter-sets")

  fmt.Println(startPos)
  
  if startPos > 0{

    startPos += strings.Index(body[startPos:], "=")
    endPos := startPos + strings.Index(body[startPos:], "\r\n")
    ppsSpsBase64 := strings.Split(body[startPos + 1:endPos], ",")
    ppsSps = make([][]byte, len(ppsSpsBase64))
    for i, item := range ppsSpsBase64{
      ppsSps[i], _ = base64.StdEncoding.DecodeString(item)
    }
  }


  var controlUrl string
  startPos = strings.Index(body, "video")
  startPos += strings.Index(body[startPos:], "control")
  if startPos > 0{
    startPos += strings.Index(body[startPos:], ":")
    endPos := startPos + strings.Index(body[startPos:], "\r\n")
    controlUrl = body[startPos+1:endPos]
  }

  playUrl := controlUrl
  headers, _ = request(conn, reader, "SETUP", playUrl, (map[string]string{
    "Transport" : "RTP/AVP/TCP;unicast;interleaved=0-1",
    }), seq)
  seq++


  session, _ := headers["SESSION"]
  headers, _ = request(conn, reader, "PLAY", playUrl, (map[string]string{
    "Session" : session,
    }), seq)
  seq++


  f, err = os.Create(*outputFile)
  defer func(){
    f.Close()
  }()

  if err != nil{
    fmt.Println(err)
    return
  }

  for _, pps := range ppsSps{
    onNalu(pps)
  }

  go func(){
    for{
      conn, _ := net.Dial("tcp", *server);
      reader := bufio.NewReader(conn)
      _, body := request(conn, reader, "GET_PARAMETER", playUrl, (map[string]string{
        "Session" : session,
        }), seq)
      seq++

      fmt.Println(body)

      conn.Close()
      conn = nil

      time.Sleep(30*time.Second)
    }
  }()

  go func(){
    readAVPData(reader)
  }()

  <- time.After(time.Duration(*recordMinutes)*time.Minute)

}

func readAVPData(reader *bufio.Reader){


  for {

  magicNum, err := reader.ReadByte()
  channel, _ := reader.ReadByte()
  dataLengthHi, _ := reader.ReadByte()
  dataLengthLow, _ := reader.ReadByte()
  dataLength := (int)(dataLengthHi)*int(256) + (int)(dataLengthLow)
  data := make([]byte, dataLength)
  offset := 0
  readed := 0
  for ;offset < dataLength; offset += readed{
    readed, err = reader.Read(data[offset:])
    if err != nil{
      break
    }
  }
  
  if err != nil{
    fmt.Println(err)
    break
  }

  fmt.Printf("magicNum %d channel %d dataLength %d\n", magicNum, channel, dataLength)

  if magicNum != 36{
    fmt.Println("magicNum wrong!")
    break
  }

  if channel != 0{
    continue
  }



  decodeRtp(data)
  

  
  }//end for

}

func decodeRtp(data []byte){

  payload :=data[12:]

  naluType := payload[0] & 0x1f; // 0x1f смотрим тип NAL
  if(naluType > 0 && naluType < 23){
    onNalu(payload)
  }else if(naluType == 28){  // если NAL = 28, значит фрейм влазит в один пакет	
    onFuA(payload) 
  }else{
    fmt.Println("unknown type"); // иначе не известный тип
  }
  
}



func onNalu(data []byte){
  f.Write([]byte{0,0,0,1})
  f.Write(data)
}

func onFuA(data []byte){
  naluType := data[0] & 0xe0
  naluType  |= data[1] &  0x1f
  
  
  start := data[1] >> 7
  end := (data[1] >> 6) & 0x01

  if start > 0{
    fuaBuffer = bytes.NewBuffer(nil)
    fuaBuffer.WriteByte(naluType)
  }

  fuaBuffer.Write(data[2:])
  
  if end > 0{
    onNalu(fuaBuffer.Bytes())
  }
}


Скрипт отрабатывает через команду
go run camrecord.go -o file1.h264 -t 1
где -o это файл который писать, -t - время работы скрипта в минутах, потом завершить запись с потока.
Вся беда в том, что за 10 минут записи файла, он весит как должен весить. Есть подозрения, что тайминг криво пишется, или что-то типа этого.

Max Lapshin

unread,
Apr 27, 2018, 2:33:57 AM4/27/18
to Golang Russian
Вы в самом начале бездны, называющейся «прочитаем h264 с камеры».

Помимо таймкодов, синхронизации их друг с другом и т.п., надо ещё учитывать что RTSP поток бывает битый. Его надо уметь синхронизировать.

Evgeny

unread,
Mar 10, 2019, 4:26:37 PM3/10/19
to Golang Russian
Я бы рекомендовал использовать ffmpeg, а не изобретать велосипед, примеров много на github.com За нас уже многое сделали, осталось научиться этим пользоваться:)
Reply all
Reply to author
Forward
0 new messages