feat: add hdl pull、h265、av1

This commit is contained in:
langhuihui
2024-04-11 20:23:23 +08:00
parent 6079337429
commit 51c85d10f7
22 changed files with 1223 additions and 121 deletions

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
# Introduction
Monibuca is a highly scalable high-performance streaming server development framework developed purely for Go
# Usage
```go
package main
import (
"context"
"m7s.live/m7s/v5"
_ "m7s.live/m7s/v5/plugin/debug"
_ "m7s.live/m7s/v5/plugin/hdl"
_ "m7s.live/m7s/v5/plugin/rtmp"
)
func main() {
m7s.Run(context.Background(), "config.yaml")
}
```
## More Example
see example directory
# Create Plugin
```go
import (
"m7s.live/m7s/v5"
)
type MyPlugin struct {
m7s.Plugin
}
var _ = m7s.InstallPlugin[MyPlugin]()
```

39
README_CN.md Normal file
View File

@@ -0,0 +1,39 @@
# 介绍
monibuca 是一款纯 go 开发的扩展性极强的高性能流媒体服务器开发框架
# 使用
```go
package main
import (
"context"
"m7s.live/m7s/v5"
_ "m7s.live/m7s/v5/plugin/debug"
_ "m7s.live/m7s/v5/plugin/hdl"
_ "m7s.live/m7s/v5/plugin/rtmp"
)
func main() {
m7s.Run(context.Background(), "config.yaml")
}
```
## 更多示例
查看 example 目录
# 创建插件
```go
import (
"m7s.live/m7s/v5"
)
type MyPlugin struct {
m7s.Plugin
}
var _ = m7s.InstallPlugin[MyPlugin]()
```

2
api.go
View File

