diff --git a/api.go b/api.go index 00c1dc4..1895349 100644 --- a/api.go +++ b/api.go @@ -80,7 +80,7 @@ func (s *Server) api_Stream_AnnexB_(rw http.ResponseWriter, r *http.Request) { var annexb pkg.AnnexB var t pkg.AVTrack - err = annexb.ConvertCtx(publisher.VideoTrack.ICodecCtx, &t) + t.ICodecCtx, t.SequenceFrame, err = annexb.ConvertCtx(publisher.VideoTrack.ICodecCtx) if t.ICodecCtx == nil { http.Error(rw, "unsupported codec", http.StatusInternalServerError) return @@ -123,8 +123,8 @@ func (s *Server) getStreamInfo(pub *Publisher) (res *pb.StreamInfoResponse, err Delta: pub.VideoTrack.Delta.String(), Gop: uint32(pub.GOP), } - res.VideoTrack.Width = uint32(t.ICodecCtx.(pkg.IVideoCodecCtx).GetWidth()) - res.VideoTrack.Height = uint32(t.ICodecCtx.(pkg.IVideoCodecCtx).GetHeight()) + res.VideoTrack.Width = uint32(t.ICodecCtx.(pkg.IVideoCodecCtx).Width()) + res.VideoTrack.Height = uint32(t.ICodecCtx.(pkg.IVideoCodecCtx).Height()) } } return diff --git a/example/default/main.go b/example/default/main.go index 02b5648..e08335c 100644 --- a/example/default/main.go +++ b/example/default/main.go @@ -11,6 +11,7 @@ import ( _ "m7s.live/m7s/v5/plugin/logrotate" _ "m7s.live/m7s/v5/plugin/rtmp" _ "m7s.live/m7s/v5/plugin/rtsp" + _ "m7s.live/m7s/v5/plugin/stress" _ "m7s.live/m7s/v5/plugin/webrtc" ) diff --git a/example/xdp/main.go b/example/xdp/main.go new file mode 100644 index 0000000..e3b7073 --- /dev/null +++ b/example/xdp/main.go @@ -0,0 +1,126 @@ +// Copyright 2019 Asavie Technologies Ltd. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +/* +dumpframes demostrates how to receive frames from a network link using +github.com/asavie/xdp package, it sets up an XDP socket attached to a +particular network link and dumps all frames it receives to standard output. +*/ +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "log" + "net" + + "github.com/asavie/xdp" + "github.com/asavie/xdp/examples/dumpframes/ebpf" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +func main() { + var linkName string + var queueID int + var protocol int64 + + log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) + + flag.StringVar(&linkName, "linkname", "enp3s0", "The network link on which rebroadcast should run on.") + flag.IntVar(&queueID, "queueid", 0, "The ID of the Rx queue to which to attach to on the network link.") + flag.Int64Var(&protocol, "ip-proto", 0, "If greater than 0 and less than or equal to 255, limit xdp bpf_redirect_map to packets with the specified IP protocol number.") + flag.Parse() + + interfaces, err := net.Interfaces() + if err != nil { + fmt.Printf("error: failed to fetch the list of network interfaces on the system: %v\n", err) + return + } + + Ifindex := -1 + for _, iface := range interfaces { + if iface.Name == linkName { + Ifindex = iface.Index + break + } + } + if Ifindex == -1 { + fmt.Printf("error: couldn't find a suitable network interface to attach to\n") + return + } + + var program *xdp.Program + + // Create a new XDP eBPF program and attach it to our chosen network link. + if protocol == 0 { + program, err = xdp.NewProgram(queueID + 1) + } else { + program, err = ebpf.NewIPProtoProgram(uint32(protocol), nil) + } + if err != nil { + fmt.Printf("error: failed to create xdp program: %v\n", err) + return + } + defer program.Close() + if err := program.Attach(Ifindex); err != nil { + fmt.Printf("error: failed to attach xdp program to interface: %v\n", err) + return + } + defer program.Detach(Ifindex) + + // Create and initialize an XDP socket attached to our chosen network + // link. + xsk, err := xdp.NewSocket(Ifindex, queueID, nil) + if err != nil { + fmt.Printf("error: failed to create an XDP socket: %v\n", err) + return + } + + // Register our XDP socket file descriptor with the eBPF program so it can be redirected packets + if err := program.Register(queueID, xsk.FD()); err != nil { + fmt.Printf("error: failed to register socket in BPF map: %v\n", err) + return + } + defer program.Unregister(queueID) + + for { + // If there are any free slots on the Fill queue... + if n := xsk.NumFreeFillSlots(); n > 0 { + // ...then fetch up to that number of not-in-use + // descriptors and push them onto the Fill ring queue + // for the kernel to fill them with the received + // frames. + xsk.Fill(xsk.GetDescs(n, true)) + } + + // Wait for receive - meaning the kernel has + // produced one or more descriptors filled with a received + // frame onto the Rx ring queue. + log.Printf("waiting for frame(s) to be received...") + numRx, _, err := xsk.Poll(-1) + if err != nil { + fmt.Printf("error: %v\n", err) + return + } + + if numRx > 0 { + // Consume the descriptors filled with received frames + // from the Rx ring queue. + rxDescs := xsk.Receive(numRx) + + // Print the received frames and also modify them + // in-place replacing the destination MAC address with + // broadcast address. + for i := 0; i < len(rxDescs); i++ { + pktData := xsk.GetFrame(rxDescs[i]) + pkt := gopacket.NewPacket(pktData, layers.LayerTypeEthernet, gopacket.Default) + log.Printf("received frame:\n%s%+v", hex.Dump(pktData[:]), pkt) + } + } + } +} diff --git a/go.mod b/go.mod index 376df82..987552b 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,12 @@ go 1.22 toolchain go1.22.1 require ( - github.com/AlexxIT/go2rtc v1.9.4 + github.com/asavie/xdp v0.3.3 + github.com/cilium/ebpf v0.15.0 github.com/cnotch/ipchub v1.1.0 + github.com/deepch/vdk v0.0.27 github.com/emiago/sipgo v0.22.0 + github.com/google/gopacket v1.1.19 github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 github.com/pion/interceptor v0.1.29 github.com/pion/rtcp v1.2.14 @@ -16,6 +19,7 @@ require ( 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 + github.com/vishvananda/netlink v1.1.0 google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.33.0 @@ -100,6 +104,7 @@ require ( github.com/thanos-io/objstore v0.0.0-20240512204237-71ef2d0cf7c4 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.etcd.io/bbolt v1.3.6 // indirect @@ -116,7 +121,7 @@ require ( github.com/bluenviron/mediacommon v1.9.2 github.com/chromedp/chromedp v0.9.5 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect github.com/gorilla/websocket v1.5.1 github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/phsym/console-slog v0.3.1 @@ -126,8 +131,8 @@ require ( golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // 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/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 golang.org/x/tools v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index c78e57e..56b9e84 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -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= @@ -11,6 +9,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/apache/arrow/go/v16 v16.1.0 h1:dwgfOya6s03CzH9JrjCBx6bkVb4yPD4ma3haj9p7FXI= github.com/apache/arrow/go/v16 v16.1.0/go.mod h1:9wnc9mn6vEDTRIm4+27pEjQpRKuTvBaessPoEXQzxWA= +github.com/asavie/xdp v0.3.3 h1:b5Aa3EkMJYBeUO5TxPTIAa4wyUqYcsQr2s8f6YLJXhE= +github.com/asavie/xdp v0.3.3/go.mod h1:Vv5p+3mZiDh7ImdSvdon3E78wXyre7df5V58ATdIYAY= github.com/benbjohnson/immutable v0.4.0 h1:CTqXbEerYso8YzVPxmWxh2gnoRQbbB9X1quUC8+vGZA= github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -27,9 +27,9 @@ github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93 github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= +github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= github.com/cnotch/apirouter v0.0.0-20200731232942-89e243a791f3/go.mod h1:5deJPLON/x/s2dLOQfuKS0lenhOIT4xX0pvtN/OEIuY= github.com/cnotch/ipchub v1.1.0 h1:hH0lh2mU3AZXPiqMwA0pdtqrwo7PFIMRGush9OobMUs= github.com/cnotch/ipchub v1.1.0/go.mod h1:2PbeBs2q2VxxTVCn1eYCDwpAWuVXbq1+N0FU7GimOH4= @@ -48,6 +48,8 @@ github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf/go.mod h1:E3G3o1h8I7cfc github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deepch/vdk v0.0.27 h1:j/SHaTiZhA47wRpaue8NRp7P9xwOOO/lunxrDJBwcao= +github.com/deepch/vdk v0.0.27/go.mod h1:JlgGyR2ld6+xOIHa7XAxJh+stSDBAkdNvIPkUIdIywk= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -57,6 +59,7 @@ github.com/efficientgo/core v1.0.0-rc.2/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrja 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/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 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= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= @@ -67,6 +70,8 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -97,6 +102,7 @@ github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZat github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -105,8 +111,10 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ= +github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -119,7 +127,6 @@ github.com/hamba/avro/v2 v2.20.1/go.mod h1:xHiKXbISpb3Ovc809XdzWow+XGTn+Oyf/F9aZ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 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= @@ -134,6 +141,7 @@ github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ib github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -155,6 +163,7 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D 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/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 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= @@ -326,6 +335,10 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -360,6 +373,8 @@ 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/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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= @@ -369,6 +384,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r 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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -394,18 +410,21 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -452,6 +471,8 @@ 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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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= diff --git a/pkg/annexb.go b/pkg/annexb.go index fa38427..19d4f67 100644 --- a/pkg/annexb.go +++ b/pkg/annexb.go @@ -17,16 +17,15 @@ type AnnexB struct { } func (a *AnnexB) Dump(t byte, w io.Writer) { - m := a.Borrow(4 + a.Size) + m := a.GetAllocator().Borrow(4 + a.Size) binary.BigEndian.PutUint32(m, uint32(a.Size)) a.CopyTo(m[4:]) w.Write(m) } // DecodeConfig implements pkg.IAVFrame. -func (a *AnnexB) ConvertCtx(ctx codec.ICodecCtx, t *AVTrack) error { - t.ICodecCtx = ctx.GetBase() - return nil +func (a *AnnexB) ConvertCtx(ctx codec.ICodecCtx) (codec.ICodecCtx, IAVFrame, error) { + return ctx.GetBase(), nil, nil } // GetSize implements pkg.IAVFrame. @@ -61,9 +60,9 @@ func (a *AnnexB) Mux(codecCtx codec.ICodecCtx, frame *AVFrame) { if frame.IDR { switch ctx := codecCtx.(type) { case *codec.H264Ctx: - a.Append(ctx.SPS[0], codec.NALU_Delimiter2, ctx.PPS[0], codec.NALU_Delimiter2) + a.Append(ctx.SPS(), codec.NALU_Delimiter2, ctx.PPS(), codec.NALU_Delimiter2) case *codec.H265Ctx: - a.Append(ctx.SPS[0], codec.NALU_Delimiter2, ctx.PPS[0], codec.NALU_Delimiter2, ctx.VPS[0], codec.NALU_Delimiter2) + a.Append(ctx.SPS(), codec.NALU_Delimiter2, ctx.PPS(), codec.NALU_Delimiter2, ctx.VPS(), codec.NALU_Delimiter2) } } for i, nalu := range frame.Raw.(Nalus) { diff --git a/pkg/avframe.go b/pkg/avframe.go index e26d9af..d2a384e 100644 --- a/pkg/avframe.go +++ b/pkg/avframe.go @@ -20,8 +20,8 @@ type ( } IVideoCodecCtx interface { codec.ICodecCtx - GetWidth() int - GetHeight() int + Width() int + Height() int } IDataFrame interface { } @@ -29,10 +29,10 @@ type ( IAVFrame interface { GetAllocator() *util.ScalableMemoryAllocator SetAllocator(*util.ScalableMemoryAllocator) - Parse(*AVTrack) error // get codec info, idr - ConvertCtx(codec.ICodecCtx, *AVTrack) error // convert codec from source stream - Demux(codec.ICodecCtx) (any, error) // demux to raw format - Mux(codec.ICodecCtx, *AVFrame) // mux from raw format + Parse(*AVTrack) error // get codec info, idr + ConvertCtx(codec.ICodecCtx) (codec.ICodecCtx, IAVFrame, error) // convert codec from source stream + Demux(codec.ICodecCtx) (any, error) // demux to raw format + Mux(codec.ICodecCtx, *AVFrame) // mux from raw format GetTimestamp() time.Duration GetCTS() time.Duration GetSize() int @@ -67,6 +67,10 @@ type ( var _ IAVFrame = (*AnnexB)(nil) +func (frame *AVFrame) Clone() { + +} + func (frame *AVFrame) Reset() { frame.Timestamp = 0 frame.Raw = nil diff --git a/pkg/codec/audio.go b/pkg/codec/audio.go index 5f43e2c..895c122 100644 --- a/pkg/codec/audio.go +++ b/pkg/codec/audio.go @@ -2,6 +2,8 @@ package codec import ( "fmt" + "github.com/deepch/vdk/codec/aacparser" + "github.com/deepch/vdk/codec/opusparser" ) type ( @@ -17,11 +19,10 @@ type ( AudioCtx } OPUSCtx struct { - AudioCtx + opusparser.CodecData } AACCtx struct { - AudioCtx - Asc []byte + aacparser.CodecData } ) @@ -41,10 +42,21 @@ func (ctx *AudioCtx) GetInfo() string { return fmt.Sprintf("sample rate: %d, channels: %d, sample size: %d", ctx.SampleRate, ctx.Channels, ctx.SampleSize) } +func (ctx *AACCtx) GetChannels() int { + return ctx.ChannelLayout().Count() +} +func (ctx *AACCtx) GetSampleSize() int { + return 16 +} +func (ctx *AACCtx) GetSampleRate() int { + return ctx.SampleRate() +} func (ctx *AACCtx) GetBase() ICodecCtx { return ctx } - +func (ctx *AACCtx) GetInfo() string { + return fmt.Sprintf("sample rate: %d, channels: %d, object type: %d", ctx.SampleRate(), ctx.GetChannels(), ctx.Config.ObjectType) +} func (*PCMUCtx) FourCC() FourCC { return FourCC_ULAW } @@ -72,3 +84,15 @@ func (*OPUSCtx) FourCC() FourCC { func (ctx *OPUSCtx) GetBase() ICodecCtx { return ctx } +func (ctx *OPUSCtx) GetChannels() int { + return ctx.ChannelLayout().Count() +} +func (ctx *OPUSCtx) GetSampleSize() int { + return 16 +} +func (ctx *OPUSCtx) GetSampleRate() int { + return ctx.SampleRate() +} +func (ctx *OPUSCtx) GetInfo() string { + return fmt.Sprintf("sample rate: %d, channels: %d", ctx.SampleRate(), ctx.ChannelLayout().Count()) +} diff --git a/pkg/codec/av1.go b/pkg/codec/av1.go index f0963af..d7318f2 100644 --- a/pkg/codec/av1.go +++ b/pkg/codec/av1.go @@ -26,11 +26,11 @@ func (ctx *AV1Ctx) GetBase() ICodecCtx { return ctx } -func (ctx *AV1Ctx) GetWidth() int { +func (ctx *AV1Ctx) Width() int { return 0 } -func (ctx *AV1Ctx) GetHeight() int { +func (ctx *AV1Ctx) Height() int { return 0 } diff --git a/pkg/codec/h264.go b/pkg/codec/h264.go index 7754c71..fff0ec1 100644 --- a/pkg/codec/h264.go +++ b/pkg/codec/h264.go @@ -3,6 +3,7 @@ package codec import ( "bytes" "fmt" + "github.com/deepch/vdk/codec/h264parser" ) // Start Code + NAL Unit -> NALU Header + NALU Body @@ -107,9 +108,7 @@ func SplitH264(payload []byte) (nalus [][]byte) { type ( H264Ctx struct { - SPSInfo - SPS [][]byte - PPS [][]byte + h264parser.CodecData } ) @@ -118,15 +117,7 @@ func (*H264Ctx) FourCC() FourCC { } func (ctx *H264Ctx) GetInfo() string { - return fmt.Sprintf("sps: % 02X,pps: % 02X", ctx.SPS[0], ctx.PPS[0]) -} - -func (h264 *H264Ctx) GetWidth() int { - return int(h264.Width) -} - -func (h264 *H264Ctx) GetHeight() int { - return int(h264.Height) + return fmt.Sprintf("fps: %d, resolution: %s", ctx.FPS(), ctx.Resolution()) } func (h264 *H264Ctx) GetBase() ICodecCtx { diff --git a/pkg/codec/h265.go b/pkg/codec/h265.go index 04c42bc..970d9c4 100644 --- a/pkg/codec/h265.go +++ b/pkg/codec/h265.go @@ -1,6 +1,7 @@ package codec import "fmt" +import "github.com/deepch/vdk/codec/h265parser" type H265NALUType byte @@ -12,100 +13,16 @@ func ParseH265NALUType(b byte) H265NALUType { return H265NALUType(b & 0x7E >> 1) } -const ( - // HEVC_VPS = 0x40 - // HEVC_SPS = 0x42 - // HEVC_PPS = 0x44 - // HEVC_SEI = 0x4E - // HEVC_IDR = 0x26 - // HEVC_PSLICE = 0x02 - - NAL_UNIT_CODED_SLICE_TRAIL_N H265NALUType = iota // 0 - NAL_UNIT_CODED_SLICE_TRAIL_R // 1 - NAL_UNIT_CODED_SLICE_TSA_N // 2 - NAL_UNIT_CODED_SLICE_TLA // 3 // Current name in the spec: TSA_R - NAL_UNIT_CODED_SLICE_STSA_N // 4 - NAL_UNIT_CODED_SLICE_STSA_R // 5 - NAL_UNIT_CODED_SLICE_RADL_N // 6 - NAL_UNIT_CODED_SLICE_DLP // 7 // Current name in the spec: RADL_R - NAL_UNIT_CODED_SLICE_RASL_N // 8 - NAL_UNIT_CODED_SLICE_TFD // 9 // Current name in the spec: RASL_R - NAL_UNIT_RESERVED_10 - NAL_UNIT_RESERVED_11 - NAL_UNIT_RESERVED_12 - NAL_UNIT_RESERVED_13 - NAL_UNIT_RESERVED_14 - NAL_UNIT_RESERVED_15 - NAL_UNIT_CODED_SLICE_BLA // 16 // Current name in the spec: BLA_W_LP - NAL_UNIT_CODED_SLICE_BLANT // 17 // Current name in the spec: BLA_W_DLP - NAL_UNIT_CODED_SLICE_BLA_N_LP // 18 - NAL_UNIT_CODED_SLICE_IDR // 19// Current name in the spec: IDR_W_DLP - NAL_UNIT_CODED_SLICE_IDR_N_LP // 20 - NAL_UNIT_CODED_SLICE_CRA // 21 - NAL_UNIT_RESERVED_22 - NAL_UNIT_RESERVED_23 - NAL_UNIT_RESERVED_24 - NAL_UNIT_RESERVED_25 - NAL_UNIT_RESERVED_26 - NAL_UNIT_RESERVED_27 - NAL_UNIT_RESERVED_28 - NAL_UNIT_RESERVED_29 - NAL_UNIT_RESERVED_30 - NAL_UNIT_RESERVED_31 - NAL_UNIT_VPS // 32 - NAL_UNIT_SPS // 33 - NAL_UNIT_PPS // 34 - NAL_UNIT_ACCESS_UNIT_DELIMITER // 35 - NAL_UNIT_EOS // 36 - NAL_UNIT_EOB // 37 - NAL_UNIT_FILLER_DATA // 38 - NAL_UNIT_SEI // 39 Prefix SEI - NAL_UNIT_SEI_SUFFIX // 40 Suffix SEI - NAL_UNIT_RESERVED_41 - NAL_UNIT_RESERVED_42 - NAL_UNIT_RESERVED_43 - NAL_UNIT_RESERVED_44 - NAL_UNIT_RESERVED_45 - NAL_UNIT_RESERVED_46 - NAL_UNIT_RESERVED_47 - NAL_UNIT_RTP_AP - NAL_UNIT_RTP_FU - NAL_UNIT_UNSPECIFIED_50 - NAL_UNIT_UNSPECIFIED_51 - NAL_UNIT_UNSPECIFIED_52 - NAL_UNIT_UNSPECIFIED_53 - NAL_UNIT_UNSPECIFIED_54 - NAL_UNIT_UNSPECIFIED_55 - NAL_UNIT_UNSPECIFIED_56 - NAL_UNIT_UNSPECIFIED_57 - NAL_UNIT_UNSPECIFIED_58 - NAL_UNIT_UNSPECIFIED_59 - NAL_UNIT_UNSPECIFIED_60 - NAL_UNIT_UNSPECIFIED_61 - NAL_UNIT_UNSPECIFIED_62 - NAL_UNIT_UNSPECIFIED_63 - NAL_UNIT_INVALID -) - var AudNalu = []byte{0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10} type ( H265Ctx struct { - H264Ctx - VPS [][]byte + h265parser.CodecData } ) func (ctx *H265Ctx) GetInfo() string { - return fmt.Sprintf("sps: % 02X,pps: % 02X,vps: % 02X", ctx.SPS[0], ctx.PPS[0], ctx.VPS[0]) -} - -func (h265 *H265Ctx) GetHeight() int { - return int(h265.Height) -} - -func (h265 *H265Ctx) GetWidth() int { - return int(h265.Width) + return fmt.Sprintf("fps: %d, resolution: %s", ctx.FPS(), ctx.Resolution()) } func (*H265Ctx) FourCC() FourCC { diff --git a/pkg/track.go b/pkg/track.go index 24471a7..ee41a8f 100644 --- a/pkg/track.go +++ b/pkg/track.go @@ -78,8 +78,29 @@ func (t *Track) AddBytesIn(n int) { } } +func (t *AVTrack) Ready(err error) { + if !t.IsReady() { + if err != nil { + t.Error("ready", "err", err) + } else { + switch ctx := t.ICodecCtx.(type) { + case IVideoCodecCtx: + t.Info("ready", "info", t.ICodecCtx.GetInfo(), "width", ctx.Width(), "height", ctx.Height()) + case IAudioCodecCtx: + t.Info("ready", "info", t.ICodecCtx.GetInfo(), "channels", ctx.GetChannels(), "sample_rate", ctx.GetSampleRate()) + } + } + t.ready.Fulfill(err) + } +} + func (t *Track) Ready(err error) { if !t.IsReady() { + if err != nil { + t.Error("ready", "err", err) + } else { + t.Info("ready") + } t.ready.Fulfill(err) } } diff --git a/pkg/util/buffers.go b/pkg/util/buffers.go index 3a67022..2ccc6f8 100644 --- a/pkg/util/buffers.go +++ b/pkg/util/buffers.go @@ -90,6 +90,10 @@ func (m *Memory) NewReader() *MemoryReader { return &reader } +func (r *MemoryReader) Offset() int { + return r.Size - r.Length +} + func (r *MemoryReader) Pop() []byte { panic("ReadableBuffers Pop not allowed") } diff --git a/pkg/util/mem.go b/pkg/util/mem.go index b8391cc..7208679 100644 --- a/pkg/util/mem.go +++ b/pkg/util/mem.go @@ -16,12 +16,12 @@ const ( ) var ( - memoryPool [BuddySize]byte - buddy = NewBuddy(BuddySize >> MinPowerOf2) - lock sync.Mutex - poolStart = int64(uintptr(unsafe.Pointer(&memoryPool[0]))) - blockPool = list.New() - EnableCheckSize bool = false + memoryPool [BuddySize]byte + buddy = NewBuddy(BuddySize >> MinPowerOf2) + lock sync.Mutex + poolStart = int64(uintptr(unsafe.Pointer(&memoryPool[0]))) + blockPool = list.New() + //EnableCheckSize bool = false ) type MemoryAllocator struct { @@ -176,9 +176,9 @@ func (sma *ScalableMemoryAllocator) Malloc(size int) (memory []byte) { if sma == nil || size > MaxBlockSize { return make([]byte, size) } - if EnableCheckSize { - defer sma.checkSize() - } + //if EnableCheckSize { + // defer sma.checkSize() + //} defer sma.addMallocCount(size) var child *MemoryAllocator for _, child = range sma.children { @@ -228,9 +228,9 @@ func (sma *ScalableMemoryAllocator) Free(mem []byte) bool { if sma == nil { return false } - if EnableCheckSize { - defer sma.checkSize() - } + //if EnableCheckSize { + // defer sma.checkSize() + //} ptr := int64(uintptr(unsafe.Pointer(&mem[0]))) size := len(mem) for i, child := range sma.children { @@ -247,57 +247,16 @@ func (sma *ScalableMemoryAllocator) Free(mem []byte) bool { return false } -type RecyclableMemory struct { - *ScalableMemoryAllocator - Memory - RecycleIndexes []int -} - -func (r *RecyclableMemory) SetAllocator(allocator *ScalableMemoryAllocator) { - r.ScalableMemoryAllocator = allocator -} - -func (r *RecyclableMemory) NextN(size int) (memory []byte) { - memory = r.ScalableMemoryAllocator.Malloc(size) - if memory == nil { - memory = make([]byte, size) - } else if r.RecycleIndexes != nil { - r.RecycleIndexes = append(r.RecycleIndexes, r.Count()) - } - r.AppendOne(memory) - return -} - -func (r *RecyclableMemory) AddRecycleBytes(b []byte) { - if r.RecycleIndexes != nil { - r.RecycleIndexes = append(r.RecycleIndexes, r.Count()) - } - r.AppendOne(b) -} - -func (r *RecyclableMemory) RemoveRecycleBytes(index int) (buf []byte) { - if index < 0 { - index = r.Count() + index - } - buf = r.Buffers[index] - if r.RecycleIndexes != nil { - i := slices.Index(r.RecycleIndexes, index) - r.RecycleIndexes = slices.Delete(r.RecycleIndexes, i, i+1) - } - r.Buffers = slices.Delete(r.Buffers, index, index+1) - r.Size -= len(buf) - return -} - -func (r *RecyclableMemory) Recycle() { - if r.RecycleIndexes != nil { - for _, index := range r.RecycleIndexes { - r.Free(r.Buffers[index]) - } - r.RecycleIndexes = r.RecycleIndexes[:0] - } else { - for _, buf := range r.Buffers { - r.Free(buf) - } - } -} +//func (r *RecyclableMemory) RemoveRecycleBytes(index int) (buf []byte) { +// if index < 0 { +// index = r.Count() + index +// } +// buf = r.Buffers[index] +// if r.recycleIndexes != nil { +// i := slices.Index(r.recycleIndexes, index) +// r.recycleIndexes = slices.Delete(r.recycleIndexes, i, i+1) +// } +// r.Buffers = slices.Delete(r.Buffers, index, index+1) +// r.Size -= len(buf) +// return +//} diff --git a/pkg/util/rm_disable.go b/pkg/util/rm_disable.go new file mode 100644 index 0000000..4d33df5 --- /dev/null +++ b/pkg/util/rm_disable.go @@ -0,0 +1,30 @@ +//go:build disable_rm + +package util + +type RecyclableMemory struct { + Memory +} + +func (r *RecyclableMemory) InitRecycleIndexes(max int) { +} + +func (r *RecyclableMemory) GetAllocator() *ScalableMemoryAllocator { + return nil +} + +func (r *RecyclableMemory) SetAllocator(allocator *ScalableMemoryAllocator) { +} + +func (r *RecyclableMemory) Recycle() { +} + +func (r *RecyclableMemory) NextN(size int) (memory []byte) { + memory = make([]byte, size) + r.AppendOne(memory) + return memory +} + +func (r *RecyclableMemory) AddRecycleBytes(b []byte) { + r.AppendOne(b) +} diff --git a/pkg/util/rm_enable.go b/pkg/util/rm_enable.go new file mode 100644 index 0000000..fa2ddda --- /dev/null +++ b/pkg/util/rm_enable.go @@ -0,0 +1,50 @@ +//go:build !disable_rm + +package util + +type RecyclableMemory struct { + allocator *ScalableMemoryAllocator + Memory + recycleIndexes []int +} + +func (r *RecyclableMemory) InitRecycleIndexes(max int) { + r.recycleIndexes = make([]int, 0, max) +} + +func (r *RecyclableMemory) GetAllocator() *ScalableMemoryAllocator { + return r.allocator +} + +func (r *RecyclableMemory) NextN(size int) (memory []byte) { + memory = r.allocator.Malloc(size) + if r.recycleIndexes != nil { + r.recycleIndexes = append(r.recycleIndexes, r.Count()) + } + r.AppendOne(memory) + return +} + +func (r *RecyclableMemory) AddRecycleBytes(b []byte) { + if r.recycleIndexes != nil { + r.recycleIndexes = append(r.recycleIndexes, r.Count()) + } + r.AppendOne(b) +} + +func (r *RecyclableMemory) SetAllocator(allocator *ScalableMemoryAllocator) { + r.allocator = allocator +} + +func (r *RecyclableMemory) Recycle() { + if r.recycleIndexes != nil { + for _, index := range r.recycleIndexes { + r.allocator.Free(r.Buffers[index]) + } + r.recycleIndexes = r.recycleIndexes[:0] + } else { + for _, buf := range r.Buffers { + r.allocator.Free(buf) + } + } +} diff --git a/pkg/util/xdp_linux.go b/pkg/util/xdp_linux.go new file mode 100644 index 0000000..dbc95ed --- /dev/null +++ b/pkg/util/xdp_linux.go @@ -0,0 +1,933 @@ +package util + +import ( + "fmt" + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" + "reflect" + "syscall" + "time" + "unsafe" +) + +// DefaultSocketOptions is the default SocketOptions used by an xdp.Socket created without specifying options. +var DefaultSocketOptions = SocketOptions{ + NumFrames: 128, + FrameSize: 2048, + FillRingNumDescs: 64, + CompletionRingNumDescs: 64, + RxRingNumDescs: 64, + TxRingNumDescs: 64, +} + +type umemRing struct { + Producer *uint32 + Consumer *uint32 + Descs []uint64 +} + +type rxTxRing struct { + Producer *uint32 + Consumer *uint32 + Descs []Desc +} + +// A Socket is an implementation of the AF_XDP Linux socket type for reading packets from a device. +type Socket struct { + fd int + umem []byte + fillRing umemRing + rxRing rxTxRing + txRing rxTxRing + completionRing umemRing + qidconfMap *ebpf.Map + xsksMap *ebpf.Map + program *ebpf.Program + ifindex int + numTransmitted int + numFilled int + freeRXDescs, freeTXDescs []bool + options SocketOptions + rxDescs []Desc + getTXDescs, getRXDescs []Desc +} + +// SocketOptions are configuration settings used to bind an XDP socket. +type SocketOptions struct { + NumFrames int + FrameSize int + FillRingNumDescs int + CompletionRingNumDescs int + RxRingNumDescs int + TxRingNumDescs int +} + +// Desc represents an XDP Rx/Tx descriptor. +type Desc unix.XDPDesc + +// Stats contains various counters of the XDP socket, such as numbers of +// sent/received frames. +type Stats struct { + // Filled is the number of items consumed thus far by the Linux kernel + // from the Fill ring queue. + Filled uint64 + + // Received is the number of items consumed thus far by the user of + // this package from the Rx ring queue. + Received uint64 + + // Transmitted is the number of items consumed thus far by the Linux + // kernel from the Tx ring queue. + Transmitted uint64 + + // Completed is the number of items consumed thus far by the user of + // this package from the Completion ring queue. + Completed uint64 + + // KernelStats contains the in-kernel statistics of the corresponding + // XDP socket, such as the number of invalid descriptors that were + // submitted into Fill or Tx ring queues. + KernelStats unix.XDPStatistics +} + +// DefaultSocketFlags are the flags which are passed to bind(2) system call +// when the XDP socket is bound, possible values include unix.XDP_SHARED_UMEM, +// unix.XDP_COPY, unix.XDP_ZEROCOPY. +var DefaultSocketFlags uint16 + +// DefaultXdpFlags are the flags which are passed when the XDP program is +// attached to the network link, possible values include +// unix.XDP_FLAGS_DRV_MODE, unix.XDP_FLAGS_HW_MODE, unix.XDP_FLAGS_SKB_MODE, +// unix.XDP_FLAGS_UPDATE_IF_NOEXIST. +var DefaultXdpFlags uint32 + +func init() { + DefaultSocketFlags = 0 + DefaultXdpFlags = 0 +} + +// NewSocket returns a new XDP socket attached to the network interface which +// has the given interface, and attached to the given queue on that network +// interface. +func NewSocket(Ifindex int, QueueID int, options *SocketOptions) (xsk *Socket, err error) { + if options == nil { + options = &DefaultSocketOptions + } + xsk = &Socket{fd: -1, ifindex: Ifindex, options: *options} + + xsk.fd, err = syscall.Socket(unix.AF_XDP, syscall.SOCK_RAW, 0) + if err != nil { + return nil, fmt.Errorf("syscall.Socket failed: %v", err) + } + + xsk.umem, err = syscall.Mmap(-1, 0, options.NumFrames*options.FrameSize, + syscall.PROT_READ|syscall.PROT_WRITE, + syscall.MAP_PRIVATE|syscall.MAP_ANONYMOUS|syscall.MAP_POPULATE) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("syscall.Mmap failed: %v", err) + } + + xdpUmemReg := unix.XDPUmemReg{ + Addr: uint64(uintptr(unsafe.Pointer(&xsk.umem[0]))), + Len: uint64(len(xsk.umem)), + Size: uint32(options.FrameSize), + Headroom: 0, + } + + var errno syscall.Errno + var rc uintptr + + rc, _, errno = unix.Syscall6(syscall.SYS_SETSOCKOPT, uintptr(xsk.fd), + unix.SOL_XDP, unix.XDP_UMEM_REG, + uintptr(unsafe.Pointer(&xdpUmemReg)), + unsafe.Sizeof(xdpUmemReg), 0) + if rc != 0 { + xsk.Close() + return nil, fmt.Errorf("unix.SetsockoptUint64 XDP_UMEM_REG failed: %v", errno) + } + + err = syscall.SetsockoptInt(xsk.fd, unix.SOL_XDP, unix.XDP_UMEM_FILL_RING, + options.FillRingNumDescs) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("unix.SetsockoptUint64 XDP_UMEM_FILL_RING failed: %v", err) + } + + err = unix.SetsockoptInt(xsk.fd, unix.SOL_XDP, unix.XDP_UMEM_COMPLETION_RING, + options.CompletionRingNumDescs) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("unix.SetsockoptUint64 XDP_UMEM_COMPLETION_RING failed: %v", err) + } + + var rxRing bool + if options.RxRingNumDescs > 0 { + err = unix.SetsockoptInt(xsk.fd, unix.SOL_XDP, unix.XDP_RX_RING, + options.RxRingNumDescs) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("unix.SetsockoptUint64 XDP_RX_RING failed: %v", err) + } + rxRing = true + } + + var txRing bool + if options.TxRingNumDescs > 0 { + err = unix.SetsockoptInt(xsk.fd, unix.SOL_XDP, unix.XDP_TX_RING, + options.TxRingNumDescs) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("unix.SetsockoptUint64 XDP_TX_RING failed: %v", err) + } + txRing = true + } + + if !(rxRing || txRing) { + return nil, fmt.Errorf("RxRingNumDescs and TxRingNumDescs cannot both be set to zero") + } + + var offsets unix.XDPMmapOffsets + var vallen uint32 + vallen = uint32(unsafe.Sizeof(offsets)) + rc, _, errno = unix.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(xsk.fd), + unix.SOL_XDP, unix.XDP_MMAP_OFFSETS, + uintptr(unsafe.Pointer(&offsets)), + uintptr(unsafe.Pointer(&vallen)), 0) + if rc != 0 { + xsk.Close() + return nil, fmt.Errorf("unix.Syscall6 getsockopt XDP_MMAP_OFFSETS failed: %v", errno) + } + + fillRingSlice, err := syscall.Mmap(xsk.fd, unix.XDP_UMEM_PGOFF_FILL_RING, + int(offsets.Fr.Desc+uint64(options.FillRingNumDescs)*uint64(unsafe.Sizeof(uint64(0)))), + syscall.PROT_READ|syscall.PROT_WRITE, + syscall.MAP_SHARED|syscall.MAP_POPULATE) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("syscall.Mmap XDP_UMEM_PGOFF_FILL_RING failed: %v", err) + } + + xsk.fillRing.Producer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&fillRingSlice[0])) + uintptr(offsets.Fr.Producer))) + xsk.fillRing.Consumer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&fillRingSlice[0])) + uintptr(offsets.Fr.Consumer))) + sh := (*reflect.SliceHeader)(unsafe.Pointer(&xsk.fillRing.Descs)) + sh.Data = uintptr(unsafe.Pointer(&fillRingSlice[0])) + uintptr(offsets.Fr.Desc) + sh.Len = options.FillRingNumDescs + sh.Cap = options.FillRingNumDescs + + completionRingSlice, err := syscall.Mmap(xsk.fd, unix.XDP_UMEM_PGOFF_COMPLETION_RING, + int(offsets.Cr.Desc+uint64(options.CompletionRingNumDescs)*uint64(unsafe.Sizeof(uint64(0)))), + syscall.PROT_READ|syscall.PROT_WRITE, + syscall.MAP_SHARED|syscall.MAP_POPULATE) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("syscall.Mmap XDP_UMEM_PGOFF_COMPLETION_RING failed: %v", err) + } + + xsk.completionRing.Producer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&completionRingSlice[0])) + uintptr(offsets.Cr.Producer))) + xsk.completionRing.Consumer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&completionRingSlice[0])) + uintptr(offsets.Cr.Consumer))) + sh = (*reflect.SliceHeader)(unsafe.Pointer(&xsk.completionRing.Descs)) + sh.Data = uintptr(unsafe.Pointer(&completionRingSlice[0])) + uintptr(offsets.Cr.Desc) + sh.Len = options.CompletionRingNumDescs + sh.Cap = options.CompletionRingNumDescs + + if rxRing { + rxRingSlice, err := syscall.Mmap(xsk.fd, unix.XDP_PGOFF_RX_RING, + int(offsets.Rx.Desc+uint64(options.RxRingNumDescs)*uint64(unsafe.Sizeof(Desc{}))), + syscall.PROT_READ|syscall.PROT_WRITE, + syscall.MAP_SHARED|syscall.MAP_POPULATE) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("syscall.Mmap XDP_PGOFF_RX_RING failed: %v", err) + } + + xsk.rxRing.Producer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&rxRingSlice[0])) + uintptr(offsets.Rx.Producer))) + xsk.rxRing.Consumer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&rxRingSlice[0])) + uintptr(offsets.Rx.Consumer))) + sh = (*reflect.SliceHeader)(unsafe.Pointer(&xsk.rxRing.Descs)) + sh.Data = uintptr(unsafe.Pointer(&rxRingSlice[0])) + uintptr(offsets.Rx.Desc) + sh.Len = options.RxRingNumDescs + sh.Cap = options.RxRingNumDescs + + xsk.rxDescs = make([]Desc, 0, options.RxRingNumDescs) + } + + if txRing { + txRingSlice, err := syscall.Mmap(xsk.fd, unix.XDP_PGOFF_TX_RING, + int(offsets.Tx.Desc+uint64(options.TxRingNumDescs)*uint64(unsafe.Sizeof(Desc{}))), + syscall.PROT_READ|syscall.PROT_WRITE, + syscall.MAP_SHARED|syscall.MAP_POPULATE) + if err != nil { + xsk.Close() + return nil, fmt.Errorf("syscall.Mmap XDP_PGOFF_TX_RING failed: %v", err) + } + + xsk.txRing.Producer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&txRingSlice[0])) + uintptr(offsets.Tx.Producer))) + xsk.txRing.Consumer = (*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&txRingSlice[0])) + uintptr(offsets.Tx.Consumer))) + sh = (*reflect.SliceHeader)(unsafe.Pointer(&xsk.txRing.Descs)) + sh.Data = uintptr(unsafe.Pointer(&txRingSlice[0])) + uintptr(offsets.Tx.Desc) + sh.Len = options.TxRingNumDescs + sh.Cap = options.TxRingNumDescs + } + + sa := unix.SockaddrXDP{ + Flags: DefaultSocketFlags, + Ifindex: uint32(Ifindex), + QueueID: uint32(QueueID), + } + if err = unix.Bind(xsk.fd, &sa); err != nil { + xsk.Close() + return nil, fmt.Errorf("syscall.Bind SockaddrXDP failed: %v", err) + } + + xsk.freeRXDescs = make([]bool, options.NumFrames) + xsk.freeTXDescs = make([]bool, options.NumFrames) + for i := range xsk.freeRXDescs { + xsk.freeRXDescs[i] = true + } + for i := range xsk.freeTXDescs { + xsk.freeTXDescs[i] = true + } + xsk.getTXDescs = make([]Desc, 0, options.CompletionRingNumDescs) + xsk.getRXDescs = make([]Desc, 0, options.FillRingNumDescs) + + return xsk, nil +} + +// Fill submits the given descriptors to be filled (i.e. to receive frames into) +// it returns how many descriptors where actually put onto Fill ring queue. +// The descriptors can be acquired either by calling the GetDescs() method or +// by calling Receive() method. +func (xsk *Socket) Fill(descs []Desc) int { + numFreeSlots := xsk.NumFreeFillSlots() + if numFreeSlots < len(descs) { + descs = descs[:numFreeSlots] + } + + prod := *xsk.fillRing.Producer + for _, desc := range descs { + xsk.fillRing.Descs[prod&uint32(xsk.options.FillRingNumDescs-1)] = desc.Addr + prod++ + xsk.freeRXDescs[desc.Addr/uint64(xsk.options.FrameSize)] = false + } + //fencer.SFence() + *xsk.fillRing.Producer = prod + + xsk.numFilled += len(descs) + + return len(descs) +} + +// Receive returns the descriptors which were filled, i.e. into which frames +// were received into. +func (xsk *Socket) Receive(num int) []Desc { + numAvailable := xsk.NumReceived() + if num > int(numAvailable) { + num = int(numAvailable) + } + + descs := xsk.rxDescs[:0] + cons := *xsk.rxRing.Consumer + //fencer.LFence() + for i := 0; i < num; i++ { + descs = append(descs, xsk.rxRing.Descs[cons&uint32(xsk.options.RxRingNumDescs-1)]) + cons++ + xsk.freeRXDescs[descs[i].Addr/uint64(xsk.options.FrameSize)] = true + } + //fencer.MFence() + *xsk.rxRing.Consumer = cons + + xsk.numFilled -= len(descs) + + return descs +} + +// Transmit submits the given descriptors to be sent out, it returns how many +// descriptors were actually pushed onto the Tx ring queue. +// The descriptors can be acquired either by calling the GetDescs() method or +// by calling Receive() method. +func (xsk *Socket) Transmit(descs []Desc) (numSubmitted int) { + numFreeSlots := xsk.NumFreeTxSlots() + if len(descs) > numFreeSlots { + descs = descs[:numFreeSlots] + } + + prod := *xsk.txRing.Producer + for _, desc := range descs { + xsk.txRing.Descs[prod&uint32(xsk.options.TxRingNumDescs-1)] = desc + prod++ + xsk.freeTXDescs[desc.Addr/uint64(xsk.options.FrameSize)] = false + } + //fencer.SFence() + *xsk.txRing.Producer = prod + + xsk.numTransmitted += len(descs) + + numSubmitted = len(descs) + + var rc uintptr + var errno syscall.Errno + for { + rc, _, errno = unix.Syscall6(syscall.SYS_SENDTO, + uintptr(xsk.fd), + 0, 0, + uintptr(unix.MSG_DONTWAIT), + 0, 0) + if rc != 0 { + switch errno { + case unix.EINTR: + // try again + case unix.EAGAIN: + return + case unix.EBUSY: // "completed but not sent" + return + default: + panic(fmt.Errorf("sendto failed with rc=%d and errno=%d", rc, errno)) + } + } else { + break + } + } + + return +} + +// FD returns the file descriptor associated with this xdp.Socket which can be +// used e.g. to do polling. +func (xsk *Socket) FD() int { + return xsk.fd +} + +// Poll blocks until kernel informs us that it has either received +// or completed (i.e. actually sent) some frames that were previously submitted +// using Fill() or Transmit() methods. +// The numReceived return value can be used as the argument for subsequent +// Receive() method call. +func (xsk *Socket) Poll(timeout int) (numReceived int, numCompleted int, err error) { + var events int16 + if xsk.numFilled > 0 { + events |= unix.POLLIN + } + if xsk.numTransmitted > 0 { + events |= unix.POLLOUT + } + if events == 0 { + return + } + + var pfds [1]unix.PollFd + pfds[0].Fd = int32(xsk.fd) + pfds[0].Events = events + for err = unix.EINTR; err == unix.EINTR; { + _, err = unix.Poll(pfds[:], timeout) + } + if err != nil { + return 0, 0, err + } + + numReceived = xsk.NumReceived() + if numCompleted = xsk.NumCompleted(); numCompleted > 0 { + xsk.Complete(numCompleted) + } + + return +} + +// GetDescs returns up to n descriptors which are not currently in use. +// if rx is true, return desc in first half of umem, 2nd half otherwise +func (xsk *Socket) GetDescs(n int, rx bool) []Desc { + if n > cap(xsk.getRXDescs) { + n = cap(xsk.getRXDescs) + } + if !rx { + if n > cap(xsk.getTXDescs) { + n = cap(xsk.getTXDescs) + } + } + // numOfUMEMChunks := len(xsk.freeRXDescs) / 2 + // if n > numOfUMEMChunks { + // n = numOfUMEMChunks + // } + + descs := xsk.getRXDescs[:0] + j := 0 + start := 0 + end := cap(xsk.getRXDescs) + freeList := xsk.freeRXDescs + if !rx { + start = cap(xsk.getRXDescs) + end = len(xsk.freeTXDescs) + freeList = xsk.freeTXDescs + descs = xsk.getTXDescs[:0] + } + for i := start; i < end && j < n; i++ { + if freeList[i] == true { + descs = append(descs, Desc{ + Addr: uint64(i) * uint64(xsk.options.FrameSize), + Len: uint32(xsk.options.FrameSize), + }) + j++ + } + } + return descs +} + +// GetFrame returns the buffer containing the frame corresponding to the given +// descriptor. The returned byte slice points to the actual buffer of the +// corresponding frame, so modiyfing this slice modifies the frame contents. +func (xsk *Socket) GetFrame(d Desc) []byte { + return xsk.umem[d.Addr : d.Addr+uint64(d.Len)] +} + +// Close closes and frees the resources allocated by the Socket. +func (xsk *Socket) Close() error { + allErrors := []error{} + var err error + + if xsk.fd != -1 { + if err = unix.Close(xsk.fd); err != nil { + allErrors = append(allErrors, fmt.Errorf("failed to close XDP socket: %v", err)) + } + xsk.fd = -1 + + var sh *reflect.SliceHeader + + sh = (*reflect.SliceHeader)(unsafe.Pointer(&xsk.completionRing.Descs)) + sh.Data = uintptr(0) + sh.Len = 0 + sh.Cap = 0 + + sh = (*reflect.SliceHeader)(unsafe.Pointer(&xsk.txRing.Descs)) + sh.Data = uintptr(0) + sh.Len = 0 + sh.Cap = 0 + + sh = (*reflect.SliceHeader)(unsafe.Pointer(&xsk.rxRing.Descs)) + sh.Data = uintptr(0) + sh.Len = 0 + sh.Cap = 0 + + sh = (*reflect.SliceHeader)(unsafe.Pointer(&xsk.fillRing.Descs)) + sh.Data = uintptr(0) + sh.Len = 0 + sh.Cap = 0 + } + + if xsk.umem != nil { + if err := syscall.Munmap(xsk.umem); err != nil { + allErrors = append(allErrors, fmt.Errorf("failed to unmap the UMEM: %v", err)) + } + xsk.umem = nil + } + + if len(allErrors) > 0 { + return allErrors[0] + } + + return nil +} + +// Complete consumes up to n descriptors from the Completion ring queue to +// which the kernel produces when it has actually transmitted a descriptor it +// got from Tx ring queue. +// You should use this method if you are doing polling on the xdp.Socket file +// descriptor yourself, rather than using the Poll() method. +func (xsk *Socket) Complete(n int) { + cons := *xsk.completionRing.Consumer + //fencer.LFence() + for i := 0; i < n; i++ { + addr := xsk.completionRing.Descs[cons&uint32(xsk.options.CompletionRingNumDescs-1)] + cons++ + xsk.freeTXDescs[addr/uint64(xsk.options.FrameSize)] = true + } + //fencer.MFence() + *xsk.completionRing.Consumer = cons + + xsk.numTransmitted -= n +} + +// NumFreeFillSlots returns how many free slots are available on the Fill ring +// queue, i.e. the queue to which we produce descriptors which should be filled +// by the kernel with incoming frames. +func (xsk *Socket) NumFreeFillSlots() int { + prod := *xsk.fillRing.Producer + cons := *xsk.fillRing.Consumer + max := uint32(xsk.options.FillRingNumDescs) + + n := max - (prod - cons) + if n > max { + n = max + } + + return int(n) +} + +// NumFreeTxSlots returns how many free slots are available on the Tx ring +// queue, i.e. the queue to which we produce descriptors which should be +// transmitted by the kernel to the wire. +func (xsk *Socket) NumFreeTxSlots() int { + prod := *xsk.txRing.Producer + cons := *xsk.txRing.Consumer + max := uint32(xsk.options.TxRingNumDescs) + + n := max - (prod - cons) + if n > max { + n = max + } + + return int(n) +} + +// NumReceived returns how many descriptors are there on the Rx ring queue +// which were produced by the kernel and which we have not yet consumed. +func (xsk *Socket) NumReceived() int { + prod := *xsk.rxRing.Producer + cons := *xsk.rxRing.Consumer + max := uint32(xsk.options.RxRingNumDescs) + + n := prod - cons + if n > max { + n = max + } + + return int(n) +} + +// NumCompleted returns how many descriptors are there on the Completion ring +// queue which were produced by the kernel and which we have not yet consumed. +func (xsk *Socket) NumCompleted() int { + prod := *xsk.completionRing.Producer + cons := *xsk.completionRing.Consumer + max := uint32(xsk.options.CompletionRingNumDescs) + + n := prod - cons + if n > max { + n = max + } + + return int(n) +} + +// NumFilled returns how many descriptors are there on the Fill ring +// queue which have not yet been consumed by the kernel. +// This method is useful if you're polling the xdp.Socket file descriptor +// yourself, rather than using the Poll() method - if it returns a number +// greater than zero it means you should set the unix.POLLIN flag. +func (xsk *Socket) NumFilled() int { + return xsk.numFilled +} + +// NumTransmitted returns how many descriptors are there on the Tx ring queue +// which have not yet been consumed by the kernel. +// Note that even after the descriptors are consumed by the kernel from the Tx +// ring queue, it doesn't mean that they have actually been sent out on the +// wire, that can be assumed only after the descriptors have been produced by +// the kernel to the Completion ring queue. +// This method is useful if you're polling the xdp.Socket file descriptor +// yourself, rather than using the Poll() method - if it returns a number +// greater than zero it means you should set the unix.POLLOUT flag. +func (xsk *Socket) NumTransmitted() int { + return xsk.numTransmitted +} + +// Stats returns various statistics for this XDP socket. +func (xsk *Socket) Stats() (Stats, error) { + var stats Stats + var size uint64 + + stats.Filled = uint64(*xsk.fillRing.Consumer) + stats.Received = uint64(*xsk.rxRing.Consumer) + if xsk.txRing.Consumer != nil { + stats.Transmitted = uint64(*xsk.txRing.Consumer) + } + if xsk.completionRing.Consumer != nil { + stats.Completed = uint64(*xsk.completionRing.Consumer) + } + size = uint64(unsafe.Sizeof(stats.KernelStats)) + rc, _, errno := unix.Syscall6(syscall.SYS_GETSOCKOPT, + uintptr(xsk.fd), + unix.SOL_XDP, unix.XDP_STATISTICS, + uintptr(unsafe.Pointer(&stats.KernelStats)), + uintptr(unsafe.Pointer(&size)), 0) + if rc != 0 { + return stats, fmt.Errorf("getsockopt XDP_STATISTICS failed with errno %d", errno) + } + return stats, nil +} + +// Program represents the necessary data structures for a simple XDP program that can filter traffic +// based on the attached rx queue. +type Program struct { + Program *ebpf.Program + Queues *ebpf.Map + Sockets *ebpf.Map +} + +// Attach the XDP Program to an interface. +func (p *Program) Attach(Ifindex int) error { + if err := removeProgram(Ifindex); err != nil { + return err + } + return attachProgram(Ifindex, p.Program) +} + +// Detach the XDP Program from an interface. +func (p *Program) Detach(Ifindex int) error { + return removeProgram(Ifindex) +} + +// Register adds the socket file descriptor as the recipient for packets from the given queueID. +func (p *Program) Register(queueID int, fd int) error { + if err := p.Sockets.Put(uint32(queueID), uint32(fd)); err != nil { + return fmt.Errorf("failed to update xsksMap: %v", err) + } + + if err := p.Queues.Put(uint32(queueID), uint32(1)); err != nil { + return fmt.Errorf("failed to update qidconfMap: %v", err) + } + return nil +} + +// Unregister removes any associated mapping to sockets for the given queueID. +func (p *Program) Unregister(queueID int) error { + if err := p.Queues.Delete(uint32(queueID)); err != nil { + return err + } + if err := p.Sockets.Delete(uint32(queueID)); err != nil { + return err + } + return nil +} + +// Close closes and frees the resources allocated for the Program. +func (p *Program) Close() error { + allErrors := []error{} + if p.Sockets != nil { + if err := p.Sockets.Close(); err != nil { + allErrors = append(allErrors, fmt.Errorf("failed to close xsksMap: %v", err)) + } + p.Sockets = nil + } + + if p.Queues != nil { + if err := p.Queues.Close(); err != nil { + allErrors = append(allErrors, fmt.Errorf("failed to close qidconfMap: %v", err)) + } + p.Queues = nil + } + + if p.Program != nil { + if err := p.Program.Close(); err != nil { + allErrors = append(allErrors, fmt.Errorf("failed to close XDP program: %v", err)) + } + p.Program = nil + } + + if len(allErrors) > 0 { + return allErrors[0] + } + return nil +} + +// NewProgram returns a translation of the default eBPF XDP program found in the +// xsk_load_xdp_prog() function in /tools/lib/bpf/xsk.c: +// https://github.com/torvalds/linux/blob/master/tools/lib/bpf/xsk.c#L259 +func NewProgram(maxQueueEntries int) (*Program, error) { + qidconfMap, err := ebpf.NewMap(&ebpf.MapSpec{ + Name: "qidconf_map", + Type: ebpf.Array, + KeySize: uint32(unsafe.Sizeof(int32(0))), + ValueSize: uint32(unsafe.Sizeof(int32(0))), + MaxEntries: uint32(maxQueueEntries), + Flags: 0, + InnerMap: nil, + }) + if err != nil { + return nil, fmt.Errorf("ebpf.NewMap qidconf_map failed (try increasing RLIMIT_MEMLOCK): %v", err) + } + + xsksMap, err := ebpf.NewMap(&ebpf.MapSpec{ + Name: "xsks_map", + Type: ebpf.XSKMap, + KeySize: uint32(unsafe.Sizeof(int32(0))), + ValueSize: uint32(unsafe.Sizeof(int32(0))), + MaxEntries: uint32(maxQueueEntries), + Flags: 0, + InnerMap: nil, + }) + if err != nil { + return nil, fmt.Errorf("ebpf.NewMap xsks_map failed (try increasing RLIMIT_MEMLOCK): %v", err) + } + + /* + This is a translation of the default eBPF XDP program found in the + xsk_load_xdp_prog() function in /tools/lib/bpf/xsk.c: + https://github.com/torvalds/linux/blob/master/tools/lib/bpf/xsk.c#L259 + + // This is the C-program: + // SEC("xdp_sock") int xdp_sock_prog(struct xdp_md *ctx) + // { + // int *qidconf, index = ctx->rx_queue_index; + // + // // A set entry here means that the correspnding queue_id + // // has an active AF_XDP socket bound to it. + // qidconf = bpf_map_lookup_elem(&qidconf_map, &index); + // if (!qidconf) + // return XDP_ABORTED; + // + // if (*qidconf) + // return bpf_redirect_map(&xsks_map, index, 0); + // + // return XDP_PASS; + // } + // + struct bpf_insn prog[] = { + // r1 = *(u32 *)(r1 + 16) + BPF_LDX_MEM(BPF_W, BPF_REG_1, BPF_REG_1, 16), // 0 + // *(u32 *)(r10 - 4) = r1 + BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_1, -4), // 1 + BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), // 2 + BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), // 3 + BPF_LD_MAP_FD(BPF_REG_1, xsk->qidconf_map_fd), // 4 (2 instructions) + BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), // 5 + BPF_MOV64_REG(BPF_REG_1, BPF_REG_0), // 6 + BPF_MOV32_IMM(BPF_REG_0, 0), // 7 + // if r1 == 0 goto +8 + BPF_JMP_IMM(BPF_JEQ, BPF_REG_1, 0, 8), // 8 + BPF_MOV32_IMM(BPF_REG_0, 2), // 9 + // r1 = *(u32 *)(r1 + 0) + BPF_LDX_MEM(BPF_W, BPF_REG_1, BPF_REG_1, 0), // 10 + // if r1 == 0 goto +5 + BPF_JMP_IMM(BPF_JEQ, BPF_REG_1, 0, 5), // 11 + // r2 = *(u32 *)(r10 - 4) + BPF_LD_MAP_FD(BPF_REG_1, xsk->xsks_map_fd), // 12 (2 instructions) + BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_10, -4), // 13 + BPF_MOV32_IMM(BPF_REG_3, 0), // 14 + BPF_EMIT_CALL(BPF_FUNC_redirect_map), // 15 + // The jumps are to this instruction + BPF_EXIT_INSN(), // 16 + }; + + eBPF instructions: + 0: code: 97 dst_reg: 1 src_reg: 1 off: 16 imm: 0 // 0 + 1: code: 99 dst_reg: 10 src_reg: 1 off: -4 imm: 0 // 1 + 2: code: 191 dst_reg: 2 src_reg: 10 off: 0 imm: 0 // 2 + 3: code: 7 dst_reg: 2 src_reg: 0 off: 0 imm: -4 // 3 + 4: code: 24 dst_reg: 1 src_reg: 1 off: 0 imm: 4 // 4 XXX use qidconfMap.FD as IMM + 5: code: 0 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // part of the same instruction + 6: code: 133 dst_reg: 0 src_reg: 0 off: 0 imm: 1 // 5 + 7: code: 191 dst_reg: 1 src_reg: 0 off: 0 imm: 0 // 6 + 8: code: 180 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // 7 + 9: code: 21 dst_reg: 1 src_reg: 0 off: 8 imm: 0 // 8 + 10: code: 180 dst_reg: 0 src_reg: 0 off: 0 imm: 2 // 9 + 11: code: 97 dst_reg: 1 src_reg: 1 off: 0 imm: 0 // 10 + 12: code: 21 dst_reg: 1 src_reg: 0 off: 5 imm: 0 // 11 + 13: code: 24 dst_reg: 1 src_reg: 1 off: 0 imm: 5 // 12 XXX use xsksMap.FD as IMM + 14: code: 0 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // part of the same instruction + 15: code: 97 dst_reg: 2 src_reg: 10 off: -4 imm: 0 // 13 + 16: code: 180 dst_reg: 3 src_reg: 0 off: 0 imm: 0 // 14 + 17: code: 133 dst_reg: 0 src_reg: 0 off: 0 imm: 51 // 15 + 18: code: 149 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // 16 + */ + + program, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Name: "xsk_ebpf", + Type: ebpf.XDP, + Instructions: asm.Instructions{ + {OpCode: 97, Dst: 1, Src: 1, Offset: 16}, // 0: code: 97 dst_reg: 1 src_reg: 1 off: 16 imm: 0 // 0 + {OpCode: 99, Dst: 10, Src: 1, Offset: -4}, // 1: code: 99 dst_reg: 10 src_reg: 1 off: -4 imm: 0 // 1 + {OpCode: 191, Dst: 2, Src: 10}, // 2: code: 191 dst_reg: 2 src_reg: 10 off: 0 imm: 0 // 2 + {OpCode: 7, Dst: 2, Src: 0, Offset: 0, Constant: -4}, // 3: code: 7 dst_reg: 2 src_reg: 0 off: 0 imm: -4 // 3 + {OpCode: 24, Dst: 1, Src: 1, Offset: 0, Constant: int64(qidconfMap.FD())}, // 4: code: 24 dst_reg: 1 src_reg: 1 off: 0 imm: 4 // 4 XXX use qidconfMap.FD as IMM + //{ OpCode: 0 }, // 5: code: 0 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // part of the same instruction + {OpCode: 133, Dst: 0, Src: 0, Constant: 1}, // 6: code: 133 dst_reg: 0 src_reg: 0 off: 0 imm: 1 // 5 + {OpCode: 191, Dst: 1, Src: 0}, // 7: code: 191 dst_reg: 1 src_reg: 0 off: 0 imm: 0 // 6 + {OpCode: 180, Dst: 0, Src: 0}, // 8: code: 180 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // 7 + {OpCode: 21, Dst: 1, Src: 0, Offset: 8}, // 9: code: 21 dst_reg: 1 src_reg: 0 off: 8 imm: 0 // 8 + {OpCode: 180, Dst: 0, Src: 0, Constant: 2}, // 10: code: 180 dst_reg: 0 src_reg: 0 off: 0 imm: 2 // 9 + {OpCode: 97, Dst: 1, Src: 1}, // 11: code: 97 dst_reg: 1 src_reg: 1 off: 0 imm: 0 // 10 + {OpCode: 21, Dst: 1, Offset: 5}, // 12: code: 21 dst_reg: 1 src_reg: 0 off: 5 imm: 0 // 11 + {OpCode: 24, Dst: 1, Src: 1, Constant: int64(xsksMap.FD())}, // 13: code: 24 dst_reg: 1 src_reg: 1 off: 0 imm: 5 // 12 XXX use xsksMap.FD as IMM + //{ OpCode: 0 }, // 14: code: 0 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // part of the same instruction + {OpCode: 97, Dst: 2, Src: 10, Offset: -4}, // 15: code: 97 dst_reg: 2 src_reg: 10 off: -4 imm: 0 // 13 + {OpCode: 180, Dst: 3}, // 16: code: 180 dst_reg: 3 src_reg: 0 off: 0 imm: 0 // 14 + {OpCode: 133, Constant: 51}, // 17: code: 133 dst_reg: 0 src_reg: 0 off: 0 imm: 51 // 15 + {OpCode: 149}, // 18: code: 149 dst_reg: 0 src_reg: 0 off: 0 imm: 0 // 16 + }, + License: "LGPL-2.1 or BSD-2-Clause", + KernelVersion: 0, + }) + if err != nil { + return nil, fmt.Errorf("error: ebpf.NewProgram failed: %v", err) + } + + return &Program{Program: program, Queues: qidconfMap, Sockets: xsksMap}, nil +} + +// LoadProgram load a external XDP program, along with queue and socket map; +// fname is the BPF kernel program file (.o); +// funcname is the function name in the program file; +// qidmapname is the Queues map name; +// xskmapname is the Sockets map name; +func LoadProgram(fname, funcname, qidmapname, xskmapname string) (*Program, error) { + prog := new(Program) + col, err := ebpf.LoadCollection(fname) + if err != nil { + return nil, err + } + var ok bool + if prog.Program, ok = col.Programs[funcname]; !ok { + return nil, fmt.Errorf("%v doesn't contain a function named %v", fname, funcname) + } + if prog.Queues, ok = col.Maps[qidmapname]; !ok { + return nil, fmt.Errorf("%v doesn't contain a queue map named %v", fname, qidmapname) + } + if prog.Sockets, ok = col.Maps[xskmapname]; !ok { + return nil, fmt.Errorf("%v doesn't contain a socket map named %v", fname, xskmapname) + } + return prog, nil +} + +// removeProgram removes an existing XDP program from the given network interface. +func removeProgram(Ifindex int) error { + var link netlink.Link + var err error + link, err = netlink.LinkByIndex(Ifindex) + if err != nil { + return err + } + if !isXdpAttached(link) { + return nil + } + if err = netlink.LinkSetXdpFd(link, -1); err != nil { + return fmt.Errorf("netlink.LinkSetXdpFd(link, -1) failed: %v", err) + } + for { + link, err = netlink.LinkByIndex(Ifindex) + if err != nil { + return err + } + if !isXdpAttached(link) { + break + } + time.Sleep(time.Second) + } + return nil +} + +func isXdpAttached(link netlink.Link) bool { + if link.Attrs() != nil && link.Attrs().Xdp != nil && link.Attrs().Xdp.Attached { + return true + } + return false +} + +// attachProgram attaches the given XDP program to the network interface. +func attachProgram(Ifindex int, program *ebpf.Program) error { + link, err := netlink.LinkByIndex(Ifindex) + if err != nil { + return err + } + return netlink.LinkSetXdpFdWithFlags(link, program.FD(), int(DefaultXdpFlags)) +} diff --git a/plugin/console/index.go b/plugin/console/index.go index b4e8e73..65b125d 100644 --- a/plugin/console/index.go +++ b/plugin/console/index.go @@ -96,6 +96,9 @@ func (cfg *ConsolePlugin) connect() (conn quic.Connection, err error) { } func (cfg *ConsolePlugin) OnInit() error { + if cfg.Secret == "" { + return nil + } conn, err := cfg.connect() if err != nil { return err diff --git a/plugin/hdl/index.go b/plugin/hdl/index.go index 2e71cdb..b3da0e5 100644 --- a/plugin/hdl/index.go +++ b/plugin/hdl/index.go @@ -59,8 +59,8 @@ func (p *HDLPlugin) WriteFlvHeader(sub *m7s.Subscriber) (flv net.Buffers) { flags |= 1 metaData["videocodecid"] = int(rtmp.ParseVideoCodec(vt.FourCC())) ctx := vt.ICodecCtx.(IVideoCodecCtx) - metaData["width"] = ctx.GetWidth() - metaData["height"] = ctx.GetHeight() + metaData["width"] = ctx.Width() + metaData["height"] = ctx.Height() } var data = amf.Marshal(metaData) var b [15]byte diff --git a/plugin/hdl/pkg/pull.go b/plugin/hdl/pkg/pull.go index a04596b..bd8ae7d 100644 --- a/plugin/hdl/pkg/pull.go +++ b/plugin/hdl/pkg/pull.go @@ -27,6 +27,10 @@ func NewHDLPuller() *HDLPuller { } } +func NewPullHandler() m7s.PullHandler { + return NewHDLPuller() +} + func (puller *HDLPuller) Connect(p *m7s.Client) (err error) { if strings.HasPrefix(p.RemoteURL, "http") { var res *http.Response @@ -108,7 +112,7 @@ func (puller *HDLPuller) Pull(p *m7s.Puller) (err error) { var frame rtmp.RTMPData switch ds := int(dataSize); t { case FLV_TAG_TYPE_AUDIO, FLV_TAG_TYPE_VIDEO: - frame.ScalableMemoryAllocator = puller.ScalableMemoryAllocator + frame.SetAllocator(puller.ScalableMemoryAllocator) err = puller.ReadNto(ds, frame.NextN(ds)) default: err = puller.Skip(ds) diff --git a/plugin/rtmp/pkg/amf.go b/plugin/rtmp/pkg/amf.go index a75ce23..efa98bc 100644 --- a/plugin/rtmp/pkg/amf.go +++ b/plugin/rtmp/pkg/amf.go @@ -271,7 +271,6 @@ func (amf *AMF) Marshal(v any) []byte { for i := 0; i < size; i++ { amf.Marshal(v.Index(i).Interface()) } - amf.Write(END_OBJ) case reflect.Ptr: vv := reflect.Indirect(v) if vv.Kind() == reflect.Struct { diff --git a/plugin/rtmp/pkg/audio.go b/plugin/rtmp/pkg/audio.go index 338b0e6..48b0367 100644 --- a/plugin/rtmp/pkg/audio.go +++ b/plugin/rtmp/pkg/audio.go @@ -1,6 +1,7 @@ package rtmp import ( + "github.com/deepch/vdk/codec/aacparser" . "m7s.live/m7s/v5/pkg" "m7s.live/m7s/v5/pkg/codec" "m7s.live/m7s/v5/pkg/util" @@ -13,7 +14,7 @@ type RTMPAudio struct { func (avcc *RTMPAudio) Parse(t *AVTrack) (err error) { reader := avcc.NewReader() - var b, b0, b1 byte + var b byte b, err = reader.ReadByte() if err != nil { return @@ -41,27 +42,10 @@ func (avcc *RTMPAudio) Parse(t *AVTrack) (err error) { return } if b == 0 { - var ctx AACCtx - b0, err = reader.ReadByte() - if err != nil { - return - } - b1, err = reader.ReadByte() - if err != nil { - return - } + var ctx codec.AACCtx var cloneFrame RTMPAudio cloneFrame.CopyFrom(&avcc.Memory) - ctx.Asc = []byte{b0, b1} - ctx.AudioObjectType = b0 >> 3 - ctx.SamplingFrequencyIndex = (b0 & 0x07 << 1) | (b1 >> 7) - ctx.ChannelConfiguration = (b1 >> 3) & 0x0F - ctx.FrameLengthFlag = (b1 >> 2) & 0x01 - ctx.DependsOnCoreCoder = (b1 >> 1) & 0x01 - ctx.ExtensionFlag = b1 & 0x01 - ctx.Channels = int(ctx.ChannelConfiguration) - ctx.SampleRate = SamplingFrequencies[ctx.SamplingFrequencyIndex] - ctx.SampleSize = 16 + ctx.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(cloneFrame.Buffers[0][2:]) t.SequenceFrame = &cloneFrame t.ICodecCtx = &ctx } @@ -69,21 +53,13 @@ func (avcc *RTMPAudio) Parse(t *AVTrack) (err error) { return } -func (avcc *RTMPAudio) ConvertCtx(from codec.ICodecCtx, t *AVTrack) (err error) { - switch fourCC := from.FourCC(); fourCC { - case codec.FourCC_MP4A: - var ctx AACCtx - ctx.AACCtx = *from.GetBase().(*codec.AACCtx) - b0, b1 := ctx.Asc[0], ctx.Asc[1] - ctx.AudioObjectType = b0 >> 3 - ctx.SamplingFrequencyIndex = (b0 & 0x07 << 1) | (b1 >> 7) - ctx.ChannelConfiguration = (b1 >> 3) & 0x0F - ctx.FrameLengthFlag = (b1 >> 2) & 0x01 - ctx.DependsOnCoreCoder = (b1 >> 1) & 0x01 - ctx.ExtensionFlag = b1 & 0x01 - t.ICodecCtx = &ctx - default: - t.ICodecCtx = from.GetBase() +func (avcc *RTMPAudio) ConvertCtx(from codec.ICodecCtx) (to codec.ICodecCtx, seq IAVFrame, err error) { + to = from.GetBase() + switch v := to.(type) { + case *codec.AACCtx: + var seqFrame RTMPAudio + seqFrame.AppendOne(append([]byte{0xAF, 0x00}, v.ConfigBytes...)) + seq = &seqFrame } return } @@ -91,7 +67,7 @@ func (avcc *RTMPAudio) ConvertCtx(from codec.ICodecCtx, t *AVTrack) (err error) func (avcc *RTMPAudio) Demux(codecCtx codec.ICodecCtx) (raw any, err error) { reader := avcc.NewReader() var result util.Memory - if _, ok := codecCtx.(*AACCtx); ok { + if _, ok := codecCtx.(*codec.AACCtx); ok { err = reader.Skip(2) reader.Range(result.AppendOne) return result, err diff --git a/plugin/rtmp/pkg/client.go b/plugin/rtmp/pkg/client.go index 6fee593..2660e9c 100644 --- a/plugin/rtmp/pkg/client.go +++ b/plugin/rtmp/pkg/client.go @@ -16,6 +16,14 @@ type Client struct { ServerInfo map[string]any } +func NewPushHandler() m7s.PushHandler { + return &Client{} +} + +func NewPullHandler() m7s.PullHandler { + return &Client{} +} + func (client *Client) Connect(p *m7s.Client) (err error) { chunkSize := 4096 addr := p.RemoteURL diff --git a/plugin/rtmp/pkg/codec.go b/plugin/rtmp/pkg/codec.go index 7719413..81b38dc 100644 --- a/plugin/rtmp/pkg/codec.go +++ b/plugin/rtmp/pkg/codec.go @@ -1,14 +1,10 @@ package rtmp import ( - "bytes" - "encoding/binary" "errors" "fmt" "io" - "github.com/cnotch/ipchub/av/codec/hevc" - "github.com/q191201771/naza/pkg/nazabits" "m7s.live/m7s/v5/pkg/codec" "m7s.live/m7s/v5/pkg/util" ) @@ -17,19 +13,9 @@ type ( AudioCodecID byte VideoCodecID byte - H264Ctx struct { - codec.H264Ctx - ConfigurationVersion byte // 8 bits Version - AVCProfileIndication byte // 8 bits - ProfileCompatibility byte // 8 bits - AVCLevelIndication byte // 8 bits - LengthSizeMinusOne byte - NalulenSize int - } - H265Ctx struct { codec.H265Ctx - NalulenSize int + Enhanced bool } AV1Ctx struct { @@ -47,63 +33,6 @@ type ( InitialPresentationDelayPresent byte InitialPresentationDelayMinusOne byte } - - AACCtx struct { - codec.AACCtx - AudioSpecificConfig - } - - GASpecificConfig struct { - FrameLengthFlag byte // 1 bit - DependsOnCoreCoder byte // 1 bit - ExtensionFlag byte // 1 bit - } - - AudioSpecificConfig struct { - AudioObjectType byte // 5 bits - SamplingFrequencyIndex byte // 4 bits - ChannelConfiguration byte // 4 bits - GASpecificConfig - } - AVCDecoderConfigurationRecord struct { - ConfigurationVersion byte // 8 bits Version - AVCProfileIndication byte // 8 bits - ProfileCompatibility byte // 8 bits - AVCLevelIndication byte // 8 bits - Reserved1 byte // 6 bits - LengthSizeMinusOne byte // 2 bits 非常重要,每个NALU包前面都(lengthSizeMinusOne & 3)+1个字节的NAL包长度描述 - Reserved2 byte // 3 bits - NumOfSequenceParameterSets byte // 5 bits SPS 的个数,计算方法是 numOfSequenceParameterSets & 0x1F - NumOfPictureParameterSets byte // 8 bits PPS 的个数 - - SequenceParameterSetLength uint16 // 16 byte SPS Length - SequenceParameterSetNALUnit []byte // n byte SPS - PictureParameterSetLength uint16 // 16 byte PPS Length - PictureParameterSetNALUnit []byte // n byte PPS - } - HVCDecoderConfigurationRecord struct { - PicWidthInLumaSamples uint32 // sps - PicHeightInLumaSamples uint32 // sps - - configurationVersion uint8 - - generalProfileSpace uint8 - generalTierFlag uint8 - generalProfileIdc uint8 - generalProfileCompatibilityFlags uint32 - generalConstraintIndicatorFlags uint64 - generalLevelIdc uint8 - - lengthSizeMinusOne uint8 - - numTemporalLayers uint8 - temporalIdNested uint8 - parallelismType uint8 - chromaFormat uint8 - bitDepthLumaMinus8 uint8 - bitDepthChromaMinus8 uint8 - avgFrameRate uint16 - } ) const ( @@ -169,525 +98,13 @@ func ParseVideoCodec(name codec.FourCC) VideoCodecID { return 0 } -func (p *AVCDecoderConfigurationRecord) Marshal(b []byte) (n int) { - b[0] = 1 - b[1] = p.AVCProfileIndication - b[2] = p.ProfileCompatibility - b[3] = p.AVCLevelIndication - b[4] = p.LengthSizeMinusOne | 0xfc - b[5] = uint8(1) | 0xe0 - n += 6 - - binary.BigEndian.PutUint16(b[n:], p.SequenceParameterSetLength) - n += 2 - copy(b[n:], p.SequenceParameterSetNALUnit) - n += len(p.SequenceParameterSetNALUnit) - b[n] = uint8(1) - n++ - binary.BigEndian.PutUint16(b[n:], p.PictureParameterSetLength) - n += 2 - copy(b[n:], p.PictureParameterSetNALUnit) - n += len(p.PictureParameterSetNALUnit) - - return -} - var ErrDecconfInvalid = errors.New("decode error") -func (ctx *H264Ctx) Unmarshal(b *util.MemoryReader) (err error) { - if b.Length < 7 { - err = errors.New("not enough len") - return - } - b.ReadByteTo(&ctx.ConfigurationVersion, &ctx.AVCProfileIndication, &ctx.ProfileCompatibility, &ctx.AVCLevelIndication, &ctx.LengthSizeMinusOne) - ctx.LengthSizeMinusOne = ctx.LengthSizeMinusOne & 0x03 - ctx.NalulenSize = int(ctx.LengthSizeMinusOne) + 1 - var numOfSequenceParameterSets byte - numOfSequenceParameterSets, err = b.ReadByteMask(0x1f) - if err != nil { - return - } - for range numOfSequenceParameterSets { - spslen, err1 := b.ReadBE(2) - if err1 != nil { - return err1 - } - spsbytes, err2 := b.ReadBytes(int(spslen)) - if err2 != nil { - return err2 - } - ctx.SPS = append(ctx.SPS, spsbytes) - } - if b.Length < 1 { - err = ErrDecconfInvalid - return - } - if err = ctx.SPSInfo.Unmarshal(ctx.SPS[0]); err != nil { - return - } - ppscount, err1 := b.ReadByte() - if err1 != nil { - return err1 - } - for range ppscount { - ppslen, err1 := b.ReadBE(2) - if err1 != nil { - return err1 - } - ppsbytes, err2 := b.ReadBytes(int(ppslen)) - if err2 != nil { - return err2 - } - ctx.PPS = append(ctx.PPS, ppsbytes) - } - return -} - -func ParseHevcSPS(data []byte) (self codec.SPSInfo, err error) { - var rawsps hevc.H265RawSPS - if err = rawsps.Decode(data); err == nil { - self.CropLeft, self.CropRight, self.CropTop, self.CropBottom = uint(rawsps.Conf_win_left_offset), uint(rawsps.Conf_win_right_offset), uint(rawsps.Conf_win_top_offset), uint(rawsps.Conf_win_bottom_offset) - self.Width = uint(rawsps.Pic_width_in_luma_samples) - self.Height = uint(rawsps.Pic_height_in_luma_samples) - } - return -} - var SamplingFrequencies = [...]int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0} var RTMP_AVC_HEAD = []byte{0x17, 0x00, 0x00, 0x00, 0x00, 0x01, 0x42, 0x00, 0x1E, 0xFF} var ErrHevc = errors.New("hevc parse config error") -func (ctx *H265Ctx) Unmarshal(b *util.MemoryReader) (err error) { - if b.Length < 23 { - err = errors.New("not enough len") - return - } - b.Skip(21) - var x byte - x, err = b.ReadByte() - if err != nil { - return ErrHevc - } - ctx.NalulenSize = int(x&0x03) + 1 - x, err = b.ReadByte() // number of arrays - if err != nil { - return ErrHevc - } - x, err = b.ReadByte() - if err != nil || x&0x7f != byte(codec.NAL_UNIT_VPS) { - return ErrHevc - } - numNalus, err := b.ReadBE(2) - if err != nil { - return ErrHevc - } - for range numNalus { - vpslen, err := b.ReadBE(2) - if err != nil { - return ErrHevc - } - vps, err := b.ReadBytes(int(vpslen)) - if err != nil { - return ErrHevc - } - ctx.VPS = append(ctx.VPS, vps) - } - x, err = b.ReadByte() - if err != nil || x&0x7f != byte(codec.NAL_UNIT_SPS) { - return ErrHevc - } - numNalus, err = b.ReadBE(2) - if err != nil { - return ErrHevc - } - for range numNalus { - spslen, err := b.ReadBE(2) - if err != nil { - return ErrHevc - } - sps, err := b.ReadBytes(int(spslen)) - if err != nil { - return ErrHevc - } - ctx.SPS = append(ctx.SPS, sps) - } - ctx.SPSInfo, err = ParseHevcSPS(ctx.SPS[0]) - if err != nil { - return ErrHevc - } - x, err = b.ReadByte() - if err != nil || x&0x7f != byte(codec.NAL_UNIT_PPS) { - return ErrHevc - } - numNalus, err = b.ReadBE(2) - if err != nil { - return ErrHevc - } - for range numNalus { - ppslen, err := b.ReadBE(2) - if err != nil { - return ErrHevc - } - pps, err := b.ReadBytes(int(ppslen)) - if err != nil { - return ErrHevc - } - ctx.PPS = append(ctx.PPS, pps) - } - return -} - -func BuildH265SeqHeaderFromVpsSpsPps(vps, sps, pps []byte) ([]byte, error) { - sh := make([]byte, 43+len(vps)+len(sps)+len(pps)) - - sh[0] = 0b1001_0000 | byte(PacketTypeSequenceStart) - copy(sh[1:], codec.FourCC_H265[:]) - // unsigned int(8) configurationVersion = 1; - sh[5] = 0x1 - - ctx := HVCDecoderConfigurationRecord{ - configurationVersion: 1, - lengthSizeMinusOne: 3, // 4 bytes - generalProfileCompatibilityFlags: 0xffffffff, - generalConstraintIndicatorFlags: 0xffffffffffff, - } - if err := ctx.ParseVps(vps); err != nil { - return nil, err - } - if err := ctx.ParseSps(sps); err != nil { - return nil, err - } - - // unsigned int(2) general_profile_space; - // unsigned int(1) general_tier_flag; - // unsigned int(5) general_profile_idc; - sh[6] = ctx.generalProfileSpace<<6 | ctx.generalTierFlag<<5 | ctx.generalProfileIdc - // unsigned int(32) general_profile_compatibility_flags - util.PutBE(sh[7:7+4], ctx.generalProfileCompatibilityFlags) - // unsigned int(48) general_constraint_indicator_flags - util.PutBE(sh[11:11+4], uint32(ctx.generalConstraintIndicatorFlags>>16)) - util.PutBE(sh[15:15+2], uint16(ctx.generalConstraintIndicatorFlags)) - // unsigned int(8) general_level_idc; - sh[17] = ctx.generalLevelIdc - - // bit(4) reserved = ‘1111’b; - // unsigned int(12) min_spatial_segmentation_idc; - // bit(6) reserved = ‘111111’b; - // unsigned int(2) parallelismType; - // TODO chef: 这两个字段没有解析 - util.PutBE(sh[18:20], 0xf000) - sh[20] = ctx.parallelismType | 0xfc - - // bit(6) reserved = ‘111111’b; - // unsigned int(2) chromaFormat; - sh[21] = ctx.chromaFormat | 0xfc - - // bit(5) reserved = ‘11111’b; - // unsigned int(3) bitDepthLumaMinus8; - sh[22] = ctx.bitDepthLumaMinus8 | 0xf8 - - // bit(5) reserved = ‘11111’b; - // unsigned int(3) bitDepthChromaMinus8; - sh[23] = ctx.bitDepthChromaMinus8 | 0xf8 - - // bit(16) avgFrameRate; - util.PutBE(sh[24:26], ctx.avgFrameRate) - - // bit(2) constantFrameRate; - // bit(3) numTemporalLayers; - // bit(1) temporalIdNested; - // unsigned int(2) lengthSizeMinusOne; - sh[26] = 0<<6 | ctx.numTemporalLayers<<3 | ctx.temporalIdNested<<2 | ctx.lengthSizeMinusOne - - // num of vps sps pps - sh[27] = 0x03 - i := 28 - sh[i] = byte(codec.NAL_UNIT_VPS) - // num of vps - util.PutBE(sh[i+1:i+3], 1) - // length - util.PutBE(sh[i+3:i+5], len(vps)) - copy(sh[i+5:], vps) - i = i + 5 + len(vps) - sh[i] = byte(codec.NAL_UNIT_SPS) - util.PutBE(sh[i+1:i+3], 1) - util.PutBE(sh[i+3:i+5], len(sps)) - copy(sh[i+5:], sps) - i = i + 5 + len(sps) - sh[i] = byte(codec.NAL_UNIT_PPS) - util.PutBE(sh[i+1:i+3], 1) - util.PutBE(sh[i+3:i+5], len(pps)) - copy(sh[i+5:], pps) - - return sh, nil -} -func (ctx *HVCDecoderConfigurationRecord) ParseVps(vps []byte) error { - if len(vps) < 2 { - return ErrHevc - } - - rbsp := nal2rbsp(vps[2:]) - br := nazabits.NewBitReader(rbsp) - - // skip - // vps_video_parameter_set_id u(4) - // vps_reserved_three_2bits u(2) - // vps_max_layers_minus1 u(6) - if _, err := br.ReadBits16(12); err != nil { - return ErrHevc - } - - vpsMaxSubLayersMinus1, err := br.ReadBits8(3) - if err != nil { - return ErrHevc - } - if vpsMaxSubLayersMinus1+1 > ctx.numTemporalLayers { - ctx.numTemporalLayers = vpsMaxSubLayersMinus1 + 1 - } - - // skip - // vps_temporal_id_nesting_flag u(1) - // vps_reserved_0xffff_16bits u(16) - if _, err := br.ReadBits32(17); err != nil { - return ErrHevc - } - - return ctx.parsePtl(&br, vpsMaxSubLayersMinus1) -} - -func (ctx *HVCDecoderConfigurationRecord) ParseSps(sps []byte) error { - var err error - - if len(sps) < 2 { - return ErrHevc - } - - rbsp := nal2rbsp(sps[2:]) - br := nazabits.NewBitReader(rbsp) - - // sps_video_parameter_set_id - if _, err = br.ReadBits8(4); err != nil { - return err - } - - spsMaxSubLayersMinus1, err := br.ReadBits8(3) - if err != nil { - return err - } - - if spsMaxSubLayersMinus1+1 > ctx.numTemporalLayers { - ctx.numTemporalLayers = spsMaxSubLayersMinus1 + 1 - } - - // sps_temporal_id_nesting_flag - if ctx.temporalIdNested, err = br.ReadBit(); err != nil { - return err - } - - if err = ctx.parsePtl(&br, spsMaxSubLayersMinus1); err != nil { - return err - } - - // sps_seq_parameter_set_id - if _, err = br.ReadGolomb(); err != nil { - return err - } - - var cf uint32 - if cf, err = br.ReadGolomb(); err != nil { - return err - } - ctx.chromaFormat = uint8(cf) - if ctx.chromaFormat == 3 { - if _, err = br.ReadBit(); err != nil { - return err - } - } - - if ctx.PicWidthInLumaSamples, err = br.ReadGolomb(); err != nil { - return err - } - if ctx.PicHeightInLumaSamples, err = br.ReadGolomb(); err != nil { - return err - } - - conformanceWindowFlag, err := br.ReadBit() - if err != nil { - return err - } - if conformanceWindowFlag != 0 { - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - } - - var bdlm8 uint32 - if bdlm8, err = br.ReadGolomb(); err != nil { - return err - } - ctx.bitDepthLumaMinus8 = uint8(bdlm8) - var bdcm8 uint32 - if bdcm8, err = br.ReadGolomb(); err != nil { - return err - } - ctx.bitDepthChromaMinus8 = uint8(bdcm8) - - _, err = br.ReadGolomb() - if err != nil { - return err - } - spsSubLayerOrderingInfoPresentFlag, err := br.ReadBit() - if err != nil { - return err - } - var i uint8 - if spsSubLayerOrderingInfoPresentFlag != 0 { - i = 0 - } else { - i = spsMaxSubLayersMinus1 - } - for ; i <= spsMaxSubLayersMinus1; i++ { - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - } - - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - if _, err = br.ReadGolomb(); err != nil { - return err - } - - return nil -} - -func (ctx *HVCDecoderConfigurationRecord) parsePtl(br *nazabits.BitReader, maxSubLayersMinus1 uint8) error { - var err error - var ptl HVCDecoderConfigurationRecord - if ptl.generalProfileSpace, err = br.ReadBits8(2); err != nil { - return err - } - if ptl.generalTierFlag, err = br.ReadBit(); err != nil { - return err - } - if ptl.generalProfileIdc, err = br.ReadBits8(5); err != nil { - return err - } - if ptl.generalProfileCompatibilityFlags, err = br.ReadBits32(32); err != nil { - return err - } - if ptl.generalConstraintIndicatorFlags, err = br.ReadBits64(48); err != nil { - return err - } - if ptl.generalLevelIdc, err = br.ReadBits8(8); err != nil { - return err - } - ctx.updatePtl(&ptl) - - if maxSubLayersMinus1 == 0 { - return nil - } - - subLayerProfilePresentFlag := make([]uint8, maxSubLayersMinus1) - subLayerLevelPresentFlag := make([]uint8, maxSubLayersMinus1) - for i := uint8(0); i < maxSubLayersMinus1; i++ { - if subLayerProfilePresentFlag[i], err = br.ReadBit(); err != nil { - return err - } - if subLayerLevelPresentFlag[i], err = br.ReadBit(); err != nil { - return err - } - } - if maxSubLayersMinus1 > 0 { - for i := maxSubLayersMinus1; i < 8; i++ { - if _, err = br.ReadBits8(2); err != nil { - return err - } - } - } - - for i := uint8(0); i < maxSubLayersMinus1; i++ { - if subLayerProfilePresentFlag[i] != 0 { - if _, err = br.ReadBits32(32); err != nil { - return err - } - if _, err = br.ReadBits32(32); err != nil { - return err - } - if _, err = br.ReadBits32(24); err != nil { - return err - } - } - - if subLayerLevelPresentFlag[i] != 0 { - if _, err = br.ReadBits8(8); err != nil { - return err - } - } - } - - return nil -} - -func (ctx *HVCDecoderConfigurationRecord) updatePtl(ptl *HVCDecoderConfigurationRecord) { - ctx.generalProfileSpace = ptl.generalProfileSpace - - if ptl.generalTierFlag > ctx.generalTierFlag { - ctx.generalLevelIdc = ptl.generalLevelIdc - - ctx.generalTierFlag = ptl.generalTierFlag - } else { - if ptl.generalLevelIdc > ctx.generalLevelIdc { - ctx.generalLevelIdc = ptl.generalLevelIdc - } - } - - if ptl.generalProfileIdc > ctx.generalProfileIdc { - ctx.generalProfileIdc = ptl.generalProfileIdc - } - - ctx.generalProfileCompatibilityFlags &= ptl.generalProfileCompatibilityFlags - - ctx.generalConstraintIndicatorFlags &= ptl.generalConstraintIndicatorFlags -} - -func nal2rbsp(nal []byte) []byte { - // TODO chef: - // 1. 输出应该可由外部申请 - // 2. 替换性能 - // 3. 该函数应该放入avc中 - return bytes.Replace(nal, []byte{0x0, 0x0, 0x3}, []byte{0x0, 0x0}, -1) -} - var ( ErrInvalidMarker = errors.New("invalid marker value found in AV1CodecConfigurationRecord") ErrInvalidVersion = errors.New("unsupported AV1CodecConfigurationRecord version") @@ -759,7 +176,3 @@ func (p *AV1Ctx) Unmarshal(data *util.MemoryReader) (err error) { } return nil } - -func (ctx *AACCtx) GetInfo() string { - return fmt.Sprintf("AudioObjectType: %d, SamplingFrequencyIndex: %d, ChannelConfiguration: %d, FrameLengthFlag: %d, DependsOnCoreCoder: %d, ExtensionFlag: %d", ctx.AudioObjectType, ctx.SamplingFrequencyIndex, ctx.ChannelConfiguration, ctx.FrameLengthFlag, ctx.DependsOnCoreCoder, ctx.ExtensionFlag) -} diff --git a/plugin/rtmp/pkg/const.go b/plugin/rtmp/pkg/const.go index 0c19bdf..8e5636d 100644 --- a/plugin/rtmp/pkg/const.go +++ b/plugin/rtmp/pkg/const.go @@ -24,7 +24,7 @@ type RTMPData struct { } func (avcc *RTMPData) Dump(t byte, w io.Writer) { - m := avcc.Borrow(9 + avcc.Size) + m := avcc.GetAllocator().Borrow(9 + avcc.Size) m[0] = t binary.BigEndian.PutUint32(m[1:], uint32(4+avcc.Size)) binary.BigEndian.PutUint32(m[5:], avcc.Timestamp) diff --git a/plugin/rtmp/pkg/net-connection.go b/plugin/rtmp/pkg/net-connection.go index 86e64fd..c76fd72 100644 --- a/plugin/rtmp/pkg/net-connection.go +++ b/plugin/rtmp/pkg/net-connection.go @@ -74,7 +74,7 @@ func NewNetConnection(conn net.Conn, logger *slog.Logger) (ret *NetConnection) { tmpBuf: make(util.Buffer, 4), chunkHeaderBuf: make(util.Buffer, 0, 20), } - ret.mediaDataPool.ScalableMemoryAllocator = util.NewScalableMemoryAllocator(1 << util.MinPowerOf2) + ret.mediaDataPool.SetAllocator(util.NewScalableMemoryAllocator(1 << util.MinPowerOf2)) ret.Info("new connection") return } @@ -154,9 +154,8 @@ func (conn *NetConnection) readChunk() (msg *Chunk, err error) { } conn.readSeqNum += uint32(bufSize) if chunk.bufLen == 0 { - chunk.AVData.RecyclableMemory = util.RecyclableMemory{ - ScalableMemoryAllocator: conn.mediaDataPool.ScalableMemoryAllocator, - } + chunk.AVData.RecyclableMemory = util.RecyclableMemory{} + chunk.AVData.SetAllocator(conn.mediaDataPool.GetAllocator()) chunk.AVData.NextN(msgLen) } buffer := chunk.AVData.Buffers[0] diff --git a/plugin/rtmp/pkg/video.go b/plugin/rtmp/pkg/video.go index ac6d21e..80fa5fc 100644 --- a/plugin/rtmp/pkg/video.go +++ b/plugin/rtmp/pkg/video.go @@ -1,8 +1,8 @@ package rtmp import ( - "context" "encoding/binary" + "github.com/deepch/vdk/codec/h265parser" "io" "time" @@ -43,14 +43,15 @@ func (avcc *RTMPVideo) Parse(t *AVTrack) (err error) { cloneFrame.CopyFrom(&avcc.Memory) switch fourCC { case codec.FourCC_H264: - var ctx H264Ctx - if err = ctx.Unmarshal(reader); err == nil { + var ctx codec.H264Ctx + if _, err = ctx.RecordInfo.Unmarshal(cloneFrame.Buffers[0][reader.Offset():]); err == nil { t.SequenceFrame = &cloneFrame t.ICodecCtx = &ctx } case codec.FourCC_H265: var ctx H265Ctx - if err = ctx.Unmarshal(reader); err == nil { + if _, err = ctx.RecordInfo.Unmarshal(cloneFrame.Buffers[0][reader.Offset():]); err == nil { + ctx.RecordInfo.LengthSizeMinusOne = 3 // Unmarshal wrong LengthSizeMinusOne t.SequenceFrame = &cloneFrame t.ICodecCtx = &ctx } @@ -110,65 +111,56 @@ func (avcc *RTMPVideo) Parse(t *AVTrack) (err error) { return } -func (avcc *RTMPVideo) ConvertCtx(from codec.ICodecCtx, t *AVTrack) (err error) { +func (avcc *RTMPVideo) ConvertCtx(from codec.ICodecCtx) (to codec.ICodecCtx, seq IAVFrame, err error) { + var b util.Buffer + var enhanced = true //TODO switch fourCC := from.FourCC(); fourCC { case codec.FourCC_H264: h264ctx := from.GetBase().(*codec.H264Ctx) - var ctx H264Ctx - ctx.H264Ctx = *h264ctx - lenSPS := len(h264ctx.SPS[0]) - lenPPS := len(h264ctx.PPS[0]) - var b util.Buffer - if lenSPS > 3 { - b.Write(RTMP_AVC_HEAD[:6]) - b.Write(h264ctx.SPS[0][1:4]) - b.Write(RTMP_AVC_HEAD[9:10]) - } else { - b.Write(RTMP_AVC_HEAD) - } - b.WriteByte(0xE1) - b.WriteUint16(uint16(lenSPS)) - b.Write(h264ctx.SPS[0]) - b.WriteByte(0x01) - b.WriteUint16(uint16(lenPPS)) - b.Write(h264ctx.PPS[0]) - t.ICodecCtx = &ctx + b = make(util.Buffer, h264ctx.RecordInfo.Len()+4) + b[0] = 0x17 + h264ctx.RecordInfo.Marshal(b[4:]) var seqFrame RTMPData seqFrame.AppendOne(b) - t.SequenceFrame = seqFrame.WrapVideo() - if t.Enabled(context.TODO(), TraceLevel) { - c := t.FourCC().String() - size := seqFrame.GetSize() - data := seqFrame.String() - t.Trace("decConfig", "codec", c, "size", size, "data", data) - } + //if t.Enabled(context.TODO(), TraceLevel) { + // c := t.FourCC().String() + // size := seqFrame.GetSize() + // data := seqFrame.String() + // t.Trace("decConfig", "codec", c, "size", size, "data", data) + //} + return h264ctx, seqFrame.WrapVideo(), err case codec.FourCC_H265: - // TODO: H265 + h265ctx := from.GetBase().(*codec.H265Ctx) + b = make(util.Buffer, h265ctx.RecordInfo.Len()+5) + if enhanced { + b[0] = 0b1001_0000 | byte(PacketTypeSequenceStart) + copy(b[1:], codec.FourCC_H265[:]) + } else { + b[0], b[1], b[2], b[3], b[4] = 0x1C, 0, 0, 0, 0 + } + h265ctx.RecordInfo.Marshal(b[5:], h265parser.SPSInfo{}) + var ctx H265Ctx + ctx.Enhanced = enhanced + ctx.H265Ctx = *h265ctx + var seqFrame RTMPData + seqFrame.AppendOne(b) + return &ctx, seqFrame.WrapVideo(), err + case codec.FourCC_AV1: } return } -func (avcc *RTMPVideo) parseH264(ctx *H264Ctx, reader *util.MemoryReader) (any, error) { - cts, err := reader.ReadBE(3) - if err != nil { - return nil, err - } - avcc.CTS = cts +func (avcc *RTMPVideo) parseH264(ctx *codec.H264Ctx, reader *util.MemoryReader) (any, error) { var nalus Nalus - if err := nalus.ParseAVCC(reader, ctx.NalulenSize); err != nil { + if err := nalus.ParseAVCC(reader, int(ctx.RecordInfo.LengthSizeMinusOne)+1); err != nil { return nalus, err } return nalus, nil } func (avcc *RTMPVideo) parseH265(ctx *H265Ctx, reader *util.MemoryReader) (any, error) { - cts, err := reader.ReadBE(3) - if err != nil { - return nil, err - } - avcc.CTS = cts var nalus Nalus - if err := nalus.ParseAVCC(reader, ctx.NalulenSize); err != nil { + if err := nalus.ParseAVCC(reader, int(ctx.RecordInfo.LengthSizeMinusOne)+1); err != nil { return nalus, err } return nalus, nil @@ -200,67 +192,95 @@ func (avcc *RTMPVideo) Demux(codecCtx codec.ICodecCtx) (any, error) { } switch packetType { case PacketTypeSequenceStart: - // if _, err = avcc.DecodeConfig(nil); err != nil { - // return nil, err - // } + // see Parse() return nil, nil case PacketTypeCodedFrames: - if codecCtx.FourCC() == codec.FourCC_H265 { - return avcc.parseH265(codecCtx.(*H265Ctx), reader) - } else { + switch ctx := codecCtx.(type) { + case *H265Ctx: + if avcc.CTS, err = reader.ReadBE(3); err != nil { + return nil, err + } + return avcc.parseH265(ctx, reader) + case *AV1Ctx: return avcc.parseAV1(reader) } - case PacketTypeCodedFramesX: + case PacketTypeCodedFramesX: // no cts + return avcc.parseH265(codecCtx.(*H265Ctx), reader) } } else { b0, err = reader.ReadByte() //sequence frame flag if err != nil { return nil, err } - if b0 == 0 { - if err = reader.Skip(3); err != nil { - return nil, err - } - var nalus Nalus - if codecCtx.FourCC() == codec.FourCC_H265 { - var ctx = codecCtx.(*H265Ctx) - nalus.Append(ctx.SPS[0]) - nalus.Append(ctx.PPS[0]) - nalus.Append(ctx.VPS[0]) + if avcc.CTS, err = reader.ReadBE(3); err != nil { + return nil, err + } + var nalus Nalus + switch ctx := codecCtx.(type) { + case *H265Ctx: + if b0 == 0 { + nalus.Append(ctx.VPS()) + nalus.Append(ctx.SPS()) + nalus.Append(ctx.PPS()) } else { - var ctx = codecCtx.(*H264Ctx) - nalus.Append(ctx.SPS[0]) - nalus.Append(ctx.PPS[0]) + return avcc.parseH265(ctx, reader) } - return nalus, nil - } else { - if codecCtx.FourCC() == codec.FourCC_H265 { - return avcc.parseH265(codecCtx.(*H265Ctx), reader) + + case *codec.H264Ctx: + if b0 == 0 { + nalus.Append(ctx.SPS()) + nalus.Append(ctx.PPS()) } else { - return avcc.parseH264(codecCtx.(*H264Ctx), reader) + return avcc.parseH264(ctx, reader) } } + return nalus, nil } return nil, nil } +func (avcc *RTMPVideo) muxOld26x(codecID VideoCodecID, from *AVFrame) { + nalus := from.Raw.(Nalus) + avcc.InitRecycleIndexes(len(nalus)) // Recycle partial data + head := avcc.NextN(5) + head[0] = util.Conditoinal[byte](from.IDR, 0x10, 0x20) | byte(codecID) + head[1] = 1 + util.PutBE(head[2:5], from.CTS/time.Millisecond) // cts + for _, nalu := range nalus { + naluLenM := avcc.NextN(4) + naluLen := uint32(nalu.Size) + binary.BigEndian.PutUint32(naluLenM, naluLen) + avcc.Append(nalu.Buffers...) + } +} + func (avcc *RTMPVideo) Mux(codecCtx codec.ICodecCtx, from *AVFrame) { avcc.Timestamp = uint32(from.Timestamp / time.Millisecond) switch ctx := codecCtx.(type) { case *AV1Ctx: panic(ctx) - default: - nalus := from.Raw.(Nalus) - avcc.RecycleIndexes = make([]int, 0, len(nalus)) // Recycle partial data - head := avcc.NextN(5) - head[0] = util.Conditoinal[byte](from.IDR, 0x10, 0x20) | byte(ParseVideoCodec(codecCtx.FourCC())) - head[1] = 1 - util.PutBE(head[2:5], from.CTS/time.Millisecond) // cts - for _, nalu := range nalus { - naluLenM := avcc.NextN(4) - naluLen := uint32(nalu.Size) - binary.BigEndian.PutUint32(naluLenM, naluLen) - avcc.Append(nalu.Buffers...) + case *codec.H264Ctx: + avcc.muxOld26x(CodecID_H264, from) + case *H265Ctx: + if ctx.Enhanced { + nalus := from.Raw.(Nalus) + avcc.InitRecycleIndexes(len(nalus)) // Recycle partial data + head := avcc.NextN(8) + if from.IDR { + head[0] = 0b1001_0000 | byte(PacketTypeCodedFrames) + } else { + head[0] = 0b1010_0000 | byte(PacketTypeCodedFrames) + } + copy(head[1:], codec.FourCC_H265[:]) + util.PutBE(head[5:8], from.CTS/time.Millisecond) // cts + for _, nalu := range nalus { + naluLenM := avcc.NextN(4) + naluLen := uint32(nalu.Size) + binary.BigEndian.PutUint32(naluLenM, naluLen) + avcc.Append(nalu.Buffers...) + } + } else { + avcc.muxOld26x(CodecID_H265, from) } } } diff --git a/plugin/rtp/pkg/audio.go b/plugin/rtp/pkg/audio.go index b273dc1..73ddd20 100644 --- a/plugin/rtp/pkg/audio.go +++ b/plugin/rtp/pkg/audio.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "github.com/bluenviron/mediacommon/pkg/bits" + "github.com/deepch/vdk/codec/aacparser" "io" "regexp" "strings" @@ -28,7 +29,7 @@ type RTPData struct { } func (r *RTPData) Dump(t byte, w io.Writer) { - m := r.Borrow(3 + len(r.Packets)*2 + r.GetSize()) + m := r.GetAllocator().Borrow(3 + len(r.Packets)*2 + r.GetSize()) m[0] = t binary.BigEndian.PutUint16(m[1:], uint16(len(r.Packets))) offset := 3 @@ -112,15 +113,16 @@ func (r *RTPCtx) parseFmtpLine(cp *webrtc.RTPCodecParameters) { func (r *RTPCtx) GetInfo() string { return r.GetRTPCodecParameter().SDPFmtpLine } - +func (r *RTPAACCtx) GetInfo() string { + return r.AACCtx.GetInfo() +} +func (r *RTPOPUSCtx) GetInfo() string { + return r.OPUSCtx.GetInfo() +} func (r *RTPCtx) GetRTPCodecParameter() webrtc.RTPCodecParameters { return r.RTPCodecParameters } -func (r *RTPCtx) GetSequenceFrame() IAVFrame { - return nil -} - func (r *RTPData) Append(ctx *RTPCtx, ts uint32, payload []byte) (lastPacket *rtp.Packet) { ctx.SequenceNumber++ lastPacket = &rtp.Packet{ @@ -137,7 +139,7 @@ func (r *RTPData) Append(ctx *RTPCtx, ts uint32, payload []byte) (lastPacket *rt return } -func (r *RTPData) ConvertCtx(from codec.ICodecCtx, t *AVTrack) (err error) { +func (r *RTPData) ConvertCtx(from codec.ICodecCtx) (to codec.ICodecCtx, seq IAVFrame, err error) { switch from.FourCC() { case codec.FourCC_H264: var ctx RTPH264Ctx @@ -146,31 +148,31 @@ func (r *RTPData) ConvertCtx(from codec.ICodecCtx, t *AVTrack) (err error) { ctx.MimeType = webrtc.MimeTypeH264 ctx.ClockRate = 90000 spsInfo := ctx.SPSInfo - ctx.SDPFmtpLine = fmt.Sprintf("sprop-parameter-sets=%s,%s;profile-level-id=%02x%02x%02x;level-asymmetry-allowed=1;packetization-mode=1", base64.StdEncoding.EncodeToString(ctx.SPS[0]), base64.StdEncoding.EncodeToString(ctx.PPS[0]), spsInfo.ProfileIdc, spsInfo.ConstraintSetFlag, spsInfo.LevelIdc) + ctx.SDPFmtpLine = fmt.Sprintf("sprop-parameter-sets=%s,%s;profile-level-id=%02x%02x%02x;level-asymmetry-allowed=1;packetization-mode=1", base64.StdEncoding.EncodeToString(ctx.SPS()), base64.StdEncoding.EncodeToString(ctx.PPS()), spsInfo.ProfileIdc, spsInfo.ConstraintSetFlag, spsInfo.LevelIdc) ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) - t.ICodecCtx = &ctx + to = &ctx case codec.FourCC_H265: var ctx RTPH265Ctx ctx.H265Ctx = *from.GetBase().(*codec.H265Ctx) ctx.PayloadType = 98 ctx.MimeType = webrtc.MimeTypeH265 - ctx.SDPFmtpLine = fmt.Sprintf("profile-id=1;sprop-sps=%s;sprop-pps=%s;sprop-vps=%s", base64.StdEncoding.EncodeToString(ctx.SPS[0]), base64.StdEncoding.EncodeToString(ctx.PPS[0]), base64.StdEncoding.EncodeToString(ctx.VPS[0])) + ctx.SDPFmtpLine = fmt.Sprintf("profile-id=1;sprop-sps=%s;sprop-pps=%s;sprop-vps=%s", base64.StdEncoding.EncodeToString(ctx.SPS()), base64.StdEncoding.EncodeToString(ctx.PPS()), base64.StdEncoding.EncodeToString(ctx.VPS())) ctx.ClockRate = 90000 ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) - t.ICodecCtx = &ctx + to = &ctx case codec.FourCC_MP4A: var ctx RTPAACCtx ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) ctx.AACCtx = *from.GetBase().(*codec.AACCtx) ctx.MimeType = "audio/MPEG4-GENERIC" - ctx.SDPFmtpLine = fmt.Sprintf("profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=%s", hex.EncodeToString(ctx.AACCtx.Asc)) + ctx.SDPFmtpLine = fmt.Sprintf("profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=%s", hex.EncodeToString(ctx.AACCtx.ConfigBytes)) ctx.IndexLength = 3 ctx.IndexDeltaLength = 3 ctx.SizeLength = 13 - ctx.RTPCtx.Channels = uint16(ctx.AACCtx.Channels) + ctx.RTPCtx.Channels = uint16(ctx.AACCtx.GetChannels()) ctx.PayloadType = 97 - ctx.ClockRate = uint32(ctx.SampleRate) - t.ICodecCtx = &ctx + ctx.ClockRate = uint32(ctx.CodecData.SampleRate()) + to = &ctx case codec.FourCC_ALAW: var ctx RTPPCMACtx ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) @@ -178,7 +180,7 @@ func (r *RTPData) ConvertCtx(from codec.ICodecCtx, t *AVTrack) (err error) { ctx.MimeType = webrtc.MimeTypePCMA ctx.PayloadType = 8 ctx.ClockRate = uint32(ctx.SampleRate) - t.ICodecCtx = &ctx + to = &ctx case codec.FourCC_ULAW: var ctx RTPPCMUCtx ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) @@ -186,15 +188,15 @@ func (r *RTPData) ConvertCtx(from codec.ICodecCtx, t *AVTrack) (err error) { ctx.MimeType = webrtc.MimeTypePCMU ctx.PayloadType = 0 ctx.ClockRate = uint32(ctx.SampleRate) - t.ICodecCtx = &ctx + to = &ctx case codec.FourCC_OPUS: var ctx RTPOPUSCtx ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) ctx.OPUSCtx = *from.GetBase().(*codec.OPUSCtx) ctx.MimeType = webrtc.MimeTypeOpus ctx.PayloadType = 111 - ctx.ClockRate = uint32(ctx.SampleRate) - t.ICodecCtx = &ctx + ctx.ClockRate = uint32(ctx.CodecData.SampleRate()) + to = &ctx } return } @@ -228,10 +230,10 @@ func (r *RTPAudio) Parse(t *AVTrack) (err error) { ctx.IndexDeltaLength = 3 ctx.SizeLength = 13 if conf, ok := ctx.Fmtp["config"]; ok { - if ctx.AACCtx.Asc, err = hex.DecodeString(conf); err == nil { - ctx.SampleRate = int(r.ClockRate) - ctx.Channels = int(r.Channels) - ctx.SampleSize = 16 + if ctx.AACCtx.ConfigBytes, err = hex.DecodeString(conf); err == nil { + if ctx.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(ctx.AACCtx.ConfigBytes); err != nil { + return + } } } t.ICodecCtx = ctx diff --git a/plugin/rtp/pkg/video.go b/plugin/rtp/pkg/video.go index b6a4ff6..01c73f9 100644 --- a/plugin/rtp/pkg/video.go +++ b/plugin/rtp/pkg/video.go @@ -4,6 +4,9 @@ import ( "encoding/base64" "errors" "fmt" + "github.com/deepch/vdk/codec/h264parser" + "github.com/deepch/vdk/codec/h265parser" + "io" "strings" "time" @@ -22,6 +25,7 @@ type ( RTPH265Ctx struct { RTPCtx codec.H265Ctx + DONL bool } RTPAV1Ctx struct { RTPCtx @@ -43,9 +47,11 @@ var ( ) const ( - startBit = 1 << 7 - endBit = 1 << 6 - MTUSize = 1460 + H265_NALU_AP = h265parser.NAL_UNIT_UNSPECIFIED_48 + H265_NALU_FU = h265parser.NAL_UNIT_UNSPECIFIED_49 + startBit = 1 << 7 + endBit = 1 << 6 + MTUSize = 1460 ) func (r *RTPVideo) Parse(t *AVTrack) (err error) { @@ -57,16 +63,20 @@ func (r *RTPVideo) Parse(t *AVTrack) (err error) { } else { ctx = &RTPH264Ctx{} ctx.parseFmtpLine(r.RTPCodecParameters) + var sps, pps []byte //packetization-mode=1; sprop-parameter-sets=J2QAKaxWgHgCJ+WagICAgQ==,KO48sA==; profile-level-id=640029 if sprop, ok := ctx.Fmtp["sprop-parameter-sets"]; ok { if sprops := strings.Split(sprop, ","); len(sprops) == 2 { - if sps, err := base64.StdEncoding.DecodeString(sprops[0]); err == nil { - ctx.SPS = [][]byte{sps} + if sps, err = base64.StdEncoding.DecodeString(sprops[0]); err != nil { + return } - if pps, err := base64.StdEncoding.DecodeString(sprops[1]); err == nil { - ctx.PPS = [][]byte{pps} + if pps, err = base64.StdEncoding.DecodeString(sprops[1]); err == nil { + return } } + if ctx.CodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil { + return + } } t.ICodecCtx = ctx } @@ -75,13 +85,13 @@ func (r *RTPVideo) Parse(t *AVTrack) (err error) { } for _, nalu := range t.Value.Raw.(Nalus) { switch codec.ParseH264NALUType(nalu.Buffers[0][0]) { - case codec.NALU_SPS: - ctx.SPS = [][]byte{nalu.ToBytes()} - if err = ctx.SPSInfo.Unmarshal(ctx.SPS[0]); err != nil { + case h264parser.NALU_SPS: + ctx.RecordInfo.SPS = [][]byte{nalu.ToBytes()} + if ctx.SPSInfo, err = h264parser.ParseSPS(ctx.SPS()); err != nil { return } - case codec.NALU_PPS: - ctx.PPS = [][]byte{nalu.ToBytes()} + case h264parser.NALU_PPS: + ctx.RecordInfo.PPS = [][]byte{nalu.ToBytes()} case codec.NALU_IDR_Picture: t.Value.IDR = true } @@ -101,6 +111,30 @@ func (r *RTPVideo) Parse(t *AVTrack) (err error) { } else { ctx = &RTPH265Ctx{} ctx.parseFmtpLine(r.RTPCodecParameters) + var vps, sps, pps []byte + if sprop_sps, ok := ctx.Fmtp["sprop-sps"]; ok { + if sps, err = base64.StdEncoding.DecodeString(sprop_sps); err != nil { + return + } + } + if sprop_pps, ok := ctx.Fmtp["sprop-pps"]; ok { + if pps, err = base64.StdEncoding.DecodeString(sprop_pps); err != nil { + return + } + } + if sprop_vps, ok := ctx.Fmtp["sprop-vps"]; ok { + if vps, err = base64.StdEncoding.DecodeString(sprop_vps); err != nil { + return + } + } + if ctx.CodecData, err = h265parser.NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps); err != nil { + return + } + if sprop_donl, ok := ctx.Fmtp["sprop-max-don-diff"]; ok { + if sprop_donl != "0" { + ctx.DONL = true + } + } t.ICodecCtx = ctx } if t.Value.Raw, err = r.Demux(ctx); err != nil { @@ -108,22 +142,24 @@ func (r *RTPVideo) Parse(t *AVTrack) (err error) { } for _, nalu := range t.Value.Raw.(Nalus) { switch codec.ParseH265NALUType(nalu.Buffers[0][0]) { - case codec.NAL_UNIT_SPS: + case h265parser.NAL_UNIT_VPS: ctx = &RTPH265Ctx{} - ctx.SPS = [][]byte{nalu.ToBytes()} - ctx.SPSInfo.Unmarshal(ctx.SPS[0]) + ctx.RecordInfo.VPS = [][]byte{nalu.ToBytes()} ctx.RTPCodecParameters = *r.RTPCodecParameters t.ICodecCtx = ctx - case codec.NAL_UNIT_PPS: - ctx.PPS = [][]byte{nalu.ToBytes()} - case codec.NAL_UNIT_VPS: - ctx.VPS = [][]byte{nalu.ToBytes()} - case codec.NAL_UNIT_CODED_SLICE_BLA, - codec.NAL_UNIT_CODED_SLICE_BLANT, - codec.NAL_UNIT_CODED_SLICE_BLA_N_LP, - codec.NAL_UNIT_CODED_SLICE_IDR, - codec.NAL_UNIT_CODED_SLICE_IDR_N_LP, - codec.NAL_UNIT_CODED_SLICE_CRA: + case h265parser.NAL_UNIT_SPS: + ctx.RecordInfo.SPS = [][]byte{nalu.ToBytes()} + if ctx.SPSInfo, err = h265parser.ParseSPS(ctx.SPS()); err != nil { + return + } + case h265parser.NAL_UNIT_PPS: + ctx.RecordInfo.PPS = [][]byte{nalu.ToBytes()} + case h265parser.NAL_UNIT_CODED_SLICE_BLA_W_LP, + h265parser.NAL_UNIT_CODED_SLICE_BLA_W_RADL, + h265parser.NAL_UNIT_CODED_SLICE_BLA_N_LP, + h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL, + h265parser.NAL_UNIT_CODED_SLICE_IDR_N_LP, + h265parser.NAL_UNIT_CODED_SLICE_CRA: t.Value.IDR = true } } @@ -156,9 +192,9 @@ func (r *RTPVideo) Mux(codecCtx codec.ICodecCtx, from *AVFrame) { ctx := &c.RTPCtx r.RTPCodecParameters = &ctx.RTPCodecParameters var lastPacket *rtp.Packet - if from.IDR && len(c.SPS) > 0 && len(c.PPS) > 0 { - r.Append(ctx, pts, c.SPS[0]) - r.Append(ctx, pts, c.PPS[0]) + if from.IDR && len(c.RecordInfo.SPS) > 0 && len(c.RecordInfo.PPS) > 0 { + r.Append(ctx, pts, c.SPS()) + r.Append(ctx, pts, c.PPS()) } for _, nalu := range from.Raw.(Nalus) { if reader := nalu.NewReader(); reader.Length > MTUSize { @@ -188,11 +224,53 @@ func (r *RTPVideo) Mux(codecCtx codec.ICodecCtx, from *AVFrame) { } } lastPacket.Header.Marker = true + case *RTPH265Ctx: + ctx := &c.RTPCtx + r.RTPCodecParameters = &ctx.RTPCodecParameters + var lastPacket *rtp.Packet + if from.IDR && len(c.RecordInfo.SPS) > 0 && len(c.RecordInfo.PPS) > 0 && len(c.RecordInfo.VPS) > 0 { + r.Append(ctx, pts, c.VPS()) + r.Append(ctx, pts, c.SPS()) + r.Append(ctx, pts, c.PPS()) + } + for _, nalu := range from.Raw.(Nalus) { + if reader := nalu.NewReader(); reader.Length > MTUSize { + var b0, b1 byte + _ = reader.ReadByteTo(&b0, &b1) + //fu + naluType := byte(codec.ParseH265NALUType(b0)) + b0 = (byte(H265_NALU_FU) << 1) | (b0 & 0b10000001) + + payloadLen := MTUSize + if reader.Length+3 < payloadLen { + payloadLen = reader.Length + 3 + } + mem := r.NextN(payloadLen) + reader.ReadBytesTo(mem[3:]) + mem[0], mem[1], mem[2] = b0, b1, naluType|startBit + lastPacket = r.Append(ctx, pts, mem) + + for payloadLen = MTUSize; reader.Length > 0; lastPacket = r.Append(ctx, pts, mem) { + if reader.Length+3 < payloadLen { + payloadLen = reader.Length + 3 + } + mem = r.NextN(payloadLen) + reader.ReadBytesTo(mem[3:]) + mem[0], mem[1], mem[2] = b0, b1, naluType + } + lastPacket.Payload[2] |= endBit + } else { + mem := r.NextN(reader.Length) + reader.ReadBytesTo(mem) + lastPacket = r.Append(ctx, pts, mem) + } + } + lastPacket.Header.Marker = true } } func (r *RTPVideo) Demux(ictx codec.ICodecCtx) (any, error) { - switch ictx.(type) { + switch c := ictx.(type) { case *RTPH264Ctx: var nalus Nalus var nalu util.Memory @@ -243,6 +321,57 @@ func (r *RTPVideo) Demux(ictx codec.ICodecCtx) (any, error) { } } return nalus, nil + case *RTPH265Ctx: + var nalus Nalus + var nalu util.Memory + gotNalu := func() { + if nalu.Size > 0 { + nalus = append(nalus, nalu) + nalu = util.Memory{} + } + } + for _, packet := range r.Packets { + b0 := packet.Payload[0] + if t := codec.ParseH265NALUType(b0); t < H265_NALU_AP { + nalu.AppendOne(packet.Payload) + gotNalu() + } else { + var buffer = util.Buffer(packet.Payload) + switch t { + case H265_NALU_AP: + buffer.ReadUint16() + if c.DONL { + buffer.ReadUint16() + } + for buffer.CanRead() { + nalu.AppendOne(buffer.ReadN(int(buffer.ReadUint16()))) + gotNalu() + } + if c.DONL { + buffer.ReadByte() + } + case H265_NALU_FU: + if buffer.Len() < 3 { + return nil, io.ErrShortBuffer + } + first3 := buffer.ReadN(3) + fuHeader := first3[2] + if c.DONL { + buffer.ReadUint16() + } + if naluType := fuHeader & 0b00111111; util.Bit1(fuHeader, 0) { + nalu.AppendOne([]byte{first3[0]&0b10000001 | (naluType << 1), first3[1]}) + } + nalu.AppendOne(buffer) + if util.Bit1(fuHeader, 1) { + gotNalu() + } + default: + return nil, fmt.Errorf("unsupported nalu type %d", t) + } + } + } + return nalus, nil } return nil, nil } diff --git a/plugin/rtsp/index.go b/plugin/rtsp/index.go index 919ddbf..ffc34fa 100644 --- a/plugin/rtsp/index.go +++ b/plugin/rtsp/index.go @@ -3,7 +3,6 @@ package plugin_rtsp import ( "errors" "fmt" - "github.com/AlexxIT/go2rtc/pkg/core" "m7s.live/m7s/v5" "m7s.live/m7s/v5/pkg/util" . "m7s.live/m7s/v5/plugin/rtsp/pkg" @@ -104,7 +103,7 @@ func (p *RTSPPlugin) OnTCPConnect(conn *net.TCPConn) { } nc.SDP = string(req.Body) // for info - var medias []*core.Media + var medias []*Media if medias, err = UnmarshalSDP(req.Body); err != nil { return } @@ -151,11 +150,11 @@ func (p *RTSPPlugin) OnTCPConnect(conn *net.TCPConn) { } sender.NetConnection = nc // convert tracks to real output medias - var medias []*core.Media + var medias []*Media if medias, err = sender.GetMedia(); err != nil { return } - if res.Body, err = core.MarshalSDP(nc.SessionName, medias); err != nil { + if res.Body, err = MarshalSDP(nc.SessionName, medias); err != nil { return } @@ -175,7 +174,8 @@ func (p *RTSPPlugin) OnTCPConnect(conn *net.TCPConn) { const transport = "RTP/AVP/TCP;unicast;interleaved=" if strings.HasPrefix(tr, transport) { - nc.Session = core.RandString(8, 10) + + nc.Session = util.RandomString(10) if sendMode { if i := reqTrackID(req); i >= 0 { diff --git a/plugin/rtsp/pkg/client.go b/plugin/rtsp/pkg/client.go index ceb517b..f0acfc8 100644 --- a/plugin/rtsp/pkg/client.go +++ b/plugin/rtsp/pkg/client.go @@ -4,8 +4,6 @@ import ( "crypto/tls" "errors" "fmt" - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/AlexxIT/go2rtc/pkg/tcp" "m7s.live/m7s/v5" "m7s.live/m7s/v5/pkg/util" "net" @@ -19,6 +17,14 @@ type Client struct { Stream } +func NewPushHandler() m7s.PushHandler { + return &Client{} +} + +func NewPullHandler() m7s.PullHandler { + return &Client{} +} + func (c *Client) Connect(p *m7s.Client) (err error) { addr := p.RemoteURL var rtspURL *url.URL @@ -69,7 +75,7 @@ func (c *Client) Pull(p *m7s.Puller) (err error) { } p.Dispose(err) }() - var media []*core.Media + var media []*Media if media, err = c.Describe(); err != nil { return } @@ -86,7 +92,7 @@ func (c *Client) Pull(p *m7s.Puller) (err error) { func (c *Client) Push(p *m7s.Pusher) (err error) { defer c.Close() sender := &Sender{Subscriber: &p.Subscriber, Stream: c.Stream} - var medias []*core.Media + var medias []*Media medias, err = sender.GetMedia() err = c.Announce(medias) if err != nil { @@ -122,12 +128,12 @@ func (c *Client) Do(req *util.Request) (*util.Response, error) { if res.StatusCode == http.StatusUnauthorized { switch c.auth.Method { - case tcp.AuthNone: + case util.AuthNone: if c.auth.ReadNone(res) { return c.Do(req) } return nil, errors.New("user/pass not provided") - case tcp.AuthUnknown: + case util.AuthUnknown: if c.auth.Read(res) { return c.Do(req) } @@ -161,7 +167,7 @@ func (c *Client) Options() error { return nil } -func (c *Client) Describe() (medias []*core.Media, err error) { +func (c *Client) Describe() (medias []*Media, err error) { // 5.3 Back channel connection // https://www.onvif.org/specs/stream/ONVIF-Streaming-Spec.pdf req := &util.Request{ @@ -201,7 +207,7 @@ func (c *Client) Describe() (medias []*core.Media, err error) { return } if c.Media != "" { - clone := make([]*core.Media, 0, len(medias)) + clone := make([]*Media, 0, len(medias)) for _, media := range medias { if strings.Contains(c.Media, media.Kind) { clone = append(clone, media) @@ -213,7 +219,7 @@ func (c *Client) Describe() (medias []*core.Media, err error) { return } -func (c *Client) Announce(medias []*core.Media) (err error) { +func (c *Client) Announce(medias []*Media) (err error) { req := &util.Request{ Method: MethodAnnounce, URL: c.URL, @@ -222,7 +228,7 @@ func (c *Client) Announce(medias []*core.Media) (err error) { }, } - req.Body, err = core.MarshalSDP(c.SessionName, medias) + req.Body, err = MarshalSDP(c.SessionName, medias) if err != nil { return err } @@ -232,7 +238,7 @@ func (c *Client) Announce(medias []*core.Media) (err error) { return } -func (c *Client) SetupMedia(media *core.Media, index int) (byte, error) { +func (c *Client) SetupMedia(media *Media, index int) (byte, error) { var transport string transport = fmt.Sprintf( // i - RTP (data channel) @@ -308,7 +314,7 @@ func (c *Client) SetupMedia(media *core.Media, index int) (byte, error) { } } - channel := core.Between(transport, "interleaved=", "-") + channel := Between(transport, "interleaved=", "-") i, err := strconv.Atoi(channel) if err != nil { return 0, err diff --git a/plugin/rtsp/pkg/codec.go b/plugin/rtsp/pkg/codec.go new file mode 100644 index 0000000..3e3e2e2 --- /dev/null +++ b/plugin/rtsp/pkg/codec.go @@ -0,0 +1,302 @@ +package rtsp + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "github.com/pion/sdp/v3" + "strconv" + "strings" + "unicode" +) + +const ( + DirectionRecvonly = "recvonly" + DirectionSendonly = "sendonly" + DirectionSendRecv = "sendrecv" + + KindVideo = "video" + KindAudio = "audio" + + CodecH264 = "H264" // payloadType: 96 + CodecH265 = "H265" + CodecVP8 = "VP8" + CodecVP9 = "VP9" + CodecAV1 = "AV1" + CodecJPEG = "JPEG" // payloadType: 26 + CodecRAW = "RAW" + + CodecPCMU = "PCMU" // payloadType: 0 + CodecPCMA = "PCMA" // payloadType: 8 + CodecAAC = "MPEG4-GENERIC" + CodecOpus = "OPUS" // payloadType: 111 + CodecG722 = "G722" + CodecMP3 = "MPA" // payload: 14, aka MPEG-1 Layer III + CodecPCM = "L16" // Linear PCM (big endian) + + CodecPCML = "PCML" // Linear PCM (little endian) + + CodecELD = "ELD" // AAC-ELD + CodecFLAC = "FLAC" + + CodecAll = "ALL" + CodecAny = "ANY" +) + +const PayloadTypeRAW byte = 255 + +func Between(s, sub1, sub2 string) string { + i := strings.Index(s, sub1) + if i < 0 { + return "" + } + s = s[i+len(sub1):] + + if i = strings.Index(s, sub2); i >= 0 { + return s[:i] + } + + return s +} + +func Atoi(s string) (i int) { + if s != "" { + i, _ = strconv.Atoi(s) + } + return +} + +type Codec struct { + Name string // H264, PCMU, PCMA, opus... + ClockRate uint32 // 90000, 8000, 16000... + Channels uint16 // 0, 1, 2 + FmtpLine string + PayloadType uint8 +} + +// MarshalJSON - return FFprobe compatible output +func (c *Codec) MarshalJSON() ([]byte, error) { + info := map[string]any{} + if name := FFmpegCodecName(c.Name); name != "" { + info["codec_name"] = name + info["codec_type"] = c.Kind() + } + if c.Name == CodecH264 { + profile, level := DecodeH264(c.FmtpLine) + if profile != "" { + info["profile"] = profile + info["level"] = level + } + } + if c.ClockRate != 0 && c.ClockRate != 90000 { + info["sample_rate"] = c.ClockRate + } + if c.Channels > 0 { + info["channels"] = c.Channels + } + return json.Marshal(info) +} + +func FFmpegCodecName(name string) string { + switch name { + case CodecH264: + return "h264" + case CodecH265: + return "hevc" + case CodecJPEG: + return "mjpeg" + case CodecRAW: + return "rawvideo" + case CodecPCMA: + return "pcm_alaw" + case CodecPCMU: + return "pcm_mulaw" + case CodecPCM: + return "pcm_s16be" + case CodecPCML: + return "pcm_s16le" + case CodecAAC: + return "aac" + case CodecOpus: + return "opus" + case CodecVP8: + return "vp8" + case CodecVP9: + return "vp9" + case CodecAV1: + return "av1" + case CodecELD: + return "aac/eld" + case CodecFLAC: + return "flac" + case CodecMP3: + return "mp3" + } + return name +} + +func (c *Codec) String() (s string) { + s = c.Name + if c.ClockRate != 0 && c.ClockRate != 90000 { + s += fmt.Sprintf("/%d", c.ClockRate) + } + if c.Channels > 0 { + s += fmt.Sprintf("/%d", c.Channels) + } + return +} + +func (c *Codec) IsRTP() bool { + return c.PayloadType != PayloadTypeRAW +} + +func (c *Codec) IsVideo() bool { + return c.Kind() == KindVideo +} + +func (c *Codec) IsAudio() bool { + return c.Kind() == KindAudio +} + +func (c *Codec) Kind() string { + return GetKind(c.Name) +} + +func (c *Codec) PrintName() string { + switch c.Name { + case CodecAAC: + return "AAC" + case CodecPCM: + return "S16B" + case CodecPCML: + return "S16L" + } + return c.Name +} + +func (c *Codec) Clone() *Codec { + clone := *c + return &clone +} + +func (c *Codec) Match(remote *Codec) bool { + switch remote.Name { + case CodecAll, CodecAny: + return true + } + + return c.Name == remote.Name && + (c.ClockRate == remote.ClockRate || remote.ClockRate == 0) && + (c.Channels == remote.Channels || remote.Channels == 0) +} + +func UnmarshalCodec(md *sdp.MediaDescription, payloadType string) *Codec { + c := &Codec{PayloadType: byte(Atoi(payloadType))} + + for _, attr := range md.Attributes { + switch { + case c.Name == "" && attr.Key == "rtpmap" && strings.HasPrefix(attr.Value, payloadType): + i := strings.IndexByte(attr.Value, ' ') + ss := strings.Split(attr.Value[i+1:], "/") + + c.Name = strings.ToUpper(ss[0]) + // fix tailing space: `a=rtpmap:96 H264/90000 ` + c.ClockRate = uint32(Atoi(strings.TrimRightFunc(ss[1], unicode.IsSpace))) + + if len(ss) == 3 && ss[2] == "2" { + c.Channels = 2 + } + case c.FmtpLine == "" && attr.Key == "fmtp" && strings.HasPrefix(attr.Value, payloadType): + if i := strings.IndexByte(attr.Value, ' '); i > 0 { + c.FmtpLine = attr.Value[i+1:] + } + } + } + + if c.Name == "" { + // https://en.wikipedia.org/wiki/RTP_payload_formats + switch payloadType { + case "0": + c.Name = CodecPCMU + c.ClockRate = 8000 + case "8": + c.Name = CodecPCMA + c.ClockRate = 8000 + case "10": + c.Name = CodecPCM + c.ClockRate = 44100 + c.Channels = 2 + case "11": + c.Name = CodecPCM + c.ClockRate = 44100 + case "14": + c.Name = CodecMP3 + c.ClockRate = 90000 // it's not real sample rate + case "26": + c.Name = CodecJPEG + c.ClockRate = 90000 + case "96", "97", "98": + if len(md.Bandwidth) == 0 { + c.Name = payloadType + break + } + + // FFmpeg + RTSP + pcm_s16le = doesn't pass info about codec name and params + // so try to guess the codec based on bitrate + // https://github.com/AlexxIT/go2rtc/issues/523 + switch md.Bandwidth[0].Bandwidth { + case 128: + c.ClockRate = 8000 + case 256: + c.ClockRate = 16000 + case 384: + c.ClockRate = 24000 + case 512: + c.ClockRate = 32000 + case 705: + c.ClockRate = 44100 + case 768: + c.ClockRate = 48000 + case 1411: + // default Windows DShow + c.ClockRate = 44100 + c.Channels = 2 + case 1536: + // default Linux ALSA + c.ClockRate = 48000 + c.Channels = 2 + default: + c.Name = payloadType + break + } + + c.Name = CodecPCML + default: + c.Name = payloadType + } + } + + return c +} + +func DecodeH264(fmtp string) (profile string, level byte) { + if ps := Between(fmtp, "sprop-parameter-sets=", ","); ps != "" { + if sps, _ := base64.StdEncoding.DecodeString(ps); len(sps) >= 4 { + switch sps[1] { + case 0x42: + profile = "Baseline" + case 0x4D: + profile = "Main" + case 0x58: + profile = "Extended" + case 0x64: + profile = "High" + default: + profile = fmt.Sprintf("0x%02X", sps[1]) + } + + level = sps[3] + } + } + return +} diff --git a/plugin/rtsp/pkg/media.go b/plugin/rtsp/pkg/media.go new file mode 100644 index 0000000..80de148 --- /dev/null +++ b/plugin/rtsp/pkg/media.go @@ -0,0 +1,202 @@ +package rtsp + +import ( + "encoding/json" + "fmt" + "github.com/pion/sdp/v3" + "strings" +) + +// Media take best from: +// - deepch/vdk/format/rtsp/sdp.Media +// - pion/sdp.MediaDescription +type Media struct { + Kind string `json:"kind,omitempty"` // video or audio + Direction string `json:"direction,omitempty"` // sendonly, recvonly + Codecs []*Codec `json:"codecs,omitempty"` + + ID string `json:"id,omitempty"` // MID for WebRTC, Control for RTSP +} + +func (m *Media) String() string { + s := fmt.Sprintf("%s, %s", m.Kind, m.Direction) + for _, codec := range m.Codecs { + name := codec.String() + + if strings.Contains(s, name) { + continue + } + + s += ", " + name + } + return s +} + +func (m *Media) MarshalJSON() ([]byte, error) { + return json.Marshal(m.String()) +} + +func (m *Media) Clone() *Media { + clone := *m + clone.Codecs = make([]*Codec, len(m.Codecs)) + for i, codec := range m.Codecs { + clone.Codecs[i] = codec.Clone() + } + return &clone +} + +func (m *Media) MatchMedia(remote *Media) (codec, remoteCodec *Codec) { + // check same kind and opposite dirrection + if m.Kind != remote.Kind || + m.Direction == DirectionSendonly && remote.Direction != DirectionRecvonly || + m.Direction == DirectionRecvonly && remote.Direction != DirectionSendonly { + return nil, nil + } + + for _, codec = range m.Codecs { + for _, remoteCodec = range remote.Codecs { + if codec.Match(remoteCodec) { + return + } + } + } + + return nil, nil +} + +func (m *Media) MatchCodec(remote *Codec) *Codec { + for _, codec := range m.Codecs { + if codec.Match(remote) { + return codec + } + } + return nil +} + +func (m *Media) MatchAll() bool { + for _, codec := range m.Codecs { + if codec.Name == CodecAll { + return true + } + } + return false +} + +func (m *Media) Equal(media *Media) bool { + if media.ID != "" { + return m.ID == media.ID + } + return m.String() == media.String() +} + +func GetKind(name string) string { + switch name { + case CodecH264, CodecH265, CodecVP8, CodecVP9, CodecAV1, CodecJPEG, CodecRAW: + return KindVideo + case CodecPCMU, CodecPCMA, CodecAAC, CodecOpus, CodecG722, CodecMP3, CodecPCM, CodecPCML, CodecELD, CodecFLAC: + return KindAudio + } + return "" +} + +func MarshalSDP(name string, medias []*Media) ([]byte, error) { + sd := &sdp.SessionDescription{ + Origin: sdp.Origin{ + Username: "-", SessionID: 1, SessionVersion: 1, + NetworkType: "IN", AddressType: "IP4", UnicastAddress: "0.0.0.0", + }, + SessionName: sdp.SessionName(name), + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "IN", AddressType: "IP4", Address: &sdp.Address{ + Address: "0.0.0.0", + }, + }, + TimeDescriptions: []sdp.TimeDescription{ + {Timing: sdp.Timing{}}, + }, + } + + for _, media := range medias { + if media.Codecs == nil { + continue + } + + codec := media.Codecs[0] + + name := codec.Name + if name == CodecELD { + name = CodecAAC + } + + md := &sdp.MediaDescription{ + MediaName: sdp.MediaName{ + Media: media.Kind, + Protos: []string{"RTP", "AVP"}, + }, + } + md.WithCodec(codec.PayloadType, name, codec.ClockRate, codec.Channels, codec.FmtpLine) + + if media.ID != "" { + md.WithValueAttribute("control", media.ID) + } + + sd.MediaDescriptions = append(sd.MediaDescriptions, md) + } + + return sd.Marshal() +} + +func UnmarshalMedia(md *sdp.MediaDescription) *Media { + m := &Media{ + Kind: md.MediaName.Media, + } + + for _, attr := range md.Attributes { + switch attr.Key { + case DirectionSendonly, DirectionRecvonly, DirectionSendRecv: + m.Direction = attr.Key + case "control", "mid": + m.ID = attr.Value + } + } + + for _, format := range md.MediaName.Formats { + m.Codecs = append(m.Codecs, UnmarshalCodec(md, format)) + } + + return m +} + +func ParseQuery(query map[string][]string) (medias []*Media) { + // set media candidates from query list + for key, values := range query { + switch key { + case KindVideo, KindAudio: + for _, value := range values { + media := &Media{Kind: key, Direction: DirectionSendonly} + + for _, name := range strings.Split(value, ",") { + name = strings.ToUpper(name) + + // check aliases + switch name { + case "", "COPY": + name = CodecAny + case "MJPEG": + name = CodecJPEG + case "AAC": + name = CodecAAC + case "MP3": + name = CodecMP3 + } + + media.Codecs = append(media.Codecs, &Codec{Name: name}) + } + + medias = append(medias, media) + } + } + } + + return +} diff --git a/plugin/rtsp/pkg/sdp.go b/plugin/rtsp/pkg/sdp.go index b53be72..7efe7c2 100644 --- a/plugin/rtsp/pkg/sdp.go +++ b/plugin/rtsp/pkg/sdp.go @@ -2,7 +2,6 @@ package rtsp import ( "bytes" - "github.com/AlexxIT/go2rtc/pkg/core" "io" "net/url" "regexp" @@ -24,7 +23,7 @@ o=- 0 0 IN IP4 0.0.0.0 s=- t=0 0` -func UnmarshalSDP(rawSDP []byte) ([]*core.Media, error) { +func UnmarshalSDP(rawSDP []byte) ([]*Media, error) { sd := &sdp.SessionDescription{} if err := sd.Unmarshal(rawSDP); err != nil { // fix multiple `s=` https://github.com/AlexxIT/WebRTC/issues/417 @@ -53,21 +52,21 @@ func UnmarshalSDP(rawSDP []byte) ([]*core.Media, error) { } } - var medias []*core.Media + var medias []*Media for _, md := range sd.MediaDescriptions { - media := core.UnmarshalMedia(md) + media := 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 == "" { + if codec.Name == CodecH264 && codec.FmtpLine == "" { codec.FmtpLine = findFmtpLine(codec.PayloadType, sd.MediaDescriptions) } } if media.Direction == "" { - media.Direction = core.DirectionRecvonly + media.Direction = DirectionRecvonly } medias = append(medias, media) @@ -79,7 +78,7 @@ func UnmarshalSDP(rawSDP []byte) ([]*core.Media, error) { func findFmtpLine(payloadType uint8, descriptions []*sdp.MediaDescription) string { s := strconv.Itoa(int(payloadType)) for _, md := range descriptions { - codec := core.UnmarshalCodec(md, s) + codec := UnmarshalCodec(md, s) if codec.FmtpLine != "" { return codec.FmtpLine } diff --git a/plugin/rtsp/pkg/transceiver.go b/plugin/rtsp/pkg/transceiver.go index 210e10e..b5d3173 100644 --- a/plugin/rtsp/pkg/transceiver.go +++ b/plugin/rtsp/pkg/transceiver.go @@ -2,7 +2,6 @@ package rtsp import ( "fmt" - "github.com/AlexxIT/go2rtc/pkg/core" "github.com/pion/rtcp" "github.com/pion/rtp" "github.com/pion/webrtc/v4" @@ -35,17 +34,17 @@ func (ns *Stream) Close() error { return nil } -func (s *Sender) GetMedia() (medias []*core.Media, err error) { +func (s *Sender) GetMedia() (medias []*Media, err error) { if s.SubAudio && s.Publisher.PubAudio && s.Publisher.HasAudioTrack() { audioTrack := s.Publisher.GetAudioTrack(reflect.TypeOf((*mrtp.RTPAudio)(nil))) if err = audioTrack.WaitReady(); err != nil { return } parameter := audioTrack.ICodecCtx.(mrtp.IRTPCtx).GetRTPCodecParameter() - media := &core.Media{ + media := &Media{ Kind: "audio", - Direction: core.DirectionRecvonly, - Codecs: []*core.Codec{{ + Direction: DirectionRecvonly, + Codecs: []*Codec{{ Name: parameter.MimeType[6:], ClockRate: parameter.ClockRate, Channels: parameter.Channels, @@ -64,17 +63,17 @@ func (s *Sender) GetMedia() (medias []*core.Media, err error) { return } parameter := videoTrack.ICodecCtx.(mrtp.IRTPCtx).GetRTPCodecParameter() - c := core.Codec{ + c := Codec{ Name: parameter.MimeType[6:], ClockRate: parameter.ClockRate, Channels: parameter.Channels, FmtpLine: parameter.SDPFmtpLine, PayloadType: uint8(parameter.PayloadType), } - media := &core.Media{ + media := &Media{ Kind: "video", - Direction: core.DirectionRecvonly, - Codecs: []*core.Codec{&c}, + Direction: DirectionRecvonly, + Codecs: []*Codec{&c}, ID: fmt.Sprintf("trackID=%d", len(medias)), } s.VideoChannelID = len(medias) << 1 @@ -120,7 +119,7 @@ func (s *Sender) Send() (err error) { return s.send() } -func (r *Receiver) SetMedia(medias []*core.Media) (err error) { +func (r *Receiver) SetMedia(medias []*Media) (err error) { r.AudioChannelID = -1 r.VideoChannelID = -1 for i, media := range medias { @@ -156,9 +155,9 @@ func (r *Receiver) SetMedia(medias []*core.Media) (err error) { } func (r *Receiver) Receive() (err error) { audioFrame, videoFrame := &mrtp.RTPAudio{}, &mrtp.RTPVideo{} - audioFrame.ScalableMemoryAllocator = r.MemoryAllocator + audioFrame.SetAllocator(r.MemoryAllocator) audioFrame.RTPCodecParameters = r.AudioCodecParameters - videoFrame.ScalableMemoryAllocator = r.MemoryAllocator + videoFrame.SetAllocator(r.MemoryAllocator) videoFrame.RTPCodecParameters = r.VideoCodecParameters var channelID byte var buf []byte @@ -189,7 +188,7 @@ func (r *Receiver) Receive() (err error) { audioFrame.AddRecycleBytes(buf) audioFrame.Packets = []*rtp.Packet{packet} audioFrame.RTPCodecParameters = r.VideoCodecParameters - audioFrame.ScalableMemoryAllocator = r.MemoryAllocator + audioFrame.SetAllocator(r.MemoryAllocator) } case r.VideoChannelID: if !r.PubVideo { @@ -210,7 +209,7 @@ func (r *Receiver) Receive() (err error) { videoFrame.AddRecycleBytes(buf) videoFrame.Packets = []*rtp.Packet{packet} videoFrame.RTPCodecParameters = r.VideoCodecParameters - videoFrame.ScalableMemoryAllocator = r.MemoryAllocator + videoFrame.SetAllocator(r.MemoryAllocator) } default: diff --git a/plugin/stress/api.go b/plugin/stress/api.go index 38d725e..ee22e5e 100644 --- a/plugin/stress/api.go +++ b/plugin/stress/api.go @@ -13,67 +13,60 @@ import ( "m7s.live/m7s/v5/plugin/stress/pb" ) -func (r *StressPlugin) PushRTMP(ctx context.Context, req *pb.PushRequest) (res *gpb.SuccessResponse, err error) { - l := r.pushers.Length - for i := range req.PushCount { - pusher, err := r.Push(req.StreamPath, fmt.Sprintf("rtmp://%s/stress/%d", req.RemoteHost, int(i)+l)) - if err != nil { - return nil, err +func (r *StressPlugin) pull(count int, format, url string, newFunc func() m7s.PullHandler) error { + if i := r.pullers.Length; count > i { + for j := i; j < count; j++ { + puller, err := r.Pull(fmt.Sprintf("stress/%d", j), fmt.Sprintf(format, url)) + if err != nil { + return err + } + go r.startPull(puller, newFunc()) + } + } else if count < i { + for j := i; j > count; j-- { + r.pullers.Items[j-1].Stop(pkg.ErrStopFromAPI) + r.pullers.Remove(r.pullers.Items[j-1]) } - go r.startPush(pusher, &rtmp.Client{}) } - return &gpb.SuccessResponse{}, nil + return nil +} + +func (r *StressPlugin) push(count int, streamPath, format, remoteHost string, newFunc func() m7s.PushHandler) (err error) { + if i := r.pushers.Length; count > i { + for j := i; j < count; j++ { + pusher, err := r.Push(streamPath, fmt.Sprintf(format, remoteHost, j)) + if err != nil { + return err + } + go r.startPush(pusher, newFunc()) + } + } else if count < i { + for j := i; j > count; j-- { + r.pushers.Items[j-1].Stop(pkg.ErrStopFromAPI) + r.pushers.Remove(r.pushers.Items[j-1]) + } + } + return nil +} + +func (r *StressPlugin) PushRTMP(ctx context.Context, req *pb.PushRequest) (res *gpb.SuccessResponse, err error) { + return &gpb.SuccessResponse{}, r.push(int(req.PushCount), req.StreamPath, "rtmp://%s/stress/%d", req.RemoteHost, rtmp.NewPushHandler) } func (r *StressPlugin) PushRTSP(ctx context.Context, req *pb.PushRequest) (res *gpb.SuccessResponse, err error) { - l := r.pushers.Length - for i := range req.PushCount { - pusher, err := r.Push(req.StreamPath, fmt.Sprintf("rtsp://%s/stress/%d", req.RemoteHost, int(i)+l)) - if err != nil { - return nil, err - } - go r.startPush(pusher, &rtsp.Client{}) - } - return &gpb.SuccessResponse{}, nil + return &gpb.SuccessResponse{}, r.push(int(req.PushCount), req.StreamPath, "rtsp://%s/stress/%d", req.RemoteHost, rtsp.NewPushHandler) } func (r *StressPlugin) PullRTMP(ctx context.Context, req *pb.PullRequest) (res *gpb.SuccessResponse, err error) { - i := r.pullers.Length - for range req.PullCount { - puller, err := r.Pull(fmt.Sprintf("stress/%d", i), fmt.Sprintf("rtmp://%s", req.RemoteURL)) - if err != nil { - return nil, err - } - go r.startPull(puller, &rtmp.Client{}) - i++ - } - return &gpb.SuccessResponse{}, nil + return &gpb.SuccessResponse{}, r.pull(int(req.PullCount), "rtmp://%s", req.RemoteURL, rtmp.NewPullHandler) } func (r *StressPlugin) PullRTSP(ctx context.Context, req *pb.PullRequest) (res *gpb.SuccessResponse, err error) { - i := r.pullers.Length - for range req.PullCount { - puller, err := r.Pull(fmt.Sprintf("stress/%d", i), fmt.Sprintf("rtsp://%s", req.RemoteURL)) - if err != nil { - return nil, err - } - go r.startPull(puller, &rtsp.Client{}) - i++ - } - return &gpb.SuccessResponse{}, nil + return &gpb.SuccessResponse{}, r.pull(int(req.PullCount), "rtsp://%s", req.RemoteURL, rtsp.NewPullHandler) } func (r *StressPlugin) PullHDL(ctx context.Context, req *pb.PullRequest) (res *gpb.SuccessResponse, err error) { - i := r.pullers.Length - for range req.PullCount { - puller, err := r.Pull(fmt.Sprintf("stress/%d", i), fmt.Sprintf("http://%s", req.RemoteURL)) - if err != nil { - return nil, err - } - go r.startPull(puller, hdl.NewHDLPuller()) - i++ - } - return &gpb.SuccessResponse{}, nil + return &gpb.SuccessResponse{}, r.pull(int(req.PullCount), "http://%s", req.RemoteURL, hdl.NewPullHandler) } func (r *StressPlugin) startPush(pusher *m7s.Pusher, handler m7s.PushHandler) { diff --git a/plugin/stress/pb/stress.pb.go b/plugin/stress/pb/stress.pb.go new file mode 100644 index 0000000..3e26c51 --- /dev/null +++ b/plugin/stress/pb/stress.pb.go @@ -0,0 +1,383 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.19.1 +// source: stress.proto + +package pb + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + pb "m7s.live/m7s/v5/pb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CountResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PushCount uint32 `protobuf:"varint,1,opt,name=pushCount,proto3" json:"pushCount,omitempty"` + PullCount uint32 `protobuf:"varint,2,opt,name=pullCount,proto3" json:"pullCount,omitempty"` +} + +func (x *CountResponse) Reset() { + *x = CountResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_stress_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CountResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CountResponse) ProtoMessage() {} + +func (x *CountResponse) ProtoReflect() protoreflect.Message { + mi := &file_stress_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CountResponse.ProtoReflect.Descriptor instead. +func (*CountResponse) Descriptor() ([]byte, []int) { + return file_stress_proto_rawDescGZIP(), []int{0} +} + +func (x *CountResponse) GetPushCount() uint32 { + if x != nil { + return x.PushCount + } + return 0 +} + +func (x *CountResponse) GetPullCount() uint32 { + if x != nil { + return x.PullCount + } + return 0 +} + +type PushRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreamPath string `protobuf:"bytes,1,opt,name=streamPath,proto3" json:"streamPath,omitempty"` + RemoteHost string `protobuf:"bytes,2,opt,name=remoteHost,proto3" json:"remoteHost,omitempty"` + PushCount int32 `protobuf:"varint,3,opt,name=pushCount,proto3" json:"pushCount,omitempty"` +} + +func (x *PushRequest) Reset() { + *x = PushRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_stress_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushRequest) ProtoMessage() {} + +func (x *PushRequest) ProtoReflect() protoreflect.Message { + mi := &file_stress_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushRequest.ProtoReflect.Descriptor instead. +func (*PushRequest) Descriptor() ([]byte, []int) { + return file_stress_proto_rawDescGZIP(), []int{1} +} + +func (x *PushRequest) GetStreamPath() string { + if x != nil { + return x.StreamPath + } + return "" +} + +func (x *PushRequest) GetRemoteHost() string { + if x != nil { + return x.RemoteHost + } + return "" +} + +func (x *PushRequest) GetPushCount() int32 { + if x != nil { + return x.PushCount + } + return 0 +} + +type PullRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RemoteURL string `protobuf:"bytes,1,opt,name=remoteURL,proto3" json:"remoteURL,omitempty"` + PullCount int32 `protobuf:"varint,2,opt,name=pullCount,proto3" json:"pullCount,omitempty"` +} + +func (x *PullRequest) Reset() { + *x = PullRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_stress_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PullRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PullRequest) ProtoMessage() {} + +func (x *PullRequest) ProtoReflect() protoreflect.Message { + mi := &file_stress_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PullRequest.ProtoReflect.Descriptor instead. +func (*PullRequest) Descriptor() ([]byte, []int) { + return file_stress_proto_rawDescGZIP(), []int{2} +} + +func (x *PullRequest) GetRemoteURL() string { + if x != nil { + return x.RemoteURL + } + return "" +} + +func (x *PullRequest) GetPullCount() int32 { + if x != nil { + return x.PullCount + } + return 0 +} + +var File_stress_proto protoreflect.FileDescriptor + +var file_stress_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, + 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x0c, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x4b, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, + 0x0a, 0x09, 0x70, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x70, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x6b, 0x0a, 0x0b, + 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x0a, 0x0a, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x70, + 0x75, 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, + 0x70, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x49, 0x0a, 0x0b, 0x50, 0x75, 0x6c, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x6c, 0x6c, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x70, 0x75, 0x6c, 0x6c, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x32, 0x84, 0x06, 0x0a, 0x03, 0x61, 0x70, 0x69, 0x12, 0x63, 0x0a, 0x08, + 0x50, 0x75, 0x73, 0x68, 0x52, 0x54, 0x4d, 0x50, 0x12, 0x13, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x73, + 0x73, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, + 0x6d, 0x37, 0x73, 0x2e, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x22, 0x21, 0x2f, 0x73, 0x74, + 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2f, 0x72, 0x74, + 0x6d, 0x70, 0x2f, 0x7b, 0x70, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x7d, 0x3a, 0x01, + 0x2a, 0x12, 0x63, 0x0a, 0x08, 0x50, 0x75, 0x73, 0x68, 0x52, 0x54, 0x53, 0x50, 0x12, 0x13, 0x2e, + 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6d, 0x37, 0x73, 0x2e, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, + 0x22, 0x21, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x75, + 0x73, 0x68, 0x2f, 0x72, 0x74, 0x73, 0x70, 0x2f, 0x7b, 0x70, 0x75, 0x73, 0x68, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x7d, 0x3a, 0x01, 0x2a, 0x12, 0x63, 0x0a, 0x08, 0x50, 0x75, 0x6c, 0x6c, 0x52, 0x54, + 0x4d, 0x50, 0x12, 0x13, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6d, 0x37, 0x73, 0x2e, 0x53, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x22, 0x21, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x70, 0x75, 0x6c, 0x6c, 0x2f, 0x72, 0x74, 0x6d, 0x70, 0x2f, 0x7b, 0x70, 0x75, + 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x7d, 0x3a, 0x01, 0x2a, 0x12, 0x63, 0x0a, 0x08, 0x50, + 0x75, 0x6c, 0x6c, 0x52, 0x54, 0x53, 0x50, 0x12, 0x13, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, + 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6d, + 0x37, 0x73, 0x2e, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x22, 0x21, 0x2f, 0x73, 0x74, 0x72, + 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x75, 0x6c, 0x6c, 0x2f, 0x72, 0x74, 0x73, + 0x70, 0x2f, 0x7b, 0x70, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x7d, 0x3a, 0x01, 0x2a, + 0x12, 0x61, 0x0a, 0x07, 0x50, 0x75, 0x6c, 0x6c, 0x48, 0x44, 0x4c, 0x12, 0x13, 0x2e, 0x73, 0x74, + 0x72, 0x65, 0x73, 0x73, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x14, 0x2e, 0x6d, 0x37, 0x73, 0x2e, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x20, + 0x2f, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x75, 0x6c, 0x6c, + 0x2f, 0x68, 0x64, 0x6c, 0x2f, 0x7b, 0x70, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x7d, + 0x3a, 0x01, 0x2a, 0x12, 0x54, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, + 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x57, 0x0a, 0x08, 0x53, 0x74, 0x6f, + 0x70, 0x50, 0x75, 0x73, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, + 0x6d, 0x37, 0x73, 0x2e, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x15, 0x2f, 0x73, 0x74, + 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x2f, 0x70, 0x75, + 0x73, 0x68, 0x12, 0x57, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x50, 0x75, 0x6c, 0x6c, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x6d, 0x37, 0x73, 0x2e, 0x53, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x17, 0x22, 0x15, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x2f, 0x70, 0x75, 0x6c, 0x6c, 0x42, 0x22, 0x5a, 0x20, 0x6d, + 0x37, 0x73, 0x2e, 0x6c, 0x69, 0x76, 0x65, 0x2f, 0x6d, 0x37, 0x73, 0x2f, 0x76, 0x35, 0x2f, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x70, 0x62, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_stress_proto_rawDescOnce sync.Once + file_stress_proto_rawDescData = file_stress_proto_rawDesc +) + +func file_stress_proto_rawDescGZIP() []byte { + file_stress_proto_rawDescOnce.Do(func() { + file_stress_proto_rawDescData = protoimpl.X.CompressGZIP(file_stress_proto_rawDescData) + }) + return file_stress_proto_rawDescData +} + +var file_stress_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_stress_proto_goTypes = []interface{}{ + (*CountResponse)(nil), // 0: stress.CountResponse + (*PushRequest)(nil), // 1: stress.PushRequest + (*PullRequest)(nil), // 2: stress.PullRequest + (*emptypb.Empty)(nil), // 3: google.protobuf.Empty + (*pb.SuccessResponse)(nil), // 4: m7s.SuccessResponse +} +var file_stress_proto_depIdxs = []int32{ + 1, // 0: stress.api.PushRTMP:input_type -> stress.PushRequest + 1, // 1: stress.api.PushRTSP:input_type -> stress.PushRequest + 2, // 2: stress.api.PullRTMP:input_type -> stress.PullRequest + 2, // 3: stress.api.PullRTSP:input_type -> stress.PullRequest + 2, // 4: stress.api.PullHDL:input_type -> stress.PullRequest + 3, // 5: stress.api.GetCount:input_type -> google.protobuf.Empty + 3, // 6: stress.api.StopPush:input_type -> google.protobuf.Empty + 3, // 7: stress.api.StopPull:input_type -> google.protobuf.Empty + 4, // 8: stress.api.PushRTMP:output_type -> m7s.SuccessResponse + 4, // 9: stress.api.PushRTSP:output_type -> m7s.SuccessResponse + 4, // 10: stress.api.PullRTMP:output_type -> m7s.SuccessResponse + 4, // 11: stress.api.PullRTSP:output_type -> m7s.SuccessResponse + 4, // 12: stress.api.PullHDL:output_type -> m7s.SuccessResponse + 0, // 13: stress.api.GetCount:output_type -> stress.CountResponse + 4, // 14: stress.api.StopPush:output_type -> m7s.SuccessResponse + 4, // 15: stress.api.StopPull:output_type -> m7s.SuccessResponse + 8, // [8:16] is the sub-list for method output_type + 0, // [0:8] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_stress_proto_init() } +func file_stress_proto_init() { + if File_stress_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_stress_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CountResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stress_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stress_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PullRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_stress_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_stress_proto_goTypes, + DependencyIndexes: file_stress_proto_depIdxs, + MessageInfos: file_stress_proto_msgTypes, + }.Build() + File_stress_proto = out.File + file_stress_proto_rawDesc = nil + file_stress_proto_goTypes = nil + file_stress_proto_depIdxs = nil +} diff --git a/plugin/stress/pb/stress.pb.gw.go b/plugin/stress/pb/stress.pb.gw.go new file mode 100644 index 0000000..4ca8fb1 --- /dev/null +++ b/plugin/stress/pb/stress.pb.gw.go @@ -0,0 +1,849 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: stress.proto + +/* +Package pb is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package pb + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/emptypb" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_Api_PushRTMP_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PushRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pushCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pushCount") + } + + protoReq.PushCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pushCount", err) + } + + msg, err := client.PushRTMP(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_PushRTMP_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PushRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pushCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pushCount") + } + + protoReq.PushCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pushCount", err) + } + + msg, err := server.PushRTMP(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Api_PushRTSP_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PushRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pushCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pushCount") + } + + protoReq.PushCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pushCount", err) + } + + msg, err := client.PushRTSP(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_PushRTSP_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PushRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pushCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pushCount") + } + + protoReq.PushCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pushCount", err) + } + + msg, err := server.PushRTSP(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Api_PullRTMP_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PullRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pullCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pullCount") + } + + protoReq.PullCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pullCount", err) + } + + msg, err := client.PullRTMP(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_PullRTMP_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PullRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pullCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pullCount") + } + + protoReq.PullCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pullCount", err) + } + + msg, err := server.PullRTMP(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Api_PullRTSP_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PullRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pullCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pullCount") + } + + protoReq.PullCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pullCount", err) + } + + msg, err := client.PullRTSP(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_PullRTSP_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PullRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pullCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pullCount") + } + + protoReq.PullCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pullCount", err) + } + + msg, err := server.PullRTSP(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Api_PullHDL_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PullRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pullCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pullCount") + } + + protoReq.PullCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pullCount", err) + } + + msg, err := client.PullHDL(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_PullHDL_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq PullRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["pullCount"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pullCount") + } + + protoReq.PullCount, err = runtime.Int32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pullCount", err) + } + + msg, err := server.PullHDL(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Api_GetCount_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq emptypb.Empty + var metadata runtime.ServerMetadata + + msg, err := client.GetCount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_GetCount_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq emptypb.Empty + var metadata runtime.ServerMetadata + + msg, err := server.GetCount(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Api_StopPush_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq emptypb.Empty + var metadata runtime.ServerMetadata + + msg, err := client.StopPush(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_StopPush_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq emptypb.Empty + var metadata runtime.ServerMetadata + + msg, err := server.StopPush(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Api_StopPull_0(ctx context.Context, marshaler runtime.Marshaler, client ApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq emptypb.Empty + var metadata runtime.ServerMetadata + + msg, err := client.StopPull(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Api_StopPull_0(ctx context.Context, marshaler runtime.Marshaler, server ApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq emptypb.Empty + var metadata runtime.ServerMetadata + + msg, err := server.StopPull(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterApiHandlerServer registers the http handlers for service Api to "mux". +// UnaryRPC :call ApiServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterApiHandlerFromEndpoint instead. +func RegisterApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ApiServer) error { + + mux.Handle("POST", pattern_Api_PushRTMP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/PushRTMP", runtime.WithHTTPPathPattern("/stress/api/push/rtmp/{pushCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_PushRTMP_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PushRTMP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PushRTSP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/PushRTSP", runtime.WithHTTPPathPattern("/stress/api/push/rtsp/{pushCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_PushRTSP_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PushRTSP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PullRTMP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/PullRTMP", runtime.WithHTTPPathPattern("/stress/api/pull/rtmp/{pullCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_PullRTMP_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PullRTMP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PullRTSP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/PullRTSP", runtime.WithHTTPPathPattern("/stress/api/pull/rtsp/{pullCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_PullRTSP_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PullRTSP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PullHDL_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/PullHDL", runtime.WithHTTPPathPattern("/stress/api/pull/hdl/{pullCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_PullHDL_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PullHDL_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Api_GetCount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/GetCount", runtime.WithHTTPPathPattern("/stress/api/count")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_GetCount_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_GetCount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_StopPush_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/StopPush", runtime.WithHTTPPathPattern("/stress/api/stop/push")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_StopPush_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_StopPush_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_StopPull_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/stress.Api/StopPull", runtime.WithHTTPPathPattern("/stress/api/stop/pull")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Api_StopPull_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_StopPull_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterApiHandlerFromEndpoint is same as RegisterApiHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterApiHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.DialContext(ctx, endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterApiHandler(ctx, mux, conn) +} + +// RegisterApiHandler registers the http handlers for service Api to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterApiHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterApiHandlerClient(ctx, mux, NewApiClient(conn)) +} + +// RegisterApiHandlerClient registers the http handlers for service Api +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ApiClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ApiClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "ApiClient" to call the correct interceptors. +func RegisterApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ApiClient) error { + + mux.Handle("POST", pattern_Api_PushRTMP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/PushRTMP", runtime.WithHTTPPathPattern("/stress/api/push/rtmp/{pushCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_PushRTMP_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PushRTMP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PushRTSP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/PushRTSP", runtime.WithHTTPPathPattern("/stress/api/push/rtsp/{pushCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_PushRTSP_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PushRTSP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PullRTMP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/PullRTMP", runtime.WithHTTPPathPattern("/stress/api/pull/rtmp/{pullCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_PullRTMP_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PullRTMP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PullRTSP_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/PullRTSP", runtime.WithHTTPPathPattern("/stress/api/pull/rtsp/{pullCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_PullRTSP_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PullRTSP_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_PullHDL_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/PullHDL", runtime.WithHTTPPathPattern("/stress/api/pull/hdl/{pullCount}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_PullHDL_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_PullHDL_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Api_GetCount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/GetCount", runtime.WithHTTPPathPattern("/stress/api/count")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_GetCount_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_GetCount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_StopPush_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/StopPush", runtime.WithHTTPPathPattern("/stress/api/stop/push")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_StopPush_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_StopPush_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Api_StopPull_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/stress.Api/StopPull", runtime.WithHTTPPathPattern("/stress/api/stop/pull")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Api_StopPull_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Api_StopPull_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Api_PushRTMP_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"stress", "api", "push", "rtmp", "pushCount"}, "")) + + pattern_Api_PushRTSP_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"stress", "api", "push", "rtsp", "pushCount"}, "")) + + pattern_Api_PullRTMP_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"stress", "api", "pull", "rtmp", "pullCount"}, "")) + + pattern_Api_PullRTSP_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"stress", "api", "pull", "rtsp", "pullCount"}, "")) + + pattern_Api_PullHDL_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"stress", "api", "pull", "hdl", "pullCount"}, "")) + + pattern_Api_GetCount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"stress", "api", "count"}, "")) + + pattern_Api_StopPush_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"stress", "api", "stop", "push"}, "")) + + pattern_Api_StopPull_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"stress", "api", "stop", "pull"}, "")) +) + +var ( + forward_Api_PushRTMP_0 = runtime.ForwardResponseMessage + + forward_Api_PushRTSP_0 = runtime.ForwardResponseMessage + + forward_Api_PullRTMP_0 = runtime.ForwardResponseMessage + + forward_Api_PullRTSP_0 = runtime.ForwardResponseMessage + + forward_Api_PullHDL_0 = runtime.ForwardResponseMessage + + forward_Api_GetCount_0 = runtime.ForwardResponseMessage + + forward_Api_StopPush_0 = runtime.ForwardResponseMessage + + forward_Api_StopPull_0 = runtime.ForwardResponseMessage +) diff --git a/plugin/stress/pb/stress_grpc.pb.go b/plugin/stress/pb/stress_grpc.pb.go new file mode 100644 index 0000000..9f4c6c4 --- /dev/null +++ b/plugin/stress/pb/stress_grpc.pb.go @@ -0,0 +1,359 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.19.1 +// source: stress.proto + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" + pb "m7s.live/m7s/v5/pb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ApiClient is the client API for Api service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ApiClient interface { + PushRTMP(ctx context.Context, in *PushRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) + PushRTSP(ctx context.Context, in *PushRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) + PullRTMP(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) + PullRTSP(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) + PullHDL(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) + GetCount(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CountResponse, error) + StopPush(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.SuccessResponse, error) + StopPull(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.SuccessResponse, error) +} + +type apiClient struct { + cc grpc.ClientConnInterface +} + +func NewApiClient(cc grpc.ClientConnInterface) ApiClient { + return &apiClient{cc} +} + +func (c *apiClient) PushRTMP(ctx context.Context, in *PushRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) { + out := new(pb.SuccessResponse) + err := c.cc.Invoke(ctx, "/stress.api/PushRTMP", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *apiClient) PushRTSP(ctx context.Context, in *PushRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) { + out := new(pb.SuccessResponse) + err := c.cc.Invoke(ctx, "/stress.api/PushRTSP", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *apiClient) PullRTMP(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) { + out := new(pb.SuccessResponse) + err := c.cc.Invoke(ctx, "/stress.api/PullRTMP", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *apiClient) PullRTSP(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) { + out := new(pb.SuccessResponse) + err := c.cc.Invoke(ctx, "/stress.api/PullRTSP", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *apiClient) PullHDL(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (*pb.SuccessResponse, error) { + out := new(pb.SuccessResponse) + err := c.cc.Invoke(ctx, "/stress.api/PullHDL", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *apiClient) GetCount(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CountResponse, error) { + out := new(CountResponse) + err := c.cc.Invoke(ctx, "/stress.api/GetCount", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *apiClient) StopPush(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.SuccessResponse, error) { + out := new(pb.SuccessResponse) + err := c.cc.Invoke(ctx, "/stress.api/StopPush", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *apiClient) StopPull(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.SuccessResponse, error) { + out := new(pb.SuccessResponse) + err := c.cc.Invoke(ctx, "/stress.api/StopPull", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ApiServer is the server API for Api service. +// All implementations must embed UnimplementedApiServer +// for forward compatibility +type ApiServer interface { + PushRTMP(context.Context, *PushRequest) (*pb.SuccessResponse, error) + PushRTSP(context.Context, *PushRequest) (*pb.SuccessResponse, error) + PullRTMP(context.Context, *PullRequest) (*pb.SuccessResponse, error) + PullRTSP(context.Context, *PullRequest) (*pb.SuccessResponse, error) + PullHDL(context.Context, *PullRequest) (*pb.SuccessResponse, error) + GetCount(context.Context, *emptypb.Empty) (*CountResponse, error) + StopPush(context.Context, *emptypb.Empty) (*pb.SuccessResponse, error) + StopPull(context.Context, *emptypb.Empty) (*pb.SuccessResponse, error) + mustEmbedUnimplementedApiServer() +} + +// UnimplementedApiServer must be embedded to have forward compatible implementations. +type UnimplementedApiServer struct { +} + +func (UnimplementedApiServer) PushRTMP(context.Context, *PushRequest) (*pb.SuccessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushRTMP not implemented") +} +func (UnimplementedApiServer) PushRTSP(context.Context, *PushRequest) (*pb.SuccessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushRTSP not implemented") +} +func (UnimplementedApiServer) PullRTMP(context.Context, *PullRequest) (*pb.SuccessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PullRTMP not implemented") +} +func (UnimplementedApiServer) PullRTSP(context.Context, *PullRequest) (*pb.SuccessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PullRTSP not implemented") +} +func (UnimplementedApiServer) PullHDL(context.Context, *PullRequest) (*pb.SuccessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PullHDL not implemented") +} +func (UnimplementedApiServer) GetCount(context.Context, *emptypb.Empty) (*CountResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCount not implemented") +} +func (UnimplementedApiServer) StopPush(context.Context, *emptypb.Empty) (*pb.SuccessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopPush not implemented") +} +func (UnimplementedApiServer) StopPull(context.Context, *emptypb.Empty) (*pb.SuccessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopPull not implemented") +} +func (UnimplementedApiServer) mustEmbedUnimplementedApiServer() {} + +// UnsafeApiServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ApiServer will +// result in compilation errors. +type UnsafeApiServer interface { + mustEmbedUnimplementedApiServer() +} + +func RegisterApiServer(s grpc.ServiceRegistrar, srv ApiServer) { + s.RegisterService(&Api_ServiceDesc, srv) +} + +func _Api_PushRTMP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).PushRTMP(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/PushRTMP", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).PushRTMP(ctx, req.(*PushRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Api_PushRTSP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).PushRTSP(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/PushRTSP", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).PushRTSP(ctx, req.(*PushRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Api_PullRTMP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PullRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).PullRTMP(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/PullRTMP", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).PullRTMP(ctx, req.(*PullRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Api_PullRTSP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PullRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).PullRTSP(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/PullRTSP", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).PullRTSP(ctx, req.(*PullRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Api_PullHDL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PullRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).PullHDL(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/PullHDL", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).PullHDL(ctx, req.(*PullRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Api_GetCount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).GetCount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/GetCount", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).GetCount(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Api_StopPush_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).StopPush(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/StopPush", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).StopPush(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Api_StopPull_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApiServer).StopPull(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stress.api/StopPull", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApiServer).StopPull(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +// Api_ServiceDesc is the grpc.ServiceDesc for Api service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Api_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "stress.api", + HandlerType: (*ApiServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PushRTMP", + Handler: _Api_PushRTMP_Handler, + }, + { + MethodName: "PushRTSP", + Handler: _Api_PushRTSP_Handler, + }, + { + MethodName: "PullRTMP", + Handler: _Api_PullRTMP_Handler, + }, + { + MethodName: "PullRTSP", + Handler: _Api_PullRTSP_Handler, + }, + { + MethodName: "PullHDL", + Handler: _Api_PullHDL_Handler, + }, + { + MethodName: "GetCount", + Handler: _Api_GetCount_Handler, + }, + { + MethodName: "StopPush", + Handler: _Api_StopPush_Handler, + }, + { + MethodName: "StopPull", + Handler: _Api_StopPull_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "stress.proto", +} diff --git a/plugin/webrtc/index.go b/plugin/webrtc/index.go index c6176e5..1c162ff 100644 --- a/plugin/webrtc/index.go +++ b/plugin/webrtc/index.go @@ -186,19 +186,19 @@ func (conf *WebRTCPlugin) Push_(w http.ResponseWriter, r *http.Request) { defer mem.Recycle() frame := &mrtp.RTPAudio{} frame.RTPCodecParameters = &codecP - frame.ScalableMemoryAllocator = mem + frame.SetAllocator(mem) for { var packet rtp.Packet - buf := frame.Malloc(mrtp.MTUSize) + buf := mem.Malloc(mrtp.MTUSize) if n, _, err = track.Read(buf); err == nil { - frame.FreeRest(&buf, n) + mem.FreeRest(&buf, n) err = packet.Unmarshal(buf) } if err != nil { return } if len(packet.Payload) == 0 { - frame.Free(buf) + mem.Free(buf) continue } if len(frame.Packets) == 0 || packet.Timestamp == frame.Packets[0].Timestamp { @@ -210,7 +210,7 @@ func (conf *WebRTCPlugin) Push_(w http.ResponseWriter, r *http.Request) { frame.AddRecycleBytes(buf) frame.Packets = []*rtp.Packet{&packet} frame.RTPCodecParameters = &codecP - frame.ScalableMemoryAllocator = mem + frame.SetAllocator(mem) } } } else { @@ -222,7 +222,7 @@ func (conf *WebRTCPlugin) Push_(w http.ResponseWriter, r *http.Request) { defer mem.Recycle() frame := &mrtp.RTPVideo{} frame.RTPCodecParameters = &codecP - frame.ScalableMemoryAllocator = mem + frame.SetAllocator(mem) for { if time.Since(lastPLISent) > conf.PLI { if rtcpErr := conn.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); rtcpErr != nil { @@ -232,16 +232,16 @@ func (conf *WebRTCPlugin) Push_(w http.ResponseWriter, r *http.Request) { lastPLISent = time.Now() } var packet rtp.Packet - buf := frame.Malloc(mrtp.MTUSize) + buf := mem.Malloc(mrtp.MTUSize) if n, _, err = track.Read(buf); err == nil { - frame.FreeRest(&buf, n) + mem.FreeRest(&buf, n) err = packet.Unmarshal(buf) } if err != nil { return } if len(packet.Payload) == 0 { - frame.Free(buf) + mem.Free(buf) continue } if len(frame.Packets) == 0 || packet.Timestamp == frame.Packets[0].Timestamp { @@ -255,7 +255,7 @@ func (conf *WebRTCPlugin) Push_(w http.ResponseWriter, r *http.Request) { frame.AddRecycleBytes(buf) frame.Packets = []*rtp.Packet{&packet} frame.RTPCodecParameters = &codecP - frame.ScalableMemoryAllocator = mem + frame.SetAllocator(mem) } } } @@ -349,7 +349,7 @@ func (conf *WebRTCPlugin) Play_(w http.ResponseWriter, r *http.Request) { } else { var rtpCtx mrtp.RTPData var tmpAVTrack AVTrack - err = rtpCtx.ConvertCtx(vt.ICodecCtx, &tmpAVTrack) + tmpAVTrack.ICodecCtx, tmpAVTrack.SequenceFrame, err = rtpCtx.ConvertCtx(vt.ICodecCtx) if err == nil { rcc = tmpAVTrack.ICodecCtx.(mrtp.IRTPCtx).GetRTPCodecParameter() } else { diff --git a/publisher.go b/publisher.go index 0b9816e..3582d4d 100644 --- a/publisher.go +++ b/publisher.go @@ -248,7 +248,6 @@ func (p *Publisher) WriteVideo(data IAVFrame) (err error) { } if t.Value.IDR { if !t.IsReady() { - p.Info("ready") t.Ready(nil) } else if idr != nil { p.GOP = int(t.Value.Sequence - idr.Value.Sequence) @@ -271,8 +270,7 @@ func (p *Publisher) WriteVideo(data IAVFrame) (err error) { toType := track.FrameType.Elem() toFrame := reflect.New(toType).Interface().(IAVFrame) if track.ICodecCtx == nil { - err = toFrame.ConvertCtx(t.ICodecCtx, track) - if err != nil { + if track.ICodecCtx, track.SequenceFrame, err = toFrame.ConvertCtx(t.ICodecCtx); err != nil { track.Error("DecodeConfig", "err", err) return } @@ -294,7 +292,7 @@ func (p *Publisher) WriteVideo(data IAVFrame) (err error) { toFrame.SetAllocator(data.GetAllocator()) toFrame.Mux(track.ICodecCtx, &t.Value) if codecCtxChanged { - err = toFrame.ConvertCtx(t.ICodecCtx, track) + track.ICodecCtx, track.SequenceFrame, err = toFrame.ConvertCtx(t.ICodecCtx) } t.Value.Wraps = append(t.Value.Wraps, toFrame) if track.ICodecCtx != nil { @@ -351,8 +349,7 @@ func (p *Publisher) WriteAudio(data IAVFrame) (err error) { toType := track.FrameType.Elem() toFrame := reflect.New(toType).Interface().(IAVFrame) if track.ICodecCtx == nil { - err = toFrame.ConvertCtx(t.ICodecCtx, track) - if err != nil { + if track.ICodecCtx, track.SequenceFrame, err = toFrame.ConvertCtx(t.ICodecCtx); err != nil { track.Error("DecodeConfig", "err", err) return } @@ -374,7 +371,7 @@ func (p *Publisher) WriteAudio(data IAVFrame) (err error) { toFrame.SetAllocator(data.GetAllocator()) toFrame.Mux(track.ICodecCtx, &t.Value) if codecCtxChanged { - err = toFrame.ConvertCtx(t.ICodecCtx, track) + track.ICodecCtx, track.SequenceFrame, err = toFrame.ConvertCtx(t.ICodecCtx) } t.Value.Wraps = append(t.Value.Wraps, toFrame) if track.ICodecCtx != nil { diff --git a/server.go b/server.go index bb6a22d..beb920b 100644 --- a/server.go +++ b/server.go @@ -102,6 +102,7 @@ func (s *Server) run(ctx context.Context, conf any) (err error) { s.handler = s s.config.HTTP.ListenAddrTLS = ":8443" s.config.HTTP.ListenAddr = ":8080" + s.config.TCP.ListenAddr = ":50051" s.LogHandler.SetLevel(slog.LevelInfo) s.LogHandler.Add(defaultLogHandler) s.Logger = slog.New(&s.LogHandler).With("server", s.ID) diff --git a/subscriber.go b/subscriber.go index a7f8c44..9dc3e8e 100644 --- a/subscriber.go +++ b/subscriber.go @@ -144,17 +144,31 @@ func (s *Subscriber) createVideoReader(dataType reflect.Type, startVideoTs time. return } +type SubscribeHandler[A any, V any] struct { + OnAudio func(A) error + OnVideo func(V) error + ProcessAudio chan func(*AVFrame) + ProcessVideo chan func(*AVFrame) +} + func PlayBlock[A any, V any](s *Subscriber, onAudio func(A) error, onVideo func(V) error) (err error) { + var handler SubscribeHandler[A, V] + handler.OnAudio = onAudio + handler.OnVideo = onVideo + return PlayBlock0(s, handler) +} + +func PlayBlock0[A any, V any](s *Subscriber, handler SubscribeHandler[A, V]) (err error) { var a1, v1 reflect.Type var startAudioTs, startVideoTs time.Duration var initState = 0 prePublisher := s.Publisher var audioFrame, videoFrame *AVFrame if s.SubAudio { - a1 = reflect.TypeOf(onAudio).In(0) + a1 = reflect.TypeOf(handler.OnAudio).In(0) } if s.SubVideo { - v1 = reflect.TypeOf(onVideo).In(0) + v1 = reflect.TypeOf(handler.OnVideo).In(0) } awi := s.createAudioReader(a1, startAudioTs) vwi := s.createVideoReader(v1, startVideoTs) @@ -172,16 +186,21 @@ func PlayBlock[A any, V any](s *Subscriber, onAudio func(A) error, onVideo func( if s.Enabled(s, TraceLevel) { s.Trace("send audio frame", "seq", audioFrame.Sequence) } - err = onAudio(audioFrame.Wraps[awi].(A)) + err = handler.OnAudio(audioFrame.Wraps[awi].(A)) } else { s.AudioReader.StopRead() } } else { - err = onAudio(any(audioFrame).(A)) + err = handler.OnAudio(any(audioFrame).(A)) } if err != nil && !errors.Is(err, ErrInterrupt) { s.Stop(err) } + if handler.ProcessAudio != nil { + if f, ok := <-handler.ProcessAudio; ok { + f(audioFrame) + } + } audioFrame = nil return } @@ -191,16 +210,21 @@ func PlayBlock[A any, V any](s *Subscriber, onAudio func(A) error, onVideo func( if s.Enabled(s, TraceLevel) { s.Trace("send video frame", "seq", videoFrame.Sequence, "data", videoFrame.Wraps[vwi].String(), "size", videoFrame.Wraps[vwi].GetSize()) } - err = onVideo(videoFrame.Wraps[vwi].(V)) + err = handler.OnVideo(videoFrame.Wraps[vwi].(V)) } else { s.VideoReader.StopRead() } } else { - err = onVideo(any(videoFrame).(V)) + err = handler.OnVideo(any(videoFrame).(V)) } if err != nil && !errors.Is(err, ErrInterrupt) { s.Stop(err) } + if handler.ProcessVideo != nil { + if f, ok := <-handler.ProcessVideo; ok { + f(videoFrame) + } + } videoFrame = nil return } @@ -242,7 +266,7 @@ func PlayBlock[A any, V any](s *Subscriber, onAudio func(A) error, onVideo func( vr.LastCodecCtx = vr.Track.ICodecCtx if seqFrame := vr.Track.SequenceFrame; seqFrame != nil { s.Debug("video codec changed", "data", seqFrame.String()) - err = onVideo(seqFrame.(V)) + err = handler.OnVideo(seqFrame.(V)) } } if ar != nil { @@ -294,7 +318,7 @@ func PlayBlock[A any, V any](s *Subscriber, onAudio func(A) error, onVideo func( if ar.DecConfChanged() { ar.LastCodecCtx = ar.Track.ICodecCtx if seqFrame := ar.Track.SequenceFrame; seqFrame != nil { - err = onAudio(seqFrame.(A)) + err = handler.OnAudio(seqFrame.(A)) } } if vr != nil && videoFrame != nil { diff --git a/transformer.go b/transformer.go index 064829e..1dd2c94 100644 --- a/transformer.go +++ b/transformer.go @@ -1,6 +1,8 @@ package m7s -import "m7s.live/m7s/v5/pkg" +import ( + "m7s.live/m7s/v5/pkg" +) type Transformer struct { *Publisher