mirror of
https://github.com/Monibuca/engine.git
synced 2025-10-06 09:06:52 +08:00
修复配置合并bug
This commit is contained in:
@@ -2,18 +2,42 @@ package codec
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AudioCodecID byte
|
||||||
|
type VideoCodecID byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ADTS_HEADER_SIZE = 7
|
ADTS_HEADER_SIZE = 7
|
||||||
CodecID_AAC = 0xA
|
CodecID_AAC AudioCodecID = 0xA
|
||||||
CodecID_PCMA = 7
|
CodecID_PCMA AudioCodecID = 7
|
||||||
CodecID_PCMU = 8
|
CodecID_PCMU AudioCodecID = 8
|
||||||
CodecID_H264 = 7
|
CodecID_H264 VideoCodecID = 7
|
||||||
CodecID_H265 = 0xC
|
CodecID_H265 VideoCodecID = 0xC
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (codecId AudioCodecID) String() string {
|
||||||
|
switch codecId {
|
||||||
|
case CodecID_AAC:
|
||||||
|
return "aac"
|
||||||
|
case CodecID_PCMA:
|
||||||
|
return "pcma"
|
||||||
|
case CodecID_PCMU:
|
||||||
|
return "pcmu"
|
||||||
|
}
|
||||||
|
return "unknow"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codecId VideoCodecID) String() string {
|
||||||
|
switch codecId {
|
||||||
|
case CodecID_H264:
|
||||||
|
return "h264"
|
||||||
|
case CodecID_H265:
|
||||||
|
return "h265"
|
||||||
|
}
|
||||||
|
return "unknow"
|
||||||
|
}
|
||||||
|
|
||||||
// ISO/IEC 14496-3 38(52)/page
|
// ISO/IEC 14496-3 38(52)/page
|
||||||
//
|
//
|
||||||
// Audio
|
// Audio
|
||||||
@@ -212,5 +236,3 @@ func ParseRTPAAC(payload []byte) (result [][]byte) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -46,7 +46,7 @@ func (nalu NALUSlice) Bytes() (b []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nalu *NALUSlice) Reset() *NALUSlice{
|
func (nalu *NALUSlice) Reset() *NALUSlice {
|
||||||
if len(*nalu) > 0 {
|
if len(*nalu) > 0 {
|
||||||
*nalu = (*nalu)[:0]
|
*nalu = (*nalu)[:0]
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ func (rtp *RTPFrame) Marshal() *RTPFrame {
|
|||||||
func (rtp *RTPFrame) Unmarshal(raw []byte) *RTPFrame {
|
func (rtp *RTPFrame) Unmarshal(raw []byte) *RTPFrame {
|
||||||
rtp.Raw = raw
|
rtp.Raw = raw
|
||||||
if err := rtp.Packet.Unmarshal(raw); err != nil {
|
if err := rtp.Packet.Unmarshal(raw); err != nil {
|
||||||
logrus.Errorln(err)
|
logrus.Error(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return rtp
|
return rtp
|
||||||
@@ -154,11 +154,11 @@ func (avcc AVCCFrame) IsSequence() bool {
|
|||||||
func (avcc AVCCFrame) CTS() uint32 {
|
func (avcc AVCCFrame) CTS() uint32 {
|
||||||
return uint32(avcc[2])<<24 | uint32(avcc[3])<<8 | uint32(avcc[4])
|
return uint32(avcc[2])<<24 | uint32(avcc[3])<<8 | uint32(avcc[4])
|
||||||
}
|
}
|
||||||
func (avcc AVCCFrame) VideoCodecID() byte {
|
func (avcc AVCCFrame) VideoCodecID() codec.VideoCodecID {
|
||||||
return avcc[0] & 0x0F
|
return codec.VideoCodecID(avcc[0] & 0x0F)
|
||||||
}
|
}
|
||||||
func (avcc AVCCFrame) AudioCodecID() byte {
|
func (avcc AVCCFrame) AudioCodecID() codec.AudioCodecID {
|
||||||
return avcc[0] >> 4
|
return codec.AudioCodecID(avcc[0] >> 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (annexb AnnexBFrame) ToSlices() (ret []NALUSlice) {
|
// func (annexb AnnexBFrame) ToSlices() (ret []NALUSlice) {
|
||||||
|
@@ -8,6 +8,7 @@ type Track interface {
|
|||||||
|
|
||||||
type AVTrack interface {
|
type AVTrack interface {
|
||||||
Track
|
Track
|
||||||
|
Attach()
|
||||||
WriteAVCC(ts uint32, frame AVCCFrame) //写入AVCC格式的数据
|
WriteAVCC(ts uint32, frame AVCCFrame) //写入AVCC格式的数据
|
||||||
Flush()
|
Flush()
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config map[string]any
|
type Config map[string]any
|
||||||
@@ -57,8 +59,13 @@ func (config Config) Unmarshal(s any) {
|
|||||||
nameMap[strings.ToLower(name)] = name
|
nameMap[strings.ToLower(name)] = name
|
||||||
}
|
}
|
||||||
for k, v := range config {
|
for k, v := range config {
|
||||||
|
name, ok := nameMap[k]
|
||||||
|
if !ok {
|
||||||
|
logrus.Error("no config named:", k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// 需要被写入的字段
|
// 需要被写入的字段
|
||||||
fv := el.FieldByName(nameMap[k])
|
fv := el.FieldByName(name)
|
||||||
if value := reflect.ValueOf(v); value.Kind() == reflect.Slice {
|
if value := reflect.ValueOf(v); value.Kind() == reflect.Slice {
|
||||||
l := value.Len()
|
l := value.Len()
|
||||||
s := reflect.MakeSlice(fv.Type(), l, value.Cap())
|
s := reflect.MakeSlice(fv.Type(), l, value.Cap())
|
||||||
@@ -110,8 +117,17 @@ func (config Config) Merge(source Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config Config) Set(key string, value any) {
|
func (config *Config) Set(key string, value any) {
|
||||||
config[strings.ToLower(key)] = value
|
if *config == nil {
|
||||||
|
*config = Config{strings.ToLower(key): value}
|
||||||
|
} else {
|
||||||
|
(*config)[strings.ToLower(key)] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) Get(key string) any {
|
||||||
|
v, _ := config[strings.ToLower(key)]
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config Config) Has(key string) (ok bool) {
|
func (config Config) Has(key string) (ok bool) {
|
||||||
@@ -148,13 +164,14 @@ func Struct2Config(s any) (config Config) {
|
|||||||
}
|
}
|
||||||
for i, j := 0, t.NumField(); i < j; i++ {
|
for i, j := 0, t.NumField(); i < j; i++ {
|
||||||
ft := t.Field(i)
|
ft := t.Field(i)
|
||||||
|
name := strings.ToLower(ft.Name)
|
||||||
switch ft.Type.Kind() {
|
switch ft.Type.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
config[ft.Name] = Struct2Config(v.Field(i))
|
config[name] = Struct2Config(v.Field(i))
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
reflect.ValueOf(config).SetMapIndex(reflect.ValueOf(strings.ToLower(ft.Name)), v.Field(i))
|
reflect.ValueOf(config).SetMapIndex(reflect.ValueOf(name), v.Field(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@@ -33,6 +33,13 @@ type Push struct {
|
|||||||
PushList map[string]string // 自动推流列表
|
PushList map[string]string // 自动推流列表
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Push) AddPush(streamPath string, url string) {
|
||||||
|
if p.PushList == nil {
|
||||||
|
p.PushList = make(map[string]string)
|
||||||
|
}
|
||||||
|
p.PushList[streamPath] = url
|
||||||
|
}
|
||||||
|
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
Publish
|
Publish
|
||||||
Subscribe
|
Subscribe
|
||||||
@@ -44,7 +51,7 @@ type Engine struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var Global = &Engine{
|
var Global = &Engine{
|
||||||
Publish{true, true, false, 10, 10},
|
Publish{true, true, false, 10, 0},
|
||||||
Subscribe{true, true, false, 10},
|
Subscribe{true, true, false, 10},
|
||||||
HTTP{ListenAddr: ":8080", CORS: true},
|
HTTP{ListenAddr: ":8080", CORS: true},
|
||||||
false, true, true, true,
|
false, true, true, true,
|
||||||
|
6
http.go
6
http.go
@@ -3,9 +3,10 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/Monibuca/engine/v4/config"
|
"github.com/Monibuca/engine/v4/config"
|
||||||
. "github.com/logrusorgru/aurora"
|
. "github.com/logrusorgru/aurora"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GlobalConfig struct {
|
type GlobalConfig struct {
|
||||||
@@ -14,8 +15,9 @@ type GlobalConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *GlobalConfig) Update(override config.Config) {
|
func (cfg *GlobalConfig) Update(override config.Config) {
|
||||||
|
// 使得RawConfig具备全量配置信息,用于合并到插件配置中
|
||||||
Engine.RawConfig = config.Struct2Config(cfg.Engine)
|
Engine.RawConfig = config.Struct2Config(cfg.Engine)
|
||||||
log.Infoln(Green("api server start at"), BrightBlue(cfg.ListenAddr), BrightBlue(cfg.ListenAddrTLS))
|
log.Info(Green("api server start at"), BrightBlue(cfg.ListenAddr), BrightBlue(cfg.ListenAddrTLS))
|
||||||
cfg.Listen(Engine, cfg)
|
cfg.Listen(Engine, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
log.go
30
log.go
@@ -2,24 +2,31 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"github.com/mattn/go-colorable"
|
"strings"
|
||||||
|
|
||||||
"github.com/Monibuca/engine/v4/util"
|
"github.com/Monibuca/engine/v4/util"
|
||||||
|
. "github.com/logrusorgru/aurora"
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var levelColors = []func(any) Value{Red, Red, Red, Yellow, Blue, Green, White}
|
||||||
|
|
||||||
// MultiLogWriter 可动态增减输出的多端写日志类
|
// MultiLogWriter 可动态增减输出的多端写日志类
|
||||||
type MultiLogWriter struct {
|
type MultiLogWriter struct {
|
||||||
writers util.Slice[io.Writer]
|
writers util.Slice[io.Writer]
|
||||||
io.Writer
|
io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
var colorableStdout = colorable.NewColorableStdout()
|
var colorableStdout = colorable.NewColorableStdout()
|
||||||
var LogWriter = MultiLogWriter{
|
var LogWriter = &MultiLogWriter{
|
||||||
writers: util.Slice[io.Writer]{colorableStdout},
|
writers: util.Slice[io.Writer]{colorableStdout},
|
||||||
Writer: colorableStdout,
|
Writer: colorableStdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log.SetOutput(LogWriter)
|
log.SetOutput(LogWriter)
|
||||||
|
log.SetFormatter(LogWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ml *MultiLogWriter) Add(w io.Writer) {
|
func (ml *MultiLogWriter) Add(w io.Writer) {
|
||||||
@@ -31,3 +38,22 @@ func (ml *MultiLogWriter) Delete(w io.Writer) {
|
|||||||
ml.writers.Delete(w)
|
ml.writers.Delete(w)
|
||||||
ml.Writer = io.MultiWriter(ml.writers...)
|
ml.Writer = io.MultiWriter(ml.writers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ml *MultiLogWriter) Format(entry *log.Entry) (b []byte, err error) {
|
||||||
|
pl := entry.Data["plugin"]
|
||||||
|
if pl == nil {
|
||||||
|
pl = "Engine"
|
||||||
|
}
|
||||||
|
l := strings.ToUpper(entry.Level.String())[:1]
|
||||||
|
var props string
|
||||||
|
if stream := entry.Data["stream"]; stream != nil {
|
||||||
|
props = Sprintf("[s:%s] ", stream)
|
||||||
|
}
|
||||||
|
if puber := entry.Data["puber"]; puber != nil {
|
||||||
|
props += Sprintf("[pub:%s] ", puber)
|
||||||
|
}
|
||||||
|
if suber := entry.Data["suber"]; suber != nil {
|
||||||
|
props += Sprintf("[sub:%s] ", suber)
|
||||||
|
}
|
||||||
|
return []byte(Sprintf(levelColors[entry.Level]("%s [%s] [%s]\t %s%s\n"), l, entry.Time.Format("15:04:05"), pl, props, entry.Message)), nil
|
||||||
|
}
|
||||||
|
12
main.go
12
main.go
@@ -49,27 +49,29 @@ func (p PullOnSubscribe) Pull(streamPath string) {
|
|||||||
func Run(ctx context.Context, configFile string) (err error) {
|
func Run(ctx context.Context, configFile string) (err error) {
|
||||||
Engine.Context = ctx
|
Engine.Context = ctx
|
||||||
if err := util.CreateShutdownScript(); err != nil {
|
if err := util.CreateShutdownScript(); err != nil {
|
||||||
log.Errorln("create shutdown script error:", err)
|
log.Error("create shutdown script error:", err)
|
||||||
}
|
}
|
||||||
StartTime = time.Now()
|
StartTime = time.Now()
|
||||||
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
|
if ConfigRaw, err = ioutil.ReadFile(configFile); err != nil {
|
||||||
log.Errorln("read config file error:", err)
|
log.Error("read config file error:", err)
|
||||||
}
|
}
|
||||||
settingDir = filepath.Join(filepath.Dir(configFile), ".m7s")
|
settingDir = filepath.Join(filepath.Dir(configFile), ".m7s")
|
||||||
if err = os.MkdirAll(settingDir, 0755); err != nil {
|
if err = os.MkdirAll(settingDir, 0755); err != nil {
|
||||||
log.Errorln("create dir .m7s error:", err)
|
log.Error("create dir .m7s error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Infoln(BgGreen(White("Ⓜ starting m7s v4")))
|
log.Info(Blink("Ⓜ starting m7s v4"))
|
||||||
var cg config.Config
|
var cg config.Config
|
||||||
if ConfigRaw != nil {
|
if ConfigRaw != nil {
|
||||||
if err = yaml.Unmarshal(ConfigRaw, &cg); err == nil {
|
if err = yaml.Unmarshal(ConfigRaw, &cg); err == nil {
|
||||||
Engine.RawConfig = cg.GetChild("global")
|
Engine.RawConfig = cg.GetChild("global")
|
||||||
|
//将配置信息同步到结构体
|
||||||
Engine.RawConfig.Unmarshal(config.Global)
|
Engine.RawConfig.Unmarshal(config.Global)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnln("no config file found , use default config values")
|
log.Warn("no config file found , use default config values")
|
||||||
}
|
}
|
||||||
|
Engine.Entry = log.WithContext(Engine)
|
||||||
Engine.registerHandler()
|
Engine.registerHandler()
|
||||||
go EngineConfig.Update(Engine.RawConfig)
|
go EngineConfig.Update(Engine.RawConfig)
|
||||||
for name, plugin := range Plugins {
|
for name, plugin := range Plugins {
|
||||||
|
10
plugin.go
10
plugin.go
@@ -35,7 +35,7 @@ func InstallPlugin(config config.Plugin) *Plugin {
|
|||||||
if config != EngineConfig {
|
if config != EngineConfig {
|
||||||
plugin.Entry = log.WithField("plugin", name)
|
plugin.Entry = log.WithField("plugin", name)
|
||||||
Plugins[name] = plugin
|
Plugins[name] = plugin
|
||||||
plugin.Infoln(Green("install"), BrightBlue(plugin.Version))
|
plugin.Info(Green("install"), BrightBlue(plugin.Version))
|
||||||
}
|
}
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
@@ -72,12 +72,12 @@ func (opt *Plugin) HandleFunc(pattern string, handler func(http.ResponseWriter,
|
|||||||
if opt != Engine {
|
if opt != Engine {
|
||||||
pattern = "/" + strings.ToLower(opt.Name) + pattern
|
pattern = "/" + strings.ToLower(opt.Name) + pattern
|
||||||
}
|
}
|
||||||
opt.Infoln("http handle added:", pattern)
|
opt.Info("http handle added:", pattern)
|
||||||
EngineConfig.HandleFunc(pattern, func(rw http.ResponseWriter, r *http.Request) {
|
EngineConfig.HandleFunc(pattern, func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if cors {
|
if cors {
|
||||||
util.CORS(rw, r)
|
util.CORS(rw, r)
|
||||||
}
|
}
|
||||||
opt.Debugln(r.RemoteAddr, " -> ", pattern)
|
opt.Debug(r.RemoteAddr, " -> ", pattern)
|
||||||
handler(rw, r)
|
handler(rw, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -99,9 +99,9 @@ func (opt *Plugin) assign() {
|
|||||||
// 用全局配置覆盖没有设置的配置
|
// 用全局配置覆盖没有设置的配置
|
||||||
for _, fname := range MergeConfigs {
|
for _, fname := range MergeConfigs {
|
||||||
if _, ok := t.FieldByName(fname); ok {
|
if _, ok := t.FieldByName(fname); ok {
|
||||||
if Engine.RawConfig.Has(fname) {
|
if v, ok := Engine.RawConfig[strings.ToLower(fname)]; ok {
|
||||||
if !opt.RawConfig.Has(fname) {
|
if !opt.RawConfig.Has(fname) {
|
||||||
opt.RawConfig.Set(fname, Engine.RawConfig[fname])
|
opt.RawConfig.Set(fname, v)
|
||||||
} else if opt.RawConfig.HasChild(fname) {
|
} else if opt.RawConfig.HasChild(fname) {
|
||||||
opt.RawConfig.GetChild(fname).Merge(Engine.RawConfig.GetChild(fname))
|
opt.RawConfig.GetChild(fname).Merge(Engine.RawConfig.GetChild(fname))
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,9 @@ type IPublisher interface {
|
|||||||
Close() // 流关闭时或者被踢时触发
|
Close() // 流关闭时或者被踢时触发
|
||||||
OnStateChange(oldState StreamState, newState StreamState) bool
|
OnStateChange(oldState StreamState, newState StreamState) bool
|
||||||
OnStateChanged(oldState StreamState, newState StreamState)
|
OnStateChanged(oldState StreamState, newState StreamState)
|
||||||
|
Publish(streamPath string, specific IPublisher, config config.Publish) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type IPuller interface {
|
type IPuller interface {
|
||||||
IPublisher
|
IPublisher
|
||||||
Pull(int)
|
Pull(int)
|
||||||
@@ -35,10 +37,10 @@ func (pub *Publisher) Publish(streamPath string, specific IPublisher, config con
|
|||||||
}
|
}
|
||||||
if s.Publisher != nil {
|
if s.Publisher != nil {
|
||||||
if config.KickExsit {
|
if config.KickExsit {
|
||||||
s.Warnln("kick", s.Publisher)
|
s.Warn("kick", s.Publisher)
|
||||||
s.Publisher.Close()
|
s.Publisher.Close()
|
||||||
} else {
|
} else {
|
||||||
s.Warnln("publisher exsit", s.Publisher)
|
s.Warn("publisher exsit", s.Publisher)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
119
stream.go
119
stream.go
@@ -62,6 +62,9 @@ var StreamFSM = [STATE_DESTROYED + 1]map[StreamAction]StreamState{
|
|||||||
{
|
{
|
||||||
ACTION_TIMEOUT: STATE_DESTROYED,
|
ACTION_TIMEOUT: STATE_DESTROYED,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streams 所有的流集合
|
// Streams 所有的流集合
|
||||||
@@ -124,11 +127,11 @@ func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream
|
|||||||
}
|
}
|
||||||
p := strings.Split(u.Path, "/")
|
p := strings.Split(u.Path, "/")
|
||||||
if len(p) < 2 {
|
if len(p) < 2 {
|
||||||
log.Warnln(Red("Stream Path Format Error:"), streamPath)
|
log.Warn(Red("Stream Path Format Error:"), streamPath)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
if s, ok := Streams.Map[u.Path]; ok {
|
if s, ok := Streams.Map[u.Path]; ok {
|
||||||
s.Debugln(Green("Stream Found"))
|
s.Debug(Green("Stream Found"))
|
||||||
return s, false
|
return s, false
|
||||||
} else {
|
} else {
|
||||||
p := strings.Split(u.Path, "/")
|
p := strings.Split(u.Path, "/")
|
||||||
@@ -138,7 +141,7 @@ func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream
|
|||||||
StreamName: util.LastElement(p),
|
StreamName: util.LastElement(p),
|
||||||
Entry: log.WithField("stream", u.Path),
|
Entry: log.WithField("stream", u.Path),
|
||||||
}
|
}
|
||||||
s.Infoln("created:", streamPath)
|
s.Info("created")
|
||||||
s.WaitTimeout = waitTimeout
|
s.WaitTimeout = waitTimeout
|
||||||
Streams.Map[u.Path] = s
|
Streams.Map[u.Path] = s
|
||||||
s.actionChan = make(chan any, 1)
|
s.actionChan = make(chan any, 1)
|
||||||
@@ -152,43 +155,48 @@ func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Stream) action(action StreamAction) bool {
|
func (r *Stream) action(action StreamAction) bool {
|
||||||
|
r.Tracef("action:%d", action)
|
||||||
if next, ok := StreamFSM[r.State][action]; ok {
|
if next, ok := StreamFSM[r.State][action]; ok {
|
||||||
if r.Publisher == nil || r.Publisher.OnStateChange(r.State, next) {
|
if r.Publisher != nil {
|
||||||
|
// 给Publisher状态变更的回调,方便进行远程拉流等操作
|
||||||
defer r.Publisher.OnStateChanged(r.State, next)
|
defer r.Publisher.OnStateChanged(r.State, next)
|
||||||
r.Debugln(action, " :", r.State, "->", next)
|
if !r.Publisher.OnStateChange(r.State, next) {
|
||||||
r.State = next
|
return false
|
||||||
switch next {
|
|
||||||
case STATE_WAITPUBLISH:
|
|
||||||
r.Publisher = nil
|
|
||||||
Bus.Publish(Event_REQUEST_PUBLISH, r)
|
|
||||||
r.timeout.Reset(r.WaitTimeout)
|
|
||||||
if _, ok = PullOnSubscribeList[r.Path]; ok {
|
|
||||||
PullOnSubscribeList[r.Path].Pull(r.Path)
|
|
||||||
}
|
|
||||||
case STATE_WAITTRACK:
|
|
||||||
r.timeout.Reset(time.Second * 5)
|
|
||||||
case STATE_PUBLISHING:
|
|
||||||
r.WaitDone()
|
|
||||||
r.timeout.Reset(r.PublishTimeout)
|
|
||||||
Bus.Publish(Event_PUBLISH, r)
|
|
||||||
case STATE_WAITCLOSE:
|
|
||||||
r.timeout.Reset(r.WaitCloseTimeout)
|
|
||||||
case STATE_CLOSED:
|
|
||||||
r.cancel()
|
|
||||||
if r.Publisher != nil {
|
|
||||||
r.Publisher.Close()
|
|
||||||
}
|
|
||||||
r.WaitDone()
|
|
||||||
Bus.Publish(Event_STREAMCLOSE, r)
|
|
||||||
Streams.Delete(r.Path)
|
|
||||||
r.timeout.Reset(time.Second) // 延迟1秒钟销毁,防止访问到已关闭的channel
|
|
||||||
case STATE_DESTROYED:
|
|
||||||
close(r.actionChan)
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
r.timeout.Stop()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
r.Debug(action, " :", r.State, "->", next)
|
||||||
|
r.State = next
|
||||||
|
switch next {
|
||||||
|
case STATE_WAITPUBLISH:
|
||||||
|
r.Publisher = nil
|
||||||
|
Bus.Publish(Event_REQUEST_PUBLISH, r)
|
||||||
|
r.timeout.Reset(r.WaitTimeout)
|
||||||
|
if _, ok = PullOnSubscribeList[r.Path]; ok {
|
||||||
|
PullOnSubscribeList[r.Path].Pull(r.Path)
|
||||||
|
}
|
||||||
|
case STATE_WAITTRACK:
|
||||||
|
r.timeout.Reset(time.Second * 5)
|
||||||
|
case STATE_PUBLISHING:
|
||||||
|
r.WaitDone()
|
||||||
|
r.timeout.Reset(r.PublishTimeout)
|
||||||
|
Bus.Publish(Event_PUBLISH, r)
|
||||||
|
case STATE_WAITCLOSE:
|
||||||
|
r.timeout.Reset(r.WaitCloseTimeout)
|
||||||
|
case STATE_CLOSED:
|
||||||
|
r.cancel()
|
||||||
|
if r.Publisher != nil {
|
||||||
|
r.Publisher.Close()
|
||||||
|
}
|
||||||
|
r.WaitDone()
|
||||||
|
Bus.Publish(Event_STREAMCLOSE, r)
|
||||||
|
Streams.Delete(r.Path)
|
||||||
|
r.timeout.Reset(time.Second) // 延迟1秒钟销毁,防止访问到已关闭的channel
|
||||||
|
case STATE_DESTROYED:
|
||||||
|
close(r.actionChan)
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
r.timeout.Stop()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -197,7 +205,7 @@ func (r *Stream) IsClosed() bool {
|
|||||||
if r == nil {
|
if r == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return r.State == STATE_CLOSED
|
return r.State >= STATE_CLOSED
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Stream) Close() {
|
func (r *Stream) Close() {
|
||||||
@@ -207,13 +215,13 @@ func (r *Stream) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Stream) UnSubscribe(sub *Subscriber) {
|
func (r *Stream) UnSubscribe(sub *Subscriber) {
|
||||||
r.Debugln("unsubscribe", sub.ID)
|
r.Debug("unsubscribe", sub.ID)
|
||||||
if !r.IsClosed() {
|
if !r.IsClosed() {
|
||||||
r.actionChan <- UnSubscibeAction(sub)
|
r.actionChan <- UnSubscibeAction(sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (r *Stream) Subscribe(sub *Subscriber) {
|
func (r *Stream) Subscribe(sub *Subscriber) {
|
||||||
r.Debugln("subscribe", sub.ID)
|
r.Debug("subscribe", sub.ID)
|
||||||
if !r.IsClosed() {
|
if !r.IsClosed() {
|
||||||
sub.Stream = r
|
sub.Stream = r
|
||||||
sub.Context, sub.cancel = context.WithCancel(r)
|
sub.Context, sub.cancel = context.WithCancel(r)
|
||||||
@@ -223,14 +231,15 @@ func (r *Stream) Subscribe(sub *Subscriber) {
|
|||||||
|
|
||||||
// 流状态处理中枢,包括接收订阅发布指令等
|
// 流状态处理中枢,包括接收订阅发布指令等
|
||||||
func (r *Stream) run() {
|
func (r *Stream) run() {
|
||||||
|
var done = r.Done()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-r.timeout.C:
|
case <-r.timeout.C:
|
||||||
r.Debugln(r.State, "timeout")
|
r.Debugf("%v timeout", r.State)
|
||||||
r.action(ACTION_TIMEOUT)
|
r.action(ACTION_TIMEOUT)
|
||||||
case <-r.Done():
|
case <-done:
|
||||||
r.action(ACTION_CLOSE)
|
r.action(ACTION_CLOSE)
|
||||||
|
done = nil
|
||||||
case action, ok := <-r.actionChan:
|
case action, ok := <-r.actionChan:
|
||||||
if ok {
|
if ok {
|
||||||
switch v := action.(type) {
|
switch v := action.(type) {
|
||||||
@@ -243,14 +252,14 @@ func (r *Stream) run() {
|
|||||||
case *Subscriber:
|
case *Subscriber:
|
||||||
r.Subscribers.Add(v)
|
r.Subscribers.Add(v)
|
||||||
Bus.Publish(Event_SUBSCRIBE, v)
|
Bus.Publish(Event_SUBSCRIBE, v)
|
||||||
v.Infoln(Sprintf(Yellow("added remains:%d"), Cyan(v.ID), Blue(len(r.Subscribers))))
|
v.Info(Sprintf(Yellow("added remains:%d") ,len(r.Subscribers)))
|
||||||
if r.Subscribers.Len() == 1 {
|
if r.Subscribers.Len() == 1 {
|
||||||
r.action(ACTION_FIRSTENTER)
|
r.action(ACTION_FIRSTENTER)
|
||||||
}
|
}
|
||||||
case UnSubscibeAction:
|
case UnSubscibeAction:
|
||||||
if r.Subscribers.Delete(v) {
|
if r.Subscribers.Delete(v) {
|
||||||
Bus.Publish(Event_UNSUBSCRIBE, v)
|
Bus.Publish(Event_UNSUBSCRIBE, v)
|
||||||
(*Subscriber)(v).Infoln(Sprintf(Yellow("removed remains:%d"), Cyan(v.ID), Blue(len(r.Subscribers))))
|
(*Subscriber)(v).Info(Sprintf(Yellow("removed remains:%d"), len(r.Subscribers)))
|
||||||
if r.Subscribers.Len() == 0 && r.WaitCloseTimeout > 0 {
|
if r.Subscribers.Len() == 0 && r.WaitCloseTimeout > 0 {
|
||||||
r.action(ACTION_LASTLEAVE)
|
r.action(ACTION_LASTLEAVE)
|
||||||
}
|
}
|
||||||
@@ -266,7 +275,7 @@ func (r *Stream) run() {
|
|||||||
// Update 更新数据重置超时定时器
|
// Update 更新数据重置超时定时器
|
||||||
func (r *Stream) Update() uint32 {
|
func (r *Stream) Update() uint32 {
|
||||||
if r.State == STATE_PUBLISHING {
|
if r.State == STATE_PUBLISHING {
|
||||||
r.Traceln("update")
|
r.Trace("update")
|
||||||
r.timeout.Reset(r.PublishTimeout)
|
r.timeout.Reset(r.PublishTimeout)
|
||||||
}
|
}
|
||||||
return atomic.AddUint32(&r.FrameCount, 1)
|
return atomic.AddUint32(&r.FrameCount, 1)
|
||||||
@@ -274,31 +283,29 @@ func (r *Stream) Update() uint32 {
|
|||||||
|
|
||||||
// 如果暂时不知道编码格式可以用这个
|
// 如果暂时不知道编码格式可以用这个
|
||||||
func (r *Stream) NewVideoTrack() (vt *track.UnknowVideo) {
|
func (r *Stream) NewVideoTrack() (vt *track.UnknowVideo) {
|
||||||
r.Debugln("create unknow video track")
|
r.Debug("create unknow video track")
|
||||||
vt = &track.UnknowVideo{
|
vt = &track.UnknowVideo{}
|
||||||
Stream: r,
|
vt.Stream = r
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (r *Stream) NewAudioTrack() (at *track.UnknowAudio) {
|
func (r *Stream) NewAudioTrack() (at *track.UnknowAudio) {
|
||||||
r.Debugln("create unknow audio track")
|
r.Debug("create unknow audio track")
|
||||||
at = &track.UnknowAudio{
|
at = &track.UnknowAudio{}
|
||||||
Stream: r,
|
at.Stream = r
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (r *Stream) NewH264Track() *track.H264 {
|
func (r *Stream) NewH264Track() *track.H264 {
|
||||||
r.Debugln("create h264 track")
|
r.Debug("create h264 track")
|
||||||
return track.NewH264(r)
|
return track.NewH264(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Stream) NewH265Track() *track.H265 {
|
func (r *Stream) NewH265Track() *track.H265 {
|
||||||
r.Debugln("create h265 track")
|
r.Debug("create h265 track")
|
||||||
return track.NewH265(r)
|
return track.NewH265(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Stream) NewAACTrack() *track.AAC {
|
func (r *Stream) NewAACTrack() *track.AAC {
|
||||||
r.Debugln("create aac track")
|
r.Debug("create aac track")
|
||||||
return track.NewAAC(r)
|
return track.NewAAC(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -48,7 +48,7 @@ func (sub *Subscriber) Subscribe(streamPath string, config config.Subscribe) boo
|
|||||||
log.Info(sub.ID, "try to subscribe", streamPath)
|
log.Info(sub.ID, "try to subscribe", streamPath)
|
||||||
s, created := findOrCreateStream(streamPath, config.WaitTimeout.Duration())
|
s, created := findOrCreateStream(streamPath, config.WaitTimeout.Duration())
|
||||||
if s.IsClosed() {
|
if s.IsClosed() {
|
||||||
log.Warnln("stream is closed")
|
log.Warn("stream is closed")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
sub.Entry = s.Entry.WithField("suber", sub.ID)
|
sub.Entry = s.Entry.WithField("suber", sub.ID)
|
||||||
|
@@ -2,7 +2,6 @@ package track
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Monibuca/engine/v4/codec"
|
"github.com/Monibuca/engine/v4/codec"
|
||||||
. "github.com/Monibuca/engine/v4/common"
|
. "github.com/Monibuca/engine/v4/common"
|
||||||
@@ -15,15 +14,20 @@ var adcflv2 = []byte{0, 0, 0, 15}
|
|||||||
|
|
||||||
type Audio struct {
|
type Audio struct {
|
||||||
Media[AudioSlice]
|
Media[AudioSlice]
|
||||||
|
CodecID codec.AudioCodecID
|
||||||
Channels byte
|
Channels byte
|
||||||
avccHead []byte
|
avccHead []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (av *Audio) GetName() string {
|
func (at *Audio) Attach() {
|
||||||
if av.Name == "" {
|
at.Stream.AddTrack(at)
|
||||||
return strings.ToLower(codec.SoundFormat[av.CodecID])
|
}
|
||||||
|
|
||||||
|
func (at *Audio) GetName() string {
|
||||||
|
if at.Name == "" {
|
||||||
|
return at.CodecID.String()
|
||||||
}
|
}
|
||||||
return av.Name
|
return at.Name
|
||||||
}
|
}
|
||||||
func (at *Audio) GetInfo() *Audio {
|
func (at *Audio) GetInfo() *Audio {
|
||||||
return at
|
return at
|
||||||
@@ -81,16 +85,15 @@ func (at *Audio) Flush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UnknowAudio struct {
|
type UnknowAudio struct {
|
||||||
Name string
|
Base
|
||||||
Stream IStream
|
AudioTrack
|
||||||
Know AVTrack
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (at *UnknowAudio) WriteAVCC(ts uint32, frame AVCCFrame) {
|
func (at *UnknowAudio) WriteAVCC(ts uint32, frame AVCCFrame) {
|
||||||
if at.Know == nil {
|
if at.AudioTrack == nil {
|
||||||
codecID := frame.AudioCodecID()
|
codecID := frame.AudioCodecID()
|
||||||
if at.Name == "" {
|
if at.Name == "" {
|
||||||
at.Name = strings.ToLower(codec.SoundFormat[codecID])
|
at.Name = codecID.String()
|
||||||
}
|
}
|
||||||
switch codecID {
|
switch codecID {
|
||||||
case codec.CodecID_AAC:
|
case codec.CodecID_AAC:
|
||||||
@@ -98,11 +101,11 @@ func (at *UnknowAudio) WriteAVCC(ts uint32, frame AVCCFrame) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
a := NewAAC(at.Stream)
|
a := NewAAC(at.Stream)
|
||||||
at.Know = a
|
at.AudioTrack = a
|
||||||
a.SampleSize = 16
|
a.SampleSize = 16
|
||||||
a.avccHead = []byte{frame[0], 1}
|
a.avccHead = []byte{frame[0], 1}
|
||||||
a.WriteAVCC(0, frame)
|
a.WriteAVCC(0, frame)
|
||||||
a.Stream.AddTrack(&a.Audio)
|
a.Attach()
|
||||||
case codec.CodecID_PCMA,
|
case codec.CodecID_PCMA,
|
||||||
codec.CodecID_PCMU:
|
codec.CodecID_PCMU:
|
||||||
alaw := true
|
alaw := true
|
||||||
@@ -110,7 +113,7 @@ func (at *UnknowAudio) WriteAVCC(ts uint32, frame AVCCFrame) {
|
|||||||
alaw = false
|
alaw = false
|
||||||
}
|
}
|
||||||
a := NewG711(at.Stream, alaw)
|
a := NewG711(at.Stream, alaw)
|
||||||
at.Know = a
|
at.AudioTrack = a
|
||||||
a.SampleRate = uint32(codec.SoundRate[(frame[0]&0x0c)>>2])
|
a.SampleRate = uint32(codec.SoundRate[(frame[0]&0x0c)>>2])
|
||||||
a.SampleSize = 16
|
a.SampleSize = 16
|
||||||
if frame[0]&0x02 == 0 {
|
if frame[0]&0x02 == 0 {
|
||||||
@@ -118,9 +121,12 @@ func (at *UnknowAudio) WriteAVCC(ts uint32, frame AVCCFrame) {
|
|||||||
}
|
}
|
||||||
a.Channels = frame[0]&0x01 + 1
|
a.Channels = frame[0]&0x01 + 1
|
||||||
a.avccHead = frame[:1]
|
a.avccHead = frame[:1]
|
||||||
a.Stream.AddTrack(&a.Audio)
|
a.Attach()
|
||||||
|
at.AudioTrack.WriteAVCC(ts, frame)
|
||||||
|
default:
|
||||||
|
at.Stream.Errorf("audio codec not support yet:", codecID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
at.Know.WriteAVCC(ts, frame)
|
at.AudioTrack.WriteAVCC(ts, frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,6 @@ func (bt *Base) Flush(bf *BaseFrame) {
|
|||||||
type Media[T RawSlice] struct {
|
type Media[T RawSlice] struct {
|
||||||
Base
|
Base
|
||||||
AVRing[T] `json:"-"`
|
AVRing[T] `json:"-"`
|
||||||
CodecID byte
|
|
||||||
SampleRate uint32
|
SampleRate uint32
|
||||||
SampleSize byte
|
SampleSize byte
|
||||||
DecoderConfiguration DecoderConfiguration[T] `json:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config)
|
DecoderConfiguration DecoderConfiguration[T] `json:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config)
|
||||||
@@ -110,7 +109,7 @@ func (av *Media[T]) UnmarshalRTP(raw []byte) (frame *RTPFrame) {
|
|||||||
av.lastSeq2 = av.lastSeq
|
av.lastSeq2 = av.lastSeq
|
||||||
av.lastSeq = frame.SequenceNumber
|
av.lastSeq = frame.SequenceNumber
|
||||||
if av.lastSeq != av.lastSeq2+1 { //序号不连续
|
if av.lastSeq != av.lastSeq2+1 { //序号不连续
|
||||||
av.Stream.Warnln("RTP SequenceNumber error", av.lastSeq2, av.lastSeq)
|
av.Stream.Warn("RTP SequenceNumber error", av.lastSeq2, av.lastSeq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -113,7 +113,7 @@ func (vt *H264) WriteRTP(raw []byte) {
|
|||||||
func (vt *H264) Flush() {
|
func (vt *H264) Flush() {
|
||||||
if vt.Value.IFrame {
|
if vt.Value.IFrame {
|
||||||
if vt.IDRing == nil {
|
if vt.IDRing == nil {
|
||||||
defer vt.Stream.AddTrack(&vt.Video)
|
defer vt.Attach()
|
||||||
}
|
}
|
||||||
vt.Video.ComputeGOP()
|
vt.Video.ComputeGOP()
|
||||||
}
|
}
|
||||||
|
@@ -116,7 +116,7 @@ func (vt *H265) WriteRTP(raw []byte) {
|
|||||||
func (vt *H265) Flush() {
|
func (vt *H265) Flush() {
|
||||||
if vt.Value.IFrame {
|
if vt.Value.IFrame {
|
||||||
if vt.IDRing == nil {
|
if vt.IDRing == nil {
|
||||||
defer vt.Stream.AddTrack(&vt.Video)
|
defer vt.Attach()
|
||||||
}
|
}
|
||||||
vt.Video.ComputeGOP()
|
vt.Video.ComputeGOP()
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package track
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Monibuca/engine/v4/codec"
|
"github.com/Monibuca/engine/v4/codec"
|
||||||
. "github.com/Monibuca/engine/v4/common"
|
. "github.com/Monibuca/engine/v4/common"
|
||||||
@@ -13,6 +12,7 @@ import (
|
|||||||
|
|
||||||
type Video struct {
|
type Video struct {
|
||||||
Media[NALUSlice]
|
Media[NALUSlice]
|
||||||
|
CodecID codec.VideoCodecID
|
||||||
IDRing *util.Ring[AVFrame[NALUSlice]] `json:"-"` //最近的关键帧位置,首屏渲染
|
IDRing *util.Ring[AVFrame[NALUSlice]] `json:"-"` //最近的关键帧位置,首屏渲染
|
||||||
SPSInfo codec.SPSInfo
|
SPSInfo codec.SPSInfo
|
||||||
GOP int //关键帧间隔
|
GOP int //关键帧间隔
|
||||||
@@ -20,9 +20,13 @@ type Video struct {
|
|||||||
idrCount int //缓存中包含的idr数量
|
idrCount int //缓存中包含的idr数量
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Video) Attach() {
|
||||||
|
t.Stream.AddTrack(t)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Video) GetName() string {
|
func (t *Video) GetName() string {
|
||||||
if t.Name == "" {
|
if t.Name == "" {
|
||||||
return strings.ToLower(codec.CodecID[t.CodecID])
|
return t.CodecID.String()
|
||||||
}
|
}
|
||||||
return t.Name
|
return t.Name
|
||||||
}
|
}
|
||||||
@@ -86,7 +90,7 @@ func (vt *Video) WriteAVCC(ts uint32, frame AVCCFrame) {
|
|||||||
vt.Value.AppendRaw(NALUSlice{nalus[vt.nalulenSize:end]})
|
vt.Value.AppendRaw(NALUSlice{nalus[vt.nalulenSize:end]})
|
||||||
nalus = nalus[end:]
|
nalus = nalus[end:]
|
||||||
} else {
|
} else {
|
||||||
vt.Stream.Errorln("WriteAVCC error,len %d,nalulenSize:%d,end:%d", len(nalus), vt.nalulenSize, end)
|
vt.Stream.Error("WriteAVCC error,len %d,nalulenSize:%d,end:%d", len(nalus), vt.nalulenSize, end)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +104,7 @@ func (vt *Video) Flush() {
|
|||||||
}
|
}
|
||||||
// AVCC格式补完
|
// AVCC格式补完
|
||||||
if vt.Value.AVCC == nil && (config.Global.EnableAVCC || config.Global.EnableFLV) {
|
if vt.Value.AVCC == nil && (config.Global.EnableAVCC || config.Global.EnableFLV) {
|
||||||
b := []byte{vt.CodecID, 1, 0, 0, 0}
|
b := []byte{byte(vt.CodecID), 1, 0, 0, 0}
|
||||||
if vt.Value.IFrame {
|
if vt.Value.IFrame {
|
||||||
b[0] |= 0x10
|
b[0] |= 0x10
|
||||||
} else {
|
} else {
|
||||||
@@ -147,9 +151,8 @@ func (vt *Video) Play(onVideo func(*AVFrame[NALUSlice]) error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UnknowVideo struct {
|
type UnknowVideo struct {
|
||||||
Name string
|
Base
|
||||||
Stream IStream
|
VideoTrack
|
||||||
Know AVTrack
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -172,24 +175,27 @@ func (vt *UnknowVideo) WriteAnnexB(pts uint32, dts uint32, frame AnnexBFrame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (vt *UnknowVideo) WriteAVCC(ts uint32, frame AVCCFrame) {
|
func (vt *UnknowVideo) WriteAVCC(ts uint32, frame AVCCFrame) {
|
||||||
if vt.Know == nil {
|
if vt.VideoTrack == nil {
|
||||||
if frame.IsSequence() {
|
if frame.IsSequence() {
|
||||||
|
ts = 0
|
||||||
codecID := frame.VideoCodecID()
|
codecID := frame.VideoCodecID()
|
||||||
if vt.Name == "" {
|
if vt.Name == "" {
|
||||||
vt.Name = strings.ToLower(codec.CodecID[codecID])
|
vt.Name = codecID.String()
|
||||||
}
|
}
|
||||||
switch codecID {
|
switch codecID {
|
||||||
case codec.CodecID_H264:
|
case codec.CodecID_H264:
|
||||||
v := NewH264(vt.Stream)
|
vt.VideoTrack = NewH264(vt.Stream)
|
||||||
vt.Know = v
|
|
||||||
v.WriteAVCC(0, frame)
|
|
||||||
case codec.CodecID_H265:
|
case codec.CodecID_H265:
|
||||||
v := NewH265(vt.Stream)
|
vt.VideoTrack = NewH265(vt.Stream)
|
||||||
vt.Know = v
|
default:
|
||||||
v.WriteAVCC(0, frame)
|
vt.Stream.Error("video codecID not support: ", codecID)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
vt.VideoTrack.WriteAVCC(ts, frame)
|
||||||
|
} else {
|
||||||
|
vt.Stream.Warnf("need sequence frame")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vt.Know.WriteAVCC(ts, frame)
|
vt.VideoTrack.WriteAVCC(ts, frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ func (s *Stream) AddTrack(t Track) {
|
|||||||
defer s.Tracks.Unlock()
|
defer s.Tracks.Unlock()
|
||||||
name := t.GetName()
|
name := t.GetName()
|
||||||
if _, ok := s.Tracks.m[name]; !ok {
|
if _, ok := s.Tracks.m[name]; !ok {
|
||||||
s.Infoln("Track", name, "added")
|
s.Infof("Track '%s' added", name)
|
||||||
if s.Tracks.m[name] = t; s.Tracks.Err() == nil {
|
if s.Tracks.m[name] = t; s.Tracks.Err() == nil {
|
||||||
for _, ch := range s.Tracks.waiters[name] {
|
for _, ch := range s.Tracks.waiters[name] {
|
||||||
if *ch != nil {
|
if *ch != nil {
|
||||||
|
Reference in New Issue
Block a user