feat: remove sei track

This commit is contained in:
langhuihui
2024-02-26 17:16:49 +08:00
parent 5e41a2f6bb
commit b58e926819
27 changed files with 656 additions and 670 deletions

View File

@@ -38,7 +38,6 @@
- 获取所有向远端推流信息 `/api/list/push` 返回{RemoteURL:"",StreamPath:"",Type:"",StartTime:""}
- 停止推流 `/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
# 引擎默认配置
```yaml
global:
@@ -60,7 +59,6 @@ global:
pubaudio: true # 是否发布音频流
pubvideo: true # 是否发布视频流
kickexist: false # 剔出已经存在的发布者,用于顶替原有发布者
insertsei: false # 是否开启插入SEI信息功能
publishtimeout: 10s # 发布流默认过期时间,超过该时间发布者没有恢复流将被删除
delayclosetimeout: 0 # 自动关闭触发后延迟的时间(期间内如果有新的订阅则取消触发关闭)0为关闭该功能保持连接。
waitclosetimeout: 0 # 发布者断开后等待时间超过该时间发布者没有恢复流将被删除0为关闭该功能由订阅者决定是否删除

View File

@@ -45,23 +45,6 @@ func (r *RTPFrame) Unmarshal(raw []byte) *RTPFrame {
return r
}
type IDataFrame[T any] interface {
Init() // 初始化
Reset() // 重置数据,复用内存
Ready() // 标记为可读取
ReaderEnter() int32 // 读取者数量+1
ReaderLeave() int32 // 读取者数量-1
StartWrite() bool // 开始写入
SetSequence(uint32) // 设置序号
GetSequence() uint32 // 获取序号
ReaderCount() int32 // 读取者数量
Discard() int32 // 如果写入时还有读取者没有离开则废弃该帧剥离RingBuffer防止并发读写
IsDiscarded() bool // 是否已废弃
IsWriting() bool // 是否正在写入
Wait() // 阻塞等待可读取
Broadcast() // 广播可读取
}
type DataFrame[T any] struct {
DeltaTime uint32 // 相对上一帧时间戳,毫秒
WriteTime time.Time // 写入时间,可用于比较两个帧的先后
@@ -126,7 +109,7 @@ func (df *DataFrame[T]) Ready() {
}
func (df *DataFrame[T]) Init() {
df.L = EmptyLocker
df.L = util.EmptyLocker
}
func (df *DataFrame[T]) Reset() {
@@ -181,6 +164,25 @@ func (av *AVFrame) Reset() {
av.DataFrame.Reset()
}
func (av *AVFrame) Assign(source *AVFrame) {
av.IFrame = source.IFrame
av.PTS = source.PTS
av.DTS = source.DTS
av.Timestamp = source.Timestamp
av.BytesIn = source.BytesIn
av.DeltaTime = source.DeltaTime
source.AUList.Range(func(au *util.BLL) bool {
var nau util.BLL
au.Range(func(b util.Buffer) bool {
nau.PushValue(b)
return true
})
nau.ByteLength = au.ByteLength
av.AUList.PushValue(&nau)
return true
})
}
type ParamaterSets [][]byte
func (v ParamaterSets) GetAnnexB() (r net.Buffers) {

View File

@@ -1,139 +0,0 @@
package common
import (
"sync/atomic"
"time"
"github.com/pion/rtp"
"go.uber.org/zap"
"m7s.live/engine/v4/log"
"m7s.live/engine/v4/util"
)
type TimelineData[T any] struct {
Timestamp time.Time
Value T
}
type TrackState byte
const (
TrackStateOnline TrackState = iota // 上线
TrackStateOffline // 下线
)
// Base 基础Track类
type Base[T any, F IDataFrame[T]] struct {
RingWriter[T, F]
Name string
log.Zap `json:"-" yaml:"-"`
Stream IStream `json:"-" yaml:"-"`
Attached atomic.Bool `json:"-" yaml:"-"`
State TrackState
ts time.Time
bytes int
frames int
DropCount int `json:"-" yaml:"-"` //丢帧数
BPS int
FPS int
Drops int // 丢帧率
RawSize int // 裸数据长度
RawPart []int // 裸数据片段用于UI上显示
}
func (bt *Base[T, F]) ComputeBPS(bytes int) {
bt.bytes += bytes
bt.frames++
if elapse := time.Since(bt.ts).Seconds(); elapse > 1 {
bt.BPS = int(float64(bt.bytes) / elapse)
bt.FPS = int(float64(bt.frames) / elapse)
bt.Drops = int(float64(bt.DropCount) / elapse)
bt.bytes = 0
bt.frames = 0
bt.DropCount = 0
bt.ts = time.Now()
}
}
func (bt *Base[T, F]) GetName() string {
return bt.Name
}
func (bt *Base[T, F]) GetBPS() int {
return bt.BPS
}
func (bt *Base[T, F]) GetFPS() int {
return bt.FPS
}
func (bt *Base[T, F]) GetDrops() int {
return bt.Drops
}
// GetRBSize 获取缓冲区大小
func (bt *Base[T, F]) GetRBSize() int {
return bt.RingWriter.Size
}
func (bt *Base[T, F]) SnapForJson() {
}
func (bt *Base[T, F]) SetStuff(stuff ...any) {
for _, s := range stuff {
switch v := s.(type) {
case IStream:
bt.Stream = v
bt.Zap = v.With(zap.String("track", bt.Name))
case TrackState:
bt.State = v
case string:
bt.Name = v
}
}
}
func (bt *Base[T, F]) Dispose() {
bt.Value.Broadcast()
}
type Track interface {
GetReaderCount() int32
GetName() string
GetBPS() int
GetFPS() int
GetDrops() int
LastWriteTime() time.Time
SnapForJson()
SetStuff(stuff ...any)
GetRBSize() int
Dispose()
}
type AVTrack interface {
Track
PreFrame() *AVFrame
CurrentFrame() *AVFrame
Attach()
Detach()
WriteAVCC(ts uint32, frame *util.BLL) error //写入AVCC格式的数据
WriteRTP(*util.ListItem[RTPFrame])
WriteRTPPack(*rtp.Packet)
WriteSequenceHead(sh []byte) error
Flush()
SetSpeedLimit(time.Duration)
GetRTPFromPool() *util.ListItem[RTPFrame]
GetFromPool(util.IBytes) *util.ListItem[util.Buffer]
}
type VideoTrack interface {
AVTrack
WriteSliceBytes(slice []byte)
WriteNalu(uint32, uint32, []byte)
WriteAnnexB(uint32, uint32, []byte)
SetLostFlag()
}
type AudioTrack interface {
AVTrack
WriteADTS(uint32, util.IBytes)
WriteRawBytes(uint32, util.IBytes)
}

90
common/type.go Normal file
View File

@@ -0,0 +1,90 @@
package common
import (
"context"
"time"
"github.com/pion/rtp"
"go.uber.org/zap/zapcore"
"m7s.live/engine/v4/codec"
"m7s.live/engine/v4/config"
"m7s.live/engine/v4/log"
"m7s.live/engine/v4/util"
)
type TimelineData[T any] struct {
Timestamp time.Time
Value T
}
type TrackState byte
const (
TrackStateOnline TrackState = iota // 上线
TrackStateOffline // 下线
)
type IIO interface {
IsClosed() bool
OnEvent(any)
Stop(reason ...zapcore.Field)
SetIO(any)
SetParentCtx(context.Context)
SetLogger(*log.Logger)
IsShutdown() bool
GetStream() IStream
log.Zap
}
type IPuber interface {
IIO
GetAudioTrack() AudioTrack
GetVideoTrack() VideoTrack
GetConfig() *config.Publish
Publish(streamPath string, pub IPuber) error
}
type Track interface {
GetPublisher() IPuber
GetReaderCount() int32
GetName() string
GetBPS() int
GetFPS() int
GetDrops() int
LastWriteTime() time.Time
SnapForJson()
SetStuff(stuff ...any)
GetRBSize() int
Dispose()
}
type AVTrack interface {
Track
PreFrame() *AVFrame
CurrentFrame() *AVFrame
Attach()
Detach()
WriteAVCC(ts uint32, frame *util.BLL) error //写入AVCC格式的数据
WriteRTP(*util.ListItem[RTPFrame])
WriteRTPPack(*rtp.Packet)
WriteSequenceHead(sh []byte) error
Flush()
SetSpeedLimit(time.Duration)
GetRTPFromPool() *util.ListItem[RTPFrame]
GetFromPool(util.IBytes) *util.ListItem[util.Buffer]
}
type VideoTrack interface {
AVTrack
GetCodec() codec.VideoCodecID
WriteSliceBytes(slice []byte)
WriteNalu(uint32, uint32, []byte)
WriteAnnexB(uint32, uint32, []byte)
SetLostFlag()
}
type AudioTrack interface {
AVTrack
GetCodec() codec.AudioCodecID
WriteADTS(uint32, util.IBytes)
WriteRawBytes(uint32, util.IBytes)
Narrow()
}

View File

@@ -33,7 +33,6 @@ type PushConfig interface {
type Publish struct {
PubAudio bool `default:"true" desc:"是否发布音频"`
PubVideo bool `default:"true" desc:"是否发布视频"`
InsertSEI bool `desc:"是否启用SEI插入"` // 是否启用SEI插入
KickExist bool `desc:"是否踢掉已经存在的发布者"` // 是否踢掉已经存在的发布者
PublishTimeout time.Duration `default:"10s" desc:"发布无数据超时"` // 发布无数据超时
WaitCloseTimeout time.Duration `desc:"延迟自动关闭(等待重连)"` // 延迟自动关闭(等待重连)

31
http.go
View File

@@ -2,7 +2,6 @@ package engine
import (
"encoding/json"
"io"
"net/http"
"os"
"strconv"
@@ -347,33 +346,3 @@ func (conf *GlobalConfig) API_replay_mp4(w http.ResponseWriter, r *http.Request)
go pub.ReadMP4Data(f)
}
}
func (conf *GlobalConfig) API_insertSEI(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
streamPath := q.Get("streamPath")
s := Streams.Get(streamPath)
if s == nil {
util.ReturnError(util.APIErrorNoStream, NO_SUCH_STREAM, w, r)
return
}
t := q.Get("type")
tb, err := strconv.ParseInt(t, 10, 8)
if err != nil {
if t == "" {
tb = 5
} else {
util.ReturnError(util.APIErrorQueryParse, "type must a number", w, r)
return
}
}
sei, err := io.ReadAll(r.Body)
if err == nil {
if s.Tracks.AddSEI(byte(tb), sei) {
util.ReturnOK(w, r)
} else {
util.ReturnError(util.APIErrorNoSEI, "no sei track", w, r)
}
} else {
util.ReturnError(util.APIErrorNoBody, err.Error(), w, r)
}
}

21
io.go
View File

@@ -14,6 +14,7 @@ import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"m7s.live/engine/v4/common"
"m7s.live/engine/v4/config"
"m7s.live/engine/v4/log"
"m7s.live/engine/v4/util"
@@ -48,7 +49,11 @@ type IO struct {
io.Writer `json:"-" yaml:"-"`
io.Closer `json:"-" yaml:"-"`
Args url.Values
Spesific IIO `json:"-" yaml:"-"`
Spesific common.IIO `json:"-" yaml:"-"`
}
func (io *IO) GetStream() common.IStream {
return io.Stream
}
func (io *IO) IsClosed() bool {
@@ -93,18 +98,6 @@ func (io *IO) IsShutdown() bool {
return io.Stream.IsShutdown()
}
type IIO interface {
receive(string, IIO) error
IsClosed() bool
OnEvent(any)
Stop(reason ...zapcore.Field)
SetIO(any)
SetParentCtx(context.Context)
SetLogger(*log.Logger)
IsShutdown() bool
log.Zap
}
func (i *IO) close(err StopError) bool {
if i.IsClosed() {
i.Warn("already closed", err...)
@@ -158,7 +151,7 @@ func (io *IO) auth(key string, secret string, expire string) bool {
}
// receive 用于接收发布或者订阅
func (io *IO) receive(streamPath string, specific IIO) error {
func (io *IO) receive(streamPath string, specific common.IIO) error {
streamPath = strings.Trim(streamPath, "/")
u, err := url.Parse(streamPath)
if err != nil {

View File

@@ -32,15 +32,15 @@ func (p *MP4Publisher) ReadMP4Data(source io.ReadSeeker) error {
p.Info("MP4 track", zap.Any("track", t))
switch t.Cid {
case mp4.MP4_CODEC_H264:
p.VideoTrack = track.NewH264(p.Stream)
p.VideoTrack = track.NewH264(p)
case mp4.MP4_CODEC_H265:
p.VideoTrack = track.NewH265(p.Stream)
p.VideoTrack = track.NewH265(p)
case mp4.MP4_CODEC_AAC:
p.AudioTrack = track.NewAAC(p.Stream)
p.AudioTrack = track.NewAAC(p)
case mp4.MP4_CODEC_G711A:
p.AudioTrack = track.NewG711(p.Stream, true)
p.AudioTrack = track.NewG711(p, true)
case mp4.MP4_CODEC_G711U:
p.AudioTrack = track.NewG711(p.Stream, false)
p.AudioTrack = track.NewG711(p, false)
}
}
for {

View File

@@ -36,9 +36,9 @@ func (t *RTPDumpPublisher) Feed(file *os.File) {
if t.VideoTrack == nil {
switch t.VCodec {
case codec.CodecID_H264:
t.VideoTrack = track.NewH264(t.Publisher.Stream, t.VPayloadType)
t.VideoTrack = track.NewH264(t, t.VPayloadType)
case codec.CodecID_H265:
t.VideoTrack = track.NewH265(t.Publisher.Stream, t.VPayloadType)
t.VideoTrack = track.NewH265(t, t.VPayloadType)
}
if t.VideoTrack != nil {
t.VideoTrack.SetSpeedLimit(500 * time.Millisecond)
@@ -47,7 +47,7 @@ func (t *RTPDumpPublisher) Feed(file *os.File) {
if t.AudioTrack == nil {
switch t.ACodec {
case codec.CodecID_AAC:
at := track.NewAAC(t.Publisher.Stream, t.APayloadType)
at := track.NewAAC(t, t.APayloadType)
t.AudioTrack = at
var c mpeg4audio.Config
c.ChannelCount = 2
@@ -55,9 +55,9 @@ func (t *RTPDumpPublisher) Feed(file *os.File) {
asc, _ := c.Marshal()
at.WriteSequenceHead(append([]byte{0xAF, 0x00}, asc...))
case codec.CodecID_PCMA:
t.AudioTrack = track.NewG711(t.Publisher.Stream, true, t.APayloadType)
t.AudioTrack = track.NewG711(t, true, t.APayloadType)
case codec.CodecID_PCMU:
t.AudioTrack = track.NewG711(t.Publisher.Stream, false, t.APayloadType)
t.AudioTrack = track.NewG711(t, false, t.APayloadType)
}
if t.AudioTrack != nil {
t.AudioTrack.SetSpeedLimit(500 * time.Millisecond)

View File

@@ -31,9 +31,9 @@ func (t *TSPublisher) OnEvent(event any) {
switch v := event.(type) {
case IPublisher:
t.pool = make(util.BytesPool, 17)
if !t.Equal(v) {
t.AudioTrack = v.getAudioTrack()
t.VideoTrack = v.getVideoTrack()
if v.GetPublisher() != &t.Publisher {
t.AudioTrack = v.GetAudioTrack()
t.VideoTrack = v.GetVideoTrack()
}
case SEKick, SEclose:
// close(t.PESChan)
@@ -47,23 +47,23 @@ func (t *TSPublisher) OnPmtStream(s mpegts.MpegTsPmtStream) {
switch s.StreamType {
case mpegts.STREAM_TYPE_H264:
if t.VideoTrack == nil {
t.VideoTrack = track.NewH264(t.Publisher.Stream, t.pool)
t.VideoTrack = track.NewH264(t, t.pool)
}
case mpegts.STREAM_TYPE_H265:
if t.VideoTrack == nil {
t.VideoTrack = track.NewH265(t.Publisher.Stream, t.pool)
t.VideoTrack = track.NewH265(t, t.pool)
}
case mpegts.STREAM_TYPE_AAC:
if t.AudioTrack == nil {
t.AudioTrack = track.NewAAC(t.Publisher.Stream, t.pool)
t.AudioTrack = track.NewAAC(t, t.pool)
}
case mpegts.STREAM_TYPE_G711A:
if t.AudioTrack == nil {
t.AudioTrack = track.NewG711(t.Publisher.Stream, true, t.pool)
t.AudioTrack = track.NewG711(t, true, t.pool)
}
case mpegts.STREAM_TYPE_G711U:
if t.AudioTrack == nil {
t.AudioTrack = track.NewG711(t.Publisher.Stream, false, t.pool)
t.AudioTrack = track.NewG711(t, false, t.pool)
}
default:
t.Warn("unsupport stream type:", zap.Uint8("type", s.StreamType))

View File

@@ -10,11 +10,8 @@ import (
)
type IPublisher interface {
IIO
common.IPuber
GetPublisher() *Publisher
getAudioTrack() common.AudioTrack
getVideoTrack() common.VideoTrack
Publish(streamPath string, pub IPublisher) error
}
var _ IPublisher = (*Publisher)(nil)
@@ -26,7 +23,7 @@ type Publisher struct {
common.VideoTrack `json:"-" yaml:"-"`
}
func (p *Publisher) Publish(streamPath string, pub IPublisher) error {
func (p *Publisher) Publish(streamPath string, pub common.IPuber) error {
return p.receive(streamPath, pub)
}
@@ -39,16 +36,15 @@ func (p *Publisher) GetPublisher() *Publisher {
// p.Stream.Receive(ACTION_PUBLISHCLOSE)
// }
func (p *Publisher) getAudioTrack() common.AudioTrack {
func (p *Publisher) GetAudioTrack() common.AudioTrack {
return p.AudioTrack
}
func (p *Publisher) getVideoTrack() common.VideoTrack {
func (p *Publisher) GetVideoTrack() common.VideoTrack {
return p.VideoTrack
}
func (p *Publisher) Equal(p2 IPublisher) bool {
return p == p2.GetPublisher()
func (p *Publisher) GetConfig() *config.Publish {
return p.Config
}
// func (p *Publisher) OnEvent(event any) {
// p.IO.OnEvent(event)
// switch event.(type) {
@@ -69,10 +65,10 @@ func (p *Publisher) WriteAVCCVideo(ts uint32, frame *util.BLL, pool util.BytesPo
fourCC := frame.GetUintN(1, 4)
switch fourCC {
case codec.FourCC_H265_32:
p.VideoTrack = track.NewH265(p.Stream, pool)
p.VideoTrack = track.NewH265(p, pool)
p.VideoTrack.WriteAVCC(ts, frame)
case codec.FourCC_AV1_32:
p.VideoTrack = track.NewAV1(p.Stream, pool)
p.VideoTrack = track.NewAV1(p, pool)
p.VideoTrack.WriteAVCC(ts, frame)
}
} else {
@@ -80,9 +76,9 @@ func (p *Publisher) WriteAVCCVideo(ts uint32, frame *util.BLL, pool util.BytesPo
ts = 0
switch codecID := codec.VideoCodecID(b0 & 0x0F); codecID {
case codec.CodecID_H264:
p.VideoTrack = track.NewH264(p.Stream, pool)
p.VideoTrack = track.NewH264(p, pool)
case codec.CodecID_H265:
p.VideoTrack = track.NewH265(p.Stream, pool)
p.VideoTrack = track.NewH265(p, pool)
default:
p.Stream.Error("video codecID not support", zap.Uint8("codeId", uint8(codecID)))
return
@@ -108,7 +104,7 @@ func (p *Publisher) WriteAVCCAudio(ts uint32, frame *util.BLL, pool util.BytesPo
if frame.GetByte(1) != 0 {
return
}
a := track.NewAAC(p.Stream, pool)
a := track.NewAAC(p, pool)
p.AudioTrack = a
a.AVCCHead = []byte{frame.GetByte(0), 1}
a.WriteAVCC(0, frame)
@@ -118,7 +114,7 @@ func (p *Publisher) WriteAVCCAudio(ts uint32, frame *util.BLL, pool util.BytesPo
if codecID == codec.CodecID_PCMU {
alaw = false
}
a := track.NewG711(p.Stream, alaw, pool)
a := track.NewG711(p, alaw, pool)
p.AudioTrack = a
a.Audio.SampleRate = uint32(codec.SoundRate[(b0&0x0c)>>2])
if b0&0x02 == 0 {

100
stream.go
View File

@@ -14,7 +14,6 @@ import (
. "github.com/logrusorgru/aurora/v4"
"go.uber.org/zap"
"m7s.live/engine/v4/common"
. "m7s.live/engine/v4/common"
"m7s.live/engine/v4/config"
"m7s.live/engine/v4/log"
"m7s.live/engine/v4/track"
@@ -128,27 +127,23 @@ type Tracks struct {
Data []common.Track
MainVideo *track.Video
MainAudio *track.Audio
SEI *track.Channel[[]byte]
marshalLock sync.Mutex
}
func (tracks *Tracks) Range(f func(name string, t Track)) {
func (tracks *Tracks) Range(f func(name string, t common.Track)) {
tracks.Map.Range(func(k, v any) bool {
f(k.(string), v.(Track))
f(k.(string), v.(common.Track))
return true
})
}
func (tracks *Tracks) Add(name string, t Track) bool {
func (tracks *Tracks) Add(name string, t common.Track) bool {
switch v := t.(type) {
case *track.Video:
if tracks.MainVideo == nil {
tracks.MainVideo = v
tracks.SetIDR(v)
}
if tracks.SEI != nil {
v.SEIReader = tracks.SEI.CreateReader(100)
}
case *track.Audio:
if tracks.MainAudio == nil {
tracks.MainAudio = v
@@ -171,9 +166,9 @@ func (tracks *Tracks) Add(name string, t Track) bool {
return !loaded
}
func (tracks *Tracks) SetIDR(video Track) {
func (tracks *Tracks) SetIDR(video common.Track) {
if video == tracks.MainVideo {
tracks.Range(func(_ string, t Track) {
tracks.Range(func(_ string, t common.Track) {
if v, ok := t.(*track.Audio); ok {
v.Narrow()
}
@@ -181,29 +176,11 @@ func (tracks *Tracks) SetIDR(video Track) {
}
}
func (tracks *Tracks) AddSEI(t byte, data []byte) bool {
if tracks.SEI != nil {
l := len(data)
var buffer util.Buffer
buffer.WriteByte(t)
for l >= 255 {
buffer.WriteByte(255)
l -= 255
}
buffer.WriteByte(byte(l))
buffer.Write(data)
buffer.WriteByte(0x80)
tracks.SEI.Write(buffer)
return true
}
return false
}
func (tracks *Tracks) MarshalJSON() ([]byte, error) {
var trackList []Track
var trackList []common.Track
tracks.marshalLock.Lock()
defer tracks.marshalLock.Unlock()
tracks.Range(func(_ string, t Track) {
tracks.Range(func(_ string, t common.Track) {
t.SnapForJson()
trackList = append(trackList, t)
})
@@ -222,6 +199,7 @@ type Stream struct {
StreamTimeoutConfig
Path string
Publisher IPublisher
publisher *Publisher
State StreamState
SEHistory []StateEvent // 事件历史
Subscribers Subscribers // 订阅者
@@ -245,7 +223,11 @@ func (s *Stream) GetType() string {
if s.Publisher == nil {
return ""
}
return s.Publisher.GetPublisher().Type
return s.publisher.Type
}
func (s *Stream) GetPath() string {
return s.Path
}
func (s *Stream) GetStartTime() time.Time {
@@ -257,15 +239,15 @@ func (s *Stream) GetPublisherConfig() *config.Publish {
s.Error("GetPublisherConfig: Publisher is nil")
return nil
}
return s.Publisher.GetPublisher().Config
return s.Publisher.GetConfig()
}
// Summary 返回流的简要信息
func (s *Stream) Summary() (r StreamSummay) {
if s.Publisher != nil {
r.Type = s.Publisher.GetPublisher().Type
if s.publisher != nil {
r.Type = s.publisher.Type
}
s.Tracks.Range(func(name string, t Track) {
s.Tracks.Range(func(name string, t common.Track) {
r.BPS += t.GetBPS()
r.Tracks = append(r.Tracks, name)
})
@@ -279,7 +261,7 @@ func (s *Stream) Summary() (r StreamSummay) {
func (s *Stream) SSRC() uint32 {
return uint32(uintptr(unsafe.Pointer(s)))
}
func (s *Stream) SetIDR(video Track) {
func (s *Stream) SetIDR(video common.Track) {
s.Tracks.SetIDR(video)
}
func findOrCreateStream(streamPath string, waitTimeout time.Duration) (s *Stream, created bool) {
@@ -330,9 +312,9 @@ func (r *Stream) action(action StreamAction) (ok bool) {
stateEvent = SEwaitPublish{event, r.Publisher}
waitTime := time.Duration(0)
if r.Publisher != nil {
waitTime = r.Publisher.GetPublisher().Config.WaitCloseTimeout
r.Tracks.Range(func(name string, t Track) {
t.SetStuff(TrackStateOffline)
waitTime = r.Publisher.GetConfig().WaitCloseTimeout
r.Tracks.Range(func(name string, t common.Track) {
t.SetStuff(common.TrackStateOffline)
})
}
r.Subscribers.OnPublisherLost(event)
@@ -373,8 +355,10 @@ func (r *Stream) action(action StreamAction) (ok bool) {
r.timeout.Stop()
stateEvent = SEclose{event}
r.Subscribers.Broadcast(stateEvent)
r.Tracks.Range(func(_ string, t Track) {
t.Dispose()
r.Tracks.Range(func(_ string, t common.Track) {
if t.GetPublisher().GetStream() == r {
t.Dispose()
}
})
r.Subscribers.Dispose()
r.actionChan.Close()
@@ -486,7 +470,7 @@ func (s *Stream) run() {
if s.IsPause {
timeout = s.PauseTimeout
}
s.Tracks.Range(func(name string, t Track) {
s.Tracks.Range(func(name string, t common.Track) {
trackCount++
switch t.(type) {
case *track.Video, *track.Audio:
@@ -504,7 +488,7 @@ func (s *Stream) run() {
s.action(ACTION_CLOSE)
continue
} else if s.Publisher != nil && s.Publisher.IsClosed() {
s.Warn("publish is closed", zap.Error(context.Cause(s.Publisher.GetPublisher())), zap.String("ptr", fmt.Sprintf("%p", s.Publisher.GetPublisher().Context)))
s.Warn("publish is closed", zap.Error(context.Cause(s.publisher)), zap.String("ptr", fmt.Sprintf("%p", s.publisher.Context)))
lost = true
if len(s.Tracks.Audio)+len(s.Tracks.Video) == 0 {
s.action(ACTION_CLOSE)
@@ -546,19 +530,17 @@ func (s *Stream) run() {
break
}
puber := v.Value.GetPublisher()
var oldPuber *Publisher
if s.Publisher != nil {
oldPuber = s.Publisher.GetPublisher()
}
oldPuber := s.publisher
s.publisher = puber
conf := puber.Config
republish := s.Publisher == v.Value // 重复发布
if republish {
s.Info("republish")
s.Tracks.Range(func(name string, t Track) {
t.SetStuff(TrackStateOffline)
s.Tracks.Range(func(name string, t common.Track) {
t.SetStuff(common.TrackStateOffline)
})
}
needKick := !republish && s.Publisher != nil && conf.KickExist // 需要踢掉老的发布者
needKick := !republish && oldPuber != nil && conf.KickExist // 需要踢掉老的发布者
if needKick {
s.Warn("kick", zap.String("old type", oldPuber.Type))
s.Publisher.OnEvent(SEKick{CreateEvent[struct{}](util.Null)})
@@ -574,12 +556,6 @@ func (s *Stream) run() {
puber.AudioTrack = oldPuber.AudioTrack
puber.VideoTrack = oldPuber.VideoTrack
}
if conf.InsertSEI {
if s.Tracks.SEI == nil {
s.Tracks.SEI = &track.Channel[[]byte]{}
s.Info("sei track added")
}
}
v.Resolve()
} else {
s.Warn("duplicate publish")
@@ -618,8 +594,8 @@ func (s *Stream) run() {
}
if s.Publisher != nil {
s.Publisher.OnEvent(v) // 通知Publisher有新的订阅者加入在回调中可以去获取订阅者数量
pubConfig := s.Publisher.GetPublisher().Config
s.Tracks.Range(func(name string, t Track) {
pubConfig := s.Publisher.GetConfig()
s.Tracks.Range(func(name string, t common.Track) {
waits.Accept(t)
})
if !pubConfig.PubAudio {
@@ -656,7 +632,7 @@ func (s *Stream) run() {
s.Subscribers.Broadcast(t)
t.(common.Track).Dispose()
}
case *util.Promise[Track]:
case *util.Promise[common.Track]:
timeOutInfo = zap.String("action", "Track")
if s.IsClosed() {
v.Reject(ErrStreamIsClosed)
@@ -706,7 +682,7 @@ func (s *Stream) run() {
}
}
func (s *Stream) AddTrack(t Track) (promise *util.Promise[Track]) {
func (s *Stream) AddTrack(t common.Track) (promise *util.Promise[common.Track]) {
promise = util.NewPromise(t)
if !s.Receive(promise) {
promise.Reject(ErrStreamIsClosed)
@@ -714,7 +690,7 @@ func (s *Stream) AddTrack(t Track) (promise *util.Promise[Track]) {
return
}
func (s *Stream) RemoveTrack(t Track) {
func (s *Stream) RemoveTrack(t common.Track) {
s.Receive(TrackRemoved{t})
}
@@ -727,7 +703,7 @@ func (s *Stream) Resume() {
}
type TrackRemoved struct {
Track
common.Track
}
type SubPulse struct {

View File

@@ -14,7 +14,7 @@ import (
var _ SpesificTrack = (*AAC)(nil)
func NewAAC(stream IStream, stuff ...any) (aac *AAC) {
func NewAAC(puber IPuber, stuff ...any) (aac *AAC) {
aac = &AAC{
Mode: 2,
}
@@ -24,7 +24,7 @@ func NewAAC(stream IStream, stuff ...any) (aac *AAC) {
aac.CodecID = codec.CodecID_AAC
aac.Channels = 2
aac.SampleSize = 16
aac.SetStuff("aac", byte(97), aac, stuff, stream)
aac.SetStuff("aac", byte(97), aac, stuff, puber)
if aac.BytesPool == nil {
aac.BytesPool = make(util.BytesPool, 17)
}

View File

@@ -20,20 +20,15 @@ type Audio struct {
}
func (a *Audio) Attach() {
if a.Attached.CompareAndSwap(false, true) {
if err := a.Stream.AddTrack(a).Await(); err != nil {
a.Error("attach audio track failed", zap.Error(err))
a.Attached.Store(false)
} else {
a.Info("audio track attached", zap.Uint32("sample rate", a.SampleRate))
}
if err := a.Publisher.GetStream().AddTrack(a).Await(); err != nil {
a.Error("attach audio track failed", zap.Error(err))
} else {
a.Info("audio track attached", zap.Uint32("sample rate", a.SampleRate))
}
}
func (a *Audio) Detach() {
if a.Attached.CompareAndSwap(true, false) {
a.Stream.RemoveTrack(a)
}
a.Publisher.GetStream().RemoveTrack(a)
}
func (a *Audio) GetName() string {
@@ -43,6 +38,10 @@ func (a *Audio) GetName() string {
return a.Name
}
func (a *Audio) GetCodec() codec.AudioCodecID {
return a.CodecID
}
func (av *Audio) WriteADTS(pts uint32, adts util.IBytes) {
}

View File

@@ -23,15 +23,15 @@ type AV1 struct {
refFrameType map[byte]byte
}
func NewAV1(stream IStream, stuff ...any) (vt *AV1) {
func NewAV1(puber IPuber, stuff ...any) (vt *AV1) {
vt = &AV1{}
vt.Video.CodecID = codec.CodecID_AV1
vt.SetStuff("av1", byte(96), uint32(90000), vt, stuff, stream)
vt.SetStuff("av1", byte(96), uint32(90000), vt, stuff, puber)
if vt.BytesPool == nil {
vt.BytesPool = make(util.BytesPool, 17)
}
vt.nalulenSize = 0
vt.dtsEst = NewDTSEstimator()
vt.dtsEst = util.NewDTSEstimator()
vt.decoder.Init()
vt.encoder.Init()
vt.encoder.PayloadType = vt.PayloadType
@@ -53,7 +53,7 @@ func (vt *AV1) WriteRTPFrame(rtpItem *util.ListItem[RTPFrame]) {
err := recover()
if err != nil {
vt.Error("WriteRTPFrame panic", zap.Any("err", err))
vt.Stream.Close()
vt.Publisher.Stop(zap.Any("err", err))
}
}()
if vt.lastSeq != vt.lastSeq2+1 && vt.lastSeq2 != 0 {

View File

@@ -2,318 +2,87 @@ package track
import (
"time"
"unsafe"
"github.com/pion/rtp"
"go.uber.org/zap"
. "m7s.live/engine/v4/common"
"m7s.live/engine/v4/config"
"m7s.live/engine/v4/common"
"m7s.live/engine/v4/log"
"m7s.live/engine/v4/util"
)
var deltaDTSRange time.Duration = 90 * 10000 // 超过 10 秒
type 流速控制 struct {
起始时间戳 time.Duration
起始dts time.Duration
等待上限 time.Duration
起始时间 time.Time
// Base 基础Track类
type Base[T any, F util.IDataFrame[T]] struct {
util.RingWriter[T, F]
Name string
log.Zap `json:"-" yaml:"-"`
Publisher common.IPuber `json:"-" yaml:"-"` //所属发布者
State common.TrackState
ts time.Time
bytes int
frames int
DropCount int `json:"-" yaml:"-"` //丢帧数
BPS int
FPS int
Drops int // 丢帧率
RawSize int // 裸数据长度
RawPart []int // 裸数据片段用于UI上显示
}
func (p *流速控制) 重置(绝对时间戳 time.Duration, dts time.Duration) {
p.起始时间 = time.Now()
p.起始时间戳 = 绝对时间戳
p.起始dts = dts
// println("重置", p.起始时间.Format("2006-01-02 15:04:05"), p.起始时间戳)
}
func (p *流速控制) 根据起始DTS计算绝对时间戳(dts time.Duration) time.Duration {
if dts < p.起始dts {
dts += (1 << 32)
}
return ((dts-p.起始dts)*time.Millisecond + p.起始时间戳*90) / 90
}
func (p *流速控制) 控制流速(绝对时间戳 time.Duration, dts time.Duration) (等待了 time.Duration) {
数据时间差, 实际时间差 := 绝对时间戳-p.起始时间戳, time.Since(p.起始时间)
// println("数据时间差", 数据时间差, "实际时间差", 实际时间差, "绝对时间戳", 绝对时间戳, "起始时间戳", p.起始时间戳, "起始时间", p.起始时间.Format("2006-01-02 15:04:05"))
// if 实际时间差 > 数据时间差 {
// p.重置(绝对时间戳)
// return
// }
// 如果收到的帧的时间戳超过实际消耗的时间100ms就休息一下100ms作为一个弹性区间防止频繁调用sleep
if 过快 := (数据时间差 - 实际时间差); 过快 > 100*time.Millisecond {
// fmt.Println("过快毫秒", 过快.Milliseconds())
// println("过快毫秒", p.name, 过快.Milliseconds())
if 过快 > p.等待上限 {
等待了 = p.等待上限
} else {
等待了 = 过快
}
time.Sleep(等待了)
} else if 过快 < -100*time.Millisecond {
// fmt.Println("过慢毫秒", 过快.Milliseconds())
// p.重置(绝对时间戳, dts)
// println("过慢毫秒", p.name, 过快.Milliseconds())
}
return
}
type SpesificTrack interface {
CompleteRTP(*AVFrame)
CompleteAVCC(*AVFrame)
WriteSliceBytes([]byte)
WriteRTPFrame(*util.ListItem[RTPFrame])
generateTimestamp(uint32)
WriteSequenceHead([]byte) error
writeAVCCFrame(uint32, *util.BLLReader, *util.BLL) error
GetNALU_SEI() *util.ListItem[util.Buffer]
Flush()
}
type IDRingList struct {
IDRList util.List[*util.Ring[*AVFrame]]
IDRing *util.Ring[*AVFrame]
HistoryRing *util.Ring[*AVFrame]
}
func (p *IDRingList) AddIDR(IDRing *util.Ring[*AVFrame]) {
p.IDRList.PushValue(IDRing)
p.IDRing = IDRing
}
func (p *IDRingList) ShiftIDR() {
p.IDRList.Shift()
p.HistoryRing = p.IDRList.Next.Value
}
// Media 基础媒体Track类
type Media struct {
Base[any, *AVFrame]
PayloadType byte
IDRingList `json:"-" yaml:"-"` //最近的关键帧位置,首屏渲染
SSRC uint32
SampleRate uint32
BytesPool util.BytesPool `json:"-" yaml:"-"`
RtpPool util.Pool[RTPFrame] `json:"-" yaml:"-"`
SequenceHead []byte `json:"-" yaml:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config)
SequenceHeadSeq int
RTPDemuxer
SpesificTrack `json:"-" yaml:"-"`
deltaTs time.Duration //用于接续发布后时间戳连续
流速控制
}
func (av *Media) GetFromPool(b util.IBytes) (item *util.ListItem[util.Buffer]) {
if b.Reuse() {
item = av.BytesPool.Get(b.Len())
copy(item.Value, b.Bytes())
} else {
return av.BytesPool.GetShell(b.Bytes())
}
return
}
func (av *Media) GetRTPFromPool() (result *util.ListItem[RTPFrame]) {
result = av.RtpPool.Get()
if result.Value.Packet == nil {
result.Value.Packet = &rtp.Packet{}
result.Value.PayloadType = av.PayloadType
result.Value.SSRC = av.SSRC
result.Value.Version = 2
result.Value.Raw = make([]byte, 1460)
}
result.Value.Raw = result.Value.Raw[:1460]
result.Value.Payload = result.Value.Raw[:0]
return
}
// 为json序列化而计算的数据
func (av *Media) SnapForJson() {
v := av.LastValue
if av.RawPart != nil {
av.RawPart = av.RawPart[:0]
} else {
av.RawPart = make([]int, 0, 10)
}
if av.RawSize = v.AUList.ByteLength; av.RawSize > 0 {
r := v.AUList.NewReader()
for b, err := r.ReadByte(); err == nil && len(av.RawPart) < 10; b, err = r.ReadByte() {
av.RawPart = append(av.RawPart, int(b))
}
func (bt *Base[T, F]) ComputeBPS(bytes int) {
bt.bytes += bytes
bt.frames++
if elapse := time.Since(bt.ts).Seconds(); elapse > 1 {
bt.BPS = int(float64(bt.bytes) / elapse)
bt.FPS = int(float64(bt.frames) / elapse)
bt.Drops = int(float64(bt.DropCount) / elapse)
bt.bytes = 0
bt.frames = 0
bt.DropCount = 0
bt.ts = time.Now()
}
}
func (av *Media) SetSpeedLimit(value time.Duration) {
av.等待上限 = value
func (bt *Base[T, F]) GetName() string {
return bt.Name
}
func (av *Media) SetStuff(stuff ...any) {
// 代表发布者已经离线该Track成为遗留Track等待下一任发布者接续发布
func (bt *Base[T, F]) GetBPS() int {
return bt.BPS
}
func (bt *Base[T, F]) GetFPS() int {
return bt.FPS
}
func (bt *Base[T, F]) GetDrops() int {
return bt.Drops
}
// GetRBSize 获取缓冲区大小
func (bt *Base[T, F]) GetRBSize() int {
return bt.RingWriter.Size
}
func (bt *Base[T, F]) SnapForJson() {
}
func (bt *Base[T, F]) SetStuff(stuff ...any) {
for _, s := range stuff {
switch v := s.(type) {
case IStream:
pubConf := v.GetPublisherConfig()
av.Base.SetStuff(v)
av.Init(256, NewAVFrame)
av.SSRC = uint32(uintptr(unsafe.Pointer(av)))
av.等待上限 = pubConf.SpeedLimit
case uint32:
av.SampleRate = v
case byte:
av.PayloadType = v
case util.BytesPool:
av.BytesPool = v
case SpesificTrack:
av.SpesificTrack = v
case []any:
av.SetStuff(v...)
default:
av.Base.SetStuff(v)
case common.IPuber:
bt.Publisher = v
bt.Zap = v.With(zap.String("track", bt.Name))
case common.TrackState:
bt.State = v
case string:
bt.Name = v
}
}
}
func (av *Media) LastWriteTime() time.Time {
return av.LastValue.WriteTime
func (bt *Base[T, F]) GetPublisher() common.IPuber {
return bt.Publisher
}
func (av *Media) CurrentFrame() *AVFrame {
return av.Value
}
func (av *Media) PreFrame() *AVFrame {
return av.LastValue
}
func (av *Media) generateTimestamp(ts uint32) {
av.Value.PTS = time.Duration(ts)
av.Value.DTS = time.Duration(ts)
}
func (av *Media) WriteSequenceHead(sh []byte) {
av.SequenceHead = sh
av.SequenceHeadSeq++
}
func (av *Media) AppendAuBytes(b ...[]byte) {
var au util.BLL
for _, bb := range b {
au.Push(av.BytesPool.GetShell(bb))
}
av.Value.AUList.PushValue(&au)
}
func (av *Media) narrow(gop int) {
if l := av.Size - gop; l > 12 {
if log.Trace {
av.Trace("resize", zap.Int("before", av.Size), zap.Int("after", av.Size-5))
}
//缩小缓冲环节省内存
av.Reduce(5)
}
}
func (av *Media) AddIDR() {
if av.Stream.GetPublisherConfig().BufferTime > 0 {
av.IDRingList.AddIDR(av.Ring)
if av.HistoryRing == nil {
av.HistoryRing = av.IDRing
}
} else {
av.IDRing = av.Ring
}
}
func (av *Media) Flush() {
curValue, preValue, nextValue := av.Value, av.LastValue, av.Next()
useDts := curValue.Timestamp == 0
originDTS := curValue.DTS
if av.State == TrackStateOffline {
av.State = TrackStateOnline
if useDts {
av.deltaTs = curValue.DTS - preValue.DTS
} else {
av.deltaTs = curValue.Timestamp - preValue.Timestamp
}
curValue.DTS = preValue.DTS + 900
curValue.PTS = preValue.PTS + 900
curValue.Timestamp = preValue.Timestamp + time.Millisecond
av.Info("track back online", zap.Duration("delta", av.deltaTs))
} else if av.deltaTs != 0 {
if useDts {
curValue.DTS -= av.deltaTs
curValue.PTS -= av.deltaTs
} else {
rtpts := av.deltaTs * 90 / time.Millisecond
curValue.DTS -= rtpts
curValue.PTS -= rtpts
curValue.Timestamp -= av.deltaTs
}
}
if av.起始时间.IsZero() {
curValue.DeltaTime = 0
if useDts {
curValue.Timestamp = time.Since(av.Stream.GetStartTime())
}
av.重置(curValue.Timestamp, curValue.DTS)
} else {
if useDts {
deltaDts := curValue.DTS - preValue.DTS
if deltaDts > deltaDTSRange || deltaDts < -deltaDTSRange {
// 时间戳跳变,等同于离线重连
av.deltaTs = originDTS - preValue.DTS
curValue.DTS = preValue.DTS + 900
curValue.PTS = preValue.PTS + 900
av.Warn("track dts reset", zap.Int64("delta1", int64(deltaDts)), zap.Int64("delta2", int64(av.deltaTs)))
}
curValue.Timestamp = av.根据起始DTS计算绝对时间戳(curValue.DTS)
}
curValue.DeltaTime = uint32(deltaTS(curValue.Timestamp, preValue.Timestamp) / time.Millisecond)
}
if log.Trace {
av.Trace("write", zap.Uint32("seq", curValue.Sequence), zap.Int64("dts0", int64(preValue.DTS)), zap.Int64("dts1", int64(originDTS)), zap.Uint64("dts2", uint64(curValue.DTS)), zap.Uint32("delta", curValue.DeltaTime), zap.Duration("timestamp", curValue.Timestamp), zap.Int("au", curValue.AUList.Length), zap.Int("rtp", curValue.RTP.Length), zap.Int("avcc", curValue.AVCC.ByteLength), zap.Int("raw", curValue.AUList.ByteLength), zap.Int("bps", av.BPS))
}
bufferTime := av.Stream.GetPublisherConfig().BufferTime
if bufferTime > 0 && av.IDRingList.IDRList.Length > 1 && deltaTS(curValue.Timestamp, av.IDRingList.IDRList.Next.Next.Value.Value.Timestamp) > bufferTime {
av.ShiftIDR()
av.narrow(int(curValue.Sequence - av.HistoryRing.Value.Sequence))
}
// 下一帧为订阅起始帧,即将覆盖,需要扩环
if nextValue == av.IDRing || nextValue == av.HistoryRing {
// if av.AVRing.Size < 512 {
if log.Trace {
av.Stream.Trace("resize", zap.Int("before", av.Size), zap.Int("after", av.Size+5), zap.String("name", av.Name))
}
av.Glow(5)
// } else {
// av.Stream.Error("sub ring overflow", zap.Int("size", av.AVRing.Size), zap.String("name", av.Name))
// }
}
if curValue.AUList.Length > 0 {
// 补完RTP
if config.Global.EnableRTP && curValue.RTP.Length == 0 {
av.CompleteRTP(curValue)
}
// 补完AVCC
if config.Global.EnableAVCC && curValue.AVCC.ByteLength == 0 {
av.CompleteAVCC(curValue)
}
}
av.ComputeBPS(curValue.BytesIn)
av.Step()
if av.等待上限 > 0 {
等待了 := av.控制流速(curValue.Timestamp, curValue.DTS)
if log.Trace && 等待了 > 0 {
av.Trace("speed control", zap.Duration("sleep", 等待了))
}
}
}
func deltaTS(curTs time.Duration, preTs time.Duration) time.Duration {
if curTs < preTs {
return curTs + (1<<32)*time.Millisecond - preTs
}
return curTs - preTs
func (bt *Base[T, F]) Dispose() {
bt.Value.Broadcast()
}

View File

@@ -11,7 +11,7 @@ import (
var _ SpesificTrack = (*G711)(nil)
func NewG711(stream IStream, alaw bool, stuff ...any) (g711 *G711) {
func NewG711(puber IPuber, alaw bool, stuff ...any) (g711 *G711) {
g711 = &G711{}
if alaw {
g711.Name = "pcma"
@@ -28,7 +28,7 @@ func NewG711(stream IStream, alaw bool, stuff ...any) (g711 *G711) {
g711.SampleSize = 8
g711.Channels = 1
g711.AVCCHead = []byte{(byte(g711.CodecID) << 4) | (1 << 1)}
g711.SetStuff(uint32(8000), g711, stuff, stream)
g711.SetStuff(uint32(8000), g711, stuff, puber)
if g711.BytesPool == nil {
g711.BytesPool = make(util.BytesPool, 17)
}

View File

@@ -18,16 +18,16 @@ type H264 struct {
buf util.Buffer // rtp 包临时缓存,对于不规范的 rtp 包sps 放到了 fua 中导致)需要缓存
}
func NewH264(stream IStream, stuff ...any) (vt *H264) {
func NewH264(puber IPuber, stuff ...any) (vt *H264) {
vt = &H264{}
vt.Video.CodecID = codec.CodecID_H264
vt.SetStuff("h264", byte(96), uint32(90000), vt, stuff, stream)
vt.SetStuff("h264", byte(96), uint32(90000), vt, stuff, puber)
if vt.BytesPool == nil {
vt.BytesPool = make(util.BytesPool, 17)
}
vt.ParamaterSets = make(ParamaterSets, 2)
vt.nalulenSize = 4
vt.dtsEst = NewDTSEstimator()
vt.dtsEst = util.NewDTSEstimator()
return
}
@@ -104,8 +104,8 @@ func (vt *H264) WriteSequenceHead(head []byte) (err error) {
vt.ParamaterSets[1] = vt.PPS
vt.Video.WriteSequenceHead(head)
} else {
vt.Stream.Error("H264 ParseSpsPps Error")
vt.Stream.Close()
vt.Error("H264 ParseSpsPps Error")
vt.Publisher.Stop(zap.Error(err))
}
return
}
@@ -115,7 +115,7 @@ func (vt *H264) WriteRTPFrame(rtpItem *util.ListItem[RTPFrame]) {
err := recover()
if err != nil {
vt.Error("WriteRTPFrame panic", zap.Any("err", err))
vt.Stream.Close()
vt.Publisher.Stop(zap.Any("err", err))
}
}()
if vt.lastSeq != vt.lastSeq2+1 && vt.lastSeq2 != 0 {

View File

@@ -15,16 +15,16 @@ type H265 struct {
VPS []byte `json:"-" yaml:"-"`
}
func NewH265(stream IStream, stuff ...any) (vt *H265) {
func NewH265(puber IPuber, stuff ...any) (vt *H265) {
vt = &H265{}
vt.Video.CodecID = codec.CodecID_H265
vt.SetStuff("h265", byte(96), uint32(90000), vt, stuff, stream)
vt.SetStuff("h265", byte(96), uint32(90000), vt, stuff, puber)
if vt.BytesPool == nil {
vt.BytesPool = make(util.BytesPool, 17)
}
vt.ParamaterSets = make(ParamaterSets, 3)
vt.nalulenSize = 4
vt.dtsEst = NewDTSEstimator()
vt.dtsEst = util.NewDTSEstimator()
return
}
@@ -92,7 +92,7 @@ func (vt *H265) WriteSequenceHead(head []byte) (err error) {
vt.Video.WriteSequenceHead(head)
} else {
vt.Error("H265 ParseVpsSpsPps Error")
vt.Stream.Close()
vt.Publisher.Stop(zap.Error(err))
}
return
}
@@ -102,7 +102,7 @@ func (vt *H265) WriteRTPFrame(rtpItem *util.ListItem[RTPFrame]) {
err := recover()
if err != nil {
vt.Error("WriteRTPFrame panic", zap.Any("err", err))
vt.Stream.Close()
vt.Publisher.Stop(zap.Any("err", err))
}
}()
frame := &rtpItem.Value

321
track/media.go Normal file
View File

@@ -0,0 +1,321 @@
package track
import (
"time"
"unsafe"
"github.com/pion/rtp"
"go.uber.org/zap"
. "m7s.live/engine/v4/common"
"m7s.live/engine/v4/config"
"m7s.live/engine/v4/log"
"m7s.live/engine/v4/util"
)
var deltaDTSRange time.Duration = 90 * 10000 // 超过 10 秒
type 流速控制 struct {
起始时间戳 time.Duration
起始dts time.Duration
等待上限 time.Duration
起始时间 time.Time
}
func (p *流速控制) 重置(绝对时间戳 time.Duration, dts time.Duration) {
p.起始时间 = time.Now()
p.起始时间戳 = 绝对时间戳
p.起始dts = dts
// println("重置", p.起始时间.Format("2006-01-02 15:04:05"), p.起始时间戳)
}
func (p *流速控制) 根据起始DTS计算绝对时间戳(dts time.Duration) time.Duration {
if dts < p.起始dts {
dts += (1 << 32)
}
return ((dts-p.起始dts)*time.Millisecond + p.起始时间戳*90) / 90
}
func (p *流速控制) 控制流速(绝对时间戳 time.Duration, dts time.Duration) (等待了 time.Duration) {
数据时间差, 实际时间差 := 绝对时间戳-p.起始时间戳, time.Since(p.起始时间)
// println("数据时间差", 数据时间差, "实际时间差", 实际时间差, "绝对时间戳", 绝对时间戳, "起始时间戳", p.起始时间戳, "起始时间", p.起始时间.Format("2006-01-02 15:04:05"))
// if 实际时间差 > 数据时间差 {
// p.重置(绝对时间戳)
// return
// }
// 如果收到的帧的时间戳超过实际消耗的时间100ms就休息一下100ms作为一个弹性区间防止频繁调用sleep
if 过快 := (数据时间差 - 实际时间差); 过快 > 100*time.Millisecond {
// fmt.Println("过快毫秒", 过快.Milliseconds())
// println("过快毫秒", p.name, 过快.Milliseconds())
if 过快 > p.等待上限 {
等待了 = p.等待上限
} else {
等待了 = 过快
}
time.Sleep(等待了)
} else if 过快 < -100*time.Millisecond {
// fmt.Println("过慢毫秒", 过快.Milliseconds())
// p.重置(绝对时间戳, dts)
// println("过慢毫秒", p.name, 过快.Milliseconds())
}
return
}
type SpesificTrack interface {
CompleteRTP(*AVFrame)
CompleteAVCC(*AVFrame)
WriteSliceBytes([]byte)
WriteRTPFrame(*util.ListItem[RTPFrame])
generateTimestamp(uint32)
WriteSequenceHead([]byte) error
writeAVCCFrame(uint32, *util.BLLReader, *util.BLL) error
GetNALU_SEI() *util.ListItem[util.Buffer]
Flush()
}
type IDRingList struct {
IDRList util.List[*util.Ring[*AVFrame]]
IDRing *util.Ring[*AVFrame]
HistoryRing *util.Ring[*AVFrame]
}
func (p *IDRingList) AddIDR(IDRing *util.Ring[*AVFrame]) {
p.IDRList.PushValue(IDRing)
p.IDRing = IDRing
}
func (p *IDRingList) ShiftIDR() {
p.IDRList.Shift()
p.HistoryRing = p.IDRList.Next.Value
}
// Media 基础媒体Track类
type Media struct {
Base[any, *AVFrame]
BufferTime time.Duration //发布者配置中的缓冲时间(时光回溯)
PayloadType byte
IDRingList `json:"-" yaml:"-"` //最近的关键帧位置,首屏渲染
SSRC uint32
SampleRate uint32
BytesPool util.BytesPool `json:"-" yaml:"-"`
RtpPool util.Pool[RTPFrame] `json:"-" yaml:"-"`
SequenceHead []byte `json:"-" yaml:"-"` //H264(SPS、PPS) H265(VPS、SPS、PPS) AAC(config)
SequenceHeadSeq int
RTPDemuxer
SpesificTrack `json:"-" yaml:"-"`
deltaTs time.Duration //用于接续发布后时间戳连续
流速控制
}
func (av *Media) GetFromPool(b util.IBytes) (item *util.ListItem[util.Buffer]) {
if b.Reuse() {
item = av.BytesPool.Get(b.Len())
copy(item.Value, b.Bytes())
} else {
return av.BytesPool.GetShell(b.Bytes())
}
return
}
func (av *Media) GetRTPFromPool() (result *util.ListItem[RTPFrame]) {
result = av.RtpPool.Get()
if result.Value.Packet == nil {
result.Value.Packet = &rtp.Packet{}
result.Value.PayloadType = av.PayloadType
result.Value.SSRC = av.SSRC
result.Value.Version = 2
result.Value.Raw = make([]byte, 1460)
}
result.Value.Raw = result.Value.Raw[:1460]
result.Value.Payload = result.Value.Raw[:0]
return
}
// 为json序列化而计算的数据
func (av *Media) SnapForJson() {
v := av.LastValue
if av.RawPart != nil {
av.RawPart = av.RawPart[:0]
} else {
av.RawPart = make([]int, 0, 10)
}
if av.RawSize = v.AUList.ByteLength; av.RawSize > 0 {
r := v.AUList.NewReader()
for b, err := r.ReadByte(); err == nil && len(av.RawPart) < 10; b, err = r.ReadByte() {
av.RawPart = append(av.RawPart, int(b))
}
}
}
func (av *Media) SetSpeedLimit(value time.Duration) {
av.等待上限 = value
}
func (av *Media) SetStuff(stuff ...any) {
// 代表发布者已经离线该Track成为遗留Track等待下一任发布者接续发布
for _, s := range stuff {
switch v := s.(type) {
case IPuber:
pubConf := v.GetConfig()
av.BufferTime = pubConf.BufferTime
av.Base.SetStuff(v)
av.Init(256, NewAVFrame)
av.SSRC = uint32(uintptr(unsafe.Pointer(av)))
av.等待上限 = pubConf.SpeedLimit
case uint32:
av.SampleRate = v
case byte:
av.PayloadType = v
case util.BytesPool:
av.BytesPool = v
case SpesificTrack:
av.SpesificTrack = v
case []any:
av.SetStuff(v...)
default:
av.Base.SetStuff(v)
}
}
}
func (av *Media) LastWriteTime() time.Time {
return av.LastValue.WriteTime
}
func (av *Media) CurrentFrame() *AVFrame {
return av.Value
}
func (av *Media) PreFrame() *AVFrame {
return av.LastValue
}
func (av *Media) generateTimestamp(ts uint32) {
av.Value.PTS = time.Duration(ts)
av.Value.DTS = time.Duration(ts)
}
func (av *Media) WriteSequenceHead(sh []byte) {
av.SequenceHead = sh
av.SequenceHeadSeq++
}
func (av *Media) AppendAuBytes(b ...[]byte) {
var au util.BLL
for _, bb := range b {
au.Push(av.BytesPool.GetShell(bb))
}
av.Value.AUList.PushValue(&au)
}
func (av *Media) narrow(gop int) {
if l := av.Size - gop; l > 12 {
if log.Trace {
av.Trace("resize", zap.Int("before", av.Size), zap.Int("after", av.Size-5))
}
//缩小缓冲环节省内存
av.Reduce(5)
}
}
func (av *Media) AddIDR() {
if av.BufferTime > 0 {
av.IDRingList.AddIDR(av.Ring)
if av.HistoryRing == nil {
av.HistoryRing = av.IDRing
}
} else {
av.IDRing = av.Ring
}
}
func (av *Media) Flush() {
curValue, preValue, nextValue := av.Value, av.LastValue, av.Next()
useDts := curValue.Timestamp == 0
originDTS := curValue.DTS
if av.State == TrackStateOffline {
av.State = TrackStateOnline
if useDts {
av.deltaTs = curValue.DTS - preValue.DTS
} else {
av.deltaTs = curValue.Timestamp - preValue.Timestamp
}
curValue.DTS = preValue.DTS + 900
curValue.PTS = preValue.PTS + 900
curValue.Timestamp = preValue.Timestamp + time.Millisecond
av.Info("track back online", zap.Duration("delta", av.deltaTs))
} else if av.deltaTs != 0 {
if useDts {
curValue.DTS -= av.deltaTs
curValue.PTS -= av.deltaTs
} else {
rtpts := av.deltaTs * 90 / time.Millisecond
curValue.DTS -= rtpts
curValue.PTS -= rtpts
curValue.Timestamp -= av.deltaTs
}
}
if av.起始时间.IsZero() {
curValue.DeltaTime = 0
if useDts {
curValue.Timestamp = time.Since(av.Publisher.GetStream().GetStartTime())
}
av.重置(curValue.Timestamp, curValue.DTS)
} else {
if useDts {
deltaDts := curValue.DTS - preValue.DTS
if deltaDts > deltaDTSRange || deltaDts < -deltaDTSRange {
// 时间戳跳变,等同于离线重连
av.deltaTs = originDTS - preValue.DTS
curValue.DTS = preValue.DTS + 900
curValue.PTS = preValue.PTS + 900
av.Warn("track dts reset", zap.Int64("delta1", int64(deltaDts)), zap.Int64("delta2", int64(av.deltaTs)))
}
curValue.Timestamp = av.根据起始DTS计算绝对时间戳(curValue.DTS)
}
curValue.DeltaTime = uint32(deltaTS(curValue.Timestamp, preValue.Timestamp) / time.Millisecond)
}
if log.Trace {
av.Trace("write", zap.Uint32("seq", curValue.Sequence), zap.Int64("dts0", int64(preValue.DTS)), zap.Int64("dts1", int64(originDTS)), zap.Uint64("dts2", uint64(curValue.DTS)), zap.Uint32("delta", curValue.DeltaTime), zap.Duration("timestamp", curValue.Timestamp), zap.Int("au", curValue.AUList.Length), zap.Int("rtp", curValue.RTP.Length), zap.Int("avcc", curValue.AVCC.ByteLength), zap.Int("raw", curValue.AUList.ByteLength), zap.Int("bps", av.BPS))
}
bufferTime := av.BufferTime
if bufferTime > 0 && av.IDRingList.IDRList.Length > 1 && deltaTS(curValue.Timestamp, av.IDRingList.IDRList.Next.Next.Value.Value.Timestamp) > bufferTime {
av.ShiftIDR()
av.narrow(int(curValue.Sequence - av.HistoryRing.Value.Sequence))
}
// 下一帧为订阅起始帧,即将覆盖,需要扩环
if nextValue == av.IDRing || nextValue == av.HistoryRing {
// if av.AVRing.Size < 512 {
if log.Trace {
av.Trace("resize", zap.Int("before", av.Size), zap.Int("after", av.Size+5), zap.String("name", av.Name))
}
av.Glow(5)
// } else {
// av.Stream.Error("sub ring overflow", zap.Int("size", av.AVRing.Size), zap.String("name", av.Name))
// }
}
if curValue.AUList.Length > 0 {
// 补完RTP
if config.Global.EnableRTP && curValue.RTP.Length == 0 {
av.CompleteRTP(curValue)
}
// 补完AVCC
if config.Global.EnableAVCC && curValue.AVCC.ByteLength == 0 {
av.CompleteAVCC(curValue)
}
}
av.ComputeBPS(curValue.BytesIn)
av.Step()
if av.等待上限 > 0 {
等待了 := av.控制流速(curValue.Timestamp, curValue.DTS)
if log.Trace && 等待了 > 0 {
av.Trace("speed control", zap.Duration("sleep", 等待了))
}
}
}
func deltaTS(curTs time.Duration, preTs time.Duration) time.Duration {
if curTs < preTs {
return curTs + (1<<32)*time.Millisecond - preTs
}
return curTs - preTs
}

View File

@@ -9,13 +9,13 @@ import (
var _ SpesificTrack = (*Opus)(nil)
func NewOpus(stream IStream, stuff ...any) (opus *Opus) {
func NewOpus(puber IPuber, stuff ...any) (opus *Opus) {
opus = &Opus{}
opus.CodecID = codec.CodecID_OPUS
opus.SampleSize = 16
opus.Channels = 2
opus.AVCCHead = []byte{(byte(opus.CodecID) << 4) | (1 << 1)}
opus.SetStuff("opus", uint32(48000), byte(111), opus, stuff, stream)
opus.SetStuff("opus", uint32(48000), byte(111), opus, stuff, puber)
if opus.BytesPool == nil {
opus.BytesPool = make(util.BytesPool, 17)
}

View File

@@ -5,7 +5,7 @@ import (
"m7s.live/engine/v4/util"
)
type RingReader[T any, F common.IDataFrame[T]] struct {
type RingReader[T any, F util.IDataFrame[T]] struct {
*util.Ring[F]
Count int // 读取的帧数
}

View File

@@ -18,31 +18,26 @@ type Video struct {
GOP int //关键帧间隔
nalulenSize int //avcc格式中表示nalu长度的字节数通常为4
dcChanged bool //解码器配置是否改变了,一般由于变码率导致
dtsEst *DTSEstimator
dtsEst *util.DTSEstimator
lostFlag bool // 是否丢帧
codec.SPSInfo
ParamaterSets `json:"-" yaml:"-"`
SPS []byte `json:"-" yaml:"-"`
PPS []byte `json:"-" yaml:"-"`
SEIReader chan []byte `json:"-" yaml:"-"`
ParamaterSets `json:"-" yaml:"-"`
SPS []byte `json:"-" yaml:"-"`
PPS []byte `json:"-" yaml:"-"`
iframeReceived bool
}
func (v *Video) Attach() {
if v.Attached.CompareAndSwap(false, true) {
v.Info("attach video track", zap.Uint("width", v.Width), zap.Uint("height", v.Height))
if err := v.Stream.AddTrack(v).Await(); err != nil {
v.Error("attach video track failed", zap.Error(err))
v.Attached.Store(false)
} else {
v.Info("video track attached", zap.Uint("width", v.Width), zap.Uint("height", v.Height))
}
v.Info("attach video track", zap.Uint("width", v.Width), zap.Uint("height", v.Height))
if err := v.Publisher.GetStream().AddTrack(v).Await(); err != nil {
v.Error("attach video track failed", zap.Error(err))
} else {
v.Info("video track attached", zap.Uint("width", v.Width), zap.Uint("height", v.Height))
}
}
func (v *Video) Detach() {
if v.Attached.CompareAndSwap(true, false) {
v.Stream.RemoveTrack(v)
}
v.Publisher.GetStream().RemoveTrack(v)
}
func (vt *Video) GetName() string {
@@ -52,6 +47,9 @@ func (vt *Video) GetName() string {
return vt.Name
}
func (vt *Video) GetCodec() codec.VideoCodecID {
return vt.CodecID
}
// PlayFullAnnexB 订阅annex-b格式的流数据每一个I帧增加sps、pps头
// func (vt *Video) PlayFullAnnexB(ctx context.Context, onMedia func(net.Buffers) error) error {
// for vr := vt.ReadRing(); ctx.Err() == nil; vr.MoveNext() {
@@ -202,7 +200,7 @@ func (vt *Video) insertDCRtp() {
func (vt *Video) generateTimestamp(ts uint32) {
if vt.State == TrackStateOffline {
vt.dtsEst = NewDTSEstimator()
vt.dtsEst = util.NewDTSEstimator()
}
vt.Value.PTS = time.Duration(ts)
vt.Value.DTS = time.Duration(vt.dtsEst.Feed(ts))
@@ -249,26 +247,17 @@ func (vt *Video) CompleteAVCC(rv *AVFrame) {
func (vt *Video) Flush() {
rv := vt.Value
if vt.SEIReader != nil && len(vt.SEIReader) > 0 {
for seiFrame := range vt.SEIReader {
var au util.BLL
au.Push(vt.SpesificTrack.GetNALU_SEI())
au.Push(vt.BytesPool.GetShell(seiFrame))
vt.Info("sei", zap.Int("len", len(seiFrame)))
vt.Value.AUList.UnshiftValue(&au)
if len(vt.SEIReader) == 0 {
break
}
}
}
if rv.IFrame {
vt.computeGOP()
vt.Stream.SetIDR(vt)
if audioTrack := vt.Publisher.GetAudioTrack(); audioTrack != nil {
audioTrack.Narrow()
}
}
if !vt.Attached.Load() {
if !vt.iframeReceived {
if vt.IDRing != nil && vt.SequenceHeadSeq > 0 {
defer vt.Attach()
vt.iframeReceived = true
} else {
rv.Reset()
return

View File

@@ -1,6 +1,4 @@
package common
import "m7s.live/engine/v4/util"
package util
// DTSEstimator is a DTS estimator.
type DTSEstimator struct {
@@ -48,7 +46,7 @@ func (d *DTSEstimator) add(pts uint32) {
// Feed provides PTS to the estimator, and returns the estimated DTS.
func (d *DTSEstimator) Feed(pts uint32) uint32 {
interval := util.Conditoinal(pts > d.prevPTS, pts-d.prevPTS, d.prevPTS-pts)
interval := Conditoinal(pts > d.prevPTS, pts-d.prevPTS, d.prevPTS-pts)
if interval > 10*d.interval {
*d = *NewDTSEstimator()
}

View File

@@ -1,4 +1,4 @@
package common
package util
import (
"testing"

View File

@@ -1,9 +1,8 @@
package common
package util
import (
"sync/atomic"
"m7s.live/engine/v4/util"
)
type emptyLocker struct{}
@@ -13,18 +12,35 @@ func (emptyLocker) Unlock() {}
var EmptyLocker emptyLocker
type IDataFrame[T any] interface {
Init() // 初始化
Reset() // 重置数据,复用内存
Ready() // 标记为可读取
ReaderEnter() int32 // 读取者数量+1
ReaderLeave() int32 // 读取者数量-1
StartWrite() bool // 开始写入
SetSequence(uint32) // 设置序号
GetSequence() uint32 // 获取序号
ReaderCount() int32 // 读取者数量
Discard() int32 // 如果写入时还有读取者没有离开则废弃该帧剥离RingBuffer防止并发读写
IsDiscarded() bool // 是否已废弃
IsWriting() bool // 是否正在写入
Wait() // 阻塞等待可读取
Broadcast() // 广播可读取
}
type RingWriter[T any, F IDataFrame[T]] struct {
*util.Ring[F] `json:"-" yaml:"-"`
*Ring[F] `json:"-" yaml:"-"`
ReaderCount atomic.Int32 `json:"-" yaml:"-"`
pool *util.Ring[F]
pool *Ring[F]
poolSize int
Size int
LastValue F
constructor func() F
}
func (rb *RingWriter[T, F]) create(n int) (ring *util.Ring[F]) {
ring = util.NewRing[F](n)
func (rb *RingWriter[T, F]) create(n int) (ring *Ring[F]) {
ring = NewRing[F](n)
for p, i := ring, n; i > 0; p, i = p.Next(), i-1 {
p.Value = rb.constructor()
p.Value.Init()
@@ -46,7 +62,7 @@ func (rb *RingWriter[T, F]) Init(n int, constructor func() F) *RingWriter[T, F]
// return rb.Value
// }
func (rb *RingWriter[T, F]) Glow(size int) (newItem *util.Ring[F]) {
func (rb *RingWriter[T, F]) Glow(size int) (newItem *Ring[F]) {
if size < rb.poolSize {
newItem = rb.pool.Unlink(size)
rb.poolSize -= size
@@ -64,7 +80,7 @@ func (rb *RingWriter[T, F]) Glow(size int) (newItem *util.Ring[F]) {
return
}
func (rb *RingWriter[T, F]) Recycle(r *util.Ring[F]) {
func (rb *RingWriter[T, F]) Recycle(r *Ring[F]) {
rb.poolSize++
r.Value.Init()
r.Value.Reset()

View File

@@ -76,6 +76,16 @@ func (p *Promise[S]) Await() (err error) {
return
}
func (p *Promise[S]) Then(resolved func(S), rejected func(error)) {
go func() {
if err := p.Await(); err == nil {
resolved(p.Value)
} else {
rejected(err)
}
}()
}
func NewPromise[S any](value S) *Promise[S] {
ctx0, cancel0 := context.WithTimeout(context.Background(), time.Second*10)
ctx, cancel := context.WithCancelCause(ctx0)