mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 05:35:57 +08:00
feat: add rtsp plugin
This commit is contained in:
@@ -1,21 +1,20 @@
|
||||
global:
|
||||
loglevel: debug
|
||||
loglevel: trace
|
||||
enableauth: true
|
||||
tcp:
|
||||
listenaddr: :50051
|
||||
# publish:
|
||||
publish:
|
||||
pubaudio: false
|
||||
# ringsize: 20-250
|
||||
# buffertime: 10s
|
||||
# speed: 1
|
||||
console:
|
||||
secret: de2c0bb9fd47684adc07a426e139239b
|
||||
logrotate:
|
||||
level: debug
|
||||
webrtc:
|
||||
publish:
|
||||
pubaudio: false
|
||||
rtmp:
|
||||
chunksize: 2048
|
||||
publish:
|
||||
pubaudio: false
|
||||
# idletimeout: 10s
|
||||
# closedelaytimeout: 4s
|
||||
subscribe:
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
_ "m7s.live/m7s/v5/plugin/hdl"
|
||||
_ "m7s.live/m7s/v5/plugin/logrotate"
|
||||
_ "m7s.live/m7s/v5/plugin/rtmp"
|
||||
_ "m7s.live/m7s/v5/plugin/rtsp"
|
||||
_ "m7s.live/m7s/v5/plugin/webrtc"
|
||||
)
|
||||
|
||||
|
26
go.mod
26
go.mod
@@ -5,11 +5,14 @@ go 1.22
|
||||
toolchain go1.22.1
|
||||
|
||||
require (
|
||||
github.com/AlexxIT/go2rtc v1.9.4
|
||||
github.com/cnotch/ipchub v1.1.0
|
||||
github.com/emiago/sipgo v0.22.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
|
||||
github.com/pion/interceptor v0.1.29
|
||||
github.com/pion/rtcp v1.2.14
|
||||
github.com/pion/rtp v1.8.6
|
||||
github.com/pion/sdp/v3 v3.0.9
|
||||
github.com/polarsignals/frostdb v0.0.0-20240613134636-1d823f7d7299
|
||||
github.com/q191201771/naza v0.30.48
|
||||
github.com/quic-go/quic-go v0.43.1
|
||||
@@ -48,12 +51,15 @@ require (
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hamba/avro/v2 v2.20.1 // indirect
|
||||
github.com/icholy/digest v0.1.22 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -65,16 +71,15 @@ require (
|
||||
github.com/parquet-go/parquet-go v0.22.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pion/datachannel v1.5.6 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.10 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.11 // indirect
|
||||
github.com/pion/ice/v3 v3.0.7 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/sctp v1.8.16 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.9 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.1 // indirect
|
||||
github.com/pion/stun/v2 v2.0.0 // indirect
|
||||
github.com/pion/transport/v2 v2.2.4 // indirect
|
||||
github.com/pion/transport/v2 v2.2.5 // indirect
|
||||
github.com/pion/transport/v3 v3.0.2 // indirect
|
||||
github.com/pion/turn/v3 v3.0.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
@@ -88,6 +93,8 @@ require (
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rs/zerolog v1.33.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
|
||||
github.com/segmentio/encoding v0.3.6 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/thanos-io/objstore v0.0.0-20240512204237-71ef2d0cf7c4 // indirect
|
||||
@@ -99,7 +106,7 @@ require (
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
)
|
||||
@@ -111,17 +118,16 @@ require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/mcuadros/go-defaults v1.2.0
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/phsym/console-slog v0.3.1
|
||||
github.com/pion/webrtc/v4 v4.0.0-beta.13
|
||||
github.com/shirou/gopsutil/v3 v3.24.3
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
62
go.sum
62
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/AlexxIT/go2rtc v1.9.4 h1:GC25fWz9S0XwZn/RV5Y4cV7UEGbtIWktYy8Aq96RROg=
|
||||
github.com/AlexxIT/go2rtc v1.9.4/go.mod h1:3nYj8jnqS0O38cCxa96fbifX1RF6GxAjlzhfEl32zeY=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU=
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
||||
@@ -40,6 +42,7 @@ github.com/coreos/etcd v3.3.27+incompatible h1:QIudLb9KeBsE5zyYxd1mjzRSkzLg9Wf9Q
|
||||
github.com/coreos/etcd v3.3.27+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf h1:GOPo6vn/vTN+3IwZBvXX0y5doJfSC7My0cdzelyOCsQ=
|
||||
github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -51,6 +54,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/efficientgo/core v1.0.0-rc.2 h1:7j62qHLnrZqO3V3UA0AqOGd5d5aXV3AX6m/NZBHp78I=
|
||||
github.com/efficientgo/core v1.0.0-rc.2/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrjaL6x/GLcQ4vJps=
|
||||
github.com/emiago/sipgo v0.22.0 h1:GaQ51m26M9QnVBVY2aDJ/mXqq/BDfZ1A+nW7XgU/4Ts=
|
||||
github.com/emiago/sipgo v0.22.0/go.mod h1:a77FgPEEjJvfYWYfP3p53u+dNhWEMb/VGVS6guvBzx0=
|
||||
github.com/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@@ -73,6 +78,7 @@ github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
|
||||
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -114,6 +120,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/icholy/digest v0.1.22 h1:dRIwCjtAcXch57ei+F0HSb5hmprL873+q7PoVojdMzM=
|
||||
github.com/icholy/digest v0.1.22/go.mod h1:uLAeDdWKIWNFMH0wqbwchbTQOmJWhzSnL7zmqSPqEEc=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@@ -138,11 +146,15 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
|
||||
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -152,7 +164,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
@@ -187,8 +198,9 @@ github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V
|
||||
github.com/pion/datachannel v1.5.6 h1:1IxKJntfSlYkpUj8LlYRSWpYiTTC02nUrOE8T3DqGeg=
|
||||
github.com/pion/datachannel v1.5.6/go.mod h1:1eKT6Q85pRnr2mHiWHxJwO50SfZRtWHTsNIVb/NfGW4=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/dtls/v2 v2.2.10 h1:u2Axk+FyIR1VFTPurktB+1zoEPGIW3bmyj3LEFrXjAA=
|
||||
github.com/pion/dtls/v2 v2.2.10/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||
github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks=
|
||||
github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||
github.com/pion/ice/v3 v3.0.3/go.mod h1:YMLU7qbKfVjmEv7EoZPIVEI+kNYxWCdPK3VS0BU+U4Q=
|
||||
github.com/pion/ice/v3 v3.0.7 h1:dfMViRKblENqzorR2cQiiRKWqQfqKZ9+nT/sREX3ra8=
|
||||
github.com/pion/ice/v3 v3.0.7/go.mod h1:pBRcCoJRC0vwvFsemfRIqRLYukV4bPboGb0B4b8AhrQ=
|
||||
@@ -226,8 +238,9 @@ github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
|
||||
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo=
|
||||
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc=
|
||||
github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4=
|
||||
github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0=
|
||||
@@ -237,6 +250,7 @@ github.com/pion/turn/v3 v3.0.3/go.mod h1:vw0Dz420q7VYAF3J4wJKzReLHIo2LGp4ev8nXQe
|
||||
github.com/pion/webrtc/v4 v4.0.0-beta.13 h1:nIhz2viUhaFvKlbLDpF/XQqlsS+PhfYTxd8qcAo1pL8=
|
||||
github.com/pion/webrtc/v4 v4.0.0-beta.13/go.mod h1:ojwmbdrsIkmRXPumQf9OFIkTJVB9AV/Z9ItMpNvsuhM=
|
||||
github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
|
||||
@@ -268,12 +282,17 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/samber/slog-formatter v1.0.0 h1:ULxHV+jNqi6aFP8xtzGHl2ejFRMl2+jI2UhCpgoXTDA=
|
||||
github.com/samber/slog-formatter v1.0.0/go.mod h1:c7pRfwhCfZQNzJz+XirmTveElxXln7M0Y8Pq781uxlo=
|
||||
github.com/samber/slog-multi v1.0.0 h1:snvP/P5GLQ8TQh5WSqdRaxDANW8AAA3egwEoytLsqvc=
|
||||
github.com/samber/slog-multi v1.0.0/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
||||
github.com/segmentio/encoding v0.3.6 h1:E6lVLyDPseWEulBmCmAKPanDd3jiyGDo5gMcugCRwZQ=
|
||||
@@ -284,6 +303,7 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@@ -336,16 +356,17 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
@@ -361,8 +382,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -390,6 +411,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -397,11 +419,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -422,17 +445,18 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -459,7 +483,6 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@@ -473,3 +496,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
|
@@ -55,11 +55,11 @@ type (
|
||||
}
|
||||
AVRing = util.Ring[AVFrame]
|
||||
DataFrame struct {
|
||||
sync.RWMutex `json:"-" yaml:"-"` // 读写锁
|
||||
sync.RWMutex
|
||||
discard bool
|
||||
Sequence uint32 // 在一个Track中的序号
|
||||
WriteTime time.Time // 写入时间,可用于比较两个帧的先后
|
||||
Raw any `json:"-" yaml:"-"` // 裸格式
|
||||
Raw any // 裸格式
|
||||
}
|
||||
)
|
||||
|
||||
|
@@ -8,6 +8,7 @@ type (
|
||||
}
|
||||
PCMACtx AudioCtx
|
||||
PCMUCtx AudioCtx
|
||||
OPUSCtx AudioCtx
|
||||
AACCtx struct {
|
||||
AudioCtx
|
||||
}
|
||||
@@ -36,3 +37,7 @@ func (*PCMACtx) FourCC() FourCC {
|
||||
func (*AACCtx) FourCC() FourCC {
|
||||
return FourCC_MP4A
|
||||
}
|
||||
|
||||
func (*OPUSCtx) FourCC() FourCC {
|
||||
return FourCC_OPUS
|
||||
}
|
||||
|
@@ -4,15 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -36,26 +33,6 @@ type Config struct {
|
||||
var durationType = reflect.TypeOf(time.Duration(0))
|
||||
var regexpType = reflect.TypeOf(Regexp{})
|
||||
|
||||
type Plugin interface {
|
||||
// 可能的入参类型:FirstConfig 第一次初始化配置,Config 后续配置更新,SE系列(StateEvent)流状态变化事件
|
||||
OnEvent(any)
|
||||
}
|
||||
|
||||
type TCPPlugin interface {
|
||||
Plugin
|
||||
ServeTCP(net.Conn)
|
||||
}
|
||||
|
||||
type HTTPPlugin interface {
|
||||
Plugin
|
||||
http.Handler
|
||||
}
|
||||
|
||||
type QuicPlugin interface {
|
||||
Plugin
|
||||
ServeQuic(quic.Connection)
|
||||
}
|
||||
|
||||
func (config *Config) Range(f func(key string, value Config)) {
|
||||
if m, ok := config.GetValue().(map[string]Config); ok {
|
||||
for k, v := range m {
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type QuicConfig interface {
|
||||
ListenQuic(context.Context, QuicPlugin) error
|
||||
ListenQuic(context.Context, func(connection quic.Connection)) error
|
||||
}
|
||||
|
||||
type Quic struct {
|
||||
@@ -18,7 +18,7 @@ type Quic struct {
|
||||
KeyFile string `desc:"私钥文件"`
|
||||
}
|
||||
|
||||
func (q *Quic) ListenQuic(ctx context.Context, plugin QuicPlugin) error {
|
||||
func (q *Quic) ListenQuic(ctx context.Context, handler func(connection quic.Connection)) error {
|
||||
listener, err := quic.ListenAddr(q.ListenAddr, q.generateTLSConfig(), &quic.Config{
|
||||
EnableDatagrams: true,
|
||||
})
|
||||
@@ -31,7 +31,7 @@ func (q *Quic) ListenQuic(ctx context.Context, plugin QuicPlugin) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go plugin.ServeQuic(conn)
|
||||
go handler(conn)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,6 +22,7 @@ type TCP struct {
|
||||
ListenNum int `desc:"同时并行监听数量,0为CPU核心数量"` //同时并行监听数量,0为CPU核心数量
|
||||
NoDelay bool `desc:"是否禁用Nagle算法"` //是否禁用Nagle算法
|
||||
KeepAlive bool `desc:"是否启用KeepAlive"` //是否启用KeepAlive
|
||||
AutoListen bool `default:"true" desc:"是否自动监听"`
|
||||
listener net.Listener
|
||||
listenerTls net.Listener
|
||||
}
|
||||
|
@@ -161,8 +161,6 @@ type Console struct {
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
EnableAVCC bool `default:"true" desc:"启用AVCC格式,rtmp、http-flv协议使用"` //启用AVCC格式,rtmp、http-flv协议使用
|
||||
EnableRTP bool `default:"true" desc:"启用RTP格式,rtsp、webrtc等协议使用"` //启用RTP格式,rtsp、webrtc等协议使用
|
||||
EnableSubEvent bool `default:"true" desc:"启用订阅事件,禁用可以提高性能"` //启用订阅事件,禁用可以提高性能
|
||||
EnableAuth bool `default:"true" desc:"启用鉴权"` //启用鉴权
|
||||
LogLang string `default:"zh" desc:"日志语言" enum:"zh:中文,en:英文"` //日志语言
|
||||
@@ -172,15 +170,16 @@ type Engine struct {
|
||||
PulseInterval time.Duration `default:"5s" desc:"心跳事件间隔"` //心跳事件间隔
|
||||
DisableAll bool `default:"false" desc:"禁用所有插件"` //禁用所有插件
|
||||
RTPReorderBufferLen int `default:"50" desc:"RTP重排序缓冲区长度"` //RTP重排序缓冲区长度
|
||||
PoolSize int `desc:"内存池大小"` //内存池大小
|
||||
}
|
||||
|
||||
type Common struct {
|
||||
PublicIP string
|
||||
Publish
|
||||
Subscribe
|
||||
HTTP
|
||||
Quic
|
||||
TCP
|
||||
UDP
|
||||
Pull
|
||||
Push
|
||||
}
|
||||
|
17
pkg/config/udp.go
Normal file
17
pkg/config/udp.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type UDPConfig interface {
|
||||
ListenUDP(context.Context, func(conn *net.UDPConn)) error
|
||||
}
|
||||
|
||||
type UDP struct {
|
||||
ListenAddr string `desc:"监听地址,格式为ip:port,ip 可省略默认监听所有网卡"`
|
||||
CertFile string `desc:"证书文件"`
|
||||
KeyFile string `desc:"私钥文件"`
|
||||
AutoListen bool `default:"true" desc:"是否自动监听"`
|
||||
}
|
@@ -8,7 +8,7 @@ const defaultBufSize = 1 << 14
|
||||
|
||||
type BufReader struct {
|
||||
reader io.Reader
|
||||
allocator *ScalableMemoryAllocator
|
||||
Allocator *ScalableMemoryAllocator
|
||||
buf MemoryReader
|
||||
BufLen int
|
||||
}
|
||||
@@ -16,7 +16,7 @@ type BufReader struct {
|
||||
func NewBufReaderWithBufLen(reader io.Reader, bufLen int) (r *BufReader) {
|
||||
r = &BufReader{
|
||||
reader: reader,
|
||||
allocator: NewScalableMemoryAllocator(bufLen),
|
||||
Allocator: NewScalableMemoryAllocator(bufLen),
|
||||
BufLen: bufLen,
|
||||
}
|
||||
r.buf.Memory = &Memory{}
|
||||
@@ -31,17 +31,34 @@ func NewBufReader(reader io.Reader) (r *BufReader) {
|
||||
func (r *BufReader) Recycle() {
|
||||
r.reader = nil
|
||||
r.buf = MemoryReader{}
|
||||
r.allocator.Recycle()
|
||||
r.Allocator.Recycle()
|
||||
}
|
||||
|
||||
func (r *BufReader) Peek(n int) (buf []byte, err error) {
|
||||
defer func(snap MemoryReader) {
|
||||
l := r.buf.Length + n
|
||||
r.buf = snap
|
||||
r.buf.Length = l
|
||||
}(
|
||||
r.buf)
|
||||
for range n {
|
||||
if b, err := r.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = append(buf, b)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *BufReader) eat() error {
|
||||
buf := r.allocator.Malloc(r.BufLen)
|
||||
buf := r.Allocator.Malloc(r.BufLen)
|
||||
if n, err := r.reader.Read(buf); err != nil {
|
||||
r.allocator.Free(buf)
|
||||
r.Allocator.Free(buf)
|
||||
return err
|
||||
} else {
|
||||
if n < r.BufLen {
|
||||
r.allocator.Free(buf[n:])
|
||||
r.Allocator.Free(buf[n:])
|
||||
buf = buf[:n]
|
||||
}
|
||||
r.buf.Buffers = append(r.buf.Buffers, buf)
|
||||
@@ -122,7 +139,12 @@ func (r *BufReader) ReadNto(n int, to []byte) (err error) {
|
||||
l += ll
|
||||
})
|
||||
}
|
||||
|
||||
func (r *BufReader) ReadString(n int) (s string, err error) {
|
||||
err = r.ReadRange(n, func(buf []byte) {
|
||||
s += string(buf)
|
||||
})
|
||||
return
|
||||
}
|
||||
func (r *BufReader) ReadBytes(n int) (mem Memory, err error) {
|
||||
err = r.ReadRange(n, func(buf []byte) {
|
||||
mem.Buffers = append(mem.Buffers, buf)
|
||||
@@ -132,5 +154,5 @@ func (r *BufReader) ReadBytes(n int) (mem Memory, err error) {
|
||||
}
|
||||
|
||||
func (r *BufReader) recycleFront() {
|
||||
r.buf.ClipFront(r.allocator.Free)
|
||||
r.buf.ClipFront(r.Allocator.Free)
|
||||
}
|
||||
|
@@ -112,6 +112,10 @@ type ITCPPlugin interface {
|
||||
OnTCPConnect(*net.TCPConn)
|
||||
}
|
||||
|
||||
type IUDPPlugin interface {
|
||||
OnUDPConnect(*net.UDPConn)
|
||||
}
|
||||
|
||||
var plugins []PluginMeta
|
||||
|
||||
func InstallPlugin[C iPlugin](options ...any) error {
|
||||
@@ -220,7 +224,7 @@ func (p *Plugin) Start() {
|
||||
tcphandler = p
|
||||
}
|
||||
|
||||
if tcpConf.ListenAddr != "" {
|
||||
if tcpConf.ListenAddr != "" && tcpConf.AutoListen {
|
||||
p.Info("listen tcp", "addr", tcpConf.ListenAddr)
|
||||
go func() {
|
||||
err := tcpConf.Listen(tcphandler.OnTCPConnect)
|
||||
@@ -230,7 +234,7 @@ func (p *Plugin) Start() {
|
||||
}
|
||||
}()
|
||||
}
|
||||
if tcpConf.ListenAddrTLS != "" {
|
||||
if tcpConf.ListenAddrTLS != "" && tcpConf.AutoListen {
|
||||
p.Info("listen tcp tls", "addr", tcpConf.ListenAddrTLS)
|
||||
go func() {
|
||||
err := tcpConf.ListenTLS(tcphandler.OnTCPConnect)
|
||||
|
33
plugin/gb28181/index.go
Normal file
33
plugin/gb28181/index.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package plugin_gb28181
|
||||
|
||||
import (
|
||||
"github.com/emiago/sipgo"
|
||||
"github.com/emiago/sipgo/sip"
|
||||
"m7s.live/m7s/v5"
|
||||
)
|
||||
|
||||
type SipConfig struct {
|
||||
ListenAddr []string
|
||||
ListenTLSAddr []string
|
||||
}
|
||||
|
||||
type GB28181Plugin struct {
|
||||
m7s.Plugin
|
||||
Sip SipConfig
|
||||
ua *sipgo.UserAgent
|
||||
server *sipgo.Server
|
||||
}
|
||||
|
||||
var _ = m7s.InstallPlugin[GB28181Plugin]()
|
||||
|
||||
func (gb *GB28181Plugin) OnInit() (err error) {
|
||||
gb.ua, err = sipgo.NewUA() // Build user agent
|
||||
gb.server, err = sipgo.NewServer(gb.ua) // Creating server handle for ua
|
||||
gb.server.OnRegister(gb.OnRegister)
|
||||
gb.server.ListenAndServe(gb, "tcp", "")
|
||||
return
|
||||
}
|
||||
|
||||
func (gb *GB28181Plugin) OnRegister(req *sip.Request, tx sip.ServerTransaction) {
|
||||
|
||||
}
|
@@ -41,7 +41,7 @@ func (p *RTMPPlugin) OnPublish(puber *m7s.Publisher) {
|
||||
|
||||
func (p *RTMPPlugin) OnTCPConnect(conn *net.TCPConn) {
|
||||
logger := p.Logger.With("remote", conn.RemoteAddr().String())
|
||||
receivers := make(map[uint32]*RTMPReceiver)
|
||||
receivers := make(map[uint32]*Receiver)
|
||||
var err error
|
||||
nc := NewNetConnection(conn, logger)
|
||||
defer func() {
|
||||
@@ -148,7 +148,7 @@ func (p *RTMPPlugin) OnTCPConnect(conn *net.TCPConn) {
|
||||
// }
|
||||
// s := engine.Streams.Get(nc.appName + "/" + cmd.StreamName)
|
||||
// if s != nil && s.Publisher != nil {
|
||||
// if p, ok := s.Publisher.(*RTMPReceiver); ok {
|
||||
// if p, ok := s.Publisher.(*Receiver); ok {
|
||||
// // m.CommandName = "releaseStream_result"
|
||||
// p.Stop()
|
||||
// delete(receivers, p.StreamID)
|
||||
@@ -156,7 +156,7 @@ func (p *RTMPPlugin) OnTCPConnect(conn *net.TCPConn) {
|
||||
// }
|
||||
// err = nc.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case *PublishMessage:
|
||||
receiver := &RTMPReceiver{
|
||||
receiver := &Receiver{
|
||||
NetStream: NetStream{
|
||||
NetConnection: nc,
|
||||
StreamID: cmd.StreamId,
|
||||
|
@@ -43,9 +43,9 @@ const (
|
||||
)
|
||||
|
||||
type NetConnection struct {
|
||||
*slog.Logger `json:"-" yaml:"-"`
|
||||
*util.BufReader `json:"-" yaml:"-"`
|
||||
net.Conn `json:"-" yaml:"-"`
|
||||
*slog.Logger
|
||||
*util.BufReader
|
||||
net.Conn
|
||||
bandwidth uint32
|
||||
readSeqNum uint32 // 当前读的字节
|
||||
writeSeqNum uint32 // 当前写的字节
|
||||
|
@@ -5,8 +5,8 @@ type NetStream struct {
|
||||
StreamID uint32
|
||||
}
|
||||
|
||||
func (ns *NetStream) CreateAudioSender(c bool) *AVSender {
|
||||
var av AVSender
|
||||
func (ns *NetStream) CreateAudioSender(c bool) *Sender {
|
||||
var av Sender
|
||||
av.NetConnection = ns.NetConnection
|
||||
av.ChunkStreamID = RTMP_CSID_AUDIO
|
||||
av.MessageTypeID = RTMP_MSG_AUDIO
|
||||
@@ -15,8 +15,8 @@ func (ns *NetStream) CreateAudioSender(c bool) *AVSender {
|
||||
return &av
|
||||
}
|
||||
|
||||
func (ns *NetStream) CreateVideoSender(c bool) *AVSender {
|
||||
var av AVSender
|
||||
func (ns *NetStream) CreateVideoSender(c bool) *Sender {
|
||||
var av Sender
|
||||
av.NetConnection = ns.NetConnection
|
||||
av.ChunkStreamID = RTMP_CSID_VIDEO
|
||||
av.MessageTypeID = RTMP_MSG_VIDEO
|
||||
@@ -25,7 +25,7 @@ func (ns *NetStream) CreateVideoSender(c bool) *AVSender {
|
||||
return &av
|
||||
}
|
||||
|
||||
func (ns *NetStream) CreateSender(c bool) (audio *AVSender, video *AVSender) {
|
||||
func (ns *NetStream) CreateSender(c bool) (audio *Sender, video *Sender) {
|
||||
return ns.CreateAudioSender(c), ns.CreateVideoSender(c)
|
||||
}
|
||||
|
||||
|
@@ -8,22 +8,22 @@ import (
|
||||
"m7s.live/m7s/v5"
|
||||
)
|
||||
|
||||
type AVSender struct {
|
||||
type Sender struct {
|
||||
*NetConnection
|
||||
ChunkHeader
|
||||
errContinue bool
|
||||
lastAbs uint32
|
||||
}
|
||||
|
||||
func (av *AVSender) HandleAudio(frame *RTMPAudio) (err error) {
|
||||
func (av *Sender) HandleAudio(frame *RTMPAudio) (err error) {
|
||||
return av.SendFrame(&frame.RTMPData)
|
||||
}
|
||||
|
||||
func (av *AVSender) HandleVideo(frame *RTMPVideo) (err error) {
|
||||
func (av *Sender) HandleVideo(frame *RTMPVideo) (err error) {
|
||||
return av.SendFrame(&frame.RTMPData)
|
||||
}
|
||||
|
||||
func (av *AVSender) SendFrame(frame *RTMPData) (err error) {
|
||||
func (av *Sender) SendFrame(frame *RTMPData) (err error) {
|
||||
// seq := frame.Sequence
|
||||
payloadLen := frame.Size
|
||||
if av.errContinue {
|
||||
@@ -67,7 +67,7 @@ func (av *AVSender) SendFrame(frame *RTMPData) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type RTMPReceiver struct {
|
||||
type Receiver struct {
|
||||
*m7s.Publisher
|
||||
NetStream
|
||||
}
|
@@ -232,7 +232,7 @@ func createH26xFrame(from *AVFrame, codecID VideoCodecID) (frame IAVFrame, err e
|
||||
rtmpVideo.Timestamp = uint32(from.Timestamp / time.Millisecond)
|
||||
rtmpVideo.ScalableMemoryAllocator = from.Wraps[0].GetScalableMemoryAllocator()
|
||||
nalus := from.Raw.(Nalus)
|
||||
rtmpVideo.RecycleIndexes = make([]int, len(nalus.Nalus)) // Recycle partial data
|
||||
rtmpVideo.RecycleIndexes = make([]int, 0, len(nalus.Nalus)) // Recycle partial data
|
||||
head := rtmpVideo.NextN(5)
|
||||
head[0] = util.Conditoinal[byte](from.IDR, 0x10, 0x20) | byte(codecID)
|
||||
head[1] = 1
|
||||
|
@@ -42,11 +42,17 @@ type (
|
||||
SequenceNumber uint16
|
||||
SSRC uint32
|
||||
}
|
||||
RTPG711Ctx struct {
|
||||
RTPPCMACtx struct {
|
||||
RTPCtx
|
||||
codec.PCMACtx
|
||||
}
|
||||
RTPPCMUCtx struct {
|
||||
RTPCtx
|
||||
codec.PCMUCtx
|
||||
}
|
||||
RTPOPUSCtx struct {
|
||||
RTPCtx
|
||||
codec.OPUSCtx
|
||||
}
|
||||
RTPAACCtx struct {
|
||||
RTPCtx
|
||||
@@ -100,20 +106,21 @@ type RTPAudio struct {
|
||||
func (r *RTPAudio) Parse(t *AVTrack) (isIDR, isSeq bool, raw any, err error) {
|
||||
switch r.MimeType {
|
||||
case webrtc.MimeTypeOpus:
|
||||
// var ctx RTPOPUSCtx
|
||||
// ctx.FourCC = codec.FourCC_OPUS
|
||||
// ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
// codecCtx = &ctx
|
||||
var ctx RTPOPUSCtx
|
||||
ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
t.ICodecCtx = &ctx
|
||||
case webrtc.MimeTypePCMA:
|
||||
// var ctx RTPG711Ctx
|
||||
// ctx.FourCC = codec.FourCC_ALAW
|
||||
// ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
// codecCtx = &ctx
|
||||
var ctx RTPPCMACtx
|
||||
ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
t.ICodecCtx = &ctx
|
||||
case webrtc.MimeTypePCMU:
|
||||
// var ctx RTPG711Ctx
|
||||
// ctx.FourCC = codec.FourCC_ULAW
|
||||
// ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
// codecCtx = &ctx
|
||||
var ctx RTPPCMUCtx
|
||||
ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
t.ICodecCtx = &ctx
|
||||
case "audio/MPEG4-GENERIC":
|
||||
var ctx RTPAACCtx
|
||||
ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
t.ICodecCtx = &ctx
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
package rtp
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
@@ -39,6 +41,7 @@ var (
|
||||
_ IVideoCodecCtx = (*RTPH264Ctx)(nil)
|
||||
_ IVideoCodecCtx = (*RTPH265Ctx)(nil)
|
||||
_ IVideoCodecCtx = (*RTPAV1Ctx)(nil)
|
||||
spropReg = regexp.MustCompile(`sprop-parameter-sets=(.+),([^;]+)(;|$)`)
|
||||
)
|
||||
|
||||
func (r *RTPVideo) Parse(t *AVTrack) (isIDR, isSeq bool, raw any, err error) {
|
||||
@@ -49,7 +52,16 @@ func (r *RTPVideo) Parse(t *AVTrack) (isIDR, isSeq bool, raw any, err error) {
|
||||
ctx = t.ICodecCtx.(*RTPH264Ctx)
|
||||
} else {
|
||||
ctx = &RTPH264Ctx{}
|
||||
//packetization-mode=1; sprop-parameter-sets=J2QAKaxWgHgCJ+WagICAgQ==,KO48sA==; profile-level-id=640029
|
||||
ctx.RTPCodecParameters = *r.RTPCodecParameters
|
||||
if match := spropReg.FindStringSubmatch(ctx.SDPFmtpLine); len(match) > 2 {
|
||||
if sps, err := base64.StdEncoding.DecodeString(match[1]); err == nil {
|
||||
ctx.SPS = [][]byte{sps}
|
||||
}
|
||||
if pps, err := base64.StdEncoding.DecodeString(match[2]); err == nil {
|
||||
ctx.PPS = [][]byte{pps}
|
||||
}
|
||||
}
|
||||
t.ICodecCtx = ctx
|
||||
}
|
||||
raw, err = r.ToRaw(ctx)
|
||||
|
434
plugin/rtsp/index.go
Normal file
434
plugin/rtsp/index.go
Normal file
@@ -0,0 +1,434 @@
|
||||
package plugin_rtsp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"m7s.live/m7s/v5"
|
||||
"m7s.live/m7s/v5/pkg/util"
|
||||
mrtp "m7s.live/m7s/v5/plugin/rtp/pkg"
|
||||
. "m7s.live/m7s/v5/plugin/rtsp/pkg"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultConfig = m7s.DefaultYaml(`tcp:
|
||||
listenaddr: :554`)
|
||||
|
||||
var _ = m7s.InstallPlugin[RTSPPlugin](defaultConfig)
|
||||
|
||||
type RTSPPlugin struct {
|
||||
m7s.Plugin
|
||||
}
|
||||
|
||||
func (p *RTSPPlugin) OnTCPConnect(conn *net.TCPConn) {
|
||||
logger := p.Logger.With("remote", conn.RemoteAddr().String())
|
||||
var receiver *Receiver
|
||||
var err error
|
||||
nc := NewNetConnection(conn, logger)
|
||||
defer func() {
|
||||
nc.Destroy()
|
||||
if p := recover(); p != nil {
|
||||
err = p.(error)
|
||||
logger.Error(err.Error(), "stack", string(debug.Stack()))
|
||||
}
|
||||
if receiver != nil {
|
||||
receiver.Dispose(err)
|
||||
}
|
||||
}()
|
||||
var req *tcp.Request
|
||||
var timeout time.Duration
|
||||
var sendMode bool
|
||||
mem := util.NewScalableMemoryAllocator(1 << 12)
|
||||
defer mem.Recycle()
|
||||
for {
|
||||
req, err = nc.ReadRequest()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if nc.URL == nil {
|
||||
nc.URL = req.URL
|
||||
logger = logger.With("url", nc.URL.String())
|
||||
nc.UserAgent = req.Header.Get("User-Agent")
|
||||
logger.Info("connect", "userAgent", nc.UserAgent)
|
||||
}
|
||||
|
||||
//if !c.auth.Validate(req) {
|
||||
// res := &tcp.Response{
|
||||
// Status: "401 Unauthorized",
|
||||
// Header: map[string][]string{"Www-Authenticate": {`Basic realm="go2rtc"`}},
|
||||
// Request: req,
|
||||
// }
|
||||
// if err = c.WriteResponse(res); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// continue
|
||||
//}
|
||||
|
||||
// Receiver: OPTIONS > DESCRIBE > SETUP... > PLAY > TEARDOWN
|
||||
// Sender: OPTIONS > ANNOUNCE > SETUP... > RECORD > TEARDOWN
|
||||
switch req.Method {
|
||||
case MethodOptions:
|
||||
res := &tcp.Response{
|
||||
Header: map[string][]string{
|
||||
"Public": {"OPTIONS, SETUP, TEARDOWN, DESCRIBE, PLAY, PAUSE, ANNOUNCE, RECORD"},
|
||||
},
|
||||
Request: req,
|
||||
}
|
||||
if err = nc.WriteResponse(res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MethodAnnounce:
|
||||
if req.Header.Get("Content-Type") != "application/sdp" {
|
||||
err = errors.New("wrong content type")
|
||||
return
|
||||
}
|
||||
|
||||
nc.SDP = string(req.Body) // for info
|
||||
|
||||
if nc.Medias, err = UnmarshalSDP(req.Body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
receiver = &Receiver{
|
||||
NetConnection: nc,
|
||||
}
|
||||
|
||||
if receiver.Publisher, err = p.Publish(strings.TrimPrefix(nc.URL.Path, "/")); err != nil {
|
||||
receiver = nil
|
||||
err = nc.WriteResponse(&tcp.Response{
|
||||
StatusCode: 500, Status: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for i, media := range nc.Medias {
|
||||
if codec := media.Codecs[0]; codec.IsAudio() {
|
||||
receiver.AudioCodecParameters = &webrtc.RTPCodecParameters{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{
|
||||
MimeType: "audio/" + codec.Name,
|
||||
ClockRate: codec.ClockRate,
|
||||
Channels: codec.Channels,
|
||||
SDPFmtpLine: codec.FmtpLine,
|
||||
RTCPFeedback: nil,
|
||||
},
|
||||
PayloadType: webrtc.PayloadType(codec.PayloadType),
|
||||
}
|
||||
receiver.AudioChannelID = byte(i) << 1
|
||||
} else if codec.IsVideo() {
|
||||
receiver.VideoChannelID = byte(i) << 1
|
||||
receiver.VideoCodecParameters = &webrtc.RTPCodecParameters{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{
|
||||
MimeType: "video/" + codec.Name,
|
||||
ClockRate: codec.ClockRate,
|
||||
Channels: codec.Channels,
|
||||
SDPFmtpLine: codec.FmtpLine,
|
||||
RTCPFeedback: nil,
|
||||
},
|
||||
PayloadType: webrtc.PayloadType(codec.PayloadType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeout = time.Second * 15
|
||||
|
||||
res := &tcp.Response{Request: req}
|
||||
if err = nc.WriteResponse(res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MethodDescribe:
|
||||
sendMode = true
|
||||
timeout = time.Second * 60
|
||||
|
||||
//if c.Senders == nil {
|
||||
// res := &tcp.Response{
|
||||
// Status: "404 Not Found",
|
||||
// Request: req,
|
||||
// }
|
||||
// return c.WriteResponse(res)
|
||||
//}
|
||||
|
||||
res := &tcp.Response{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/sdp"},
|
||||
},
|
||||
Request: req,
|
||||
}
|
||||
|
||||
// convert tracks to real output medias
|
||||
var medias []*core.Media
|
||||
|
||||
//for i, track := range c.Senders {
|
||||
// media := &core.Media{
|
||||
// Kind: core.GetKind(track.Codec.Name),
|
||||
// Direction: core.DirectionRecvonly,
|
||||
// Codecs: []*core.Codec{track.Codec},
|
||||
// ID: "trackID=" + strconv.Itoa(i),
|
||||
// }
|
||||
// medias = append(medias, media)
|
||||
//}
|
||||
|
||||
res.Body, err = core.MarshalSDP(nc.SessionName, medias)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nc.SDP = string(res.Body) // for info
|
||||
|
||||
if err = nc.WriteResponse(res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MethodSetup:
|
||||
tr := req.Header.Get("Transport")
|
||||
|
||||
res := &tcp.Response{
|
||||
Header: map[string][]string{},
|
||||
Request: req,
|
||||
}
|
||||
|
||||
const transport = "RTP/AVP/TCP;unicast;interleaved="
|
||||
if strings.HasPrefix(tr, transport) {
|
||||
nc.Session = core.RandString(8, 10)
|
||||
|
||||
if sendMode {
|
||||
//if i := reqTrackID(req); i >= 0 && i < len(c.Senders) {
|
||||
// // mark sender as SETUP
|
||||
// c.Senders[i].Media.ID = MethodSetup
|
||||
// tr = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", i*2, i*2+1)
|
||||
// res.Header.Set("Transport", tr)
|
||||
//} else {
|
||||
// res.Status = "400 Bad Request"
|
||||
//}
|
||||
} else {
|
||||
res.Header.Set("Transport", tr[:len(transport)+3])
|
||||
}
|
||||
} else {
|
||||
res.Status = "461 Unsupported transport"
|
||||
}
|
||||
|
||||
if err = nc.WriteResponse(res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case MethodRecord, MethodPlay:
|
||||
if sendMode {
|
||||
// stop unconfigured senders
|
||||
//for _, track := range c.Senders {
|
||||
// if track.Media.ID != MethodSetup {
|
||||
// track.Close()
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
res := &tcp.Response{Request: req}
|
||||
err = nc.WriteResponse(res)
|
||||
audioFrame := &mrtp.RTPAudio{}
|
||||
audioFrame.ScalableMemoryAllocator = mem
|
||||
audioFrame.RTPCodecParameters = receiver.AudioCodecParameters
|
||||
videoFrame := &mrtp.RTPVideo{}
|
||||
videoFrame.ScalableMemoryAllocator = mem
|
||||
videoFrame.RTPCodecParameters = receiver.VideoCodecParameters
|
||||
for err == nil {
|
||||
ts := time.Now()
|
||||
|
||||
if err = conn.SetReadDeadline(ts.Add(timeout)); err != nil {
|
||||
return
|
||||
}
|
||||
var magic []byte
|
||||
// we can read:
|
||||
// 1. RTP interleaved: `$` + 1B channel number + 2B size
|
||||
// 2. RTSP response: RTSP/1.0 200 OK
|
||||
// 3. RTSP request: OPTIONS ...
|
||||
magic, err = nc.Peek(4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var channelID byte
|
||||
var size int
|
||||
var buf []byte
|
||||
if magic[0] != '$' {
|
||||
logger.Warn("not magic")
|
||||
switch string(magic) {
|
||||
case "RTSP":
|
||||
var res *tcp.Response
|
||||
if res, err = nc.ReadResponse(); err != nil {
|
||||
return
|
||||
}
|
||||
logger.Warn(string(res.Body))
|
||||
// for playing backchannel only after OK response on play
|
||||
|
||||
continue
|
||||
|
||||
case "OPTI", "TEAR", "DESC", "SETU", "PLAY", "PAUS", "RECO", "ANNO", "GET_", "SET_":
|
||||
var req *tcp.Request
|
||||
if req, err = nc.ReadRequest(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Method == MethodOptions {
|
||||
res := &tcp.Response{Request: req}
|
||||
if err = nc.WriteResponse(res); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
||||
default:
|
||||
logger.Error("wrong input")
|
||||
//c.Fire("RTSP wrong input")
|
||||
//
|
||||
//for i := 0; ; i++ {
|
||||
// // search next start symbol
|
||||
// if _, err = c.reader.ReadBytes('$'); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// if channelID, err = c.reader.ReadByte(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // TODO: better check maximum good channel ID
|
||||
// if channelID >= 20 {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// buf4 = make([]byte, 2)
|
||||
// if _, err = io.ReadFull(c.reader, buf4); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // check if size good for RTP
|
||||
// size = binary.BigEndian.Uint16(buf4)
|
||||
// if size <= 1500 {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// // 10 tries to find good packet
|
||||
// if i >= 10 {
|
||||
// return fmt.Errorf("RTSP wrong input")
|
||||
// }
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
// hope that the odd channels are always RTCP
|
||||
|
||||
channelID = magic[1]
|
||||
|
||||
// get data size
|
||||
size = int(binary.BigEndian.Uint16(magic[2:]))
|
||||
// skip 4 bytes from c.reader.Peek
|
||||
if err = nc.Skip(4); err != nil {
|
||||
return
|
||||
}
|
||||
buf = mem.Malloc(size)
|
||||
if err = nc.ReadNto(size, buf); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if channelID&1 == 0 {
|
||||
switch channelID {
|
||||
case receiver.AudioChannelID:
|
||||
if !receiver.PubAudio {
|
||||
continue
|
||||
}
|
||||
packet := &rtp.Packet{}
|
||||
if err = packet.Unmarshal(buf); err != nil {
|
||||
return
|
||||
}
|
||||
if len(audioFrame.Packets) == 0 || packet.Timestamp == audioFrame.Packets[0].Timestamp {
|
||||
audioFrame.AddRecycleBytes(buf)
|
||||
audioFrame.Packets = append(audioFrame.Packets, packet)
|
||||
} else {
|
||||
err = receiver.WriteAudio(audioFrame)
|
||||
audioFrame = &mrtp.RTPAudio{}
|
||||
audioFrame.AddRecycleBytes(buf)
|
||||
audioFrame.Packets = []*rtp.Packet{packet}
|
||||
audioFrame.RTPCodecParameters = receiver.VideoCodecParameters
|
||||
audioFrame.ScalableMemoryAllocator = mem
|
||||
}
|
||||
case receiver.VideoChannelID:
|
||||
if !receiver.PubVideo {
|
||||
continue
|
||||
}
|
||||
packet := &rtp.Packet{}
|
||||
if err = packet.Unmarshal(buf); err != nil {
|
||||
return
|
||||
}
|
||||
if len(videoFrame.Packets) == 0 || packet.Timestamp == videoFrame.Packets[0].Timestamp {
|
||||
videoFrame.AddRecycleBytes(buf)
|
||||
videoFrame.Packets = append(videoFrame.Packets, packet)
|
||||
} else {
|
||||
// t := time.Now()
|
||||
err = receiver.WriteVideo(videoFrame)
|
||||
// fmt.Println("write video", time.Since(t))
|
||||
videoFrame = &mrtp.RTPVideo{}
|
||||
videoFrame.AddRecycleBytes(buf)
|
||||
videoFrame.Packets = []*rtp.Packet{packet}
|
||||
videoFrame.RTPCodecParameters = receiver.VideoCodecParameters
|
||||
videoFrame.ScalableMemoryAllocator = mem
|
||||
}
|
||||
default:
|
||||
|
||||
}
|
||||
} else {
|
||||
msg := &RTCP{Channel: channelID}
|
||||
mem.Free(buf)
|
||||
if err = msg.Header.Unmarshal(buf); err != nil {
|
||||
return
|
||||
}
|
||||
if msg.Packets, err = rtcp.Unmarshal(buf); err != nil {
|
||||
return
|
||||
}
|
||||
logger.Debug("rtcp", "type", msg.Header.Type, "length", msg.Header.Length)
|
||||
// TODO: rtcp msg
|
||||
}
|
||||
|
||||
//if keepaliveDT != 0 && ts.After(keepaliveTS) {
|
||||
// req := &tcp.Request{Method: MethodOptions, URL: c.URL}
|
||||
// if err = c.WriteRequest(req); err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// keepaliveTS = ts.Add(keepaliveDT)
|
||||
//}
|
||||
}
|
||||
return
|
||||
|
||||
case MethodTeardown:
|
||||
res := &tcp.Response{Request: req}
|
||||
_ = nc.WriteResponse(res)
|
||||
return
|
||||
|
||||
default:
|
||||
p.Warn("unsupported method", "method", req.Method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reqTrackID(req *tcp.Request) int {
|
||||
var s string
|
||||
if req.URL.RawQuery != "" {
|
||||
s = req.URL.RawQuery
|
||||
} else {
|
||||
s = req.URL.Path
|
||||
}
|
||||
if i := strings.LastIndexByte(s, '='); i > 0 {
|
||||
if i, err := strconv.Atoi(s[i+1:]); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
190
plugin/rtsp/pkg/connection.go
Normal file
190
plugin/rtsp/pkg/connection.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
||||
"log/slog"
|
||||
"m7s.live/m7s/v5/pkg/util"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const Timeout = time.Second * 5
|
||||
|
||||
func NewNetConnection(conn net.Conn, logger *slog.Logger) *NetConnection {
|
||||
defer logger.Info("new connection")
|
||||
return &NetConnection{
|
||||
conn: conn,
|
||||
Logger: logger,
|
||||
BufReader: util.NewBufReader(conn),
|
||||
textReader: bufio.NewReader(conn),
|
||||
}
|
||||
}
|
||||
|
||||
type NetConnection struct {
|
||||
*slog.Logger
|
||||
*util.BufReader
|
||||
textReader *bufio.Reader
|
||||
Backchannel bool
|
||||
Media string
|
||||
PacketSize uint16
|
||||
SessionName string
|
||||
Timeout int
|
||||
Transport string // custom transport support, ex. RTSP over WebSocket
|
||||
|
||||
Medias []*core.Media
|
||||
UserAgent string
|
||||
URL *url.URL
|
||||
|
||||
// internal
|
||||
|
||||
auth *tcp.Auth
|
||||
conn net.Conn
|
||||
keepalive int
|
||||
mode core.Mode
|
||||
sequence int
|
||||
Session string
|
||||
sdp string
|
||||
uri string
|
||||
|
||||
state State
|
||||
stateMu sync.Mutex
|
||||
SDP string
|
||||
}
|
||||
|
||||
func (c *NetConnection) Destroy() {
|
||||
c.conn.Close()
|
||||
c.BufReader.Recycle()
|
||||
c.Info("destroy connection")
|
||||
}
|
||||
|
||||
const (
|
||||
ProtoRTSP = "RTSP/1.0"
|
||||
MethodOptions = "OPTIONS"
|
||||
MethodSetup = "SETUP"
|
||||
MethodTeardown = "TEARDOWN"
|
||||
MethodDescribe = "DESCRIBE"
|
||||
MethodPlay = "PLAY"
|
||||
MethodPause = "PAUSE"
|
||||
MethodAnnounce = "ANNOUNCE"
|
||||
MethodRecord = "RECORD"
|
||||
)
|
||||
|
||||
type State byte
|
||||
|
||||
func (s State) String() string {
|
||||
switch s {
|
||||
case StateNone:
|
||||
return "NONE"
|
||||
case StateConn:
|
||||
|
||||
return "CONN"
|
||||
case StateSetup:
|
||||
return MethodSetup
|
||||
case StatePlay:
|
||||
return MethodPlay
|
||||
}
|
||||
return strconv.Itoa(int(s))
|
||||
}
|
||||
|
||||
const (
|
||||
StateNone State = iota
|
||||
StateConn
|
||||
StateSetup
|
||||
StatePlay
|
||||
)
|
||||
|
||||
func (c *NetConnection) WriteRequest(req *tcp.Request) error {
|
||||
if req.Proto == "" {
|
||||
req.Proto = ProtoRTSP
|
||||
}
|
||||
|
||||
if req.Header == nil {
|
||||
req.Header = make(map[string][]string)
|
||||
}
|
||||
|
||||
c.sequence++
|
||||
// important to send case sensitive CSeq
|
||||
// https://github.com/AlexxIT/go2rtc/issues/7
|
||||
req.Header["CSeq"] = []string{strconv.Itoa(c.sequence)}
|
||||
|
||||
c.auth.Write(req)
|
||||
|
||||
if c.Session != "" {
|
||||
req.Header.Set("Session", c.Session)
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
val := strconv.Itoa(len(req.Body))
|
||||
req.Header.Set("Content-Length", val)
|
||||
}
|
||||
|
||||
if err := c.conn.SetWriteDeadline(time.Now().Add(Timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return req.Write(c.conn)
|
||||
}
|
||||
|
||||
func (c *NetConnection) ReadRequest() (req *tcp.Request, err error) {
|
||||
if err = c.conn.SetReadDeadline(time.Now().Add(Timeout)); err != nil {
|
||||
return
|
||||
}
|
||||
req, err = tcp.ReadRequest(c.textReader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Debug(req.String())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *NetConnection) WriteResponse(res *tcp.Response) error {
|
||||
if res.Proto == "" {
|
||||
res.Proto = ProtoRTSP
|
||||
}
|
||||
|
||||
if res.Status == "" {
|
||||
res.Status = "200 OK"
|
||||
}
|
||||
|
||||
if res.Header == nil {
|
||||
res.Header = make(map[string][]string)
|
||||
}
|
||||
|
||||
if res.Request != nil && res.Request.Header != nil {
|
||||
seq := res.Request.Header.Get("CSeq")
|
||||
if seq != "" {
|
||||
res.Header.Set("CSeq", seq)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Session != "" {
|
||||
if res.Request != nil && res.Request.Method == MethodSetup {
|
||||
res.Header.Set("Session", c.Session+";timeout=60")
|
||||
} else {
|
||||
res.Header.Set("Session", c.Session)
|
||||
}
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
val := strconv.Itoa(len(res.Body))
|
||||
res.Header.Set("Content-Length", val)
|
||||
}
|
||||
|
||||
if err := c.conn.SetWriteDeadline(time.Now().Add(Timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Debug(res.String())
|
||||
return res.Write(c.conn)
|
||||
}
|
||||
|
||||
func (c *NetConnection) ReadResponse() (*tcp.Response, error) {
|
||||
if err := c.conn.SetReadDeadline(time.Now().Add(Timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tcp.ReadResponse(c.textReader)
|
||||
}
|
108
plugin/rtsp/pkg/sdp.go
Normal file
108
plugin/rtsp/pkg/sdp.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"io"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/sdp/v3"
|
||||
)
|
||||
|
||||
type RTCP struct {
|
||||
Channel byte
|
||||
Header rtcp.Header
|
||||
Packets []rtcp.Packet
|
||||
}
|
||||
|
||||
const sdpHeader = `v=0
|
||||
o=- 0 0 IN IP4 0.0.0.0
|
||||
s=-
|
||||
t=0 0`
|
||||
|
||||
func UnmarshalSDP(rawSDP []byte) ([]*core.Media, error) {
|
||||
sd := &sdp.SessionDescription{}
|
||||
if err := sd.Unmarshal(rawSDP); err != nil {
|
||||
// fix multiple `s=` https://github.com/AlexxIT/WebRTC/issues/417
|
||||
re, _ := regexp.Compile("\ns=[^\n]+")
|
||||
rawSDP = re.ReplaceAll(rawSDP, nil)
|
||||
|
||||
// fix SDP header for some cameras
|
||||
if i := bytes.Index(rawSDP, []byte("\nm=")); i > 0 {
|
||||
rawSDP = append([]byte(sdpHeader), rawSDP[i:]...)
|
||||
}
|
||||
|
||||
// Fix invalid media type (errSDPInvalidValue) caused by
|
||||
// some TP-LINK IP camera, e.g. TL-IPC44GW
|
||||
rawSDP = bytes.ReplaceAll(rawSDP, []byte("m=application/TP-LINK "), []byte("m=application "))
|
||||
// more tplink ipcams
|
||||
rawSDP = bytes.ReplaceAll(rawSDP, []byte("m=application/tp-link "), []byte("m=application "))
|
||||
|
||||
if err == io.EOF {
|
||||
rawSDP = append(rawSDP, '\n')
|
||||
}
|
||||
|
||||
sd = &sdp.SessionDescription{}
|
||||
err = sd.Unmarshal(rawSDP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var medias []*core.Media
|
||||
|
||||
for _, md := range sd.MediaDescriptions {
|
||||
media := core.UnmarshalMedia(md)
|
||||
|
||||
// Check buggy SDP with fmtp for H264 on another track
|
||||
// https://github.com/AlexxIT/WebRTC/issues/419
|
||||
for _, codec := range media.Codecs {
|
||||
if codec.Name == core.CodecH264 && codec.FmtpLine == "" {
|
||||
codec.FmtpLine = findFmtpLine(codec.PayloadType, sd.MediaDescriptions)
|
||||
}
|
||||
}
|
||||
|
||||
if media.Direction == "" {
|
||||
media.Direction = core.DirectionRecvonly
|
||||
}
|
||||
|
||||
medias = append(medias, media)
|
||||
}
|
||||
|
||||
return medias, nil
|
||||
}
|
||||
|
||||
func findFmtpLine(payloadType uint8, descriptions []*sdp.MediaDescription) string {
|
||||
s := strconv.Itoa(int(payloadType))
|
||||
for _, md := range descriptions {
|
||||
codec := core.UnmarshalCodec(md, s)
|
||||
if codec.FmtpLine != "" {
|
||||
return codec.FmtpLine
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// urlParse fix bugs:
|
||||
// 1. Content-Base: rtsp://::ffff:192.168.1.123/onvif/profile.1/
|
||||
// 2. Content-Base: rtsp://rtsp://turret2-cam.lan:554/stream1/
|
||||
func urlParse(rawURL string) (*url.URL, error) {
|
||||
if strings.HasPrefix(rawURL, "rtsp://rtsp://") {
|
||||
rawURL = rawURL[7:]
|
||||
}
|
||||
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil && strings.HasSuffix(err.Error(), "after host") {
|
||||
if i1 := strings.Index(rawURL, "://"); i1 > 0 {
|
||||
if i2 := strings.IndexByte(rawURL[i1+3:], '/'); i2 > 0 {
|
||||
return urlParse(rawURL[:i1+3+i2] + ":" + rawURL[i1+3+i2:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return u, err
|
||||
}
|
15
plugin/rtsp/pkg/transceiver.go
Normal file
15
plugin/rtsp/pkg/transceiver.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package rtsp
|
||||
|
||||
import (
|
||||
"github.com/pion/webrtc/v4"
|
||||
"m7s.live/m7s/v5"
|
||||
)
|
||||
|
||||
type Receiver struct {
|
||||
*m7s.Publisher
|
||||
*NetConnection
|
||||
AudioCodecParameters *webrtc.RTPCodecParameters
|
||||
VideoCodecParameters *webrtc.RTPCodecParameters
|
||||
AudioChannelID byte
|
||||
VideoChannelID byte
|
||||
}
|
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
var (
|
||||
Version = "v5.0.0"
|
||||
MergeConfigs = []string{"Publish", "Subscribe", "HTTP"}
|
||||
MergeConfigs = []string{"Publish", "Subscribe", "HTTP", "PublicIP"}
|
||||
ExecPath = os.Args[0]
|
||||
ExecDir = filepath.Dir(ExecPath)
|
||||
serverIndexG atomic.Uint32
|
||||
@@ -377,7 +377,9 @@ func (s *Server) onUnsubscribe(subscriber *Subscriber) {
|
||||
|
||||
func (s *Server) onUnpublish(publisher *Publisher) {
|
||||
s.Streams.Remove(publisher)
|
||||
if publisher.Subscribers.Length > 0 {
|
||||
s.Waiting.Add(publisher)
|
||||
}
|
||||
s.Info("unpublish", "streamPath", publisher.StreamPath, "count", s.Streams.Length, "reason", publisher.StopReason())
|
||||
for subscriber := range publisher.SubscriberRange {
|
||||
waitCloseTimeout := publisher.WaitCloseTimeout
|
||||
|
18
transformer.go
Normal file
18
transformer.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package m7s
|
||||
|
||||
import "m7s.live/m7s/v5/pkg"
|
||||
|
||||
type Transformer struct {
|
||||
*Publisher
|
||||
*Subscriber
|
||||
}
|
||||
|
||||
func (t *Transformer) Transform() {
|
||||
PlayBlock(t.Subscriber, func(audioFrame *pkg.AVFrame) error {
|
||||
//t.Publisher.WriteAudio()
|
||||
return nil
|
||||
}, func(videoFrame *pkg.AVFrame) error {
|
||||
//t.Publisher.WriteVideo()
|
||||
return nil
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user