mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 01:15:52 +08:00
feat: add hdl pull、h265、av1
This commit is contained in:
42
README.md
Normal file
42
README.md
Normal 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
39
README_CN.md
Normal 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
2
api.go
@@ -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
2
go.mod
@@ -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
42
go.sum
@@ -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
14
pkg/codec/av1.go
Normal 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
104
pkg/codec/h264.go
Normal 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
88
pkg/codec/h265.go
Normal 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}
|
@@ -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
|
||||
|
@@ -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)
|
||||
})
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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")
|
||||
)
|
||||
|
@@ -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:"-"` //最近的关键帧位置,首屏渲染
|
||||
|
@@ -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
46
plugin/hdl/READEME.md
Normal 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
111
plugin/hdl/pkg/pull.go
Normal 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
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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 = ‘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")
|
||||
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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
server.go
32
server.go
@@ -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
26
test/server_test.go
Normal 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")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user