@@ -24,7 +24,7 @@ func (s *Server) StreamSnap(ctx context.Context, req *pb.StreamSnapRequest) (res
func (s *Server) Restart(ctx context.Context, req *pb.RequestWithId) (res *emptypb.Empty, err error) {
if Servers[req.Id] != nil {
Servers[req.Id].Stop(errRestart)
Servers[req.Id].Stop(pkg.ErrRestart)
}
return &emptypb.Empty{}, err
}

2
go.mod
View File

@@ -5,7 +5,9 @@ go 1.22
toolchain go1.22.1
require (
github.com/cnotch/ipchub v1.1.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
github.com/q191201771/naza v0.30.48
github.com/quic-go/quic-go v0.42.0
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de
google.golang.org/grpc v1.63.2

42
go.sum
View File

@@ -1,17 +1,29 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/bluenviron/mediacommon v1.9.2 h1:EHcvoC5YMXRcFE010bTNf07ZiSlB/e/AdZyG7GsEYN0=
github.com/bluenviron/mediacommon v1.9.2/go.mod h1:lt8V+wMyPw8C69HAqDWV5tsAwzN9u2Z+ca8B6C//+n0=
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/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=
github.com/cnotch/loader v0.0.0-20200405015128-d9d964d09439/go.mod h1:oWpDagHB6p+Kqqq7RoRZKyC4XAXft50hR8pbTxdbYYs=
github.com/cnotch/queue v0.0.0-20200326024423-6e88bdbf2ad4/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
github.com/cnotch/queue v0.0.0-20201224060551-4191569ce8f6/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
github.com/cnotch/scheduler v0.0.0-20200522024700-1d2da93eefc5/go.mod h1:F4GE3SZkJZ8an1Y0ZCqvSM3jeozNuKzoC67erG1PhIo=
github.com/cnotch/xlog v0.0.0-20201208005456-cfda439cd3a0/go.mod h1:RW9oHsR79ffl3sR3yMGgxYupMn2btzdtJUwoxFPUE5E=
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/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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-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=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -20,11 +32,16 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
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/kelindar/process v0.0.0-20170730150328-69a29e249ec3/go.mod h1:+lTCLnZFXOkqwD8sLPl6u4erAc0cP8wFegQHfipz7KE=
github.com/kelindar/rate v1.0.0/go.mod h1:AjT4G+hTItNwt30lucEGZIz8y7Uk5zPho6vurIZ+1Es=
github.com/kelindar/tcp v1.0.0/go.mod h1:JB5hj1cshLU60XrLij2BBxW3JQ4hOye8vqbyvuKb52k=
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=
@@ -35,14 +52,22 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/q191201771/naza v0.30.48 h1:lbYUaa7A15kJKYwOiU4AbFS1Zo8oQwppl2tLEbJTqnw=
github.com/q191201771/naza v0.30.48/go.mod h1:n+dpJjQSh90PxBwxBNuifOwQttywvSIN5TkWSSYCeBk=
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
@@ -53,10 +78,12 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -71,15 +98,23 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -87,6 +122,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -108,6 +145,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

14
pkg/codec/av1.go Normal file
View File

@@ -0,0 +1,14 @@
package codec
const (
AV1_OBU_SEQUENCE_HEADER = 1
AV1_OBU_TEMPORAL_DELIMITER = 2
AV1_OBU_FRAME_HEADER = 3
AV1_OBU_TILE_GROUP = 4
AV1_OBU_METADATA = 5
AV1_OBU_FRAME = 6
AV1_OBU_REDUNDANT_FRAME_HEADER = 7
AV1_OBU_TILE_LIST = 8
AV1_OBU_PADDING = 15
)

104
pkg/codec/h264.go Normal file
View File

@@ -0,0 +1,104 @@
package codec
import (
"bytes"
)
// Start Code + NAL Unit -> NALU Header + NALU Body
// RTP Packet -> NALU Header + NALU Body
// NALU Body -> Slice Header + Slice data
// Slice data -> flags + Macroblock layer1 + Macroblock layer2 + ...
// Macroblock layer1 -> mb_type + PCM Data
// Macroblock layer2 -> mb_type + Sub_mb_pred or mb_pred + Residual Data
// Residual Data ->
type H264NALUType byte
func (b H264NALUType) Or(b2 byte) byte {
return byte(b) | b2
}
func (b H264NALUType) Offset() int {
switch b {
case NALU_STAPA:
return 1
case NALU_STAPB:
return 3
case NALU_FUA:
return 2
case NALU_FUB:
return 4
}
return 0
}
func (b H264NALUType) Byte() byte {
return byte(b)
}
func ParseH264NALUType(b byte) H264NALUType {
return H264NALUType(b & 0x1F)
}
func (H264NALUType) Parse(b byte) H264NALUType {
return H264NALUType(b & 0x1F)
}
func (H264NALUType) ParseBytes(bs []byte) H264NALUType {
return H264NALUType(bs[0] & 0x1F)
}
const (
// NALU Type
NALU_Unspecified H264NALUType = iota
NALU_Non_IDR_Picture // 1
NALU_Data_Partition_A // 2
NALU_Data_Partition_B // 3
NALU_Data_Partition_C // 4
NALU_IDR_Picture // 5
NALU_SEI // 6
NALU_SPS // 7
NALU_PPS // 8
NALU_Access_Unit_Delimiter // 9
NALU_Sequence_End // 10
NALU_Stream_End // 11
NALU_Filler_Data // 12
NALU_SPS_Extension // 13
NALU_Prefix // 14
NALU_SPS_Subset // 15
NALU_DPS // 16
NALU_Reserved1 // 17
NALU_Reserved2 // 18
NALU_Not_Auxiliary_Coded // 19
NALU_Coded_Slice_Extension // 20
NALU_Reserved3 // 21
NALU_Reserved4 // 22
NALU_Reserved5 // 23
NALU_STAPA // 24
NALU_STAPB
NALU_MTAP16
NALU_MTAP24
NALU_FUA // 28
NALU_FUB
// 24 - 31 NALU_NotReserved
)
var (
NALU_AUD_BYTE = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xF0}
NALU_Delimiter1 = []byte{0x00, 0x00, 0x01}
NALU_Delimiter2 = []byte{0x00, 0x00, 0x00, 0x01}
)
// H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)
// NAL - Network Abstract Layer
// raw byte sequence payload (RBSP) 原始字节序列载荷
// SplitH264 以0x00000001分割H264裸数据
func SplitH264(payload []byte) (nalus [][]byte) {
for _, v := range bytes.SplitN(payload, NALU_Delimiter2, -1) {
if len(v) == 0 {
continue
}
nalus = append(nalus, bytes.SplitN(v, NALU_Delimiter1, -1)...)
}
return
}

88
pkg/codec/h265.go Normal file
View File

@@ -0,0 +1,88 @@
package codec
type H265NALUType byte
func (H265NALUType) Parse(b byte) H265NALUType {
return H265NALUType(b & 0x7E >> 1)
}
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}

View File

