feat: add rtsp plugin

This commit is contained in:
langhuihui
2024-06-21 16:04:21 +08:00
parent d39e3c1ed0
commit 9ef8b7ad02
27 changed files with 990 additions and 114 deletions

View File

@@ -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:

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -55,11 +55,11 @@ type (
}
AVRing = util.Ring[AVFrame]
DataFrame struct {
sync.RWMutex `json:"-" yaml:"-"` // 读写锁
discard bool
Sequence uint32 // 在一个Track中的序号
WriteTime time.Time // 写入时间,可用于比较两个帧的先后
Raw any `json:"-" yaml:"-"` // 裸格式
sync.RWMutex
discard bool
Sequence uint32 // 在一个Track中的序号
WriteTime time.Time // 写入时间,可用于比较两个帧的先后
Raw any // 裸格式
}
)

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
View 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:portip 可省略默认监听所有网卡"`
CertFile string `desc:"证书文件"`
KeyFile string `desc:"私钥文件"`
AutoListen bool `default:"true" desc:"是否自动监听"`
}

View File

@@ -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)
}

View File

@@ -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
View 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) {
}

View File

@@ -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,

View File

@@ -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 // 当前写的字节

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -1,8 +1,10 @@
package rtp
import (
"encoding/base64"
"errors"
"fmt"
"regexp"
"slices"
"time"
@@ -35,10 +37,11 @@ type (
)
var (
_ IAVFrame = (*RTPVideo)(nil)
_ IVideoCodecCtx = (*RTPH264Ctx)(nil)
_ IVideoCodecCtx = (*RTPH265Ctx)(nil)
_ IVideoCodecCtx = (*RTPAV1Ctx)(nil)
_ IAVFrame = (*RTPVideo)(nil)
_ 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
View 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
}

View 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
View 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
}

View 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
}

View File

@@ -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)
s.Waiting.Add(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
View 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
})
}