mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-06 08:46:57 +08:00
267 lines
5.6 KiB
Go
267 lines
5.6 KiB
Go
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.SuperProducer
|
|
|
|
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, false),
|
|
}
|
|
|
|
//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) Stop() error {
|
|
return c.client.Close()
|
|
}
|
|
|
|
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:], false)
|
|
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)
|
|
//}
|