mirror of
				https://github.com/langhuihui/monibuca.git
				synced 2025-11-01 00:52:36 +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
	 langhuihui
					langhuihui