From e9e19da25d8bb87f3fa03118e4df597224561415 Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Fri, 12 Sep 2025 21:58:38 +0300 Subject: [PATCH 1/9] add ipeye --- internal/ipeye/ipeye.go | 10 ++ main.go | 2 + pkg/ipeye/ipeye.go | 242 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+) create mode 100644 internal/ipeye/ipeye.go create mode 100644 pkg/ipeye/ipeye.go diff --git a/internal/ipeye/ipeye.go b/internal/ipeye/ipeye.go new file mode 100644 index 000000000..a230d8565 --- /dev/null +++ b/internal/ipeye/ipeye.go @@ -0,0 +1,10 @@ +package ipeye + +import ( + "github.com/AlexxIT/go2rtc/internal/streams" + "github.com/AlexxIT/go2rtc/pkg/ipeye" +) + +func Init() { + streams.HandleFunc("ipeye", ipeye.Dial) +} diff --git a/main.go b/main.go index e85c5900f..00348815b 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "github.com/AlexxIT/go2rtc/internal/hls" "github.com/AlexxIT/go2rtc/internal/homekit" "github.com/AlexxIT/go2rtc/internal/http" + "github.com/AlexxIT/go2rtc/internal/ipeye" "github.com/AlexxIT/go2rtc/internal/isapi" "github.com/AlexxIT/go2rtc/internal/ivideon" "github.com/AlexxIT/go2rtc/internal/mjpeg" @@ -98,6 +99,7 @@ func main() { flussonic.Init() eseecloud.Init() yandex.Init() + ipeye.Init() // 6. Helper modules diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go new file mode 100644 index 000000000..619752d05 --- /dev/null +++ b/pkg/ipeye/ipeye.go @@ -0,0 +1,242 @@ +package ipeye + +import ( + "bytes" + "errors" + "log" + "net/http" + "strings" + "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/iso" + "github.com/gorilla/websocket" + "github.com/pion/rtp" +) + +type Producer struct { + core.Connection + conn *websocket.Conn + videoTrackID uint32 + sps, pps []byte +} + +// Dial подключается к ipeye WebSocket с обязательным Origin +func Dial(source string) (core.Producer, error) { + url, _ := strings.CutPrefix(source, "ipeye:") + + dialer := websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 10 * time.Second, + } + header := http.Header{} + header.Set("Origin", "https://ipeye.ru") + + conn, _, err := dialer.Dial(url, header) + if err != nil { + return nil, err + } + + prod := &Producer{ + Connection: core.Connection{ + ID: core.NewID(), + FormatName: "ipeye", + Protocol: "wss", + RemoteAddr: conn.RemoteAddr().String(), + URL: url, + Transport: conn, + }, + conn: conn, + } + + if err = prod.probe(); err != nil { + _ = conn.Close() + return nil, err + } + + return prod, nil +} + +// первый пакет содержит строку кодеков ("avc1.42001F,mp4a.40.2") +func (p *Producer) probe() error { + _, b, err := p.conn.ReadMessage() + if err != nil { + return err + } + if len(b) == 0 || b[0] != 6 { + return errors.New("ipeye: invalid first packet (codec info not found)") + } + + codecStr := string(b[1:]) + log.Printf("[ipeye] codecs string: %s", codecStr) + + if strings.Contains(codecStr, "avc1") { + // создаём provisional H264 codec без SPS/PPS + codec := &core.Codec{ + Name: core.CodecH264, + ClockRate: 90000, + FmtpLine: "packetization-mode=1", + PayloadType: core.PayloadTypeRAW, + } + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindVideo, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{codec}, + }) + log.Printf("[ipeye] provisional H264 codec created") + } + return nil +} + +func (p *Producer) Start() error { + receivers := make(map[uint32]*core.Receiver) + for _, receiver := range p.Receivers { + if receiver.Codec.Kind() == core.KindVideo { + receivers[0] = receiver // trackID узнаем позже + } + } + + sequencer := rtp.NewRandomSequencer() + payloader := &h264.Payloader{IsAVC: false} + + log.Printf("[ipeye] start streaming") + + var trackDetected bool + + for { + mType, b, err := p.conn.ReadMessage() + if err != nil { + log.Printf("[ipeye] read error: %v", err) + return err + } + if mType != websocket.BinaryMessage { + continue + } + + atoms, err := iso.DecodeAtoms(b) + if err != nil { + log.Printf("[ipeye] iso.DecodeAtoms error: %v", err) + continue + } + + var trackID uint32 + var decodeTime uint64 + var data []byte + + for _, atom := range atoms { + switch atom := atom.(type) { + case *iso.AtomTfhd: + trackID = atom.TrackID + if !trackDetected { + p.videoTrackID = trackID + if recv0, ok := receivers[0]; ok { + receivers = make(map[uint32]*core.Receiver) + receivers[trackID] = recv0 + } + trackDetected = true + log.Printf("[ipeye] detected video trackID=%d", trackID) + } + case *iso.AtomTfdt: + decodeTime = atom.DecodeTime + case *iso.AtomMdat: + data = atom.Data + } + } + + if recv := receivers[trackID]; recv != nil && len(data) > 0 { + annex := annexb.DecodeAVCC(data, true) + if annex == nil { + continue + } + + // если ещё нет SPS/PPS — пробуем достать + if p.sps == nil || p.pps == nil { + sps, pps := extractSPSPPS(annex) + if sps != nil && pps != nil { + p.sps, p.pps = sps, pps + codec := h264.ConfigToCodec(h264.EncodeConfig(sps, pps)) + p.Medias[0].Codecs = []*core.Codec{codec} + log.Printf("[ipeye] Codec updated with SPS/PPS: %s", codec.FmtpLine) + } + } + + // timestamp в 90 кГц + ts := uint32(decodeTime * 90) + + // если это ключевой кадр — добавляем SPS/PPS в поток + if h264.IsKeyframe(annex) && p.sps != nil && p.pps != nil { + ps := h264.JoinNALU(p.sps, p.pps) + annex = append(ps, annex...) + log.Printf("[ipeye] prepended SPS/PPS to keyframe") + } + + payloads := payloader.Payload(1400, annex) + if len(payloads) == 0 { + continue + } + + last := len(payloads) - 1 + for i, pl := range payloads { + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + PayloadType: recv.Codec.PayloadType, + SequenceNumber: sequencer.NextSequenceNumber(), + Timestamp: ts, + Marker: i == last, + }, + Payload: pl, + } + recv.WriteRTP(pkt) + } + + log.Printf("[ipeye] sent frame track=%d ts=%d parts=%d", trackID, ts, len(payloads)) + } + } +} + +// extractSPSPPS ищет SPS/PPS в AnnexB-потоке +func extractSPSPPS(b []byte) (sps, pps []byte) { + const startCode = "\x00\x00\x00\x01" + for { + i := bytes.Index(b, []byte(startCode)) + if i < 0 || i+4 >= len(b) { + break + } + b = b[i+4:] + + ntype := h264.NALUType(b) + size := nextNALUSize(b) + + switch ntype { + case h264.NALUTypeSPS: + if sps == nil { + sps = b[:size] + } + case h264.NALUTypePPS: + if pps == nil { + pps = b[:size] + } + } + + if sps != nil && pps != nil { + return + } + if size <= 0 || size >= len(b) { + break + } + b = b[size:] + } + return +} + +func nextNALUSize(b []byte) int { + const startCode = "\x00\x00\x00\x01" + i := bytes.Index(b, []byte(startCode)) + if i < 0 { + return len(b) + } + return i +} From 732a443e2f6b55657444fcf48477a9c690e07d68 Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Fri, 12 Sep 2025 22:48:38 +0300 Subject: [PATCH 2/9] playing version --- go.mod | 3 +- go.sum | 51 +--------- pkg/ipeye/ipeye.go | 232 ++++++++++++++++++++------------------------- 3 files changed, 110 insertions(+), 176 deletions(-) diff --git a/go.mod b/go.mod index 7abf1eddf..0990bdf68 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AlexxIT/go2rtc go 1.23.0 require ( - github.com/asticode/go-astits v1.13.0 + github.com/bluenviron/mediacommon v1.14.0 github.com/expr-lang/expr v1.17.5 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 @@ -27,7 +27,6 @@ require ( ) require ( - github.com/asticode/go-astikit v0.56.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect diff --git a/go.sum b/go.sum index 7e1b0ceee..1ffb0ce3d 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,14 @@ -github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= -github.com/asticode/go-astikit v0.54.0 h1:uq9eurgisdkYwJU9vSWIQaPH4MH0cac82sQH00kmSNQ= -github.com/asticode/go-astikit v0.54.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= -github.com/asticode/go-astikit v0.56.0 h1:DmD2p7YnvxiPdF0h+dRmos3bsejNEXbycENsY5JfBqw= -github.com/asticode/go-astikit v0.56.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= -github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwfKZ1c= -github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= +github.com/bluenviron/mediacommon v1.14.0 h1:lWCwOBKNKgqmspRpwpvvg3CidYm+XOc2+z/Jw7LM5dQ= +github.com/bluenviron/mediacommon v1.14.0/go.mod h1:z5LP9Tm1ZNfQV5Co54PyOzaIhGMusDfRKmh42nQSnyo= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/expr-lang/expr v1.17.2 h1:o0A99O/Px+/DTjEnQiodAgOIK9PPxL8DtXhBRKC+Iso= -github.com/expr-lang/expr v1.17.2/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k= github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -30,24 +24,16 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= -github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= -github.com/pion/ice/v4 v4.0.9 h1:VKgU4MwA2LUDVLq+WBkpEHTcAb8c5iCvFMECeuPOZNk= -github.com/pion/ice/v4 v4.0.9/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= -github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= -github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= -github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= -github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= @@ -56,37 +42,24 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= -github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg= -github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI= github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= -github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs= -github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI= -github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= -github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M= -github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= -github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= -github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= -github.com/pion/webrtc/v4 v4.0.14 h1:nyds/sFRR+HvmWoBa6wrL46sSfpArE0qR883MBW96lg= -github.com/pion/webrtc/v4 v4.0.14/go.mod h1:R3+qTnQTS03UzwDarYecgioNf7DYgTsldxnCXB821Kk= github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c= github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -98,43 +71,29 @@ github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfU github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go index 619752d05..9286d65a5 100644 --- a/pkg/ipeye/ipeye.go +++ b/pkg/ipeye/ipeye.go @@ -1,8 +1,6 @@ package ipeye import ( - "bytes" - "errors" "log" "net/http" "strings" @@ -10,10 +8,12 @@ import ( "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264" - "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/iso" "github.com/gorilla/websocket" "github.com/pion/rtp" + "github.com/pion/rtp/codecs" + + mediacommonh264 "github.com/bluenviron/mediacommon/pkg/codecs/h264" ) type Producer struct { @@ -59,51 +59,70 @@ func Dial(source string) (core.Producer, error) { return prod, nil } -// первый пакет содержит строку кодеков ("avc1.42001F,mp4a.40.2") +// probe ждёт init с avcC и извлекает SPS/PPS func (p *Producer) probe() error { - _, b, err := p.conn.ReadMessage() - if err != nil { - return err - } - if len(b) == 0 || b[0] != 6 { - return errors.New("ipeye: invalid first packet (codec info not found)") - } + for { + mType, b, err := p.conn.ReadMessage() + if err != nil { + return err + } - codecStr := string(b[1:]) - log.Printf("[ipeye] codecs string: %s", codecStr) + if mType == websocket.BinaryMessage { + atoms, err := iso.DecodeAtoms(b) + if err != nil { + continue + } - if strings.Contains(codecStr, "avc1") { - // создаём provisional H264 codec без SPS/PPS - codec := &core.Codec{ - Name: core.CodecH264, - ClockRate: 90000, - FmtpLine: "packetization-mode=1", - PayloadType: core.PayloadTypeRAW, + var trackID uint32 + + for _, atom := range atoms { + switch atom := atom.(type) { + case *iso.AtomTkhd: + trackID = atom.TrackID + case *iso.AtomVideo: + if atom.Name == "avc1" { + codec := h264.AVCCToCodec(atom.Config) + sps, pps := parseSPSPPS(atom.Config) + p.sps, p.pps = sps, pps + + p.Medias = append(p.Medias, &core.Media{ + Kind: core.KindVideo, + Direction: core.DirectionRecvonly, + Codecs: []*core.Codec{codec}, + }) + p.videoTrackID = trackID + + log.Printf("[ipeye] fMP4 video avc1 trackID=%d", trackID) + } + } + } + + if len(p.Medias) > 0 { + log.Printf("[ipeye] detected fMP4 init with %d medias", len(p.Medias)) + return nil + } } - p.Medias = append(p.Medias, &core.Media{ - Kind: core.KindVideo, - Direction: core.DirectionRecvonly, - Codecs: []*core.Codec{codec}, - }) - log.Printf("[ipeye] provisional H264 codec created") } - return nil } +// Start запускает основной цикл чтения фрагментов func (p *Producer) Start() error { receivers := make(map[uint32]*core.Receiver) - for _, receiver := range p.Receivers { - if receiver.Codec.Kind() == core.KindVideo { - receivers[0] = receiver // trackID узнаем позже + if p.videoTrackID != 0 { + for _, receiver := range p.Receivers { + if receiver.Codec.Kind() == core.KindVideo { + receivers[p.videoTrackID] = receiver + } } } - sequencer := rtp.NewRandomSequencer() - payloader := &h264.Payloader{IsAVC: false} - - log.Printf("[ipeye] start streaming") + // RTP packetizer + h264Pay := &codecs.H264Payloader{} + seq := rtp.NewRandomSequencer() + h264pkt := rtp.NewPacketizer(1200, 96, 0, h264Pay, seq, 90000) - var trackDetected bool + var tsCounter uint32 = 0 + const frameDur = 90000 / 25 // фиксированный шаг для 25fps for { mType, b, err := p.conn.ReadMessage() @@ -117,126 +136,83 @@ func (p *Producer) Start() error { atoms, err := iso.DecodeAtoms(b) if err != nil { - log.Printf("[ipeye] iso.DecodeAtoms error: %v", err) continue } var trackID uint32 - var decodeTime uint64 - var data []byte + var mdatData []byte for _, atom := range atoms { switch atom := atom.(type) { case *iso.AtomTfhd: trackID = atom.TrackID - if !trackDetected { - p.videoTrackID = trackID - if recv0, ok := receivers[0]; ok { - receivers = make(map[uint32]*core.Receiver) - receivers[trackID] = recv0 - } - trackDetected = true - log.Printf("[ipeye] detected video trackID=%d", trackID) - } - case *iso.AtomTfdt: - decodeTime = atom.DecodeTime case *iso.AtomMdat: - data = atom.Data + mdatData = atom.Data } } - if recv := receivers[trackID]; recv != nil && len(data) > 0 { - annex := annexb.DecodeAVCC(data, true) - if annex == nil { + if recv := receivers[trackID]; recv != nil && len(mdatData) > 0 { + var avcc mediacommonh264.AVCC + if err := avcc.Unmarshal(mdatData); err != nil { + log.Printf("[ipeye] avcc unmarshal error: %v", err) continue } - // если ещё нет SPS/PPS — пробуем достать - if p.sps == nil || p.pps == nil { - sps, pps := extractSPSPPS(annex) - if sps != nil && pps != nil { - p.sps, p.pps = sps, pps - codec := h264.ConfigToCodec(h264.EncodeConfig(sps, pps)) - p.Medias[0].Codecs = []*core.Codec{codec} - log.Printf("[ipeye] Codec updated with SPS/PPS: %s", codec.FmtpLine) - } - } - - // timestamp в 90 кГц - ts := uint32(decodeTime * 90) + for _, nalu := range avcc { + typ := nalu[0] & 0x1F - // если это ключевой кадр — добавляем SPS/PPS в поток - if h264.IsKeyframe(annex) && p.sps != nil && p.pps != nil { - ps := h264.JoinNALU(p.sps, p.pps) - annex = append(ps, annex...) - log.Printf("[ipeye] prepended SPS/PPS to keyframe") - } - - payloads := payloader.Payload(1400, annex) - if len(payloads) == 0 { - continue - } + // Если IDR — добавляем SPS/PPS + if typ == 5 { + if len(p.sps) > 0 { + for _, pkt := range h264pkt.Packetize(p.sps, tsCounter) { + recv.WriteRTP(pkt) + } + } + if len(p.pps) > 0 { + for _, pkt := range h264pkt.Packetize(p.pps, tsCounter) { + recv.WriteRTP(pkt) + } + } + } - last := len(payloads) - 1 - for i, pl := range payloads { - pkt := &rtp.Packet{ - Header: rtp.Header{ - Version: 2, - PayloadType: recv.Codec.PayloadType, - SequenceNumber: sequencer.NextSequenceNumber(), - Timestamp: ts, - Marker: i == last, - }, - Payload: pl, + for _, pkt := range h264pkt.Packetize(nalu, tsCounter) { + recv.WriteRTP(pkt) } - recv.WriteRTP(pkt) } - - log.Printf("[ipeye] sent frame track=%d ts=%d parts=%d", trackID, ts, len(payloads)) + tsCounter += frameDur } } } -// extractSPSPPS ищет SPS/PPS в AnnexB-потоке -func extractSPSPPS(b []byte) (sps, pps []byte) { - const startCode = "\x00\x00\x00\x01" - for { - i := bytes.Index(b, []byte(startCode)) - if i < 0 || i+4 >= len(b) { - break - } - b = b[i+4:] - - ntype := h264.NALUType(b) - size := nextNALUSize(b) - - switch ntype { - case h264.NALUTypeSPS: - if sps == nil { - sps = b[:size] - } - case h264.NALUTypePPS: - if pps == nil { - pps = b[:size] - } - } - - if sps != nil && pps != nil { +// parseSPSPPS парсит SPS/PPS из avcC +func parseSPSPPS(avcc []byte) (sps, pps []byte) { + if len(avcc) < 7 { + return + } + numSPS := int(avcc[5] & 0x1F) + pos := 6 + for i := 0; i < numSPS && pos+2 <= len(avcc); i++ { + size := int(avcc[pos])<<8 | int(avcc[pos+1]) + pos += 2 + if pos+size > len(avcc) { return } - if size <= 0 || size >= len(b) { - break + sps = avcc[pos : pos+size] + pos += size + } + if pos >= len(avcc) { + return + } + numPPS := int(avcc[pos]) + pos++ + for i := 0; i < numPPS && pos+2 <= len(avcc); i++ { + size := int(avcc[pos])<<8 | int(avcc[pos+1]) + pos += 2 + if pos+size > len(avcc) { + return } - b = b[size:] + pps = avcc[pos : pos+size] + pos += size } return } - -func nextNALUSize(b []byte) int { - const startCode = "\x00\x00\x00\x01" - i := bytes.Index(b, []byte(startCode)) - if i < 0 { - return len(b) - } - return i -} From ac520e7106b094705f2447f24cdf533f40235178 Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Sat, 13 Sep 2025 06:40:40 +0300 Subject: [PATCH 3/9] fix time --- pkg/ipeye/ipeye.go | 117 +++++++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 30 deletions(-) diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go index 9286d65a5..97c4fdacc 100644 --- a/pkg/ipeye/ipeye.go +++ b/pkg/ipeye/ipeye.go @@ -2,6 +2,7 @@ package ipeye import ( "log" + "math/rand" "net/http" "strings" "time" @@ -18,9 +19,13 @@ import ( type Producer struct { core.Connection - conn *websocket.Conn - videoTrackID uint32 - sps, pps []byte + conn *websocket.Conn + + videoTrackID uint32 + clockRate uint32 + sps, pps []byte + baseSet bool + baseDecodeTime uint64 } // Dial подключается к ipeye WebSocket с обязательным Origin @@ -73,12 +78,14 @@ func (p *Producer) probe() error { continue } - var trackID uint32 + var trackID, timeScale uint32 for _, atom := range atoms { switch atom := atom.(type) { case *iso.AtomTkhd: trackID = atom.TrackID + case *iso.AtomMdhd: + timeScale = atom.TimeScale case *iso.AtomVideo: if atom.Name == "avc1" { codec := h264.AVCCToCodec(atom.Config) @@ -91,8 +98,9 @@ func (p *Producer) probe() error { Codecs: []*core.Codec{codec}, }) p.videoTrackID = trackID + p.clockRate = codec.ClockRate - log.Printf("[ipeye] fMP4 video avc1 trackID=%d", trackID) + log.Printf("[ipeye] fMP4 video avc1 trackID=%d timeScale=%d", trackID, timeScale) } } } @@ -108,6 +116,7 @@ func (p *Producer) probe() error { // Start запускает основной цикл чтения фрагментов func (p *Producer) Start() error { receivers := make(map[uint32]*core.Receiver) + if p.videoTrackID != 0 { for _, receiver := range p.Receivers { if receiver.Codec.Kind() == core.KindVideo { @@ -119,10 +128,13 @@ func (p *Producer) Start() error { // RTP packetizer h264Pay := &codecs.H264Payloader{} seq := rtp.NewRandomSequencer() - h264pkt := rtp.NewPacketizer(1200, 96, 0, h264Pay, seq, 90000) + h264pkt := rtp.NewPacketizer(1200, 96, 0, h264Pay, seq, p.clockRate) - var tsCounter uint32 = 0 - const frameDur = 90000 / 25 // фиксированный шаг для 25fps + // глобальные счётчики + rtpStart := rand.Uint32() + var dts uint64 + var defaultDur uint32 + var initialized bool for { mType, b, err := p.conn.ReadMessage() @@ -141,45 +153,90 @@ func (p *Producer) Start() error { var trackID uint32 var mdatData []byte + var samplesDur []uint32 for _, atom := range atoms { switch atom := atom.(type) { case *iso.AtomTfhd: trackID = atom.TrackID + defaultDur = atom.SampleDuration + + case *iso.AtomTfdt: + if !initialized { + log.Printf("[ipeye] init DecodeTime=%d rtpStart=%d", + atom.DecodeTime, rtpStart) + dts = atom.DecodeTime + initialized = true + } + + case *iso.AtomTrun: + samplesDur = atom.SamplesDuration + case *iso.AtomMdat: mdatData = atom.Data } } - if recv := receivers[trackID]; recv != nil && len(mdatData) > 0 { - var avcc mediacommonh264.AVCC - if err := avcc.Unmarshal(mdatData); err != nil { - log.Printf("[ipeye] avcc unmarshal error: %v", err) - continue - } + recv := receivers[trackID] + if recv == nil || len(mdatData) == 0 { + continue + } + + var avcc mediacommonh264.AVCC + if err := avcc.Unmarshal(mdatData); err != nil { + log.Printf("[ipeye] avcc unmarshal error: %v", err) + continue + } + + // бежим по сэмплам + for i, nalu := range avcc { + typ := nalu[0] & 0x1F - for _, nalu := range avcc { - typ := nalu[0] & 0x1F + // считаем RTP TS для этого сэмпла + ts := rtpStart + uint32(dts*uint64(p.clockRate)/90000) - // Если IDR — добавляем SPS/PPS - if typ == 5 { - if len(p.sps) > 0 { - for _, pkt := range h264pkt.Packetize(p.sps, tsCounter) { - recv.WriteRTP(pkt) - } + // SPS/PPS перед IDR + if typ == 5 { + if len(p.sps) > 0 { + for _, pkt := range h264pkt.Packetize(p.sps, ts) { + pkt.Timestamp = ts + recv.Input(pkt) + log.Printf("[ipeye] RTP ts=%d seq=%d size=%d type=SPS", + pkt.Timestamp, pkt.SequenceNumber, len(pkt.Payload)) } - if len(p.pps) > 0 { - for _, pkt := range h264pkt.Packetize(p.pps, tsCounter) { - recv.WriteRTP(pkt) - } + } + if len(p.pps) > 0 { + for _, pkt := range h264pkt.Packetize(p.pps, ts) { + pkt.Timestamp = ts + recv.Input(pkt) + log.Printf("[ipeye] RTP ts=%d seq=%d size=%d type=PPS", + pkt.Timestamp, pkt.SequenceNumber, len(pkt.Payload)) } } + } - for _, pkt := range h264pkt.Packetize(nalu, tsCounter) { - recv.WriteRTP(pkt) - } + // сам кадр + for _, pkt := range h264pkt.Packetize(nalu, ts) { + pkt.Timestamp = ts // фиксируем TS у всех пакетов + recv.Input(pkt) + log.Printf("[ipeye] RTP ts=%d seq=%d size=%d type=%d", + pkt.Timestamp, pkt.SequenceNumber, len(pkt.Payload), typ) } - tsCounter += frameDur + + // выбираем длительность + dur := defaultDur + if i < len(samplesDur) && samplesDur[i] != 0 { + dur = samplesDur[i] + } + if dur == 0 { + dur = p.clockRate / 25 // fallback + } + + log.Printf("[ipeye] sample=%d dur=%d DTS=%d RTP.ts=%d NALU.type=%d size=%d", + i, dur, dts, ts, typ, len(nalu)) + + // двигаем DTS + dts += uint64(dur) } } } From ccaf271bffd6d044131a465811a246b61fa8c078 Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Sat, 13 Sep 2025 06:45:21 +0300 Subject: [PATCH 4/9] added dts reset --- pkg/ipeye/ipeye.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go index 97c4fdacc..9a9c2c7d7 100644 --- a/pkg/ipeye/ipeye.go +++ b/pkg/ipeye/ipeye.go @@ -136,6 +136,8 @@ func (p *Producer) Start() error { var defaultDur uint32 var initialized bool + const wrapPeriod = uint64(1) << 32 // RTP TS цикличный (mod 2^32) + for { mType, b, err := p.conn.ReadMessage() if err != nil { @@ -192,8 +194,8 @@ func (p *Producer) Start() error { for i, nalu := range avcc { typ := nalu[0] & 0x1F - // считаем RTP TS для этого сэмпла - ts := rtpStart + uint32(dts*uint64(p.clockRate)/90000) + // считаем RTP TS для этого сэмпла (mod 2^32) + ts := rtpStart + uint32((dts*uint64(p.clockRate)/90000)%wrapPeriod) // SPS/PPS перед IDR if typ == 5 { @@ -237,6 +239,9 @@ func (p *Producer) Start() error { // двигаем DTS dts += uint64(dur) + if dts >= wrapPeriod { + dts %= wrapPeriod + } } } } From 86a0df93115be429bd3be304eb060e390eb27236 Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Sat, 13 Sep 2025 06:50:57 +0300 Subject: [PATCH 5/9] added app logger --- pkg/ipeye/ipeye.go | 84 ++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go index 9a9c2c7d7..20174c805 100644 --- a/pkg/ipeye/ipeye.go +++ b/pkg/ipeye/ipeye.go @@ -1,12 +1,12 @@ package ipeye import ( - "log" "math/rand" "net/http" "strings" "time" + "github.com/AlexxIT/go2rtc/internal/app" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264" "github.com/AlexxIT/go2rtc/pkg/iso" @@ -28,7 +28,7 @@ type Producer struct { baseDecodeTime uint64 } -// Dial подключается к ipeye WebSocket с обязательным Origin +// Dial connects to ipeye WebSocket with required Origin func Dial(source string) (core.Producer, error) { url, _ := strings.CutPrefix(source, "ipeye:") @@ -64,8 +64,10 @@ func Dial(source string) (core.Producer, error) { return prod, nil } -// probe ждёт init с avcC и извлекает SPS/PPS +// probe waits for init with avcC and extracts SPS/PPS func (p *Producer) probe() error { + log := app.GetLogger("ipeye") + for { mType, b, err := p.conn.ReadMessage() if err != nil { @@ -100,23 +102,27 @@ func (p *Producer) probe() error { p.videoTrackID = trackID p.clockRate = codec.ClockRate - log.Printf("[ipeye] fMP4 video avc1 trackID=%d timeScale=%d", trackID, timeScale) + log.Info(). + Uint32("trackID", trackID). + Uint32("timeScale", timeScale). + Msg("fMP4 video detected") } } } if len(p.Medias) > 0 { - log.Printf("[ipeye] detected fMP4 init with %d medias", len(p.Medias)) + log.Info().Int("medias", len(p.Medias)).Msg("fMP4 init complete") return nil } } } } -// Start запускает основной цикл чтения фрагментов +// Start runs the main fragment reading loop func (p *Producer) Start() error { - receivers := make(map[uint32]*core.Receiver) + log := app.GetLogger("ipeye") + receivers := make(map[uint32]*core.Receiver) if p.videoTrackID != 0 { for _, receiver := range p.Receivers { if receiver.Codec.Kind() == core.KindVideo { @@ -130,18 +136,18 @@ func (p *Producer) Start() error { seq := rtp.NewRandomSequencer() h264pkt := rtp.NewPacketizer(1200, 96, 0, h264Pay, seq, p.clockRate) - // глобальные счётчики + // global counters rtpStart := rand.Uint32() var dts uint64 var defaultDur uint32 var initialized bool - const wrapPeriod = uint64(1) << 32 // RTP TS цикличный (mod 2^32) + const wrapPeriod = uint64(1) << 32 // RTP TS wraps (mod 2^32) for { mType, b, err := p.conn.ReadMessage() if err != nil { - log.Printf("[ipeye] read error: %v", err) + log.Error().Err(err).Msg("read error") return err } if mType != websocket.BinaryMessage { @@ -165,8 +171,10 @@ func (p *Producer) Start() error { case *iso.AtomTfdt: if !initialized { - log.Printf("[ipeye] init DecodeTime=%d rtpStart=%d", - atom.DecodeTime, rtpStart) + log.Info(). + Uint64("decodeTime", atom.DecodeTime). + Uint32("rtpStart", rtpStart). + Msg("stream initialized") dts = atom.DecodeTime initialized = true } @@ -186,46 +194,58 @@ func (p *Producer) Start() error { var avcc mediacommonh264.AVCC if err := avcc.Unmarshal(mdatData); err != nil { - log.Printf("[ipeye] avcc unmarshal error: %v", err) + log.Warn().Err(err).Msg("AVCC unmarshal failed") continue } - // бежим по сэмплам + // iterate over samples for i, nalu := range avcc { typ := nalu[0] & 0x1F - // считаем RTP TS для этого сэмпла (mod 2^32) + // RTP TS for this sample (mod 2^32) ts := rtpStart + uint32((dts*uint64(p.clockRate)/90000)%wrapPeriod) - // SPS/PPS перед IDR + // SPS/PPS before IDR if typ == 5 { if len(p.sps) > 0 { for _, pkt := range h264pkt.Packetize(p.sps, ts) { pkt.Timestamp = ts recv.Input(pkt) - log.Printf("[ipeye] RTP ts=%d seq=%d size=%d type=SPS", - pkt.Timestamp, pkt.SequenceNumber, len(pkt.Payload)) + log.Debug(). + Uint32("ts", pkt.Timestamp). + Uint16("seq", pkt.SequenceNumber). + Int("size", len(pkt.Payload)). + Str("type", "SPS"). + Msg("RTP packet") } } if len(p.pps) > 0 { for _, pkt := range h264pkt.Packetize(p.pps, ts) { pkt.Timestamp = ts recv.Input(pkt) - log.Printf("[ipeye] RTP ts=%d seq=%d size=%d type=PPS", - pkt.Timestamp, pkt.SequenceNumber, len(pkt.Payload)) + log.Debug(). + Uint32("ts", pkt.Timestamp). + Uint16("seq", pkt.SequenceNumber). + Int("size", len(pkt.Payload)). + Str("type", "PPS"). + Msg("RTP packet") } } } - // сам кадр + // actual frame for _, pkt := range h264pkt.Packetize(nalu, ts) { - pkt.Timestamp = ts // фиксируем TS у всех пакетов + pkt.Timestamp = ts recv.Input(pkt) - log.Printf("[ipeye] RTP ts=%d seq=%d size=%d type=%d", - pkt.Timestamp, pkt.SequenceNumber, len(pkt.Payload), typ) + log.Debug(). + Uint32("ts", pkt.Timestamp). + Uint16("seq", pkt.SequenceNumber). + Int("size", len(pkt.Payload)). + Int("nalType", int(typ)). + Msg("RTP packet") } - // выбираем длительность + // duration selection dur := defaultDur if i < len(samplesDur) && samplesDur[i] != 0 { dur = samplesDur[i] @@ -234,10 +254,16 @@ func (p *Producer) Start() error { dur = p.clockRate / 25 // fallback } - log.Printf("[ipeye] sample=%d dur=%d DTS=%d RTP.ts=%d NALU.type=%d size=%d", - i, dur, dts, ts, typ, len(nalu)) + log.Trace(). + Int("sample", i). + Uint32("dur", dur). + Uint64("dts", dts). + Uint32("rtpTS", ts). + Int("nalType", int(typ)). + Int("size", len(nalu)). + Msg("sample processed") - // двигаем DTS + // increment DTS dts += uint64(dur) if dts >= wrapPeriod { dts %= wrapPeriod @@ -246,7 +272,7 @@ func (p *Producer) Start() error { } } -// parseSPSPPS парсит SPS/PPS из avcC +// parseSPSPPS extracts SPS/PPS from avcC func parseSPSPPS(avcc []byte) (sps, pps []byte) { if len(avcc) < 7 { return From 024be8ef532e5aa885f468f2d253453eb7b23d31 Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Sat, 13 Sep 2025 06:58:47 +0300 Subject: [PATCH 6/9] removed mediacommonh264 --- pkg/ipeye/ipeye.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go index 20174c805..0aaa0bb0e 100644 --- a/pkg/ipeye/ipeye.go +++ b/pkg/ipeye/ipeye.go @@ -1,6 +1,7 @@ package ipeye import ( + "bytes" "math/rand" "net/http" "strings" @@ -9,12 +10,11 @@ import ( "github.com/AlexxIT/go2rtc/internal/app" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264" + "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/iso" "github.com/gorilla/websocket" "github.com/pion/rtp" "github.com/pion/rtp/codecs" - - mediacommonh264 "github.com/bluenviron/mediacommon/pkg/codecs/h264" ) type Producer struct { @@ -192,21 +192,22 @@ func (p *Producer) Start() error { continue } - var avcc mediacommonh264.AVCC - if err := avcc.Unmarshal(mdatData); err != nil { - log.Warn().Err(err).Msg("AVCC unmarshal failed") - continue - } + // convert AVCC -> AnnexB + annexbData := annexb.DecodeAVCC(mdatData, true) + nalus := bytes.Split(annexbData, []byte{0, 0, 0, 1}) - // iterate over samples - for i, nalu := range avcc { + // iterate over NALUs + for i, nalu := range nalus { + if len(nalu) == 0 { + continue + } typ := nalu[0] & 0x1F // RTP TS for this sample (mod 2^32) ts := rtpStart + uint32((dts*uint64(p.clockRate)/90000)%wrapPeriod) // SPS/PPS before IDR - if typ == 5 { + if typ == h264.NALUTypeIFrame { if len(p.sps) > 0 { for _, pkt := range h264pkt.Packetize(p.sps, ts) { pkt.Timestamp = ts From 0e073b45b43fc0525f272308b95a8907b0682699 Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 8 Jul 2025 12:43:14 +0300 Subject: [PATCH 7/9] rollback go.mod go.sum --- go.mod | 3 ++- go.sum | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0990bdf68..7abf1eddf 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AlexxIT/go2rtc go 1.23.0 require ( - github.com/bluenviron/mediacommon v1.14.0 + github.com/asticode/go-astits v1.13.0 github.com/expr-lang/expr v1.17.5 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 @@ -27,6 +27,7 @@ require ( ) require ( + github.com/asticode/go-astikit v0.56.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect diff --git a/go.sum b/go.sum index 1ffb0ce3d..7e1b0ceee 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,20 @@ -github.com/bluenviron/mediacommon v1.14.0 h1:lWCwOBKNKgqmspRpwpvvg3CidYm+XOc2+z/Jw7LM5dQ= -github.com/bluenviron/mediacommon v1.14.0/go.mod h1:z5LP9Tm1ZNfQV5Co54PyOzaIhGMusDfRKmh42nQSnyo= +github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= +github.com/asticode/go-astikit v0.54.0 h1:uq9eurgisdkYwJU9vSWIQaPH4MH0cac82sQH00kmSNQ= +github.com/asticode/go-astikit v0.54.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= +github.com/asticode/go-astikit v0.56.0 h1:DmD2p7YnvxiPdF0h+dRmos3bsejNEXbycENsY5JfBqw= +github.com/asticode/go-astikit v0.56.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= +github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwfKZ1c= +github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/expr-lang/expr v1.17.2 h1:o0A99O/Px+/DTjEnQiodAgOIK9PPxL8DtXhBRKC+Iso= +github.com/expr-lang/expr v1.17.2/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k= github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -24,16 +30,24 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= +github.com/pion/ice/v4 v4.0.9 h1:VKgU4MwA2LUDVLq+WBkpEHTcAb8c5iCvFMECeuPOZNk= +github.com/pion/ice/v4 v4.0.9/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= +github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= +github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= +github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= +github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= @@ -42,24 +56,37 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= +github.com/pion/rtp v1.8.13 h1:8uSUPpjSL4OlwZI8Ygqu7+h2p9NPFB+yAZ461Xn5sNg= +github.com/pion/rtp v1.8.13/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI= github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= +github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs= +github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= +github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI= +github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= +github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M= +github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= +github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= +github.com/pion/webrtc/v4 v4.0.14 h1:nyds/sFRR+HvmWoBa6wrL46sSfpArE0qR883MBW96lg= +github.com/pion/webrtc/v4 v4.0.14/go.mod h1:R3+qTnQTS03UzwDarYecgioNf7DYgTsldxnCXB821Kk= github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c= github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -71,29 +98,43 @@ github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfU github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA= github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From debbb4539bef3c70aa2ef4fe07291572e4271c84 Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Sat, 13 Sep 2025 21:02:55 +0300 Subject: [PATCH 8/9] fixes from review --- pkg/ipeye/ipeye.go | 135 +++++++++++---------------------------------- 1 file changed, 32 insertions(+), 103 deletions(-) diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go index 0aaa0bb0e..06527b62a 100644 --- a/pkg/ipeye/ipeye.go +++ b/pkg/ipeye/ipeye.go @@ -1,20 +1,16 @@ package ipeye import ( - "bytes" "math/rand" "net/http" "strings" "time" - "github.com/AlexxIT/go2rtc/internal/app" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/h264" - "github.com/AlexxIT/go2rtc/pkg/h264/annexb" "github.com/AlexxIT/go2rtc/pkg/iso" "github.com/gorilla/websocket" "github.com/pion/rtp" - "github.com/pion/rtp/codecs" ) type Producer struct { @@ -66,7 +62,6 @@ func Dial(source string) (core.Producer, error) { // probe waits for init with avcC and extracts SPS/PPS func (p *Producer) probe() error { - log := app.GetLogger("ipeye") for { mType, b, err := p.conn.ReadMessage() @@ -80,14 +75,12 @@ func (p *Producer) probe() error { continue } - var trackID, timeScale uint32 + var trackID uint32 for _, atom := range atoms { switch atom := atom.(type) { case *iso.AtomTkhd: trackID = atom.TrackID - case *iso.AtomMdhd: - timeScale = atom.TimeScale case *iso.AtomVideo: if atom.Name == "avc1" { codec := h264.AVCCToCodec(atom.Config) @@ -101,17 +94,11 @@ func (p *Producer) probe() error { }) p.videoTrackID = trackID p.clockRate = codec.ClockRate - - log.Info(). - Uint32("trackID", trackID). - Uint32("timeScale", timeScale). - Msg("fMP4 video detected") } } } if len(p.Medias) > 0 { - log.Info().Int("medias", len(p.Medias)).Msg("fMP4 init complete") return nil } } @@ -120,7 +107,6 @@ func (p *Producer) probe() error { // Start runs the main fragment reading loop func (p *Producer) Start() error { - log := app.GetLogger("ipeye") receivers := make(map[uint32]*core.Receiver) if p.videoTrackID != 0 { @@ -131,23 +117,18 @@ func (p *Producer) Start() error { } } - // RTP packetizer - h264Pay := &codecs.H264Payloader{} - seq := rtp.NewRandomSequencer() - h264pkt := rtp.NewPacketizer(1200, 96, 0, h264Pay, seq, p.clockRate) - - // global counters + // RTP счётчики rtpStart := rand.Uint32() + seq := uint16(rand.Uint32()) var dts uint64 var defaultDur uint32 var initialized bool - const wrapPeriod = uint64(1) << 32 // RTP TS wraps (mod 2^32) + const wrapPeriod = uint64(1) << 32 // RTP TS wrap (mod 2^32) for { mType, b, err := p.conn.ReadMessage() if err != nil { - log.Error().Err(err).Msg("read error") return err } if mType != websocket.BinaryMessage { @@ -171,10 +152,6 @@ func (p *Producer) Start() error { case *iso.AtomTfdt: if !initialized { - log.Info(). - Uint64("decodeTime", atom.DecodeTime). - Uint32("rtpStart", rtpStart). - Msg("stream initialized") dts = atom.DecodeTime initialized = true } @@ -192,83 +169,35 @@ func (p *Producer) Start() error { continue } - // convert AVCC -> AnnexB - annexbData := annexb.DecodeAVCC(mdatData, true) - nalus := bytes.Split(annexbData, []byte{0, 0, 0, 1}) - - // iterate over NALUs - for i, nalu := range nalus { - if len(nalu) == 0 { - continue - } - typ := nalu[0] & 0x1F - - // RTP TS for this sample (mod 2^32) - ts := rtpStart + uint32((dts*uint64(p.clockRate)/90000)%wrapPeriod) - - // SPS/PPS before IDR - if typ == h264.NALUTypeIFrame { - if len(p.sps) > 0 { - for _, pkt := range h264pkt.Packetize(p.sps, ts) { - pkt.Timestamp = ts - recv.Input(pkt) - log.Debug(). - Uint32("ts", pkt.Timestamp). - Uint16("seq", pkt.SequenceNumber). - Int("size", len(pkt.Payload)). - Str("type", "SPS"). - Msg("RTP packet") - } - } - if len(p.pps) > 0 { - for _, pkt := range h264pkt.Packetize(p.pps, ts) { - pkt.Timestamp = ts - recv.Input(pkt) - log.Debug(). - Uint32("ts", pkt.Timestamp). - Uint16("seq", pkt.SequenceNumber). - Int("size", len(pkt.Payload)). - Str("type", "PPS"). - Msg("RTP packet") - } - } - } - - // actual frame - for _, pkt := range h264pkt.Packetize(nalu, ts) { - pkt.Timestamp = ts - recv.Input(pkt) - log.Debug(). - Uint32("ts", pkt.Timestamp). - Uint16("seq", pkt.SequenceNumber). - Int("size", len(pkt.Payload)). - Int("nalType", int(typ)). - Msg("RTP packet") - } - - // duration selection - dur := defaultDur - if i < len(samplesDur) && samplesDur[i] != 0 { - dur = samplesDur[i] - } - if dur == 0 { - dur = p.clockRate / 25 // fallback - } + // RTP TS для этого sample + ts := rtpStart + uint32((dts*uint64(p.clockRate)/90000)%wrapPeriod) + + // Отправляем один RTP-пакет с полезной нагрузкой в AVCC + seq++ + recv.Input(&rtp.Packet{ + Header: rtp.Header{ + Version: 2, + PayloadType: 96, + SequenceNumber: seq, + Timestamp: ts, + SSRC: 1, + }, + Payload: mdatData, + }) + + // выбираем длительность + dur := defaultDur + if len(samplesDur) > 0 && samplesDur[0] != 0 { + dur = samplesDur[0] + } + if dur == 0 { + dur = p.clockRate / 25 // fallback + } - log.Trace(). - Int("sample", i). - Uint32("dur", dur). - Uint64("dts", dts). - Uint32("rtpTS", ts). - Int("nalType", int(typ)). - Int("size", len(nalu)). - Msg("sample processed") - - // increment DTS - dts += uint64(dur) - if dts >= wrapPeriod { - dts %= wrapPeriod - } + // инкремент DTS + dts += uint64(dur) + if dts >= wrapPeriod { + dts %= wrapPeriod } } } From 10eaf31e92c4ab139811f6e74e32be431f0ef8de Mon Sep 17 00:00:00 2001 From: msnikit7 Date: Sun, 14 Sep 2025 14:06:09 +0300 Subject: [PATCH 9/9] comments translated --- pkg/ipeye/ipeye.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/ipeye/ipeye.go b/pkg/ipeye/ipeye.go index 06527b62a..1c1e1e76f 100644 --- a/pkg/ipeye/ipeye.go +++ b/pkg/ipeye/ipeye.go @@ -117,7 +117,7 @@ func (p *Producer) Start() error { } } - // RTP счётчики + // RTP counters rtpStart := rand.Uint32() seq := uint16(rand.Uint32()) var dts uint64 @@ -169,10 +169,10 @@ func (p *Producer) Start() error { continue } - // RTP TS для этого sample + // RTP TS for this sample ts := rtpStart + uint32((dts*uint64(p.clockRate)/90000)%wrapPeriod) - // Отправляем один RTP-пакет с полезной нагрузкой в AVCC + // Send one RTP packet with payload in AVCC seq++ recv.Input(&rtp.Packet{ Header: rtp.Header{ @@ -185,7 +185,7 @@ func (p *Producer) Start() error { Payload: mdatData, }) - // выбираем длительность + // Choose duration dur := defaultDur if len(samplesDur) > 0 && samplesDur[0] != 0 { dur = samplesDur[0] @@ -194,7 +194,7 @@ func (p *Producer) Start() error { dur = p.clockRate / 25 // fallback } - // инкремент DTS + // Increment DTS dts += uint64(dur) if dts >= wrapPeriod { dts %= wrapPeriod