mirror of
https://github.com/Monibuca/engine.git
synced 2025-10-06 17:16:55 +08:00
配置合并和覆盖
This commit is contained in:
@@ -2,6 +2,7 @@ package codec
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -134,14 +135,7 @@ type ADTSVariableHeader struct {
|
|||||||
// NumberOfRawDataBlockInFrame, 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧
|
// NumberOfRawDataBlockInFrame, 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧
|
||||||
|
|
||||||
// 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
|
// 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
|
||||||
func ADTSToAudioSpecificConfig(data []byte) []byte {
|
|
||||||
profile := ((data[2] & 0xc0) >> 6) + 1
|
|
||||||
sampleRate := (data[2] & 0x3c) >> 2
|
|
||||||
channel := ((data[2] & 0x1) << 2) | ((data[3] & 0xc0) >> 6)
|
|
||||||
config1 := (profile << 3) | ((sampleRate & 0xe) >> 1)
|
|
||||||
config2 := ((sampleRate & 0x1) << 7) | (channel << 3)
|
|
||||||
return []byte{0xAF, 0x00, config1, config2}
|
|
||||||
}
|
|
||||||
func AudioSpecificConfigToADTS(asc AudioSpecificConfig, rawDataLength int) (adts ADTS, adtsByte []byte, err error) {
|
func AudioSpecificConfigToADTS(asc AudioSpecificConfig, rawDataLength int) (adts ADTS, adtsByte []byte, err error) {
|
||||||
if asc.ChannelConfiguration > 8 || asc.FrameLengthFlag > 13 {
|
if asc.ChannelConfiguration > 8 || asc.FrameLengthFlag > 13 {
|
||||||
err = errors.New("Reserved field.")
|
err = errors.New("Reserved field.")
|
||||||
@@ -218,3 +212,5 @@ func ParseRTPAAC(payload []byte) (result [][]byte) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
11
codec/flv.go
11
codec/flv.go
@@ -108,3 +108,14 @@ func ReadFLVTag(r io.Reader) (t byte, timestamp uint32, payload []byte, err erro
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func VideoAVCC2FLV(avcc net.Buffers, ts uint32) (flv net.Buffers) {
|
||||||
|
b := util.Buffer(make([]byte, 0, 15))
|
||||||
|
b.WriteByte(FLV_TAG_TYPE_VIDEO)
|
||||||
|
dataSize := util.SizeOfBuffers(avcc)
|
||||||
|
b.WriteUint24(uint32(dataSize))
|
||||||
|
b.WriteUint24(ts)
|
||||||
|
b.WriteByte(byte(ts >> 24))
|
||||||
|
b.WriteUint24(0)
|
||||||
|
return append(append(append(flv, b), avcc...), util.PutBE(b.Malloc(4), dataSize+11))
|
||||||
|
}
|
||||||
|
@@ -4,11 +4,12 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Monibuca/engine/v4/util"
|
"github.com/Monibuca/engine/v4/codec"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NALUSlice net.Buffers
|
type NALUSlice net.Buffers
|
||||||
|
|
||||||
// type H264Slice NALUSlice
|
// type H264Slice NALUSlice
|
||||||
// type H265Slice NALUSlice
|
// type H265Slice NALUSlice
|
||||||
|
|
||||||
@@ -82,14 +83,8 @@ func (av *AVFrame[T]) AppendRaw(raw ...T) {
|
|||||||
av.Raw = append(av.Raw, raw...)
|
av.Raw = append(av.Raw, raw...)
|
||||||
}
|
}
|
||||||
func (av *AVFrame[T]) FillFLV(t byte, ts uint32) {
|
func (av *AVFrame[T]) FillFLV(t byte, ts uint32) {
|
||||||
b := util.Buffer(make([]byte, 0, 15))
|
av.FLV = codec.VideoAVCC2FLV(av.AVCC, ts)
|
||||||
b.WriteByte(t)
|
av.FLV[0][0] = t
|
||||||
dataSize := util.SizeOfBuffers(av.AVCC)
|
|
||||||
b.WriteUint24(uint32(dataSize))
|
|
||||||
b.WriteUint24(ts)
|
|
||||||
b.WriteByte(byte(ts >> 24))
|
|
||||||
b.WriteUint24(0)
|
|
||||||
av.FLV = append(append(append(av.FLV, b), av.AVCC...), util.PutBE(b.Malloc(4), dataSize+11))
|
|
||||||
}
|
}
|
||||||
func (av *AVFrame[T]) AppendAVCC(avcc ...[]byte) {
|
func (av *AVFrame[T]) AppendAVCC(avcc ...[]byte) {
|
||||||
av.AVCC = append(av.AVCC, avcc...)
|
av.AVCC = append(av.AVCC, avcc...)
|
||||||
@@ -151,3 +146,8 @@ func (avcc AVCCFrame) AudioCodecID() byte {
|
|||||||
// }
|
// }
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
type DecoderConfiguration[T RawSlice] struct {
|
||||||
|
AVCC T
|
||||||
|
Raw T
|
||||||
|
FLV net.Buffers
|
||||||
|
}
|
||||||
|
207
config.go
207
config.go
@@ -1,207 +0,0 @@
|
|||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Second int
|
|
||||||
|
|
||||||
func (s Second) Duration() time.Duration {
|
|
||||||
return time.Duration(s) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
type PluginConfig interface {
|
|
||||||
Update(Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TCPPluginConfig interface {
|
|
||||||
PluginConfig
|
|
||||||
context.Context
|
|
||||||
ServeTCP(*net.TCPConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPPluginConfig interface {
|
|
||||||
PluginConfig
|
|
||||||
context.Context
|
|
||||||
http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config map[string]any
|
|
||||||
|
|
||||||
func (config Config) Unmarshal(s any) {
|
|
||||||
var el reflect.Value
|
|
||||||
if v, ok := s.(reflect.Value); ok {
|
|
||||||
el = v
|
|
||||||
} else {
|
|
||||||
el = reflect.ValueOf(s).Elem()
|
|
||||||
}
|
|
||||||
t := el.Type()
|
|
||||||
for k, v := range config {
|
|
||||||
var fv reflect.Value
|
|
||||||
value := reflect.ValueOf(v)
|
|
||||||
if f, ok := t.FieldByName(strings.ToUpper(k[:1]) + k[1:]); ok {
|
|
||||||
// 兼容首字母大写的属性
|
|
||||||
fv = el.FieldByName(f.Name)
|
|
||||||
} else if f, ok := t.FieldByName(strings.ToUpper(k)); ok {
|
|
||||||
// 兼容全部大写的属性
|
|
||||||
fv = el.FieldByName(f.Name)
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if t.Kind() == reflect.Slice {
|
|
||||||
l := value.Len()
|
|
||||||
s := reflect.MakeSlice(t.Elem(), l, value.Cap())
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
fv := value.Field(i)
|
|
||||||
if fv.Type() == reflect.TypeOf(config) {
|
|
||||||
fv.FieldByName("Unmarshal").Call([]reflect.Value{s.Field(i)})
|
|
||||||
} else {
|
|
||||||
s.Field(i).Set(fv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fv.Set(s)
|
|
||||||
} else if child, ok := v.(Config); ok {
|
|
||||||
child.Unmarshal(fv)
|
|
||||||
} else {
|
|
||||||
fv.Set(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config Config) Assign(source Config) {
|
|
||||||
for k, v := range source {
|
|
||||||
m, isMap := v.(map[string]any)
|
|
||||||
if _, ok := config[k]; !ok || !isMap {
|
|
||||||
config[k] = v
|
|
||||||
} else {
|
|
||||||
Config(config[k].(map[string]any)).Assign(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config Config) Has(key string) (ok bool) {
|
|
||||||
if config == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, ok = config[key]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type TCPConfig struct {
|
|
||||||
ListenAddr string
|
|
||||||
ListenNum int //同时并行监听数量,0为CPU核心数量
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tcp *TCPConfig) listen(l net.Listener, handler func(*net.TCPConn)) {
|
|
||||||
var tempDelay time.Duration
|
|
||||||
for {
|
|
||||||
conn, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
|
||||||
if tempDelay == 0 {
|
|
||||||
tempDelay = 5 * time.Millisecond
|
|
||||||
} else {
|
|
||||||
tempDelay *= 2
|
|
||||||
}
|
|
||||||
if max := 1 * time.Second; tempDelay > max {
|
|
||||||
tempDelay = max
|
|
||||||
}
|
|
||||||
log.Printf("%s: Accept error: %v; retrying in %v", tcp.ListenAddr, err, tempDelay)
|
|
||||||
time.Sleep(tempDelay)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conn.(*net.TCPConn).SetNoDelay(false)
|
|
||||||
tempDelay = 0
|
|
||||||
go handler(conn.(*net.TCPConn))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (tcp *TCPConfig) Listen(plugin TCPPluginConfig) error {
|
|
||||||
l, err := net.Listen("tcp", tcp.ListenAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
count := tcp.ListenNum
|
|
||||||
if count == 0 {
|
|
||||||
count = runtime.NumCPU()
|
|
||||||
}
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
go tcp.listen(l, plugin.ServeTCP)
|
|
||||||
}
|
|
||||||
<-plugin.Done()
|
|
||||||
return l.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPConfig struct {
|
|
||||||
ListenAddr string
|
|
||||||
ListenAddrTLS string
|
|
||||||
CertFile string
|
|
||||||
KeyFile string
|
|
||||||
CORS bool //是否自动添加CORS头
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAddrs Listen http and https
|
|
||||||
func (config *HTTPConfig) Listen(plugin HTTPPluginConfig) error {
|
|
||||||
var g errgroup.Group
|
|
||||||
if config.ListenAddrTLS != "" {
|
|
||||||
g.Go(func() error {
|
|
||||||
return http.ListenAndServeTLS(config.ListenAddrTLS, config.CertFile, config.KeyFile, plugin)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if config.ListenAddr != "" {
|
|
||||||
g.Go(func() error { return http.ListenAndServe(config.ListenAddr, plugin) })
|
|
||||||
}
|
|
||||||
g.Go(func() error {
|
|
||||||
<-plugin.Done()
|
|
||||||
return plugin.Err()
|
|
||||||
})
|
|
||||||
return g.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PublishConfig struct {
|
|
||||||
PubAudio bool
|
|
||||||
PubVideo bool
|
|
||||||
KillExit bool // 是否踢掉已经存在的发布者
|
|
||||||
PublishTimeout Second // 发布无数据超时
|
|
||||||
WaitCloseTimeout Second // 延迟自动关闭(无订阅时)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscribeConfig struct {
|
|
||||||
SubAudio bool
|
|
||||||
SubVideo bool
|
|
||||||
IFrameOnly bool // 只要关键帧
|
|
||||||
WaitTimeout Second // 等待流超时
|
|
||||||
}
|
|
||||||
|
|
||||||
type PullConfig struct {
|
|
||||||
AutoReconnect bool // 自动重连
|
|
||||||
PullOnStart bool // 启动时拉流
|
|
||||||
PullOnSubscribe bool // 订阅时自动拉流
|
|
||||||
AutoPullList map[string]string // 自动拉流列表
|
|
||||||
}
|
|
||||||
|
|
||||||
type PushConfig struct {
|
|
||||||
AutoPushList map[string]string // 自动推流列表
|
|
||||||
}
|
|
||||||
|
|
||||||
type EngineConfig struct {
|
|
||||||
*http.ServeMux
|
|
||||||
context.Context
|
|
||||||
Publish PublishConfig
|
|
||||||
Subscribe SubscribeConfig
|
|
||||||
HTTP HTTPConfig
|
|
||||||
RTPReorder bool
|
|
||||||
EnableAVCC bool //启用AVCC格式,rtmp协议使用
|
|
||||||
EnableRTP bool //启用RTP格式,rtsp、gb18181等协议使用
|
|
||||||
EnableFLV bool //开启FLV格式,hdl协议使用
|
|
||||||
}
|
|
151
config/config.go
Normal file
151
config/config.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config map[string]any
|
||||||
|
|
||||||
|
type Second int
|
||||||
|
|
||||||
|
func (s Second) Duration() time.Duration {
|
||||||
|
return time.Duration(s) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
Update(Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TCPPlugin interface {
|
||||||
|
Plugin
|
||||||
|
ServeTCP(*net.TCPConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPPlugin interface {
|
||||||
|
Plugin
|
||||||
|
http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) Unmarshal(s any) {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var el reflect.Value
|
||||||
|
if v, ok := s.(reflect.Value); ok {
|
||||||
|
el = v
|
||||||
|
} else {
|
||||||
|
el = reflect.ValueOf(s)
|
||||||
|
}
|
||||||
|
if el.Kind() == reflect.Pointer {
|
||||||
|
el = el.Elem()
|
||||||
|
}
|
||||||
|
t := el.Type()
|
||||||
|
|
||||||
|
//字段映射,小写对应的大写
|
||||||
|
nameMap := make(map[string]string)
|
||||||
|
for i, j := 0, t.NumField(); i < j; i++ {
|
||||||
|
name := t.Field(i).Name
|
||||||
|
nameMap[strings.ToLower(name)] = name
|
||||||
|
}
|
||||||
|
for k, v := range config {
|
||||||
|
value := reflect.ValueOf(v)
|
||||||
|
// 需要被写入的字段
|
||||||
|
fv := el.FieldByName(nameMap[k])
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
l := value.Len()
|
||||||
|
s := reflect.MakeSlice(t.Elem(), l, value.Cap())
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
fv := value.Field(i)
|
||||||
|
if fv.Type() == reflect.TypeOf(config) {
|
||||||
|
fv.FieldByName("Unmarshal").Call([]reflect.Value{s.Field(i)})
|
||||||
|
} else {
|
||||||
|
s.Field(i).Set(fv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fv.Set(s)
|
||||||
|
} else if child, ok := v.(Config); ok {
|
||||||
|
child.Unmarshal(fv)
|
||||||
|
} else {
|
||||||
|
fv.Set(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 覆盖配置
|
||||||
|
func (config Config) Assign(source Config) {
|
||||||
|
for k, v := range source {
|
||||||
|
switch m := config[k].(type) {
|
||||||
|
case Config:
|
||||||
|
m.Assign(v.(Config))
|
||||||
|
default:
|
||||||
|
config[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并配置,不覆盖
|
||||||
|
func (config Config) Merge(source Config) {
|
||||||
|
for k, v := range source {
|
||||||
|
if _, ok := config[k]; !ok {
|
||||||
|
switch m := config[k].(type) {
|
||||||
|
case Config:
|
||||||
|
m.Merge(v.(Config))
|
||||||
|
default:
|
||||||
|
config[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) Set(key string, value any) {
|
||||||
|
config[strings.ToLower(key)] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) Has(key string) (ok bool) {
|
||||||
|
_, ok = config[strings.ToLower(key)]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) HasChild(key string) (ok bool) {
|
||||||
|
_, ok = config[strings.ToLower(key)].(Config)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) GetChild(key string) Config {
|
||||||
|
return config[strings.ToLower(key)].(Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Struct2Config(s any) (config Config) {
|
||||||
|
var t reflect.Type
|
||||||
|
var v reflect.Value
|
||||||
|
if vv, ok := s.(reflect.Value); ok {
|
||||||
|
v = vv
|
||||||
|
t = vv.Type()
|
||||||
|
} else {
|
||||||
|
t = reflect.TypeOf(s)
|
||||||
|
v = reflect.ValueOf(s)
|
||||||
|
if t.Kind() == reflect.Pointer {
|
||||||
|
v = v.Elem()
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, j := 0, t.NumField(); i < j; i++ {
|
||||||
|
ft := t.Field(i)
|
||||||
|
switch ft.Type.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
config[ft.Name] = Struct2Config(v.Field(i))
|
||||||
|
case reflect.Slice:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
if config == nil {
|
||||||
|
config = make(Config)
|
||||||
|
}
|
||||||
|
reflect.ValueOf(config).SetMapIndex(reflect.ValueOf(strings.ToLower(ft.Name)), v.Field(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
34
config/http.go
Normal file
34
config/http.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTP struct {
|
||||||
|
ListenAddr string
|
||||||
|
ListenAddrTLS string
|
||||||
|
CertFile string
|
||||||
|
KeyFile string
|
||||||
|
CORS bool //是否自动添加CORS头
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAddrs Listen http and https
|
||||||
|
func (config *HTTP) Listen(ctx context.Context, plugin HTTPPlugin) error {
|
||||||
|
var g errgroup.Group
|
||||||
|
if config.ListenAddrTLS != "" {
|
||||||
|
g.Go(func() error {
|
||||||
|
return http.ListenAndServeTLS(config.ListenAddrTLS, config.CertFile, config.KeyFile, plugin)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if config.ListenAddr != "" {
|
||||||
|
g.Go(func() error { return http.ListenAndServe(config.ListenAddr, plugin) })
|
||||||
|
}
|
||||||
|
g.Go(func() error {
|
||||||
|
<-ctx.Done()
|
||||||
|
return ctx.Err()
|
||||||
|
})
|
||||||
|
return g.Wait()
|
||||||
|
}
|
55
config/tcp.go
Normal file
55
config/tcp.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCP struct {
|
||||||
|
ListenAddr string
|
||||||
|
ListenNum int //同时并行监听数量,0为CPU核心数量
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tcp *TCP) listen(l net.Listener, handler func(*net.TCPConn)) {
|
||||||
|
var tempDelay time.Duration
|
||||||
|
for {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||||
|
if tempDelay == 0 {
|
||||||
|
tempDelay = 5 * time.Millisecond
|
||||||
|
} else {
|
||||||
|
tempDelay *= 2
|
||||||
|
}
|
||||||
|
if max := 1 * time.Second; tempDelay > max {
|
||||||
|
tempDelay = max
|
||||||
|
}
|
||||||
|
log.Printf("%s: Accept error: %v; retrying in %v", tcp.ListenAddr, err, tempDelay)
|
||||||
|
time.Sleep(tempDelay)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.(*net.TCPConn).SetNoDelay(false)
|
||||||
|
tempDelay = 0
|
||||||
|
go handler(conn.(*net.TCPConn))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (tcp *TCP) Listen(ctx context.Context, plugin TCPPlugin) error {
|
||||||
|
l, err := net.Listen("tcp", tcp.ListenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
count := tcp.ListenNum
|
||||||
|
if count == 0 {
|
||||||
|
count = runtime.NumCPU()
|
||||||
|
}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
go tcp.listen(l, plugin.ServeTCP)
|
||||||
|
}
|
||||||
|
<-ctx.Done()
|
||||||
|
return l.Close()
|
||||||
|
}
|
48
config/types.go
Normal file
48
config/types.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Publish struct {
|
||||||
|
PubAudio bool
|
||||||
|
PubVideo bool
|
||||||
|
KillExit bool // 是否踢掉已经存在的发布者
|
||||||
|
PublishTimeout Second // 发布无数据超时
|
||||||
|
WaitCloseTimeout Second // 延迟自动关闭(无订阅时)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscribe struct {
|
||||||
|
SubAudio bool
|
||||||
|
SubVideo bool
|
||||||
|
IFrameOnly bool // 只要关键帧
|
||||||
|
WaitTimeout Second // 等待流超时
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pull struct {
|
||||||
|
AutoReconnect bool // 自动重连
|
||||||
|
PullOnStart bool // 启动时拉流
|
||||||
|
PullOnSubscribe bool // 订阅时自动拉流
|
||||||
|
AutoPullList map[string]string // 自动拉流列表
|
||||||
|
}
|
||||||
|
|
||||||
|
type Push struct {
|
||||||
|
AutoPushList map[string]string // 自动推流列表
|
||||||
|
}
|
||||||
|
|
||||||
|
type Engine struct {
|
||||||
|
Publish
|
||||||
|
Subscribe
|
||||||
|
HTTP
|
||||||
|
RTPReorder bool
|
||||||
|
EnableAVCC bool //启用AVCC格式,rtmp协议使用
|
||||||
|
EnableRTP bool //启用RTP格式,rtsp、gb18181等协议使用
|
||||||
|
EnableFLV bool //开启FLV格式,hdl协议使用
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Engine) Update(override Config) {
|
||||||
|
override.Unmarshal(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Global = &Engine{
|
||||||
|
Publish{true, true, false, 10, 10},
|
||||||
|
Subscribe{true, true, false, 10},
|
||||||
|
HTTP{ListenAddr: ":8080", CORS: true},
|
||||||
|
false, true, true, true,
|
||||||
|
}
|
40
http.go
40
http.go
@@ -4,31 +4,31 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Monibuca/engine/v4/config"
|
||||||
"github.com/Monibuca/engine/v4/util"
|
"github.com/Monibuca/engine/v4/util"
|
||||||
. "github.com/logrusorgru/aurora"
|
. "github.com/logrusorgru/aurora"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (config *EngineConfig) Update(override Config) {
|
type GlobalConfig struct {
|
||||||
override.Unmarshal(config)
|
*http.ServeMux
|
||||||
if config.Context == nil {
|
*config.Engine
|
||||||
config.Context = Ctx
|
|
||||||
handleFunc("/sysInfo", sysInfo)
|
|
||||||
handleFunc("/closeStream", closeStream)
|
|
||||||
util.Print(Green("api server start at "), BrightBlue(config.HTTP.ListenAddr), BrightBlue(config.HTTP.ListenAddrTLS))
|
|
||||||
config.HTTP.Listen(config)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
|
func (cfg *GlobalConfig) Update(override config.Config) {
|
||||||
config.HandleFunc("/api"+pattern, func(rw http.ResponseWriter, r *http.Request) {
|
cfg.Engine.Update(override)
|
||||||
if config.HTTP.CORS {
|
Engine.RawConfig = config.Struct2Config(cfg.Engine)
|
||||||
util.CORS(rw, r)
|
util.Print(Green("api server start at "), BrightBlue(cfg.ListenAddr), BrightBlue(cfg.ListenAddrTLS))
|
||||||
}
|
cfg.Listen(Engine, cfg)
|
||||||
handler(rw, r)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeStream(w http.ResponseWriter, r *http.Request) {
|
func (config *GlobalConfig) API_sysInfo(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
json.NewEncoder(rw).Encode(&struct {
|
||||||
|
Version string
|
||||||
|
StartTime string
|
||||||
|
}{Engine.Version, StartTime.Format("2006-01-02 15:04:05")})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *GlobalConfig) API_closeStream(w http.ResponseWriter, r *http.Request) {
|
||||||
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
|
if streamPath := r.URL.Query().Get("stream"); streamPath != "" {
|
||||||
if s := Streams.Get(streamPath); s != nil {
|
if s := Streams.Get(streamPath); s != nil {
|
||||||
s.Close()
|
s.Close()
|
||||||
@@ -40,9 +40,3 @@ func closeStream(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte("no query stream"))
|
w.Write([]byte("no query stream"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func sysInfo(w http.ResponseWriter, r *http.Request) {
|
|
||||||
json.NewEncoder(w).Encode(&struct {
|
|
||||||
Version string
|
|
||||||
StartTime string
|
|
||||||
}{Version, StartTime.Format("2006-01-02 15:04:05")})
|
|
||||||
}
|
|
||||||
|
124
main.go
124
main.go
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time" // colorable
|
"time" // colorable
|
||||||
|
|
||||||
|
"github.com/Monibuca/engine/v4/config"
|
||||||
"github.com/Monibuca/engine/v4/util"
|
"github.com/Monibuca/engine/v4/util"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
@@ -20,37 +21,25 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "4.0.0"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultPublishConfig = PublishConfig{
|
|
||||||
true, true, false, 10, 10,
|
|
||||||
}
|
|
||||||
DefaultSubscribeConfig = SubscribeConfig{
|
|
||||||
true, true, false, 10,
|
|
||||||
}
|
|
||||||
config = &EngineConfig{
|
|
||||||
http.NewServeMux(),
|
|
||||||
Ctx,
|
|
||||||
DefaultPublishConfig,
|
|
||||||
DefaultSubscribeConfig,
|
|
||||||
HTTPConfig{ListenAddr: ":8080", CORS: true},
|
|
||||||
false, true, true, true,
|
|
||||||
}
|
|
||||||
// ConfigRaw 配置信息的原始数据
|
// ConfigRaw 配置信息的原始数据
|
||||||
ConfigRaw []byte
|
ConfigRaw []byte
|
||||||
StartTime time.Time //启动时间
|
StartTime time.Time //启动时间
|
||||||
Plugins = make(map[string]*Plugin) // Plugins 所有的插件配置
|
Plugins = make(map[string]*Plugin) // Plugins 所有的插件配置
|
||||||
Ctx context.Context
|
|
||||||
settingDir string
|
settingDir string
|
||||||
|
EngineConfig = &GlobalConfig{
|
||||||
|
Engine: config.Global,
|
||||||
|
ServeMux: http.NewServeMux(),
|
||||||
|
}
|
||||||
|
Engine = InstallPlugin(EngineConfig)
|
||||||
)
|
)
|
||||||
|
|
||||||
func InstallPlugin(config PluginConfig) *Plugin {
|
func InstallPlugin[T config.Plugin](config T) *Plugin {
|
||||||
name := strings.TrimSuffix(reflect.TypeOf(config).Elem().Name(), "Config")
|
t := reflect.TypeOf(config).Elem()
|
||||||
|
name := strings.TrimSuffix(t.Name(), "Config")
|
||||||
plugin := &Plugin{
|
plugin := &Plugin{
|
||||||
Name: name,
|
Name: name,
|
||||||
Config: config,
|
Config: config,
|
||||||
Modified: make(Config),
|
|
||||||
}
|
}
|
||||||
_, pluginFilePath, _, _ := runtime.Caller(1)
|
_, pluginFilePath, _, _ := runtime.Caller(1)
|
||||||
configDir := filepath.Dir(pluginFilePath)
|
configDir := filepath.Dir(pluginFilePath)
|
||||||
@@ -65,24 +54,20 @@ func InstallPlugin(config PluginConfig) *Plugin {
|
|||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugin 插件配置定义
|
// Plugin 插件信息
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
|
context.Context `json:"-"`
|
||||||
|
context.CancelFunc `json:"-"`
|
||||||
Name string //插件名称
|
Name string //插件名称
|
||||||
Config PluginConfig //插件配置
|
Config config.Plugin //插件配置
|
||||||
Version string //插件版本
|
Version string //插件版本
|
||||||
RawConfig Config //配置的map形式方便查询
|
RawConfig config.Config //配置的map形式方便查询
|
||||||
Modified Config //修改过的配置项
|
Modified config.Config //修改过的配置项
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if parts := strings.Split(util.CurrentDir(), "@"); len(parts) > 1 {
|
|
||||||
Version = parts[len(parts)-1]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run 启动Monibuca引擎
|
// Run 启动Monibuca引擎
|
||||||
func Run(ctx context.Context, configFile string) (err error) {
|
func Run(ctx context.Context, configFile string) (err error) {
|
||||||
Ctx = ctx
|
Engine.Context = ctx
|
||||||
if err := util.CreateShutdownScript(); err != nil {
|
if err := util.CreateShutdownScript(); err != nil {
|
||||||
log.Print(Red("create shutdown script error:"), err)
|
log.Print(Red("create shutdown script error:"), err)
|
||||||
}
|
}
|
||||||
@@ -95,29 +80,24 @@ func Run(ctx context.Context, configFile string) (err error) {
|
|||||||
log.Print(Red("create dir .m7s error:"), err)
|
log.Print(Red("create dir .m7s error:"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
util.Print(BgGreen(Black("Ⓜ starting m7s ")), BrightBlue(Version))
|
util.Print(BgGreen(White("Ⓜ starting m7s ")))
|
||||||
var cg Config
|
var cg config.Config
|
||||||
var engineCg Config
|
|
||||||
if ConfigRaw != nil {
|
if ConfigRaw != nil {
|
||||||
if err = yaml.Unmarshal(ConfigRaw, &cg); err == nil {
|
if err = yaml.Unmarshal(ConfigRaw, &cg); err == nil {
|
||||||
if cfg, ok := cg["engine"]; ok {
|
Engine.RawConfig = cg.GetChild("global")
|
||||||
engineCg = cfg.(Config)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Engine.registerHandler()
|
||||||
go config.Update(engineCg)
|
go EngineConfig.Update(Engine.RawConfig)
|
||||||
for name, config := range Plugins {
|
for name, config := range Plugins {
|
||||||
if v, ok := cg[strings.ToLower(name)]; ok {
|
config.RawConfig = cg.GetChild(name)
|
||||||
config.RawConfig = v.(Config)
|
config.assign()
|
||||||
}
|
}
|
||||||
config.merge()
|
|
||||||
}
|
|
||||||
|
|
||||||
UUID := uuid.NewString()
|
UUID := uuid.NewString()
|
||||||
reportTimer := time.NewTimer(time.Minute)
|
reportTimer := time.NewTimer(time.Minute)
|
||||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://monibuca.com:2022/report/engine", nil)
|
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://monibuca.com:2022/report/engine", nil)
|
||||||
req.Header.Set("os", runtime.GOOS)
|
req.Header.Set("os", runtime.GOOS)
|
||||||
req.Header.Set("version", Version)
|
req.Header.Set("version", Engine.Version)
|
||||||
req.Header.Set("uuid", UUID)
|
req.Header.Set("uuid", UUID)
|
||||||
var c http.Client
|
var c http.Client
|
||||||
for {
|
for {
|
||||||
@@ -132,13 +112,19 @@ func Run(ctx context.Context, configFile string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Plugin) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
|
func (opt *Plugin) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
|
||||||
|
if opt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
var cors bool
|
var cors bool
|
||||||
if v, ok := opt.RawConfig["cors"]; ok {
|
if v, ok := opt.RawConfig["cors"]; ok {
|
||||||
cors = v.(bool)
|
cors = v.(bool)
|
||||||
} else if config.HTTP.CORS {
|
} else if EngineConfig.CORS {
|
||||||
cors = true
|
cors = true
|
||||||
}
|
}
|
||||||
config.HandleFunc("/"+strings.ToLower(opt.Name)+pattern, func(rw http.ResponseWriter, r *http.Request) {
|
if opt != Engine {
|
||||||
|
pattern = "/" + strings.ToLower(opt.Name) + pattern
|
||||||
|
}
|
||||||
|
Engine.HandleFunc(pattern, func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if cors {
|
if cors {
|
||||||
util.CORS(rw, r)
|
util.CORS(rw, r)
|
||||||
}
|
}
|
||||||
@@ -147,11 +133,16 @@ func (opt *Plugin) HandleFunc(pattern string, handler func(http.ResponseWriter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Plugin) HandleApi(pattern string, handler func(http.ResponseWriter, *http.Request)) {
|
func (opt *Plugin) HandleApi(pattern string, handler func(http.ResponseWriter, *http.Request)) {
|
||||||
opt.HandleFunc("/api"+pattern, handler)
|
if opt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pattern = "/api" + pattern
|
||||||
|
util.Println("http handle added:", pattern)
|
||||||
|
opt.HandleFunc(pattern, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取独立配置合并入总配置中
|
// 读取独立配置合并入总配置中
|
||||||
func (opt *Plugin) merge() {
|
func (opt *Plugin) assign() {
|
||||||
f, err := os.Open(opt.settingPath())
|
f, err := os.Open(opt.settingPath())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = yaml.NewDecoder(f).Decode(&opt.Modified); err == nil {
|
if err = yaml.NewDecoder(f).Decode(&opt.Modified); err == nil {
|
||||||
@@ -162,8 +153,45 @@ func (opt *Plugin) merge() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
t := reflect.TypeOf(opt.Config).Elem()
|
||||||
|
// 用全局配置覆盖没有设置的配置
|
||||||
|
for i, j := 0, t.NumField(); i < j; i++ {
|
||||||
|
fname := t.Field(i).Name
|
||||||
|
if Engine.RawConfig.Has(fname) {
|
||||||
|
if !opt.RawConfig.Has(fname) {
|
||||||
|
opt.RawConfig.Set(fname, Engine.RawConfig[fname])
|
||||||
|
} else if opt.RawConfig.HasChild(fname) {
|
||||||
|
opt.RawConfig.GetChild(fname).Merge(Engine.RawConfig.GetChild(fname))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt.registerHandler()
|
||||||
|
opt.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *Plugin) Update() {
|
||||||
|
if opt.CancelFunc != nil {
|
||||||
|
opt.CancelFunc()
|
||||||
|
}
|
||||||
|
opt.Context, opt.CancelFunc = context.WithCancel(Engine)
|
||||||
go opt.Config.Update(opt.RawConfig)
|
go opt.Config.Update(opt.RawConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opt *Plugin) registerHandler() {
|
||||||
|
t := reflect.TypeOf(opt.Config).Elem()
|
||||||
|
v := reflect.ValueOf(opt.Config).Elem()
|
||||||
|
// 注册http响应
|
||||||
|
for i, j := 0, t.NumMethod(); i < j; i++ {
|
||||||
|
mt := t.Method(i)
|
||||||
|
if strings.HasPrefix(mt.Name, "API") {
|
||||||
|
parts := strings.Split(mt.Name, "_")
|
||||||
|
parts[0] = ""
|
||||||
|
patten := reflect.ValueOf(strings.Join(parts, "/"))
|
||||||
|
reflect.ValueOf(opt.HandleApi).Call([]reflect.Value{patten, v.Method(i)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (opt *Plugin) settingPath() string {
|
func (opt *Plugin) settingPath() string {
|
||||||
return filepath.Join(settingDir, strings.ToLower(opt.Name)+".yaml")
|
return filepath.Join(settingDir, strings.ToLower(opt.Name)+".yaml")
|
||||||
}
|
}
|
||||||
|
28
publisher.go
28
publisher.go
@@ -1,8 +1,11 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Monibuca/engine/v4/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IPublisher interface {
|
type IPublisher interface {
|
||||||
@@ -12,26 +15,28 @@ type IPublisher interface {
|
|||||||
|
|
||||||
type Publisher struct {
|
type Publisher struct {
|
||||||
Type string
|
Type string
|
||||||
PullURL *url.URL
|
|
||||||
*Stream `json:"-"`
|
*Stream `json:"-"`
|
||||||
Config PublishConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pub *Publisher) Publish(streamPath string, realPub IPublisher) bool {
|
func (pub *Publisher) Publish(streamPath string, realPub IPublisher, config config.Publish) bool {
|
||||||
Streams.Lock()
|
Streams.Lock()
|
||||||
defer Streams.Unlock()
|
defer Streams.Unlock()
|
||||||
s, created := findOrCreateStream(streamPath, time.Second)
|
s, created := findOrCreateStream(streamPath, time.Second)
|
||||||
if s.IsClosed() {
|
if s.IsClosed() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if s.Publisher != nil && pub.Config.KillExit {
|
if s.Publisher != nil {
|
||||||
|
if config.KillExit {
|
||||||
s.Publisher.Close()
|
s.Publisher.Close()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub.Stream = s
|
pub.Stream = s
|
||||||
s.Publisher = realPub
|
s.Publisher = realPub
|
||||||
if created {
|
if created {
|
||||||
s.PublishTimeout = pub.Config.PublishTimeout.Duration()
|
s.PublishTimeout = config.PublishTimeout.Duration()
|
||||||
s.WaitCloseTimeout = pub.Config.WaitCloseTimeout.Duration()
|
s.WaitCloseTimeout = config.WaitCloseTimeout.Duration()
|
||||||
go s.run()
|
go s.run()
|
||||||
}
|
}
|
||||||
s.actionChan <- PublishAction{}
|
s.actionChan <- PublishAction{}
|
||||||
@@ -41,3 +46,14 @@ func (pub *Publisher) Publish(streamPath string, realPub IPublisher) bool {
|
|||||||
func (pub *Publisher) OnStateChange(oldState StreamState, newState StreamState) bool {
|
func (pub *Publisher) OnStateChange(oldState StreamState, newState StreamState) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用于远程拉流的发布者
|
||||||
|
type Puller struct {
|
||||||
|
Publisher
|
||||||
|
RemoteURL *url.URL
|
||||||
|
io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (puller *Puller) Close() {
|
||||||
|
puller.ReadCloser.Close()
|
||||||
|
}
|
||||||
|
13
stream.go
13
stream.go
@@ -65,6 +65,17 @@ var StreamFSM = [STATE_DESTROYED + 1]map[StreamAction]StreamState{
|
|||||||
// Streams 所有的流集合
|
// Streams 所有的流集合
|
||||||
var Streams = util.Map[string, *Stream]{Map: make(map[string]*Stream)}
|
var Streams = util.Map[string, *Stream]{Map: make(map[string]*Stream)}
|
||||||
|
|
||||||
|
func FilterStreams[T IPublisher]() (ss []*Stream) {
|
||||||
|
Streams.RLock()
|
||||||
|
defer Streams.RUnlock()
|
||||||
|
for _, s := range Streams.Map {
|
||||||
|
if _, ok := s.Publisher.(T); ok {
|
||||||
|
ss = append(ss, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type UnSubscibeAction *Subscriber
|
type UnSubscibeAction *Subscriber
|
||||||
type PublishAction struct{}
|
type PublishAction struct{}
|
||||||
type UnPublishAction struct{}
|
type UnPublishAction struct{}
|
||||||
@@ -125,7 +136,7 @@ func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream
|
|||||||
s.actionChan = make(chan any, 1)
|
s.actionChan = make(chan any, 1)
|
||||||
s.StartTime = time.Now()
|
s.StartTime = time.Now()
|
||||||
s.timeout = time.NewTimer(waitTimeout)
|
s.timeout = time.NewTimer(waitTimeout)
|
||||||
s.Context, s.cancel = context.WithCancel(Ctx)
|
s.Context, s.cancel = context.WithCancel(Engine)
|
||||||
s.Init(s)
|
s.Init(s)
|
||||||
return s, true
|
return s, true
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/Monibuca/engine/v4/common"
|
. "github.com/Monibuca/engine/v4/common"
|
||||||
|
"github.com/Monibuca/engine/v4/config"
|
||||||
"github.com/Monibuca/engine/v4/track"
|
"github.com/Monibuca/engine/v4/track"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ type VideoFrame AVFrame[NALUSlice]
|
|||||||
type Subscriber struct {
|
type Subscriber struct {
|
||||||
context.Context `json:"-"`
|
context.Context `json:"-"`
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
Config SubscribeConfig
|
Config config.Subscribe
|
||||||
Stream *Stream `json:"-"`
|
Stream *Stream `json:"-"`
|
||||||
ID string
|
ID string
|
||||||
TotalDrop int //总丢帧
|
TotalDrop int //总丢帧
|
||||||
@@ -39,7 +40,7 @@ func (s *Subscriber) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Subscribe 开始订阅 将Subscriber与Stream关联
|
//Subscribe 开始订阅 将Subscriber与Stream关联
|
||||||
func (sub *Subscriber) Subscribe(streamPath string, config SubscribeConfig) bool {
|
func (sub *Subscriber) Subscribe(streamPath string, config config.Subscribe) bool {
|
||||||
Streams.Lock()
|
Streams.Lock()
|
||||||
defer Streams.Unlock()
|
defer Streams.Unlock()
|
||||||
s, created := findOrCreateStream(streamPath, config.WaitTimeout.Duration())
|
s, created := findOrCreateStream(streamPath, config.WaitTimeout.Duration())
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package track
|
package track
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Monibuca/engine/v4/codec"
|
"github.com/Monibuca/engine/v4/codec"
|
||||||
@@ -21,8 +22,7 @@ type AAC Audio
|
|||||||
|
|
||||||
func (aac *AAC) WriteAVCC(ts uint32, frame AVCCFrame) {
|
func (aac *AAC) WriteAVCC(ts uint32, frame AVCCFrame) {
|
||||||
if frame.IsSequence() {
|
if frame.IsSequence() {
|
||||||
aac.DecoderConfiguration.Reset()
|
aac.DecoderConfiguration.AVCC = AudioSlice(frame)
|
||||||
aac.DecoderConfiguration.AppendAVCC(frame)
|
|
||||||
config1, config2 := frame[2], frame[3]
|
config1, config2 := frame[2], frame[3]
|
||||||
//audioObjectType = (config1 & 0xF8) >> 3
|
//audioObjectType = (config1 & 0xF8) >> 3
|
||||||
// 1 AAC MAIN ISO/IEC 14496-3 subpart 4
|
// 1 AAC MAIN ISO/IEC 14496-3 subpart 4
|
||||||
@@ -31,8 +31,8 @@ func (aac *AAC) WriteAVCC(ts uint32, frame AVCCFrame) {
|
|||||||
// 4 AAC LTP ISO/IEC 14496-3 subpart 4
|
// 4 AAC LTP ISO/IEC 14496-3 subpart 4
|
||||||
aac.Channels = ((config2 >> 3) & 0x0F) //声道
|
aac.Channels = ((config2 >> 3) & 0x0F) //声道
|
||||||
aac.SampleRate = uint32(codec.SamplingFrequencies[((config1&0x7)<<1)|(config2>>7)])
|
aac.SampleRate = uint32(codec.SamplingFrequencies[((config1&0x7)<<1)|(config2>>7)])
|
||||||
aac.DecoderConfiguration.AppendRaw(AudioSlice(frame[2:]))
|
aac.DecoderConfiguration.Raw = AudioSlice(frame[2:])
|
||||||
aac.DecoderConfiguration.FillFLV(codec.FLV_TAG_TYPE_AUDIO, 0)
|
aac.DecoderConfiguration.FLV = net.Buffers{adcflv1, frame, adcflv2}
|
||||||
} else {
|
} else {
|
||||||
(*Audio)(aac).WriteAVCC(ts, frame)
|
(*Audio)(aac).WriteAVCC(ts, frame)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package track
|
package track
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Monibuca/engine/v4/codec"
|
"github.com/Monibuca/engine/v4/codec"
|
||||||
@@ -8,6 +9,9 @@ import (
|
|||||||
"github.com/Monibuca/engine/v4/util"
|
"github.com/Monibuca/engine/v4/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var adcflv1 = []byte{codec.FLV_TAG_TYPE_AUDIO, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0}
|
||||||
|
var adcflv2 = []byte{0, 0, 0, 15}
|
||||||
|
|
||||||
type Audio struct {
|
type Audio struct {
|
||||||
Media[AudioSlice]
|
Media[AudioSlice]
|
||||||
Channels byte
|
Channels byte
|
||||||
@@ -36,12 +40,18 @@ func (at *Audio) Play(onAudio func(*AVFrame[AudioSlice]) error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (at *Audio) WriteADTS(adts []byte) {
|
func (at *Audio) WriteADTS(adts []byte) {
|
||||||
at.SampleRate = uint32(codec.SamplingFrequencies[(adts[2]&0x3c)>>2])
|
profile := ((adts[2] & 0xc0) >> 6) + 1
|
||||||
at.Channels = ((adts[2] & 0x1) << 2) | ((adts[3] & 0xc0) >> 6)
|
sampleRate := (adts[2] & 0x3c) >> 2
|
||||||
at.DecoderConfiguration.AppendAVCC(codec.ADTSToAudioSpecificConfig(adts))
|
channel := ((adts[2] & 0x1) << 2) | ((adts[3] & 0xc0) >> 6)
|
||||||
at.DecoderConfiguration.AppendRaw(at.DecoderConfiguration.AVCC[0][2:])
|
config1 := (profile << 3) | ((sampleRate & 0xe) >> 1)
|
||||||
at.DecoderConfiguration.FillFLV(codec.FLV_TAG_TYPE_AUDIO, 0)
|
config2 := ((sampleRate & 0x1) << 7) | (channel << 3)
|
||||||
|
at.SampleRate = uint32(codec.SamplingFrequencies[sampleRate])
|
||||||
|
at.Channels = channel
|
||||||
|
at.DecoderConfiguration.AVCC = []byte{0xAF, 0x00, config1, config2}
|
||||||
|
at.DecoderConfiguration.Raw = at.DecoderConfiguration.AVCC[:2]
|
||||||
|
at.DecoderConfiguration.FLV = net.Buffers{adcflv1, at.DecoderConfiguration.AVCC, adcflv2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (at *Audio) WriteAVCC(ts uint32, frame AVCCFrame) {
|
func (at *Audio) WriteAVCC(ts uint32, frame AVCCFrame) {
|
||||||
at.Media.WriteAVCC(ts, frame)
|
at.Media.WriteAVCC(ts, frame)
|
||||||
at.Flush()
|
at.Flush()
|
||||||
|
@@ -29,7 +29,7 @@ type Media[T RawSlice] struct {
|
|||||||
CodecID byte
|
CodecID byte
|
||||||
SampleRate uint32
|
SampleRate uint32
|
||||||
SampleSize byte
|
SampleSize byte
|
||||||
DecoderConfiguration AVFrame[T] `json:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config)
|
DecoderConfiguration DecoderConfiguration[T] `json:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config)
|
||||||
util.BytesPool //无锁内存池,用于发布者(在同一个协程中)复用小块的内存,通常是解包时需要临时使用
|
util.BytesPool //无锁内存池,用于发布者(在同一个协程中)复用小块的内存,通常是解包时需要临时使用
|
||||||
lastAvccTS uint32 //上一个avcc帧的时间戳
|
lastAvccTS uint32 //上一个avcc帧的时间戳
|
||||||
}
|
}
|
||||||
|
@@ -28,20 +28,29 @@ func (vt *H264) WriteAnnexB(pts uint32, dts uint32, frame AnnexBFrame) {
|
|||||||
func (vt *H264) WriteSlice(slice NALUSlice) {
|
func (vt *H264) WriteSlice(slice NALUSlice) {
|
||||||
switch slice.H264Type() {
|
switch slice.H264Type() {
|
||||||
case codec.NALU_SPS:
|
case codec.NALU_SPS:
|
||||||
vt.DecoderConfiguration.Reset()
|
if len(vt.DecoderConfiguration.Raw) > 0 {
|
||||||
vt.DecoderConfiguration.AppendRaw(slice)
|
vt.DecoderConfiguration.Raw = vt.DecoderConfiguration.Raw[:0]
|
||||||
|
}
|
||||||
|
vt.DecoderConfiguration.Raw = append(vt.DecoderConfiguration.Raw, slice[0])
|
||||||
case codec.NALU_PPS:
|
case codec.NALU_PPS:
|
||||||
vt.DecoderConfiguration.AppendRaw(slice)
|
vt.DecoderConfiguration.Raw = append(vt.DecoderConfiguration.Raw, slice[0])
|
||||||
vt.SPSInfo, _ = codec.ParseSPS(slice[0])
|
vt.SPSInfo, _ = codec.ParseSPS(slice[0])
|
||||||
lenSPS := util.SizeOfBuffers(net.Buffers(vt.DecoderConfiguration.Raw[0]))
|
if len(vt.DecoderConfiguration.Raw) > 0 {
|
||||||
lenPPS := util.SizeOfBuffers(net.Buffers(vt.DecoderConfiguration.Raw[1]))
|
vt.DecoderConfiguration.Raw = vt.DecoderConfiguration.Raw[:0]
|
||||||
|
}
|
||||||
|
lenSPS := len(vt.DecoderConfiguration.Raw[0])
|
||||||
|
lenPPS := len(vt.DecoderConfiguration.Raw[1])
|
||||||
|
if len(vt.DecoderConfiguration.AVCC) > 0 {
|
||||||
|
vt.DecoderConfiguration.AVCC = vt.DecoderConfiguration.AVCC[:0]
|
||||||
|
}
|
||||||
if lenSPS > 3 {
|
if lenSPS > 3 {
|
||||||
vt.DecoderConfiguration.AppendAVCC(codec.RTMP_AVC_HEAD[:6], vt.DecoderConfiguration.Raw[0][0][1:4])
|
vt.DecoderConfiguration.AVCC = append(vt.DecoderConfiguration.AVCC, codec.RTMP_AVC_HEAD[:6], vt.DecoderConfiguration.Raw[0][1:4])
|
||||||
} else {
|
} else {
|
||||||
vt.DecoderConfiguration.AppendAVCC(codec.RTMP_AVC_HEAD)
|
vt.DecoderConfiguration.AVCC = append(vt.DecoderConfiguration.AVCC, codec.RTMP_AVC_HEAD)
|
||||||
}
|
}
|
||||||
tmp := []byte{0xE1, 0, 0, 0x01, 0, 0}
|
tmp := []byte{0xE1, 0, 0, 0x01, 0, 0}
|
||||||
vt.DecoderConfiguration.AppendAVCC(tmp[:1], util.PutBE(tmp[1:3], lenSPS), vt.DecoderConfiguration.Raw[0][0], tmp[3:4], util.PutBE(tmp[3:6], lenPPS), vt.DecoderConfiguration.Raw[1][0])
|
vt.DecoderConfiguration.AVCC = append(vt.DecoderConfiguration.AVCC, tmp[:1], util.PutBE(tmp[1:3], lenSPS), vt.DecoderConfiguration.Raw[0], tmp[3:4], util.PutBE(tmp[3:6], lenPPS), vt.DecoderConfiguration.Raw[1])
|
||||||
|
vt.DecoderConfiguration.FLV = codec.VideoAVCC2FLV(net.Buffers(vt.DecoderConfiguration.AVCC), 0)
|
||||||
case codec.NALU_IDR_Picture:
|
case codec.NALU_IDR_Picture:
|
||||||
vt.Value.IFrame = true
|
vt.Value.IFrame = true
|
||||||
fallthrough
|
fallthrough
|
||||||
@@ -53,16 +62,17 @@ func (vt *H264) WriteSlice(slice NALUSlice) {
|
|||||||
|
|
||||||
func (vt *H264) WriteAVCC(ts uint32, frame AVCCFrame) {
|
func (vt *H264) WriteAVCC(ts uint32, frame AVCCFrame) {
|
||||||
if frame.IsSequence() {
|
if frame.IsSequence() {
|
||||||
vt.DecoderConfiguration.Reset()
|
if len(vt.DecoderConfiguration.AVCC) > 0 {
|
||||||
vt.DecoderConfiguration.SeqInTrack = vt.Value.SeqInTrack
|
vt.DecoderConfiguration.AVCC = vt.DecoderConfiguration.AVCC[:0]
|
||||||
vt.DecoderConfiguration.AppendAVCC(frame)
|
}
|
||||||
|
vt.DecoderConfiguration.AVCC = append(vt.DecoderConfiguration.AVCC, frame)
|
||||||
var info codec.AVCDecoderConfigurationRecord
|
var info codec.AVCDecoderConfigurationRecord
|
||||||
if _, err := info.Unmarshal(frame[5:]); err == nil {
|
if _, err := info.Unmarshal(frame[5:]); err == nil {
|
||||||
vt.SPSInfo, _ = codec.ParseSPS(info.SequenceParameterSetNALUnit)
|
vt.SPSInfo, _ = codec.ParseSPS(info.SequenceParameterSetNALUnit)
|
||||||
vt.nalulenSize = int(info.LengthSizeMinusOne&3 + 1)
|
vt.nalulenSize = int(info.LengthSizeMinusOne&3 + 1)
|
||||||
vt.DecoderConfiguration.AppendRaw(NALUSlice{info.SequenceParameterSetNALUnit}, NALUSlice{info.PictureParameterSetNALUnit})
|
vt.DecoderConfiguration.Raw = NALUSlice{info.SequenceParameterSetNALUnit, info.PictureParameterSetNALUnit}
|
||||||
}
|
}
|
||||||
vt.DecoderConfiguration.FillFLV(codec.FLV_TAG_TYPE_VIDEO, 0)
|
vt.DecoderConfiguration.FLV = codec.VideoAVCC2FLV(net.Buffers(vt.DecoderConfiguration.AVCC), 0)
|
||||||
} else {
|
} else {
|
||||||
(*Video)(vt).WriteAVCC(ts, frame)
|
(*Video)(vt).WriteAVCC(ts, frame)
|
||||||
vt.Value.IFrame = frame.IsIDR()
|
vt.Value.IFrame = frame.IsIDR()
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package track
|
package track
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Monibuca/engine/v4/codec"
|
"github.com/Monibuca/engine/v4/codec"
|
||||||
@@ -26,17 +27,23 @@ func (vt *H265) WriteAnnexB(pts uint32, dts uint32, frame AnnexBFrame) {
|
|||||||
func (vt *H265) WriteSlice(slice NALUSlice) {
|
func (vt *H265) WriteSlice(slice NALUSlice) {
|
||||||
switch slice.H265Type() {
|
switch slice.H265Type() {
|
||||||
case codec.NAL_UNIT_VPS:
|
case codec.NAL_UNIT_VPS:
|
||||||
vt.DecoderConfiguration.Reset()
|
if len(vt.DecoderConfiguration.Raw) > 0 {
|
||||||
vt.DecoderConfiguration.AppendRaw(slice)
|
vt.DecoderConfiguration.Raw = vt.DecoderConfiguration.Raw[:0]
|
||||||
|
}
|
||||||
|
vt.DecoderConfiguration.Raw = append(vt.DecoderConfiguration.Raw, slice[0])
|
||||||
case codec.NAL_UNIT_SPS:
|
case codec.NAL_UNIT_SPS:
|
||||||
vt.DecoderConfiguration.AppendRaw(slice)
|
vt.DecoderConfiguration.Raw = append(vt.DecoderConfiguration.Raw, slice[0])
|
||||||
vt.SPSInfo, _ = codec.ParseHevcSPS(slice[0])
|
vt.SPSInfo, _ = codec.ParseHevcSPS(slice[0])
|
||||||
case codec.NAL_UNIT_PPS:
|
case codec.NAL_UNIT_PPS:
|
||||||
vt.DecoderConfiguration.AppendRaw(slice)
|
vt.DecoderConfiguration.Raw = append(vt.DecoderConfiguration.Raw, slice[0])
|
||||||
extraData, err := codec.BuildH265SeqHeaderFromVpsSpsPps(vt.DecoderConfiguration.Raw[0][0], vt.DecoderConfiguration.Raw[1][0], vt.DecoderConfiguration.Raw[2][0])
|
extraData, err := codec.BuildH265SeqHeaderFromVpsSpsPps(vt.DecoderConfiguration.Raw[0], vt.DecoderConfiguration.Raw[1], vt.DecoderConfiguration.Raw[2])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
vt.DecoderConfiguration.AppendAVCC(extraData)
|
if len(vt.DecoderConfiguration.AVCC) > 0 {
|
||||||
|
vt.DecoderConfiguration.AVCC = vt.DecoderConfiguration.AVCC[:0]
|
||||||
}
|
}
|
||||||
|
vt.DecoderConfiguration.AVCC = append(vt.DecoderConfiguration.AVCC, extraData)
|
||||||
|
}
|
||||||
|
vt.DecoderConfiguration.FLV = codec.VideoAVCC2FLV(net.Buffers(vt.DecoderConfiguration.AVCC), 0)
|
||||||
case
|
case
|
||||||
codec.NAL_UNIT_CODED_SLICE_BLA,
|
codec.NAL_UNIT_CODED_SLICE_BLA,
|
||||||
codec.NAL_UNIT_CODED_SLICE_BLANT,
|
codec.NAL_UNIT_CODED_SLICE_BLANT,
|
||||||
@@ -52,15 +59,16 @@ func (vt *H265) WriteSlice(slice NALUSlice) {
|
|||||||
}
|
}
|
||||||
func (vt *H265) WriteAVCC(ts uint32, frame AVCCFrame) {
|
func (vt *H265) WriteAVCC(ts uint32, frame AVCCFrame) {
|
||||||
if frame.IsSequence() {
|
if frame.IsSequence() {
|
||||||
vt.DecoderConfiguration.Reset()
|
if len(vt.DecoderConfiguration.AVCC) > 0 {
|
||||||
vt.DecoderConfiguration.SeqInTrack = vt.Value.SeqInTrack
|
vt.DecoderConfiguration.AVCC = vt.DecoderConfiguration.AVCC[:0]
|
||||||
vt.DecoderConfiguration.AppendAVCC(frame)
|
}
|
||||||
|
vt.DecoderConfiguration.AVCC = append(vt.DecoderConfiguration.AVCC, frame)
|
||||||
if vps, sps, pps, err := codec.ParseVpsSpsPpsFromSeqHeaderWithoutMalloc(frame); err == nil {
|
if vps, sps, pps, err := codec.ParseVpsSpsPpsFromSeqHeaderWithoutMalloc(frame); err == nil {
|
||||||
vt.SPSInfo, _ = codec.ParseHevcSPS(frame)
|
vt.SPSInfo, _ = codec.ParseHevcSPS(frame)
|
||||||
vt.nalulenSize = int(frame[26]) & 0x03
|
vt.nalulenSize = int(frame[26]) & 0x03
|
||||||
vt.DecoderConfiguration.AppendRaw(NALUSlice{vps}, NALUSlice{sps}, NALUSlice{pps})
|
vt.DecoderConfiguration.Raw = NALUSlice{vps, sps, pps}
|
||||||
}
|
}
|
||||||
vt.DecoderConfiguration.FillFLV(codec.FLV_TAG_TYPE_VIDEO, 0)
|
vt.DecoderConfiguration.FLV = codec.VideoAVCC2FLV(net.Buffers(vt.DecoderConfiguration.AVCC), 0)
|
||||||
} else {
|
} else {
|
||||||
(*Video)(vt).WriteAVCC(ts, frame)
|
(*Video)(vt).WriteAVCC(ts, frame)
|
||||||
vt.Value.IFrame = frame.IsIDR()
|
vt.Value.IFrame = frame.IsIDR()
|
||||||
|
@@ -95,6 +95,7 @@ func SizeOfBuffers(buf net.Buffers) (size int) {
|
|||||||
func CutBuffers(buf net.Buffers, size int) {
|
func CutBuffers(buf net.Buffers, size int) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitBuffers 按照一定大小分割 Buffers
|
// SplitBuffers 按照一定大小分割 Buffers
|
||||||
func SplitBuffers(buf net.Buffers, size int) (result []net.Buffers) {
|
func SplitBuffers(buf net.Buffers, size int) (result []net.Buffers) {
|
||||||
for total := SizeOfBuffers(buf); total > 0; {
|
for total := SizeOfBuffers(buf); total > 0; {
|
||||||
@@ -122,3 +123,5 @@ func SplitBuffers(buf net.Buffers, size int) (result []net.Buffers) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -19,3 +19,14 @@ func (s *Slice[T]) Delete(v T) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Slice[T]) Reset() {
|
||||||
|
if len(*s) > 0 {
|
||||||
|
*s = (*s)[:0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slice[T]) ResetAppend(first T) {
|
||||||
|
s.Reset()
|
||||||
|
s.Add(first)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user