mirror of
https://github.com/Monibuca/engine.git
synced 2025-10-05 00:32:44 +08:00
feat: add stop subscribe api, show reasons for subscriber closure
This commit is contained in:
@@ -36,7 +36,8 @@
|
|||||||
- 热更新配置信息 `/api/updateconfig?name=xxx` 热更新xxx插件的配置信息,如果不带参数或参数为空则热更新全局配置
|
- 热更新配置信息 `/api/updateconfig?name=xxx` 热更新xxx插件的配置信息,如果不带参数或参数为空则热更新全局配置
|
||||||
- 获取所有远端拉流信息 `/api/list/pull` 返回{RemoteURL:"",StreamPath:"",Type:"",StartTime:""}
|
- 获取所有远端拉流信息 `/api/list/pull` 返回{RemoteURL:"",StreamPath:"",Type:"",StartTime:""}
|
||||||
- 获取所有向远端推流信息 `/api/list/push` 返回{RemoteURL:"",StreamPath:"",Type:"",StartTime:""}
|
- 获取所有向远端推流信息 `/api/list/push` 返回{RemoteURL:"",StreamPath:"",Type:"",StartTime:""}
|
||||||
- 停止推流 `/api/stoppush?url=xxx` 停止向xxx推流 ,成功返回ok
|
- 停止推流 `/api/stop/push?url=xxx` 停止向xxx推流 ,成功返回ok
|
||||||
|
- 停止某个订阅者 `/api/stop/subscribe?streamPath=xxx&id=xxx` 停止xxx流的xxx订阅者 ,成功返回ok
|
||||||
- 插入SEI帧 `/api/insertsei?streamPath=xxx&type=5` 向xxx流内插入SEI帧 ,成功返回ok。type为SEI类型,可选,默认是5
|
- 插入SEI帧 `/api/insertsei?streamPath=xxx&type=5` 向xxx流内插入SEI帧 ,成功返回ok。type为SEI类型,可选,默认是5
|
||||||
# 引擎默认配置
|
# 引擎默认配置
|
||||||
```yaml
|
```yaml
|
||||||
|
@@ -177,6 +177,7 @@ func (av *AVFrame) Reset() {
|
|||||||
av.ADTS = nil
|
av.ADTS = nil
|
||||||
}
|
}
|
||||||
av.Timestamp = 0
|
av.Timestamp = 0
|
||||||
|
av.IFrame = false
|
||||||
av.DataFrame.Reset()
|
av.DataFrame.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -135,7 +135,6 @@ type Engine struct {
|
|||||||
LogLang string `default:"zh"` //日志语言
|
LogLang string `default:"zh"` //日志语言
|
||||||
LogLevel string `default:"info"` //日志级别
|
LogLevel string `default:"info"` //日志级别
|
||||||
RTPReorderBufferLen int `default:"50"` //RTP重排序缓冲长度
|
RTPReorderBufferLen int `default:"50"` //RTP重排序缓冲长度
|
||||||
SpeedLimit time.Duration `default:"500ms"` //速度限制最大等待时间
|
|
||||||
EventBusSize int `default:"10"` //事件总线大小
|
EventBusSize int `default:"10"` //事件总线大小
|
||||||
PulseInterval time.Duration `default:"5s"` //心跳事件间隔
|
PulseInterval time.Duration `default:"5s"` //心跳事件间隔
|
||||||
DisableAll bool `default:"false"` //禁用所有插件
|
DisableAll bool `default:"false"` //禁用所有插件
|
||||||
|
16
go.mod
16
go.mod
@@ -3,18 +3,18 @@ module m7s.live/engine/v4
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aler9/gortsplib/v2 v2.2.2
|
github.com/bluenviron/mediacommon v0.7.0
|
||||||
github.com/cnotch/ipchub v1.1.0
|
github.com/cnotch/ipchub v1.1.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mcuadros/go-defaults v1.2.0
|
github.com/mcuadros/go-defaults v1.2.0
|
||||||
github.com/pion/rtp v1.7.13
|
github.com/pion/rtp v1.8.0
|
||||||
github.com/pion/webrtc/v3 v3.1.49
|
github.com/pion/webrtc/v3 v3.1.56
|
||||||
github.com/q191201771/naza v0.30.8
|
github.com/q191201771/naza v0.30.8
|
||||||
github.com/quic-go/quic-go v0.32.0
|
github.com/quic-go/quic-go v0.32.0
|
||||||
github.com/shirou/gopsutil/v3 v3.22.10
|
github.com/shirou/gopsutil/v3 v3.22.11
|
||||||
go.uber.org/zap v1.23.0
|
go.uber.org/zap v1.24.0
|
||||||
golang.org/x/net v0.8.0
|
golang.org/x/net v0.12.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.1.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
@@ -43,9 +43,9 @@ require (
|
|||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274
|
github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.4.0 // indirect
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||||
golang.org/x/mod v0.7.0 // indirect
|
golang.org/x/mod v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
golang.org/x/tools v0.3.0 // indirect
|
golang.org/x/tools v0.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
8
go.sum
8
go.sum
@@ -1,7 +1,7 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/aler9/gortsplib/v2 v2.2.2 h1:tTw8pdKSOEjlZjjE1S4ftXPHJkYOqjNNv3hjQ0Nto9M=
|
|
||||||
github.com/aler9/gortsplib/v2 v2.2.2/go.mod h1:k6uBVHGwsIc/0L5SLLqWwi6bSJUb4VR0HfvncyHlKQI=
|
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
|
github.com/bluenviron/mediacommon v0.7.0 h1:dJWLLL9oDbAqfK8KuNfnDUQwNbeMAtGeRjZc9Vo95js=
|
||||||
|
github.com/bluenviron/mediacommon v0.7.0/go.mod h1:wuLJdxcITiSPgY1MvQqrX+qPlKmNfeV9wNvXth5M98I=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
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/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/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -141,15 +141,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.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=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||||
github.com/yapingcat/gomedia v0.0.0-20230222121919-c67df405bf33 h1:uyZY++dluUg7iTSsNzuOVln/mC2U2KXwgKLfKLCJ74Y=
|
|
||||||
github.com/yapingcat/gomedia v0.0.0-20230222121919-c67df405bf33/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc=
|
|
||||||
github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274 h1:cj4I+bvWX9I+Hg6tnZ7DAiOVxzhyLhdvYVKp+WpM/2c=
|
github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274 h1:cj4I+bvWX9I+Hg6tnZ7DAiOVxzhyLhdvYVKp+WpM/2c=
|
||||||
github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc=
|
github.com/yapingcat/gomedia v0.0.0-20230426092936-387031404274/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
57
http.go
57
http.go
@@ -2,6 +2,7 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"m7s.live/engine/v4/codec"
|
"m7s.live/engine/v4/codec"
|
||||||
"m7s.live/engine/v4/config"
|
"m7s.live/engine/v4/config"
|
||||||
@@ -40,33 +42,12 @@ func (conf *GlobalConfig) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchSummary() *Summary {
|
|
||||||
return &summary
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *GlobalConfig) API_summary(rw http.ResponseWriter, r *http.Request) {
|
func (conf *GlobalConfig) API_summary(rw http.ResponseWriter, r *http.Request) {
|
||||||
y := ShouldYaml(r)
|
y := ShouldYaml(r)
|
||||||
if r.Header.Get("Accept") == "text/event-stream" {
|
if y {
|
||||||
summary.Add()
|
util.ReturnYaml(util.FetchValue(&summary), time.Second, rw, r)
|
||||||
defer summary.Done()
|
|
||||||
if y {
|
|
||||||
util.ReturnYaml(fetchSummary, time.Second, rw, r)
|
|
||||||
} else {
|
|
||||||
util.ReturnJson(fetchSummary, time.Second, rw, r)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if !summary.Running() {
|
util.ReturnJson(util.FetchValue(&summary), time.Second, rw, r)
|
||||||
summary.collect()
|
|
||||||
}
|
|
||||||
summary.rw.RLock()
|
|
||||||
defer summary.rw.RUnlock()
|
|
||||||
if y {
|
|
||||||
if err := yaml.NewEncoder(rw).Encode(&summary); err != nil {
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
} else if err := json.NewEncoder(rw).Encode(&summary); err != nil {
|
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +65,9 @@ func (conf *GlobalConfig) API_stream(rw http.ResponseWriter, r *http.Request) {
|
|||||||
if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" {
|
if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" {
|
||||||
if s := Streams.Get(streamPath); s != nil {
|
if s := Streams.Get(streamPath); s != nil {
|
||||||
if ShouldYaml(r) {
|
if ShouldYaml(r) {
|
||||||
util.ReturnYaml(func() *Stream { return s }, time.Second, rw, r)
|
util.ReturnYaml(util.FetchValue(s), time.Second, rw, r)
|
||||||
} else {
|
} else {
|
||||||
util.ReturnJson(func() *Stream { return s }, time.Second, rw, r)
|
util.ReturnJson(util.FetchValue(s), time.Second, rw, r)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
http.Error(rw, NO_SUCH_STREAM, http.StatusNotFound)
|
http.Error(rw, NO_SUCH_STREAM, http.StatusNotFound)
|
||||||
@@ -218,17 +199,35 @@ func (conf *GlobalConfig) API_list_push(w http.ResponseWriter, r *http.Request)
|
|||||||
}, time.Second, w, r)
|
}, time.Second, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conf *GlobalConfig) API_stopPush(w http.ResponseWriter, r *http.Request) {
|
func (conf *GlobalConfig) API_stop_push(w http.ResponseWriter, r *http.Request) {
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
pusher, ok := Pushers.Load(q.Get("url"))
|
pusher, ok := Pushers.Load(q.Get("url"))
|
||||||
if ok {
|
if ok {
|
||||||
pusher.(IPusher).Stop()
|
pusher.(IPusher).Stop()
|
||||||
w.Write([]byte("ok"))
|
fmt.Fprintln(w, "ok")
|
||||||
} else {
|
} else {
|
||||||
http.Error(w, "no such pusher", http.StatusNotFound)
|
http.Error(w, "no such pusher", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conf *GlobalConfig) API_stop_subscribe(w http.ResponseWriter, r *http.Request) {
|
||||||
|
q := r.URL.Query()
|
||||||
|
streamPath := q.Get("streamPath")
|
||||||
|
id := q.Get("id")
|
||||||
|
s := Streams.Get(streamPath)
|
||||||
|
if s == nil {
|
||||||
|
http.Error(w, NO_SUCH_STREAM, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
suber := s.Subscribers.Find(id)
|
||||||
|
if suber == nil {
|
||||||
|
http.Error(w, "no such subscriber", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
suber.Stop(zap.String("reason", "stop by api"))
|
||||||
|
fmt.Fprintln(w, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
func (conf *GlobalConfig) API_replay_rtpdump(w http.ResponseWriter, r *http.Request) {
|
func (conf *GlobalConfig) API_replay_rtpdump(w http.ResponseWriter, r *http.Request) {
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
streamPath := q.Get("streamPath")
|
streamPath := q.Get("streamPath")
|
||||||
@@ -314,7 +313,7 @@ func (conf *GlobalConfig) API_replay_ts(w http.ResponseWriter, r *http.Request)
|
|||||||
} else {
|
} else {
|
||||||
tsReader := NewTSReader(&pub)
|
tsReader := NewTSReader(&pub)
|
||||||
pub.SetIO(f)
|
pub.SetIO(f)
|
||||||
go func(){
|
go func() {
|
||||||
tsReader.Feed(f)
|
tsReader.Feed(f)
|
||||||
tsReader.Close()
|
tsReader.Close()
|
||||||
}()
|
}()
|
||||||
|
11
io.go
11
io.go
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
"m7s.live/engine/v4/config"
|
"m7s.live/engine/v4/config"
|
||||||
"m7s.live/engine/v4/log"
|
"m7s.live/engine/v4/log"
|
||||||
"m7s.live/engine/v4/util"
|
"m7s.live/engine/v4/util"
|
||||||
@@ -36,6 +37,7 @@ type AuthPub interface {
|
|||||||
type IO struct {
|
type IO struct {
|
||||||
ID string
|
ID string
|
||||||
Type string
|
Type string
|
||||||
|
RemoteAddr string
|
||||||
context.Context `json:"-" yaml:"-"` //不要直接设置,应当通过OnEvent传入父级Context
|
context.Context `json:"-" yaml:"-"` //不要直接设置,应当通过OnEvent传入父级Context
|
||||||
context.CancelFunc `json:"-" yaml:"-"` //流关闭是关闭发布者或者订阅者
|
context.CancelFunc `json:"-" yaml:"-"` //流关闭是关闭发布者或者订阅者
|
||||||
*log.Logger `json:"-" yaml:"-"`
|
*log.Logger `json:"-" yaml:"-"`
|
||||||
@@ -92,7 +94,7 @@ type IIO interface {
|
|||||||
receive(string, IIO) error
|
receive(string, IIO) error
|
||||||
IsClosed() bool
|
IsClosed() bool
|
||||||
OnEvent(any)
|
OnEvent(any)
|
||||||
Stop()
|
Stop(reason ...zapcore.Field)
|
||||||
SetIO(any)
|
SetIO(any)
|
||||||
SetParentCtx(context.Context)
|
SetParentCtx(context.Context)
|
||||||
SetLogger(*log.Logger)
|
SetLogger(*log.Logger)
|
||||||
@@ -113,9 +115,11 @@ func (i *IO) close() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop 停止订阅或者发布,由订阅者或者发布者调用
|
// Stop 停止订阅或者发布,由订阅者或者发布者调用
|
||||||
func (io *IO) Stop() {
|
func (io *IO) Stop(reason ...zapcore.Field) {
|
||||||
if io.close() {
|
if io.close() {
|
||||||
io.Debug("stop", zap.Stack("stack"))
|
io.Info("stop", reason...)
|
||||||
|
} else {
|
||||||
|
io.Warn("already stopped", reason...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +198,7 @@ func (io *IO) receive(streamPath string, specific IIO) error {
|
|||||||
} else if oldPublisher == specific {
|
} else if oldPublisher == specific {
|
||||||
//断线重连
|
//断线重连
|
||||||
} else {
|
} else {
|
||||||
|
s.Warn("duplicate publish", zap.String("type", oldPublisher.GetPublisher().Type))
|
||||||
return ErrDuplicatePublish
|
return ErrDuplicatePublish
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ state: 状态
|
|||||||
initialize: 初始化
|
initialize: 初始化
|
||||||
"start read": 开始读取
|
"start read": 开始读取
|
||||||
"start pull": 开始从远端拉流
|
"start pull": 开始从远端拉流
|
||||||
|
"restart pull": 重新拉流
|
||||||
"pull failed": 拉取失败
|
"pull failed": 拉取失败
|
||||||
"wait publisher": 等待发布者发布
|
"wait publisher": 等待发布者发布
|
||||||
"wait timeout": 等待超时
|
"wait timeout": 等待超时
|
||||||
@@ -45,6 +46,7 @@ reamins: 剩余
|
|||||||
"video track attached": 视频轨道已附加
|
"video track attached": 视频轨道已附加
|
||||||
"audio track attached": 音频轨道已附加
|
"audio track attached": 音频轨道已附加
|
||||||
"data track attached": 数据轨道已附加
|
"data track attached": 数据轨道已附加
|
||||||
|
"track back online": 轨道已恢复
|
||||||
"first frame read": 第一帧已读取
|
"first frame read": 第一帧已读取
|
||||||
"fu have no start": rtp的FU起始包丢了
|
"fu have no start": rtp的FU起始包丢了
|
||||||
"disabled by env": 被环境变量禁用
|
"disabled by env": 被环境变量禁用
|
||||||
@@ -54,4 +56,6 @@ reamins: 剩余
|
|||||||
firstTs: 第一帧时间戳
|
firstTs: 第一帧时间戳
|
||||||
firstSeq: 第一帧序列号
|
firstSeq: 第一帧序列号
|
||||||
skipSeq: 跳过序列号
|
skipSeq: 跳过序列号
|
||||||
skipTs: 跳过时间戳
|
skipTs: 跳过时间戳
|
||||||
|
"nalu type not supported": nalu类型不支持
|
||||||
|
"create file": 创建文件
|
||||||
|
@@ -50,6 +50,9 @@ func (ts *MemoryTs) WritePESPacket(frame *mpegts.MpegtsPESFrame, packet mpegts.M
|
|||||||
var tsHeaderLength int
|
var tsHeaderLength int
|
||||||
for i := 0; len(pesBuffers) > 0; i++ {
|
for i := 0; len(pesBuffers) > 0; i++ {
|
||||||
if bigLen < mpegts.TS_PACKET_SIZE {
|
if bigLen < mpegts.TS_PACKET_SIZE {
|
||||||
|
if i == 0 {
|
||||||
|
ts.Recycle()
|
||||||
|
}
|
||||||
headerItem := ts.Get(mpegts.TS_PACKET_SIZE)
|
headerItem := ts.Get(mpegts.TS_PACKET_SIZE)
|
||||||
ts.BLL.Push(headerItem)
|
ts.BLL.Push(headerItem)
|
||||||
bwTsHeader = &headerItem.Value
|
bwTsHeader = &headerItem.Value
|
||||||
|
104
plugin.go
104
plugin.go
@@ -294,7 +294,9 @@ func (opt *Plugin) Subscribe(streamPath string, sub ISubscriber) error {
|
|||||||
copyConfig := *conf.GetSubscribeConfig()
|
copyConfig := *conf.GetSubscribeConfig()
|
||||||
suber.Config = ©Config
|
suber.Config = ©Config
|
||||||
}
|
}
|
||||||
suber.ID = fmt.Sprintf("%s_%d", suber.ID, uintptr(unsafe.Pointer(suber)))
|
if suber.ID == "" {
|
||||||
|
suber.ID = fmt.Sprintf("%d", uintptr(unsafe.Pointer(suber)))
|
||||||
|
}
|
||||||
return sub.Subscribe(streamPath, sub)
|
return sub.Subscribe(streamPath, sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,60 +312,66 @@ var ErrNoPullConfig = errors.New("no pull config")
|
|||||||
var Pullers sync.Map
|
var Pullers sync.Map
|
||||||
|
|
||||||
func (opt *Plugin) Pull(streamPath string, url string, puller IPuller, save int) (err error) {
|
func (opt *Plugin) Pull(streamPath string, url string, puller IPuller, save int) (err error) {
|
||||||
zurl := zap.String("url", url)
|
|
||||||
zpath := zap.String("path", streamPath)
|
|
||||||
opt.Info("pull", zpath, zurl)
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
opt.Error("pull failed", zurl, zap.Error(err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
conf, ok := opt.Config.(config.PullConfig)
|
conf, ok := opt.Config.(config.PullConfig)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrNoPullConfig
|
return ErrNoPullConfig
|
||||||
}
|
}
|
||||||
pullConf := conf.GetPullConfig()
|
pullConf := conf.GetPullConfig()
|
||||||
|
if save < 2 {
|
||||||
puller.init(streamPath, url, pullConf)
|
zurl := zap.String("url", url)
|
||||||
puller.SetLogger(opt.Logger.With(zpath, zurl))
|
zpath := zap.String("path", streamPath)
|
||||||
go func() {
|
opt.Info("pull", zpath, zurl)
|
||||||
Pullers.Store(puller, url)
|
defer func() {
|
||||||
defer Pullers.Delete(puller)
|
if err != nil {
|
||||||
for opt.Info("start pull", zurl); puller.Reconnect(); opt.Warn("restart pull", zurl) {
|
opt.Error("pull failed", zurl, zap.Error(err))
|
||||||
if err = puller.Connect(); err != nil {
|
}
|
||||||
if err == io.EOF {
|
}()
|
||||||
puller.GetPublisher().Stream.Close()
|
puller.init(streamPath, url, pullConf)
|
||||||
opt.Info("pull complete", zurl)
|
puller.SetLogger(opt.Logger.With(zpath, zurl))
|
||||||
return
|
badPuller := true
|
||||||
}
|
go func() {
|
||||||
opt.Error("pull connect", zurl, zap.Error(err))
|
Pullers.Store(puller, url)
|
||||||
time.Sleep(time.Second * 5)
|
defer Pullers.Delete(puller)
|
||||||
} else {
|
for opt.Info("start pull", zurl); puller.Reconnect(); opt.Warn("restart pull", zurl) {
|
||||||
if err = opt.Publish(streamPath, puller); err != nil {
|
if err = puller.Connect(); err != nil {
|
||||||
if stream := Streams.Get(streamPath); stream != nil {
|
if err == io.EOF {
|
||||||
if stream.Publisher != puller && stream.Publisher != nil {
|
puller.GetPublisher().Stream.Close()
|
||||||
io := stream.Publisher.GetPublisher()
|
opt.Info("pull complete", zurl)
|
||||||
opt.Error("puller is not publisher", zap.String("ID", io.ID), zap.String("Type", io.Type), zap.Error(err))
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
opt.Warn("pull publish", zurl, zap.Error(err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
opt.Error("pull publish", zurl, zap.Error(err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
opt.Error("pull connect", zurl, zap.Error(err))
|
||||||
|
if badPuller {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
} else {
|
||||||
|
if err = opt.Publish(streamPath, puller); err != nil {
|
||||||
|
if stream := Streams.Get(streamPath); stream != nil {
|
||||||
|
if stream.Publisher != puller && stream.Publisher != nil {
|
||||||
|
io := stream.Publisher.GetPublisher()
|
||||||
|
opt.Error("puller is not publisher", zap.String("ID", io.ID), zap.String("Type", io.Type), zap.Error(err))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
opt.Warn("pull publish", zurl, zap.Error(err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opt.Error("pull publish", zurl, zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
badPuller = false
|
||||||
|
if err = puller.Pull(); err != nil && !puller.IsShutdown() {
|
||||||
|
opt.Error("pull", zurl, zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err = puller.Pull(); err != nil && !puller.IsShutdown() {
|
if puller.IsShutdown() {
|
||||||
opt.Error("pull", zurl, zap.Error(err))
|
opt.Info("stop pull shutdown", zurl)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if puller.IsShutdown() {
|
opt.Warn("stop pull stop reconnect", zurl)
|
||||||
opt.Info("stop pull shutdown", zurl)
|
}()
|
||||||
return
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
opt.Warn("stop pull stop reconnect", zurl)
|
|
||||||
}()
|
|
||||||
switch save {
|
switch save {
|
||||||
case 1:
|
case 1:
|
||||||
pullConf.AddPullOnStart(streamPath, url)
|
pullConf.AddPullOnStart(streamPath, url)
|
||||||
@@ -400,7 +408,7 @@ func (opt *Plugin) Push(streamPath string, url string, pusher IPusher, save bool
|
|||||||
pushConfig := conf.GetPushConfig()
|
pushConfig := conf.GetPushConfig()
|
||||||
|
|
||||||
pusher.init(streamPath, url, pushConfig)
|
pusher.init(streamPath, url, pushConfig)
|
||||||
|
badPusher := true
|
||||||
go func() {
|
go func() {
|
||||||
Pushers.Store(url, pusher)
|
Pushers.Store(url, pusher)
|
||||||
defer Pushers.Delete(url)
|
defer Pushers.Delete(url)
|
||||||
@@ -418,10 +426,14 @@ func (opt *Plugin) Push(streamPath string, url string, pusher IPusher, save bool
|
|||||||
opt.Error("push connect", zp, zu, zap.Error(err))
|
opt.Error("push connect", zp, zu, zap.Error(err))
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(time.Second * 5)
|
||||||
stream.Receive(pusher) // 通知stream移除订阅者
|
stream.Receive(pusher) // 通知stream移除订阅者
|
||||||
|
if badPusher {
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if err = pusher.Push(); err != nil && !stream.IsClosed() {
|
} else if err = pusher.Push(); err != nil && !stream.IsClosed() {
|
||||||
opt.Error("push", zp, zu, zap.Error(err))
|
opt.Error("push", zp, zu, zap.Error(err))
|
||||||
pusher.Stop()
|
pusher.Stop()
|
||||||
}
|
}
|
||||||
|
badPusher = false
|
||||||
if stream.IsClosed() {
|
if stream.IsClosed() {
|
||||||
opt.Info("stop push closed", zp, zu)
|
opt.Info("stop push closed", zp, zu)
|
||||||
return
|
return
|
||||||
|
@@ -5,7 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/v2/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
"github.com/pion/webrtc/v3/pkg/media/rtpdump"
|
"github.com/pion/webrtc/v3/pkg/media/rtpdump"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"m7s.live/engine/v4/codec"
|
"m7s.live/engine/v4/codec"
|
||||||
@@ -20,7 +20,7 @@ type RTPDumpPublisher struct {
|
|||||||
ACodec codec.AudioCodecID
|
ACodec codec.AudioCodecID
|
||||||
VPayloadType uint8
|
VPayloadType uint8
|
||||||
APayloadType uint8
|
APayloadType uint8
|
||||||
other *rtpdump.Packet
|
other rtpdump.Packet
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,15 +77,15 @@ func (t *RTPDumpPublisher) Feed(file *os.File) {
|
|||||||
if needLock {
|
if needLock {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
}
|
}
|
||||||
if t.other == nil {
|
if t.other.Payload == nil {
|
||||||
t.other = &packet
|
t.other = packet
|
||||||
t.Unlock()
|
t.Unlock()
|
||||||
needLock = true
|
needLock = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if packet.Offset > t.other.Offset {
|
if packet.Offset >= t.other.Offset {
|
||||||
t.WriteRTP(t.other.Payload)
|
t.WriteRTP(t.other.Payload)
|
||||||
t.other = &packet
|
t.other = packet
|
||||||
t.Unlock()
|
t.Unlock()
|
||||||
needLock = true
|
needLock = true
|
||||||
continue
|
continue
|
||||||
|
@@ -2,6 +2,7 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
"m7s.live/engine/v4/codec"
|
"m7s.live/engine/v4/codec"
|
||||||
"m7s.live/engine/v4/common"
|
"m7s.live/engine/v4/common"
|
||||||
"m7s.live/engine/v4/config"
|
"m7s.live/engine/v4/config"
|
||||||
@@ -34,10 +35,11 @@ func (p *Publisher) GetPublisher() *Publisher {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Publisher) Stop() {
|
func (p *Publisher) Stop(reason ...zapcore.Field) {
|
||||||
p.IO.Stop()
|
p.IO.Stop(reason...)
|
||||||
p.Stream.Receive(ACTION_PUBLISHLOST)
|
p.Stream.Receive(ACTION_PUBLISHLOST)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Publisher) getAudioTrack() common.AudioTrack {
|
func (p *Publisher) getAudioTrack() common.AudioTrack {
|
||||||
return p.AudioTrack
|
return p.AudioTrack
|
||||||
}
|
}
|
||||||
|
@@ -105,7 +105,7 @@ type ISubscriber interface {
|
|||||||
PlayRaw()
|
PlayRaw()
|
||||||
PlayBlock(byte)
|
PlayBlock(byte)
|
||||||
PlayFLV()
|
PlayFLV()
|
||||||
Stop()
|
Stop(reason ...zapcore.Field)
|
||||||
Subscribe(streamPath string, sub ISubscriber) error
|
Subscribe(streamPath string, sub ISubscriber) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +316,9 @@ func (s *Subscriber) PlayBlock(subType byte) {
|
|||||||
if hasVideo {
|
if hasVideo {
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
err := s.VideoReader.ReadFrame(subMode)
|
err := s.VideoReader.ReadFrame(subMode)
|
||||||
|
if err == nil {
|
||||||
|
err = ctx.Err()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stopReason = zap.Error(err)
|
stopReason = zap.Error(err)
|
||||||
return
|
return
|
||||||
@@ -361,6 +364,9 @@ func (s *Subscriber) PlayBlock(subType byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := s.AudioReader.ReadFrame(subMode)
|
err := s.AudioReader.ReadFrame(subMode)
|
||||||
|
if err == nil {
|
||||||
|
err = ctx.Err()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stopReason = zap.Error(err)
|
stopReason = zap.Error(err)
|
||||||
return
|
return
|
||||||
|
@@ -94,6 +94,15 @@ func (s *Subscribers) AbortWait() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Subscribers) Find(id string) ISubscriber {
|
||||||
|
for sub := range s.public {
|
||||||
|
if sub.GetSubscriber().ID == id {
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Subscribers) Delete(suber ISubscriber) {
|
func (s *Subscribers) Delete(suber ISubscriber) {
|
||||||
delete(s.public, suber)
|
delete(s.public, suber)
|
||||||
io := suber.GetSubscriber()
|
io := suber.GetSubscriber()
|
||||||
|
91
summary.go
91
summary.go
@@ -1,25 +1,23 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"github.com/shirou/gopsutil/v3/disk"
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
"m7s.live/engine/v4/log"
|
|
||||||
"m7s.live/engine/v4/util"
|
"m7s.live/engine/v4/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var summary Summary
|
var (
|
||||||
var children util.Map[string, *Summary]
|
summary SummaryUtil
|
||||||
|
lastSummary Summary
|
||||||
func init() {
|
children util.Map[string, *Summary]
|
||||||
go summary.Start()
|
collectLock sync.Mutex
|
||||||
}
|
)
|
||||||
|
|
||||||
// ServerSummary 系统摘要定义
|
// ServerSummary 系统摘要定义
|
||||||
type Summary struct {
|
type Summary struct {
|
||||||
Address string
|
Address string
|
||||||
@@ -36,11 +34,9 @@ type Summary struct {
|
|||||||
Used uint64
|
Used uint64
|
||||||
Usage float64
|
Usage float64
|
||||||
}
|
}
|
||||||
NetWork []NetWorkInfo
|
NetWork []NetWorkInfo
|
||||||
Streams []StreamSummay
|
Streams []StreamSummay
|
||||||
lastNetWork []net.IOCountersStat
|
ts time.Time //上次更新时间
|
||||||
ref atomic.Int32
|
|
||||||
rw sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetWorkInfo 网速信息
|
// NetWorkInfo 网速信息
|
||||||
@@ -51,51 +47,28 @@ type NetWorkInfo struct {
|
|||||||
ReceiveSpeed uint64
|
ReceiveSpeed uint64
|
||||||
SentSpeed uint64
|
SentSpeed uint64
|
||||||
}
|
}
|
||||||
|
type SummaryUtil Summary
|
||||||
// StartSummary 开始定时采集数据,每秒一次
|
|
||||||
func (s *Summary) Start() {
|
|
||||||
for range time.Tick(time.Second) {
|
|
||||||
if s.Running() {
|
|
||||||
summary.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (s *Summary) Point() *Summary {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Running 是否正在采集数据
|
|
||||||
func (s *Summary) Running() bool {
|
|
||||||
return s.ref.Load() > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add 增加订阅者
|
|
||||||
func (s *Summary) Add() {
|
|
||||||
if count := s.ref.Add(1); count == 1 {
|
|
||||||
log.Info("start report summary")
|
|
||||||
} else {
|
|
||||||
log.Info("summary count", count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done 删除订阅者
|
|
||||||
func (s *Summary) Done() {
|
|
||||||
if count := s.ref.Add(-1); count == 0 {
|
|
||||||
log.Info("stop report summary")
|
|
||||||
s.lastNetWork = nil
|
|
||||||
} else {
|
|
||||||
log.Info("summary count", count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report 上报数据
|
// Report 上报数据
|
||||||
func (s *Summary) Report(slave *Summary) {
|
func (s *Summary) Report(slave *Summary) {
|
||||||
children.Set(slave.Address, slave)
|
children.Set(slave.Address, slave)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Summary) collect() *Summary {
|
func (s *SummaryUtil) MarshalJSON() ([]byte, error) {
|
||||||
s.rw.Lock()
|
return json.Marshal(s.collect())
|
||||||
defer s.rw.Unlock()
|
}
|
||||||
|
|
||||||
|
func (s *SummaryUtil) MarshalYAML() (any, error) {
|
||||||
|
return s.collect(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SummaryUtil) collect() *Summary {
|
||||||
|
collectLock.Lock()
|
||||||
|
defer collectLock.Unlock()
|
||||||
|
dur := time.Since(s.ts)
|
||||||
|
if dur < time.Second {
|
||||||
|
return &lastSummary
|
||||||
|
}
|
||||||
|
s.ts = time.Now()
|
||||||
v, _ := mem.VirtualMemory()
|
v, _ := mem.VirtualMemory()
|
||||||
d, _ := disk.Usage("/")
|
d, _ := disk.Usage("/")
|
||||||
nv, _ := net.IOCounters(true)
|
nv, _ := net.IOCounters(true)
|
||||||
@@ -119,16 +92,16 @@ func (s *Summary) collect() *Summary {
|
|||||||
Receive: n.BytesRecv,
|
Receive: n.BytesRecv,
|
||||||
Sent: n.BytesSent,
|
Sent: n.BytesSent,
|
||||||
}
|
}
|
||||||
if s.lastNetWork != nil && len(s.lastNetWork) > i {
|
if len(lastSummary.NetWork) > i {
|
||||||
info.ReceiveSpeed = n.BytesRecv - s.lastNetWork[i].BytesRecv
|
info.ReceiveSpeed = (n.BytesRecv - lastSummary.NetWork[i].Receive) / uint64(dur.Seconds())
|
||||||
info.SentSpeed = n.BytesSent - s.lastNetWork[i].BytesSent
|
info.SentSpeed = (n.BytesSent - lastSummary.NetWork[i].Sent) / uint64(dur.Seconds())
|
||||||
}
|
}
|
||||||
netWorks = append(netWorks, info)
|
netWorks = append(netWorks, info)
|
||||||
}
|
}
|
||||||
s.NetWork = netWorks
|
s.NetWork = netWorks
|
||||||
s.lastNetWork = nv
|
|
||||||
s.Streams = util.MapList(&Streams, func(name string, ss *Stream) StreamSummay {
|
s.Streams = util.MapList(&Streams, func(name string, ss *Stream) StreamSummay {
|
||||||
return ss.Summary()
|
return ss.Summary()
|
||||||
})
|
})
|
||||||
return s
|
lastSummary = Summary(*s)
|
||||||
|
return &lastSummary
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/v2/pkg/bits"
|
"github.com/bluenviron/mediacommon/pkg/bits"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"m7s.live/engine/v4/codec"
|
"m7s.live/engine/v4/codec"
|
||||||
. "m7s.live/engine/v4/common"
|
. "m7s.live/engine/v4/common"
|
||||||
@@ -171,7 +171,7 @@ func (aac *AAC) WriteSequenceHead(sh []byte) {
|
|||||||
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.Parse(aac.SequenceHead[2:])
|
aac.Parse(aac.SequenceHead[2:])
|
||||||
aac.Attach()
|
go aac.Attach()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aac *AAC) WriteAVCC(ts uint32, frame *util.BLL) error {
|
func (aac *AAC) WriteAVCC(ts uint32, frame *util.BLL) error {
|
||||||
|
@@ -32,7 +32,7 @@ func (p *流速控制) 根据起始DTS计算绝对时间戳(dts time.Duration) t
|
|||||||
return ((dts-p.起始dts)*time.Millisecond + p.起始时间戳*90) / 90
|
return ((dts-p.起始dts)*time.Millisecond + p.起始时间戳*90) / 90
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *流速控制) 控制流速(绝对时间戳 time.Duration, dts time.Duration) {
|
func (p *流速控制) 控制流速(绝对时间戳 time.Duration, dts time.Duration) (等待了 time.Duration) {
|
||||||
数据时间差, 实际时间差 := 绝对时间戳-p.起始时间戳, time.Since(p.起始时间)
|
数据时间差, 实际时间差 := 绝对时间戳-p.起始时间戳, time.Since(p.起始时间)
|
||||||
// println("数据时间差", 数据时间差, "实际时间差", 实际时间差, "绝对时间戳", 绝对时间戳, "起始时间戳", p.起始时间戳, "起始时间", p.起始时间.Format("2006-01-02 15:04:05"))
|
// println("数据时间差", 数据时间差, "实际时间差", 实际时间差, "绝对时间戳", 绝对时间戳, "起始时间戳", p.起始时间戳, "起始时间", p.起始时间.Format("2006-01-02 15:04:05"))
|
||||||
// if 实际时间差 > 数据时间差 {
|
// if 实际时间差 > 数据时间差 {
|
||||||
@@ -43,19 +43,18 @@ func (p *流速控制) 控制流速(绝对时间戳 time.Duration, dts time.Dura
|
|||||||
if 过快 := (数据时间差 - 实际时间差); 过快 > 100*time.Millisecond {
|
if 过快 := (数据时间差 - 实际时间差); 过快 > 100*time.Millisecond {
|
||||||
// fmt.Println("过快毫秒", 过快.Milliseconds())
|
// fmt.Println("过快毫秒", 过快.Milliseconds())
|
||||||
// println("过快毫秒", p.name, 过快.Milliseconds())
|
// println("过快毫秒", p.name, 过快.Milliseconds())
|
||||||
// if log.Trace {
|
|
||||||
// log.Trace("sleep", zap.Duration("sleep", 过快))
|
|
||||||
// }
|
|
||||||
if 过快 > p.等待上限 {
|
if 过快 > p.等待上限 {
|
||||||
time.Sleep(p.等待上限)
|
等待了 = p.等待上限
|
||||||
} else {
|
} else {
|
||||||
time.Sleep(过快)
|
等待了 = 过快
|
||||||
}
|
}
|
||||||
|
time.Sleep(等待了)
|
||||||
} else if 过快 < -100*time.Millisecond {
|
} else if 过快 < -100*time.Millisecond {
|
||||||
// fmt.Println("过慢毫秒", 过快.Milliseconds())
|
// fmt.Println("过慢毫秒", 过快.Milliseconds())
|
||||||
// p.重置(绝对时间戳, dts)
|
// p.重置(绝对时间戳, dts)
|
||||||
// println("过慢毫秒", p.name, 过快.Milliseconds())
|
// println("过慢毫秒", p.name, 过快.Milliseconds())
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpesificTrack interface {
|
type SpesificTrack interface {
|
||||||
@@ -305,7 +304,10 @@ func (av *Media) Flush() {
|
|||||||
av.ComputeBPS(curValue.BytesIn)
|
av.ComputeBPS(curValue.BytesIn)
|
||||||
av.Step()
|
av.Step()
|
||||||
if av.等待上限 > 0 {
|
if av.等待上限 > 0 {
|
||||||
av.控制流速(curValue.Timestamp, curValue.DTS)
|
等待了 := av.控制流速(curValue.Timestamp, curValue.DTS)
|
||||||
|
if log.Trace && 等待了 > 0 {
|
||||||
|
av.Trace("speed control", zap.Duration("sleep", 等待了))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -66,3 +66,16 @@ func (g711 *G711) WriteRTPFrame(frame *RTPFrame) {
|
|||||||
g711.AppendAuBytes(frame.Payload)
|
g711.AppendAuBytes(frame.Payload)
|
||||||
g711.Flush()
|
g711.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g711 *G711) CompleteRTP(value *AVFrame) {
|
||||||
|
if value.AUList.ByteLength > RTPMTU {
|
||||||
|
var packets [][][]byte
|
||||||
|
r := value.AUList.Next.Value.NewReader()
|
||||||
|
for bufs := r.ReadN(RTPMTU); len(bufs) > 0; bufs = r.ReadN(RTPMTU) {
|
||||||
|
packets = append(packets, bufs)
|
||||||
|
}
|
||||||
|
g711.PacketizeRTP(packets...)
|
||||||
|
} else {
|
||||||
|
g711.Audio.CompleteRTP(value)
|
||||||
|
}
|
||||||
|
}
|
@@ -35,6 +35,10 @@ func (vt *H264) WriteSliceBytes(slice []byte) {
|
|||||||
if log.Trace {
|
if log.Trace {
|
||||||
vt.Trace("naluType", zap.Uint8("naluType", naluType.Byte()))
|
vt.Trace("naluType", zap.Uint8("naluType", naluType.Byte()))
|
||||||
}
|
}
|
||||||
|
if vt.Value.IFrame {
|
||||||
|
vt.AppendAuBytes(slice)
|
||||||
|
return
|
||||||
|
}
|
||||||
switch naluType {
|
switch naluType {
|
||||||
case codec.NALU_SPS:
|
case codec.NALU_SPS:
|
||||||
spsInfo, _ := codec.ParseSPS(slice)
|
spsInfo, _ := codec.ParseSPS(slice)
|
||||||
@@ -78,7 +82,7 @@ func (vt *H264) WriteSliceBytes(slice []byte) {
|
|||||||
case codec.NALU_Access_Unit_Delimiter:
|
case codec.NALU_Access_Unit_Delimiter:
|
||||||
case codec.NALU_Filler_Data:
|
case codec.NALU_Filler_Data:
|
||||||
default:
|
default:
|
||||||
vt.Error("WriteSliceBytes naluType not support", zap.Int("naluType", int(naluType)))
|
vt.Error("nalu type not support", zap.Int("type", int(naluType)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +159,7 @@ func (vt *H264) CompleteRTP(value *AVFrame) {
|
|||||||
if value.IFrame {
|
if value.IFrame {
|
||||||
out = append(out, [][]byte{vt.SPS}, [][]byte{vt.PPS})
|
out = append(out, [][]byte{vt.SPS}, [][]byte{vt.PPS})
|
||||||
}
|
}
|
||||||
|
startIndex := len(out)
|
||||||
vt.Value.AUList.Range(func(au *util.BLL) bool {
|
vt.Value.AUList.Range(func(au *util.BLL) bool {
|
||||||
if au.ByteLength < RTPMTU {
|
if au.ByteLength < RTPMTU {
|
||||||
out = append(out, au.ToBuffers())
|
out = append(out, au.ToBuffers())
|
||||||
@@ -164,14 +169,11 @@ func (vt *H264) CompleteRTP(value *AVFrame) {
|
|||||||
b0, _ := r.ReadByte()
|
b0, _ := r.ReadByte()
|
||||||
naluType = naluType.Parse(b0)
|
naluType = naluType.Parse(b0)
|
||||||
b0 = codec.NALU_FUA.Or(b0 & 0x60)
|
b0 = codec.NALU_FUA.Or(b0 & 0x60)
|
||||||
buf := [][]byte{{b0, naluType.Or(1 << 7)}}
|
|
||||||
buf = append(buf, r.ReadN(RTPMTU-2)...)
|
|
||||||
out = append(out, buf)
|
|
||||||
for bufs := r.ReadN(RTPMTU); len(bufs) > 0; bufs = r.ReadN(RTPMTU) {
|
for bufs := r.ReadN(RTPMTU); len(bufs) > 0; bufs = r.ReadN(RTPMTU) {
|
||||||
buf = append([][]byte{{b0, naluType.Byte()}}, bufs...)
|
out = append(out, append([][]byte{{b0, naluType.Byte()}}, bufs...))
|
||||||
out = append(out, buf)
|
|
||||||
}
|
}
|
||||||
buf[0][1] |= 1 << 6 // set end bit
|
out[startIndex][0][1] |= 1 << 7 // set start bit
|
||||||
|
out[len(out)-1][0][1] |= 1 << 6 // set end bit
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@@ -69,10 +69,10 @@ func (vt *H265) WriteSliceBytes(slice []byte) {
|
|||||||
case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
|
case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
|
||||||
vt.Value.IFrame = false
|
vt.Value.IFrame = false
|
||||||
vt.AppendAuBytes(slice)
|
vt.AppendAuBytes(slice)
|
||||||
case codec.NAL_UNIT_SEI:
|
case codec.NAL_UNIT_SEI, codec.NAL_UNIT_SEI_SUFFIX:
|
||||||
vt.AppendAuBytes(slice)
|
vt.AppendAuBytes(slice)
|
||||||
default:
|
default:
|
||||||
vt.Warn("h265 slice type not supported", zap.Uint("type", uint(t)))
|
vt.Warn("nalu type not supported", zap.Uint("type", uint(t)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (vt *H265) writeSequenceHead(head []byte) (err error) {
|
func (vt *H265) writeSequenceHead(head []byte) (err error) {
|
||||||
@@ -189,6 +189,7 @@ func (vt *H265) CompleteRTP(value *AVFrame) {
|
|||||||
if value.IFrame {
|
if value.IFrame {
|
||||||
out = append(out, [][]byte{vt.VPS}, [][]byte{vt.SPS}, [][]byte{vt.PPS})
|
out = append(out, [][]byte{vt.VPS}, [][]byte{vt.SPS}, [][]byte{vt.PPS})
|
||||||
}
|
}
|
||||||
|
startIndex := len(out)
|
||||||
vt.Value.AUList.Range(func(au *util.BLL) bool {
|
vt.Value.AUList.Range(func(au *util.BLL) bool {
|
||||||
if au.ByteLength < RTPMTU {
|
if au.ByteLength < RTPMTU {
|
||||||
out = append(out, au.ToBuffers())
|
out = append(out, au.ToBuffers())
|
||||||
@@ -199,14 +200,11 @@ func (vt *H265) CompleteRTP(value *AVFrame) {
|
|||||||
b1, _ := r.ReadByte()
|
b1, _ := r.ReadByte()
|
||||||
naluType = naluType.Parse(b0)
|
naluType = naluType.Parse(b0)
|
||||||
b0 = (byte(codec.NAL_UNIT_RTP_FU) << 1) | (b0 & 0b10000001)
|
b0 = (byte(codec.NAL_UNIT_RTP_FU) << 1) | (b0 & 0b10000001)
|
||||||
buf := [][]byte{{b0, b1, (1 << 7) | byte(naluType)}}
|
|
||||||
buf = append(buf, r.ReadN(RTPMTU-3)...)
|
|
||||||
out = append(out, buf)
|
|
||||||
for bufs := r.ReadN(RTPMTU); len(bufs) > 0; bufs = r.ReadN(RTPMTU) {
|
for bufs := r.ReadN(RTPMTU); len(bufs) > 0; bufs = r.ReadN(RTPMTU) {
|
||||||
buf = append([][]byte{{b0, b1, byte(naluType)}}, bufs...)
|
out = append(out, append([][]byte{{b0, b1, byte(naluType)}}, bufs...))
|
||||||
out = append(out, buf)
|
|
||||||
}
|
}
|
||||||
buf[0][2] |= 1 << 6 // set end bit
|
out[startIndex][0][2] |= 1 << 7 // set start bit
|
||||||
|
out[len(out)-1][0][2] |= 1 << 6 // set end bit
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
10
track/rtp.go
10
track/rtp.go
@@ -60,7 +60,15 @@ func (av *Media) PacketizeRTP(payloads ...[][]byte) {
|
|||||||
}
|
}
|
||||||
packet.Marker = false
|
packet.Marker = false
|
||||||
for _, p := range pp {
|
for _, p := range pp {
|
||||||
br.Write(p)
|
if _, err := br.Write(p); err != nil {
|
||||||
|
av.Error("rtp payload write error", zap.Error(err))
|
||||||
|
for i, pp := range payloads {
|
||||||
|
for j, p := range pp {
|
||||||
|
av.Error("rtp payload", zap.Int("i", i), zap.Int("j", j), zap.Int("len", len(p)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
packet.Payload = br.Bytes()
|
packet.Payload = br.Bytes()
|
||||||
av.Value.RTP.Push(rtpItem)
|
av.Value.RTP.Push(rtpItem)
|
||||||
|
@@ -55,7 +55,8 @@ func (b *LimitBuffer) Write(a []byte) (n int, err error) {
|
|||||||
l := b.Len()
|
l := b.Len()
|
||||||
newL := l + len(a)
|
newL := l + len(a)
|
||||||
if c := b.Cap(); newL > c {
|
if c := b.Cap(); newL > c {
|
||||||
panic(fmt.Sprintf("LimitBuffer Write %d > %d", newL, c))
|
return 0, fmt.Errorf("LimitBuffer Write %d > %d", newL, c)
|
||||||
|
// panic(fmt.Sprintf("LimitBuffer Write %d > %d", newL, c))
|
||||||
} else {
|
} else {
|
||||||
b.Buffer = b.Buffer.SubBuf(0, newL)
|
b.Buffer = b.Buffer.SubBuf(0, newL)
|
||||||
copy(b.Buffer[l:], a)
|
copy(b.Buffer[l:], a)
|
||||||
|
@@ -56,3 +56,11 @@ func IsSubdir(baseDir, joinedDir string) bool {
|
|||||||
}
|
}
|
||||||
return !strings.HasPrefix(rel, "..") && !strings.HasPrefix(rel, "/")
|
return !strings.HasPrefix(rel, "..") && !strings.HasPrefix(rel, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Conditoinal[T any](cond bool, t, f T) T {
|
||||||
|
if cond {
|
||||||
|
return t
|
||||||
|
} else {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
@@ -11,6 +11,12 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func FetchValue[T any](t T) func() T {
|
||||||
|
return func() T {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ReturnJson[T any](fetch func() T, tickDur time.Duration, rw http.ResponseWriter, r *http.Request) {
|
func ReturnJson[T any](fetch func() T, tickDur time.Duration, rw http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get("Accept") == "text/event-stream" {
|
if r.Header.Get("Accept") == "text/event-stream" {
|
||||||
sse := NewSSE(rw, r.Context())
|
sse := NewSSE(rw, r.Context())
|
||||||
|
Reference in New Issue
Block a user