@@ -1,5 +1,29 @@
package codec
import "encoding/binary"
type FourCC [4]byte
var (
FourCC_H264 = FourCC{'a', 'v', 'c', '1'}
FourCC_H265 = FourCC{'h', 'v', 'c', '1'}
FourCC_AV1 = FourCC{'a', 'v', '0', '1'}
FourCC_VP9 = FourCC{'v', 'p', '0', '9'}
FourCC_VP8 = FourCC{'v', 'p', '8', '0'}
FourCC_MP4A = FourCC{'m', 'p', '4', 'a'}
FourCC_OPUS = FourCC{'O', 'p', 'u', 's'}
FourCC_ALAW = FourCC{'a', 'l', 'a', 'w'}
FourCC_ULAW = FourCC{'u', 'l', 'a', 'w'}
)
func (f *FourCC) String() string {
return string(f[:])
}
func (f *FourCC) Uint32() uint32 {
return binary.BigEndian.Uint32(f[:])
}
type SPSInfo struct {
ProfileIdc uint
LevelIdc uint

View File

@@ -5,6 +5,7 @@ import (
"crypto/subtle"
"crypto/tls"
"net/http"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
@@ -62,6 +63,9 @@ func (config *HTTP) Handle(path string, f http.Handler) {
case *http.ServeMux:
mux.Handle(path, f)
case *runtime.ServeMux:
if strings.HasSuffix(path, "/") {
path += "{streamPath=**}"
}
mux.HandlePath("GET", path, func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
f.ServeHTTP(w, r)
})

View File

@@ -8,7 +8,7 @@ import (
)
type PublishConfig interface {
GetPublishConfig() Publish
GetPublishConfig() *Publish
}
type SubscribeConfig interface {
@@ -39,7 +39,7 @@ type Publish struct {
RingSize string `default:"256-1024" desc:"缓冲范围"` // 初始缓冲区大小
}
func (c Publish) GetPublishConfig() Publish {
func (c *Publish) GetPublishConfig() *Publish {
return c
}

View File

@@ -12,4 +12,5 @@ var (
ErrPublishIdleTimeout = errors.New("publish idle timeout")
ErrPublishDelayCloseTimeout = errors.New("publish delay close timeout")
ErrSubscribeTimeout = errors.New("subscribe timeout")
ErrRestart = errors.New("restart")
)

View File

@@ -4,6 +4,7 @@ import (
"log/slog"
"slices"
"m7s.live/m7s/v5/pkg/codec"
"m7s.live/m7s/v5/pkg/util"
)
@@ -23,7 +24,7 @@ type (
}
AVTrack struct {
Codec string
Codec codec.FourCC
Track
RingWriter
IDRingList `json:"-" yaml:"-"` //最近的关键帧位置,首屏渲染

View File

@@ -31,7 +31,29 @@ func (buffers *Buffers) ReadFromBytes(b ...[]byte) {
buffers.Length += len(level0)
}
}
func (buffers *Buffers) ReadBytesTo(buf []byte) (err error) {
n := len(buf)
if n > buffers.Length {
return io.EOF
}
l := n
for n > 0 {
level1 := buffers.GetLevel1()
level1Len := len(level1)
if n < level1Len {
copy(buf[l-n:], level1[:n])
buffers.move1(n)
break
}
copy(buf[l-n:], level1)
n -= level1Len
buffers.move0()
if buffers.Length == 0 && n > 0 {
return io.EOF
}
}
return
}
func (buffers *Buffers) ReadByteTo(b ...*byte) (err error) {
for i := range b {
if buffers.Length == 0 {
@@ -132,24 +154,9 @@ func (buffers *Buffers) ReadBytes(n int) ([]byte, error) {
if n > buffers.Length {
return nil, io.EOF
}
l := n
b := make([]byte, n)
for n > 0 {
level1 := buffers.GetLevel1()
level1Len := len(level1)
if n < level1Len {
copy(b[l-n:], level1[:n])
buffers.move1(n)
break
}
copy(b[l-n:], level1)
n -= level1Len
buffers.move0()
if buffers.Length == 0 && n > 0 {
return nil, io.EOF
}
}
return b, nil
err := buffers.ReadBytesTo(b)
return b, err
}
func (buffers *Buffers) WriteNTo(n int, result *net.Buffers) (actual int) {

46
plugin/hdl/READEME.md Normal file
View File

@@ -0,0 +1,46 @@
# HDL Plugin
The main function of the HDL plugin is to provide access to the HTTP-FLV protocol.
HTTP-FLV protocol (HDL: Http Dynamic Live) is a dynamic streaming media live broadcast protocol, which implements the function of live broadcast of FLV format video on the ordinary HTTP protocol. The meaning of its name can be mainly divided into three parts:
- HTTP (HyperText Transfer Protocol): Hypertext Transfer Protocol, a protocol for information transfer on the World Wide Web. In the HTTP-FLV protocol, HTTP serves as the basic protocol to provide the basic structure of data transmission.
- FLV (Flash Video): A streaming media video format, initially designed by Adobe, mainly used for online playback of short videos or live broadcasts.
- HDL: Http Dynamic Live, is the abbreviation of HTTP-FLV protocol alias, which can be understood as "HTTP-based dynamic live broadcast protocol", emphasizing that it is based on the original HTTP protocol, through dynamic technology to achieve the function of video live broadcast.
## Plugin Address
https://github.com/Monibuca/plugin-hdl
## Plugin Introduction
```go
import (
_ "m7s.live/plugin/hdl/v4"
)
```
## Default Plugin Configuration
```yaml
hdl:
pull: # Format: https://m7s.live/guide/config.html#%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE
```
## Plugin Features
### Pulling HTTP-FLV Streams from M7S
If the live/test stream already exists in M7S, then HTTP-FLV protocol can be used for playback. If the listening port is not configured, then the global HTTP port is used (default 8080).
```bash
ffplay http://localhost:8080/hdl/live/test.flv
```
### M7S Pull HTTP-FLV Streams from Remote
The available API is:
`/hdl/api/pull?target=[HTTP-FLV address]&streamPath=[stream identifier]&save=[0|1|2]`
- save meaning: 0 - do not save 1 - save to pullonstart 2 - save to pullonsub
- HTTP-FLV address needs to be urlencoded to prevent special characters from affecting parsing

111
plugin/hdl/pkg/pull.go Normal file
View File

@@ -0,0 +1,111 @@
package hdl
import (
"bufio"
"errors"
"io"
"net/http"
"net/url"
"os"
"strings"
"m7s.live/m7s/v5"
"m7s.live/m7s/v5/pkg/util"
rtmp "m7s.live/m7s/v5/plugin/rtmp/pkg"
)
type HDLPuller struct {
*bufio.Reader
absTS uint32 //绝对时间戳
pool *util.ScalableMemoryAllocator
}
func (puller *HDLPuller) Connect(p *m7s.Puller) (err error) {
if strings.HasPrefix(p.RemoteURL, "http") {
var res *http.Response
client := http.DefaultClient
if proxyConf := p.GetPullConfig().Proxy; proxyConf != "" {
proxy, err := url.Parse(proxyConf)
if err != nil {
return err
}
transport := &http.Transport{Proxy: http.ProxyURL(proxy)}
client = &http.Client{Transport: transport}
}
if res, err = client.Get(p.RemoteURL); err == nil {
if res.StatusCode != http.StatusOK {
return io.EOF
}
p.Closer = res.Body
puller.Reader = bufio.NewReader(res.Body)
}
} else {
var res *os.File
if res, err = os.Open(p.RemoteURL); err == nil {
p.Closer = res
puller.Reader = bufio.NewReader(res)
}
}
if err == nil {
header := puller.pool.Malloc(13)
defer puller.pool.Free(header)
if _, err = io.ReadFull(puller, header); err == nil {
if header[0] != 'F' || header[1] != 'L' || header[2] != 'V' {
err = errors.New("not flv file")
} else {
configCopy := p.GetPublishConfig()
if header[4]&0x04 == 0 {
configCopy.PubAudio = false
}
if header[4]&0x01 == 0 {
configCopy.PubVideo = false
}
}
}
}
return
}
func (puller *HDLPuller) Pull(p *m7s.Puller) (err error) {
var startTs uint32
var buf15 [15]byte
pubaudio, pubvideo := p.GetPublishConfig().PubAudio, p.GetPublishConfig().PubVideo
for offsetTs := puller.absTS; err == nil; _, err = io.ReadFull(puller, buf15[11:]) {
tmp := util.Buffer(buf15[:11])
_, err = io.ReadFull(puller, tmp)
if err != nil {
return
}
t := tmp.ReadByte()
dataSize := tmp.ReadUint24()
timestamp := tmp.ReadUint24() | uint32(tmp.ReadByte())<<24
if startTs == 0 {
startTs = timestamp
}
tmp.ReadUint24()
var frame rtmp.RTMPData
frame.ScalableMemoryAllocator = puller.pool
mem := frame.Malloc(int(dataSize))
_, err = io.ReadFull(puller, mem)
if err != nil {
return
}
puller.absTS = offsetTs + (timestamp - startTs)
frame.Timestamp = puller.absTS
// fmt.Println(t, offsetTs, timestamp, startTs, puller.absTS)
switch t {
case FLV_TAG_TYPE_AUDIO:
if pubaudio {
p.WriteAudio(&rtmp.RTMPAudio{frame})
}
case FLV_TAG_TYPE_VIDEO:
if pubvideo {
p.WriteVideo(&rtmp.RTMPVideo{frame})
}
case FLV_TAG_TYPE_SCRIPT:
p.Info("script", "data", mem)
frame.Recycle()
}
}
return
}

View File

@@ -2,6 +2,7 @@ package rtmp
import (
. "m7s.live/m7s/v5/pkg"
"m7s.live/m7s/v5/pkg/codec"
)
type RTMPAudio struct {
@@ -21,21 +22,21 @@ func (avcc *RTMPAudio) DecodeConfig(track *AVTrack) error {
if b1 == 0 {
switch b0 & 0b1111_0000 >> 4 {
case 7:
track.Codec = "pcmu"
track.Codec = codec.FourCC_ALAW
var ctx G711Ctx
ctx.SampleRate = 8000
ctx.Channels = 1
ctx.SampleSize = 8
track.ICodecCtx = &ctx
case 8:
track.Codec = "pcma"
track.Codec = codec.FourCC_ULAW
var ctx G711Ctx
ctx.SampleRate = 8000
ctx.Channels = 1
ctx.SampleSize = 8
track.ICodecCtx = &ctx
case 10:
track.Codec = "aac"
track.Codec = codec.FourCC_MP4A
var ctx AACCtx
b0, err = reader.ReadByte()
if err != nil {
@@ -63,7 +64,7 @@ func (avcc *RTMPAudio) DecodeConfig(track *AVTrack) error {
func (avcc *RTMPAudio) ToRaw(track *AVTrack) (any, error) {
reader := avcc.Buffers
if track.Codec == "aac" {
if track.Codec == codec.FourCC_MP4A {
err := reader.Skip(2)
return reader.Buffers, err
} else {

View File

@@ -4,7 +4,10 @@ import (
"bytes"
"encoding/binary"
"errors"
"io"
"github.com/cnotch/ipchub/av/codec/hevc"
"github.com/q191201771/naza/pkg/nazabits"
. "m7s.live/m7s/v5/pkg"
"m7s.live/m7s/v5/pkg/codec"
"m7s.live/m7s/v5/pkg/util"
@@ -15,18 +18,38 @@ type (
VideoCodecID byte
H264Ctx struct {
SequenceFrame *RTMPVideo
ConfigurationVersion byte // 8 bits Version
AVCProfileIndication byte // 8 bits
ProfileCompatibility byte // 8 bits
AVCLevelIndication byte // 8 bits
LengthSizeMinusOne byte
SequenceFrame *RTMPVideo
codec.SPSInfo
NalulenSize int
SPS []byte
PPS []byte
SPS [][]byte
PPS [][]byte
}
H265Ctx struct {
H264Ctx
VPS []byte
VPS [][]byte
}
AV1Ctx struct {
SequenceFrame *RTMPVideo
Version byte
SeqProfile byte
SeqLevelIdx0 byte
SeqTier0 byte
HighBitdepth byte
TwelveBit byte
MonoChrome byte
ChromaSubsamplingX byte
ChromaSubsamplingY byte
ChromaSamplePosition byte
InitialPresentationDelayPresent byte
InitialPresentationDelayMinusOne byte
ConfigOBUs []byte
}
G711Ctx struct {
SampleRate int
Channels int
@@ -67,6 +90,29 @@ type (
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 (
@@ -94,15 +140,15 @@ func (codecId AudioCodecID) String() string {
return "unknow"
}
func ParseAudioCodec(name string) AudioCodecID {
func ParseAudioCodec(name codec.FourCC) AudioCodecID {
switch name {
case "aac":
case codec.FourCC_MP4A:
return CodecID_AAC
case "pcma":
case codec.FourCC_ALAW:
return CodecID_PCMA
case "pcmu":
case codec.FourCC_ULAW:
return CodecID_PCMU
case "opus":
case codec.FourCC_OPUS:
return CodecID_OPUS
}
return 0
@@ -120,13 +166,13 @@ func (codecId VideoCodecID) String() string {
return "unknow"
}
func ParseVideoCodec(name string) VideoCodecID {
func ParseVideoCodec(name codec.FourCC) VideoCodecID {
switch name {
case "h264":
case codec.FourCC_H264:
return CodecID_H264
case "h265":
case codec.FourCC_H265:
return CodecID_H265
case "av1":
case codec.FourCC_AV1:
return CodecID_AV1
}
return 0
@@ -157,19 +203,20 @@ func (p *AVCDecoderConfigurationRecord) Marshal(b []byte) (n int) {
var ErrDecconfInvalid = errors.New("decode error")
func (p *AVCDecoderConfigurationRecord) Unmarshal(b *util.Buffers) (err error) {
func (ctx *H264Ctx) Unmarshal(b *util.Buffers) (err error) {
if b.Length < 7 {
err = errors.New("not enough len")
return
}
b.ReadByteTo(&p.ConfigurationVersion, &p.AVCProfileIndication, &p.ProfileCompatibility, &p.AVCLevelIndication, &p.LengthSizeMinusOne)
p.LengthSizeMinusOne = p.LengthSizeMinusOne & 0x03
p.NumOfSequenceParameterSets, err = b.ReadByteMask(0x1f)
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
}
var sps, pps [][]byte
for range p.NumOfSequenceParameterSets {
for range numOfSequenceParameterSets {
spslen, err1 := b.ReadBE(2)
if err1 != nil {
return err1
@@ -178,15 +225,16 @@ func (p *AVCDecoderConfigurationRecord) Unmarshal(b *util.Buffers) (err error) {
if err2 != nil {
return err2
}
sps = append(sps, spsbytes)
ctx.SPS = append(ctx.SPS, spsbytes)
}
p.SequenceParameterSetLength = uint16(len(sps[0]))
p.SequenceParameterSetNALUnit = sps[0]
if b.Length < 1 {
err = ErrDecconfInvalid
return
}
ctx.SPSInfo, err = ParseSPS(ctx.SPS[0])
if err != nil {
return
}
ppscount, err1 := b.ReadByte()
if err1 != nil {
return err1
@@ -200,13 +248,7 @@ func (p *AVCDecoderConfigurationRecord) Unmarshal(b *util.Buffers) (err error) {
if err2 != nil {
return err2
}
pps = append(pps, ppsbytes)
}
if ppscount >= 1 {
p.PictureParameterSetLength = uint16(len(pps[0]))
p.PictureParameterSetNALUnit = pps[0]
} else {
err = ErrDecconfInvalid
ctx.PPS = append(ctx.PPS, ppsbytes)
}
return
}
@@ -403,15 +445,15 @@ func ParseSPS(data []byte) (self codec.SPSInfo, err error) {
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
// }
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}
@@ -446,3 +488,507 @@ func (ctx *G711Ctx) GetSequenceFrame() IAVFrame {
func (ctx *AACCtx) GetSequenceFrame() IAVFrame {
return ctx.SequenceFrame
}
func (ctx *AV1Ctx) GetSequenceFrame() IAVFrame {
return ctx.SequenceFrame
}
var ErrHevc = errors.New("hevc parse config error")
func (ctx *H265Ctx) Unmarshal(b *util.Buffers) (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(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(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(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 = 1111b;
// unsigned int(12) min_spatial_segmentation_idc;
// bit(6) reserved = 111111b;
// unsigned int(2) parallelismType;
// TODO chef: 这两个字段没有解析
util.PutBE(sh[18:20], 0xf000)
sh[20] = ctx.parallelismType | 0xfc
// bit(6) reserved = 111111b;
// unsigned int(2) chromaFormat;
sh[21] = ctx.chromaFormat | 0xfc
// bit(5) reserved = 11111b;
// unsigned int(3) bitDepthLumaMinus8;
sh[22] = ctx.bitDepthLumaMinus8 | 0xf8
// bit(5) reserved = 11111b;
// 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")
ErrNonZeroReservedBits = errors.New("non-zero reserved bits found in AV1CodecConfigurationRecord")
)
func (p *AV1Ctx) Unmarshal(data *util.Buffers) (err error) {
if data.Length < 4 {
err = io.ErrShortWrite
return
}
var b byte
b, err = data.ReadByte()
if err != nil {
return
}
Marker := b >> 7
if Marker != 1 {
return ErrInvalidMarker
}
p.Version = b & 0x7F
if p.Version != 1 {
return ErrInvalidVersion
}
b, err = data.ReadByte()
if err != nil {
return
}
p.SeqProfile = b >> 5
p.SeqLevelIdx0 = b & 0x1F
b, err = data.ReadByte()
if err != nil {
return
}
p.SeqTier0 = b >> 7
p.HighBitdepth = (b >> 6) & 0x01
p.TwelveBit = (b >> 5) & 0x01
p.MonoChrome = (b >> 4) & 0x01
p.ChromaSubsamplingX = (b >> 3) & 0x01
p.ChromaSubsamplingY = (b >> 2) & 0x01
p.ChromaSamplePosition = b & 0x03
b, err = data.ReadByte()
if err != nil {
return
}
if b>>5 != 0 {
return ErrNonZeroReservedBits
}
p.InitialPresentationDelayPresent = (b >> 4) & 0x01
if p.InitialPresentationDelayPresent == 1 {
p.InitialPresentationDelayMinusOne = b & 0x0F
} else {
if b&0x0F != 0 {
return ErrNonZeroReservedBits
}
p.InitialPresentationDelayMinusOne = 0
}
if data.Length > 0 {
p.ConfigOBUs, err = data.ReadBytes(data.Length)
}
return nil
}

View File

@@ -16,9 +16,6 @@ const (
PacketTypeMPEG2TSSequenceStart
)
var FourCC_H265 = [4]byte{'H', '2', '6', '5'}
var FourCC_AV1 = [4]byte{'a', 'v', '0', '1'}
type RTMPData struct {
Timestamp uint32
util.Buffers

View File

@@ -4,6 +4,7 @@ import (
"time"
. "m7s.live/m7s/v5/pkg"
"m7s.live/m7s/v5/pkg/codec"
"m7s.live/m7s/v5/pkg/util"
)
@@ -12,7 +13,7 @@ type RTMPVideo struct {
}
func (avcc *RTMPVideo) IsIDR() bool {
return avcc.Buffers.Buffers[0][0]&0b1111_0000>>4 == 1
return avcc.Buffers.Buffers[0][0]&0b0111_0000>>4 == 1
}
func (avcc *RTMPVideo) DecodeConfig(track *AVTrack) error {
@@ -27,35 +28,32 @@ func (avcc *RTMPVideo) DecodeConfig(track *AVTrack) error {
parseSequence := func() (err error) {
switch track.Codec {
case "h264":
case codec.FourCC_H264:
var ctx H264Ctx
var info AVCDecoderConfigurationRecord
if err = info.Unmarshal(&reader); err == nil {
ctx.SPSInfo, _ = ParseSPS(info.SequenceParameterSetNALUnit)
ctx.NalulenSize = int(info.LengthSizeMinusOne&3 + 1)
ctx.SPS = info.SequenceParameterSetNALUnit
ctx.PPS = info.PictureParameterSetNALUnit
if err = ctx.Unmarshal(&reader); err == nil {
ctx.SequenceFrame = avcc
track.ICodecCtx = &ctx
}
case codec.FourCC_H265:
var ctx H265Ctx
if err = ctx.Unmarshal(&reader); err == nil {
ctx.SequenceFrame = avcc
track.ICodecCtx = &ctx
}
case codec.FourCC_AV1:
var ctx AV1Ctx
if err = ctx.Unmarshal(&reader); err == nil {
ctx.SequenceFrame = avcc
track.ICodecCtx = &ctx
}
case "h265":
// var ctx H265Ctx
case "av1":
}
return
}
if enhanced {
var fourCC [4]byte
_, err = reader.Read(fourCC[:])
err = reader.ReadBytesTo(track.Codec[:])
if err != nil {
return err
}
switch fourCC {
case FourCC_H265:
track.Codec = "h265"
case FourCC_AV1:
track.Codec = "av1"
}
switch packetType {
case PacketTypeSequenceStart:
if err = parseSequence(); err != nil {
@@ -72,9 +70,9 @@ func (avcc *RTMPVideo) DecodeConfig(track *AVTrack) error {
return err
}
if VideoCodecID(b0&0x0F) == CodecID_H265 {
track.Codec = "h265"
track.Codec = codec.FourCC_H265
} else {
track.Codec = "h264"
track.Codec = codec.FourCC_H264
}
_, err = reader.ReadBE(3) // cts == 0
if err != nil {
@@ -89,9 +87,12 @@ func (avcc *RTMPVideo) DecodeConfig(track *AVTrack) error {
return nil
}
func (avcc *RTMPVideo) parseH264(track *AVTrack, reader *util.Buffers, cts uint32) (any, error) {
func (avcc *RTMPVideo) parseH264(ctx *H264Ctx, reader *util.Buffers) (any, error) {
cts, err := reader.ReadBE(3)
if err != nil {
return nil, err
}
var nalus Nalus
ctx := track.ICodecCtx.(*H264Ctx)
nalus.PTS = time.Duration(avcc.Timestamp+uint32(cts)) * 90
nalus.DTS = time.Duration(avcc.Timestamp) * 90
if err := nalus.ParseAVCC(reader, ctx.NalulenSize); err != nil {
@@ -100,9 +101,12 @@ func (avcc *RTMPVideo) parseH264(track *AVTrack, reader *util.Buffers, cts uint3
return nalus, nil
}
func (avcc *RTMPVideo) parseH265(track *AVTrack, reader *util.Buffers, cts uint32) (any, error) {
func (avcc *RTMPVideo) parseH265(ctx *H265Ctx, reader *util.Buffers) (any, error) {
cts, err := reader.ReadBE(3)
if err != nil {
return nil, err
}
var nalus Nalus
ctx := track.ICodecCtx.(*H265Ctx)
nalus.PTS = time.Duration(avcc.Timestamp+uint32(cts)) * 90
nalus.DTS = time.Duration(avcc.Timestamp) * 90
if err := nalus.ParseAVCC(reader, ctx.NalulenSize); err != nil {
@@ -111,7 +115,7 @@ func (avcc *RTMPVideo) parseH265(track *AVTrack, reader *util.Buffers, cts uint3
return nalus, nil
}
func (avcc *RTMPVideo) parseAV1(track *AVTrack, reader *util.Buffers) (any, error) {
func (avcc *RTMPVideo) parseAV1(reader *util.Buffers) (any, error) {
var obus OBUs
obus.PTS = time.Duration(avcc.Timestamp) * 90
if err := obus.ParseAVCC(reader); err != nil {
@@ -143,14 +147,10 @@ func (avcc *RTMPVideo) ToRaw(track *AVTrack) (any, error) {
}
return nil, nil
case PacketTypeCodedFrames:
if track.Codec == "h265" {
cts, err := reader.ReadBE(3) //cts, only h265
if err != nil {
return nil, err
}
return avcc.parseH265(track, &reader, uint32(cts))
if track.Codec == codec.FourCC_H265 {
return avcc.parseH265(track.ICodecCtx.(*H265Ctx), &reader)
} else {
return avcc.parseAV1(track, &reader)
return avcc.parseAV1(&reader)
}
case PacketTypeCodedFramesX:
}
@@ -159,19 +159,18 @@ func (avcc *RTMPVideo) ToRaw(track *AVTrack) (any, error) {
if err != nil {
return nil, err
}
cts, err := reader.ReadBE(3)
if err != nil {
return nil, err
}
if b0 == 0 {
if err = reader.Skip(3); err != nil {
return nil, err
}
if err = avcc.DecodeConfig(track); err != nil {
return nil, err
}
} else {
if track.Codec == "h265" {
return avcc.parseH265(track, &reader, uint32(cts))
if track.Codec == codec.FourCC_H265 {
return avcc.parseH265(track.ICodecCtx.(*H265Ctx), &reader)
} else {
return avcc.parseH264(track, &reader, uint32(cts))
return avcc.parseH264(track.ICodecCtx.(*H264Ctx), &reader)
}
}
}

View File

@@ -37,8 +37,7 @@ var (
Name: "Global",
Version: Version,
}
Servers = make([]*Server, 10)
errRestart = errors.New("restart")
Servers = make([]*Server, 10)
)
type Server struct {
@@ -102,7 +101,7 @@ func (s *Server) reset() {
}
func (s *Server) Run(ctx context.Context, conf any) (err error) {
for err = s.run(ctx, conf); err == errRestart; err = s.run(ctx, conf) {
for err = s.run(ctx, conf); err == ErrRestart; err = s.run(ctx, conf) {
s.reset()
}
return
@@ -148,15 +147,21 @@ func (s *Server) run(ctx context.Context, conf any) (err error) {
if httpConf.ListenAddrTLS != "" {
s.Info("https listen at ", "addr", httpConf.ListenAddrTLS)
go func() {
s.Stop(httpConf.ListenTLS())
}()
go func(addr string) {
if err := httpConf.ListenTLS(); err != http.ErrServerClosed {
s.Stop(err)
}
s.Info("https stop listen at ", "addr", addr)
}(httpConf.ListenAddrTLS)
}
if httpConf.ListenAddr != "" {
s.Info("http listen at ", "addr", httpConf.ListenAddr)
go func() {
s.Stop(httpConf.Listen())
}()
go func(addr string) {
if err := httpConf.Listen(); err != http.ErrServerClosed {
s.Stop(err)
}
s.Info("http stop listen at ", "addr", addr)
}(httpConf.ListenAddr)
}
if tcpConf.ListenAddr != "" {
var opts []grpc.ServerOption
@@ -173,9 +178,12 @@ func (s *Server) run(ctx context.Context, conf any) (err error) {
return err
}
defer lis.Close()
go func() {
s.Stop(s.grpcServer.Serve(lis))
}()
go func(addr string) {
if err := s.grpcServer.Serve(lis); err != nil {
s.Stop(err)
}
s.Info("grpc stop listen at ", "addr", addr)
}(tcpConf.ListenAddr)
}
for _, plugin := range plugins {
plugin.Init(s, cg[strings.ToLower(plugin.Name)])

26
test/server_test.go Normal file
View File

@@ -0,0 +1,26 @@
package test
import (
"context"
"testing"
"time"
"m7s.live/m7s/v5"
"m7s.live/m7s/v5/pkg"
)
func TestRestart(b *testing.T) {
ctx := context.TODO()
var server = m7s.NewServer()
go func() {
time.Sleep(time.Second * 2)
server.Stop(pkg.ErrRestart)
time.Sleep(time.Second * 2)
server.Stop(pkg.ErrRestart)
time.Sleep(time.Second * 2)
server.Stop(pkg.ErrStopFromAPI)
}()
if server.Run(ctx, "test") != pkg.ErrStopFromAPI {
b.Error("server.Run should return ErrStopFromAPI")
}
}