123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- package dvrip
- import (
- "encoding/base64"
- "encoding/binary"
- "errors"
- "fmt"
- "time"
- "github.com/AlexxIT/go2rtc/pkg/core"
- "github.com/AlexxIT/go2rtc/pkg/h264"
- "github.com/AlexxIT/go2rtc/pkg/h264/annexb"
- "github.com/AlexxIT/go2rtc/pkg/h265"
- "github.com/pion/rtp"
- )
- type Producer struct {
- core.Connection
- client *Client
- video, audio *core.Receiver
- videoTS uint32
- videoDT uint32
- audioTS uint32
- audioSeq uint16
- }
- func (c *Producer) Start() error {
- for {
- pType, b, err := c.client.ReadPacket()
- if err != nil {
- return err
- }
- //log.Printf("[DVR] type: %d, len: %d", dataType, len(b))
- switch pType {
- case 0xFC, 0xFE, 0xFD:
- if c.video == nil {
- continue
- }
- var payload []byte
- if pType != 0xFD {
- payload = b[16:] // iframe
- } else {
- payload = b[8:] // pframe
- }
- c.videoTS += c.videoDT
- packet := &rtp.Packet{
- Header: rtp.Header{Timestamp: c.videoTS},
- Payload: annexb.EncodeToAVCC(payload),
- }
- //log.Printf("[AVC] %v, len: %d, ts: %10d", h265.Types(payload), len(payload), packet.Timestamp)
- c.video.WriteRTP(packet)
- case 0xFA: // audio
- if c.audio == nil {
- continue
- }
- payload := b[8:]
- c.audioTS += uint32(len(payload))
- c.audioSeq++
- packet := &rtp.Packet{
- Header: rtp.Header{
- Version: 2,
- Marker: true,
- SequenceNumber: c.audioSeq,
- Timestamp: c.audioTS,
- },
- Payload: payload,
- }
- //log.Printf("[DVR] len: %d, ts: %10d", len(packet.Payload), packet.Timestamp)
- c.audio.WriteRTP(packet)
- case 0xF9: // unknown
- default:
- println(fmt.Sprintf("dvrip: unknown packet type: %d", pType))
- }
- }
- }
- func (c *Producer) probe() error {
- if err := c.client.Play(); err != nil {
- return err
- }
- rd := core.NewReadBuffer(c.client.rd)
- rd.BufferSize = core.ProbeSize
- defer func() {
- c.client.buf = nil
- rd.Reset()
- }()
- c.client.rd = rd
- // some awful cameras has VERY rare keyframes
- // so we wait video+audio for default probe time
- // and wait anything for 15 seconds
- timeoutBoth := time.Now().Add(core.ProbeTimeout)
- timeoutAny := time.Now().Add(time.Second * 15)
- for {
- if now := time.Now(); now.Before(timeoutBoth) {
- if c.video != nil && c.audio != nil {
- return nil
- }
- } else if now.Before(timeoutAny) {
- if c.video != nil || c.audio != nil {
- return nil
- }
- } else {
- return errors.New("dvrip: can't probe medias")
- }
- tag, b, err := c.client.ReadPacket()
- if err != nil {
- return err
- }
- switch tag {
- case 0xFC, 0xFE: // video
- if c.video != nil {
- continue
- }
- fps := b[5]
- //width := uint16(b[6]) * 8
- //height := uint16(b[7]) * 8
- //println(width, height)
- ts := b[8:]
- // the exact value of the start TS does not matter
- c.videoTS = binary.LittleEndian.Uint32(ts)
- c.videoDT = 90000 / uint32(fps)
- payload := annexb.EncodeToAVCC(b[16:])
- c.addVideoTrack(b[4], payload)
- case 0xFA: // audio
- if c.audio != nil {
- continue
- }
- // the exact value of the start TS does not matter
- c.audioTS = c.videoTS
- c.addAudioTrack(b[4], b[5])
- }
- }
- }
- func (c *Producer) addVideoTrack(mediaCode byte, payload []byte) {
- var codec *core.Codec
- switch mediaCode {
- case 0x02, 0x12:
- codec = &core.Codec{
- Name: core.CodecH264,
- ClockRate: 90000,
- PayloadType: core.PayloadTypeRAW,
- FmtpLine: h264.GetFmtpLine(payload),
- }
- case 0x03, 0x13, 0x43, 0x53:
- codec = &core.Codec{
- Name: core.CodecH265,
- ClockRate: 90000,
- PayloadType: core.PayloadTypeRAW,
- FmtpLine: "profile-id=1",
- }
- for {
- size := 4 + int(binary.BigEndian.Uint32(payload))
- switch h265.NALUType(payload) {
- case h265.NALUTypeVPS:
- codec.FmtpLine += ";sprop-vps=" + base64.StdEncoding.EncodeToString(payload[4:size])
- case h265.NALUTypeSPS:
- codec.FmtpLine += ";sprop-sps=" + base64.StdEncoding.EncodeToString(payload[4:size])
- case h265.NALUTypePPS:
- codec.FmtpLine += ";sprop-pps=" + base64.StdEncoding.EncodeToString(payload[4:size])
- }
- if size < len(payload) {
- payload = payload[size:]
- } else {
- break
- }
- }
- default:
- println("[DVRIP] unsupported video codec:", mediaCode)
- return
- }
- media := &core.Media{
- Kind: core.KindVideo,
- Direction: core.DirectionRecvonly,
- Codecs: []*core.Codec{codec},
- }
- c.Medias = append(c.Medias, media)
- c.video = core.NewReceiver(media, codec)
- c.Receivers = append(c.Receivers, c.video)
- }
- var sampleRates = []uint32{4000, 8000, 11025, 16000, 20000, 22050, 32000, 44100, 48000}
- func (c *Producer) addAudioTrack(mediaCode byte, sampleRate byte) {
- // https://github.com/vigoss30611/buildroot-ltc/blob/master/system/qm/ipc/ProtocolService/src/ZhiNuo/inc/zn_dh_base_type.h
- // PCM8 = 7, G729, IMA_ADPCM, G711U, G721, PCM8_VWIS, MS_ADPCM, G711A, PCM16
- var codec *core.Codec
- switch mediaCode {
- case 10: // G711U
- codec = &core.Codec{
- Name: core.CodecPCMU,
- }
- case 14: // G711A
- codec = &core.Codec{
- Name: core.CodecPCMA,
- }
- default:
- println("[DVRIP] unsupported audio codec:", mediaCode)
- return
- }
- if sampleRate <= byte(len(sampleRates)) {
- codec.ClockRate = sampleRates[sampleRate-1]
- }
- media := &core.Media{
- Kind: core.KindAudio,
- Direction: core.DirectionRecvonly,
- Codecs: []*core.Codec{codec},
- }
- c.Medias = append(c.Medias, media)
- c.audio = core.NewReceiver(media, codec)
- c.Receivers = append(c.Receivers, c.audio)
- }
- //func (c *Client) MarshalJSON() ([]byte, error) {
- // info := &core.Info{
- // Type: "DVRIP active producer",
- // RemoteAddr: c.conn.RemoteAddr().String(),
- // Medias: c.Medias,
- // Receivers: c.Receivers,
- // Recv: c.Recv,
- // }
- // return json.Marshal(info)
- //}
|