From f45fef29d841e052e5625792b49581ec67b59f3e Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 8 Apr 2025 19:55:51 +0300 Subject: [PATCH] Add support eseecloud source #1690 --- internal/eseecloud/eseecloud.go | 10 ++ main.go | 2 + pkg/eseecloud/eseecloud.go | 180 ++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 internal/eseecloud/eseecloud.go create mode 100644 pkg/eseecloud/eseecloud.go diff --git a/internal/eseecloud/eseecloud.go b/internal/eseecloud/eseecloud.go new file mode 100644 index 00000000..bb4d9d31 --- /dev/null +++ b/internal/eseecloud/eseecloud.go @@ -0,0 +1,10 @@ +package eseecloud + +import ( + "github.com/AlexxIT/go2rtc/internal/streams" + "github.com/AlexxIT/go2rtc/pkg/eseecloud" +) + +func Init() { + streams.HandleFunc("eseecloud", eseecloud.Dial) +} diff --git a/main.go b/main.go index f3589ebf..0f36cafb 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "github.com/AlexxIT/go2rtc/internal/doorbird" "github.com/AlexxIT/go2rtc/internal/dvrip" "github.com/AlexxIT/go2rtc/internal/echo" + "github.com/AlexxIT/go2rtc/internal/eseecloud" "github.com/AlexxIT/go2rtc/internal/exec" "github.com/AlexxIT/go2rtc/internal/expr" "github.com/AlexxIT/go2rtc/internal/ffmpeg" @@ -90,6 +91,7 @@ func main() { doorbird.Init() // doorbird source v4l2.Init() // v4l2 source flussonic.Init() + eseecloud.Init() // 6. Helper modules diff --git a/pkg/eseecloud/eseecloud.go b/pkg/eseecloud/eseecloud.go new file mode 100644 index 00000000..05209d22 --- /dev/null +++ b/pkg/eseecloud/eseecloud.go @@ -0,0 +1,180 @@ +package eseecloud + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net/http" + "regexp" + "strings" + + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" + "github.com/pion/rtp" +) + +type Producer struct { + core.Connection + rd *core.ReadBuffer + + videoPT, audioPT uint8 +} + +func Dial(rawURL string) (core.Producer, error) { + rawURL, _ = strings.CutPrefix(rawURL, "eseecloud") + res, err := http.Get("http" + rawURL) + if err != nil { + return nil, err + } + + prod, err := Open(res.Body) + if err != nil { + return nil, err + } + + if info, ok := prod.(core.Info); ok { + info.SetProtocol("http") + info.SetURL(rawURL) + } + + return prod, nil +} + +func Open(r io.Reader) (core.Producer, error) { + prod := &Producer{ + Connection: core.Connection{ + ID: core.NewID(), + FormatName: "eseecloud", + Transport: r, + }, + rd: core.NewReadBuffer(r), + } + + if err := prod.probe(); err != nil { + return nil, err + } + + return prod, nil +} + +func (p *Producer) probe() error { + b, err := p.rd.Peek(1024) + if err != nil { + return err + } + + i := bytes.Index(b, []byte("\r\n\r\n")) + if i == -1 { + return io.EOF + } + + b = make([]byte, i+4) + _, _ = p.rd.Read(b) + + re := regexp.MustCompile(`m=(video|audio) (\d+) (\w+)/(\d+)\S*`) + for _, item := range re.FindAllStringSubmatch(string(b), 2) { + p.SDP += item[0] + "\n" + + switch item[3] { + case "H264", "H265": + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindVideo, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{ + { + Name: item[3], + ClockRate: 90000, + PayloadType: core.PayloadTypeRAW, + }, + }, + }) + p.videoPT = byte(core.Atoi(item[2])) + + case "G711": + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindAudio, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{ + { + Name: core.CodecPCMA, + ClockRate: 8000, + }, + }, + }) + p.audioPT = byte(core.Atoi(item[2])) + } + } + + return nil +} + +func (p *Producer) Start() error { + receivers := make(map[uint8]*core.Receiver) + + for _, receiver := range p.Receivers { + switch receiver.Codec.Kind() { + case core.KindVideo: + receivers[p.videoPT] = receiver + case core.KindAudio: + receivers[p.audioPT] = receiver + } + } + + for { + pkt, err := p.readPacket() + if err != nil { + return err + } + + if recv := receivers[pkt.PayloadType]; recv != nil { + switch recv.Codec.Name { + case core.CodecH264, core.CodecH265: + // timestamp = seconds x 1000000 + pkt = &rtp.Packet{ + Header: rtp.Header{ + Timestamp: uint32(uint64(pkt.Timestamp) * 90000 / 1000000), + }, + Payload: annexb.EncodeToAVCC(pkt.Payload), + } + case core.CodecPCMA: + pkt = &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: pkt.SequenceNumber, + Timestamp: uint32(uint64(pkt.Timestamp) * 8000 / 1000000), + }, + Payload: pkt.Payload, + } + } + recv.WriteRTP(pkt) + } + } +} + +func (p *Producer) readPacket() (*core.Packet, error) { + b := make([]byte, 8) + + if _, err := io.ReadFull(p.rd, b); err != nil { + return nil, err + } + + if b[0] != '$' { + return nil, errors.New("eseecloud: wrong start byte") + } + + size := binary.BigEndian.Uint32(b[4:]) + b = make([]byte, size) + if _, err := io.ReadFull(p.rd, b); err != nil { + return nil, err + } + + pkt := &core.Packet{} + if err := pkt.Unmarshal(b); err != nil { + return nil, err + } + + p.Recv += int(size) + + return pkt, nil +}