重构合并写实现, TCP流使用异步发送

This commit is contained in:
yangjiechina
2024-07-10 20:17:37 +08:00
parent e27f12f5a4
commit 99658f417a
14 changed files with 309 additions and 174 deletions

View File

@@ -1,125 +1,225 @@
package stream
import (
"github.com/yangjiechina/avformat/utils"
"github.com/yangjiechina/lkm/collections"
)
// MergeWritingBuffer 实现针对RTMP/FLV/HLS等基于TCP传输流的合并写缓存
// 和GOP缓存一样, 也以视频关键帧为界. 遇到视频关键帧, 发送剩余输出流, 清空buffer
// 包含多个合并写块, 循环使用, 至少需要等到第二个I帧才开始循环. webrtcI帧间隔可能会高达几十秒,
// 容量根据write_timeout发送超时和合并写时间来计算, write_timeout/mw_latency.如果I帧间隔大于发送超时时间, 则需要创建新的块.
type MergeWritingBuffer interface {
Allocate(size int) []byte
Allocate(size int, ts int64, videoKey bool) []byte
// PeekCompletedSegment 返回当前完整合并写切片
PeekCompletedSegment(ts int64) []byte
// PeekCompletedSegment 返回当前完整切片, 如果不满, 返回nil.
PeekCompletedSegment() []byte
// PopSegment 返回当前合并写切片, 并清空内存池
PopSegment() []byte
// SegmentList 返回所有完整切片
SegmentList() []byte
// FlushSegment 保存当前切片, 创建新的切片
FlushSegment() []byte
IsFull(ts int64) bool
IsCompleted() bool
// IsNewSegment 新切片, 还未写数据
IsNewSegment() bool
IsEmpty() bool
// Reserve 从当前切片中预留指定长度数据
Reserve(number int)
Reserve(count int)
// ReadSegmentsFromKeyFrameIndex 从最近的关键帧读取切片
ReadSegmentsFromKeyFrameIndex(cb func([]byte))
}
type mergeWritingBuffer struct {
transStreamBuffer MemoryPool
mwBlocks []collections.MemoryPool
segmentOffset int //当前合并写包位于memoryPool的开始偏移量
//空闲合并写
freeKeyFrameMWBlocks collections.LinkedList[collections.MemoryPool]
freeNoneKeyFrameMWBlocks collections.LinkedList[collections.MemoryPool]
prePacketTS int64 //前一个包的时间戳
index int //当前切片位于mwBlocks的索引
startTS int64 //当前切片的开始时间
duration int //当前切片时长
lastKeyFrameIndex int //最新关键帧所在切片的索引
existVideo bool //是否存在视频
keyFrameBufferMaxLength int
nonKeyFrameBufferMaxLength int
keyFrameMap map[int]int
}
func (m *mergeWritingBuffer) Allocate(size int) []byte {
return m.transStreamBuffer.Allocate(size)
func (m *mergeWritingBuffer) createMWBlock(videoKey bool) collections.MemoryPool {
if videoKey {
return collections.NewDirectMemoryPool(m.keyFrameBufferMaxLength)
} else {
return collections.NewDirectMemoryPool(m.nonKeyFrameBufferMaxLength)
}
}
func (m *mergeWritingBuffer) PeekCompletedSegment(ts int64) []byte {
if !AppConfig.GOPCache {
data, _ := m.transStreamBuffer.Data()
m.transStreamBuffer.Clear()
func (m *mergeWritingBuffer) grow() {
pools := make([]collections.MemoryPool, cap(m.mwBlocks)*3/2)
for i := 0; i < cap(m.mwBlocks); i++ {
pools[i] = m.mwBlocks[i]
}
m.mwBlocks = pools
}
func (m *mergeWritingBuffer) Allocate(size int, ts int64, videoKey bool) []byte {
if !AppConfig.GOPCache || !m.existVideo {
return m.mwBlocks[0].Allocate(size)
}
utils.Assert(ts != -1)
//新的切片
if m.startTS == -1 {
if _, ok := m.keyFrameMap[m.index]; ok {
delete(m.keyFrameMap, m.index)
}
if m.mwBlocks[m.index] == nil {
//创建内存块
m.mwBlocks[m.index] = m.createMWBlock(videoKey)
} else {
//循环使用
if !videoKey {
//I帧间隔长, 不够写一组GOP, 扩容!
if len(m.keyFrameMap) < 1 {
capacity := len(m.mwBlocks)
m.grow()
m.index = capacity
m.mwBlocks[m.index] = m.createMWBlock(videoKey)
}
}
m.mwBlocks[m.index].Clear()
}
m.startTS = ts
}
if videoKey {
//请务必确保关键帧帧从新的切片开始
//外部遇到关键帧请先调用FlushSegment
utils.Assert(m.mwBlocks[m.index].IsEmpty())
m.lastKeyFrameIndex = m.index
m.keyFrameMap[m.index] = m.index
}
if ts < m.startTS {
m.startTS = ts
}
m.duration = int(ts - m.startTS)
return m.mwBlocks[m.index].Allocate(size)
}
func (m *mergeWritingBuffer) FlushSegment() []byte {
if m.mwBlocks[m.index] == nil {
return nil
}
data, _ := m.mwBlocks[m.index].Data()
if len(data) == 0 {
return nil
}
//更新缓冲长度
if m.lastKeyFrameIndex == m.index && m.keyFrameBufferMaxLength < len(data) {
m.keyFrameBufferMaxLength = len(data) * 3 / 2
} else if m.lastKeyFrameIndex != m.index && m.nonKeyFrameBufferMaxLength < len(data) {
m.nonKeyFrameBufferMaxLength = len(data) * 3 / 2
}
m.index = (m.index + 1) % cap(m.mwBlocks)
m.startTS = -1
m.duration = 0
return data
}
func (m *mergeWritingBuffer) PeekCompletedSegment() []byte {
if !AppConfig.GOPCache || !m.existVideo {
data, _ := m.mwBlocks[0].Data()
m.mwBlocks[0].Clear()
return data
}
if m.prePacketTS == -1 {
m.prePacketTS = ts
}
if ts < m.prePacketTS {
m.prePacketTS = ts
}
if int(ts-m.prePacketTS) < AppConfig.MergeWriteLatency {
if m.duration < AppConfig.MergeWriteLatency {
return nil
}
head, _ := m.transStreamBuffer.Data()
data := head[m.segmentOffset:]
m.segmentOffset = len(head)
m.prePacketTS = -1
return data
return m.FlushSegment()
}
func (m *mergeWritingBuffer) IsFull(ts int64) bool {
if m.prePacketTS == -1 {
if m.startTS == -1 {
return false
}
return int(ts-m.prePacketTS) >= AppConfig.MergeWriteLatency
return int(ts-m.startTS) >= AppConfig.MergeWriteLatency
}
func (m *mergeWritingBuffer) IsCompleted() bool {
data, _ := m.transStreamBuffer.Data()
return m.segmentOffset == len(data)
}
func (m *mergeWritingBuffer) IsEmpty() bool {
data, _ := m.transStreamBuffer.Data()
return len(data) <= m.segmentOffset
}
func (m *mergeWritingBuffer) Reserve(count int) {
_ = m.transStreamBuffer.Allocate(count)
}
func (m *mergeWritingBuffer) PopSegment() []byte {
if !AppConfig.GOPCache {
return nil
func (m *mergeWritingBuffer) IsNewSegment() bool {
if m.mwBlocks[m.index] == nil {
return true
}
head, _ := m.transStreamBuffer.Data()
data := head[m.segmentOffset:]
m.transStreamBuffer.Clear()
m.segmentOffset = 0
m.prePacketTS = -1
return data
data, _ := m.mwBlocks[m.index].Data()
return len(data) == 0
}
func (m *mergeWritingBuffer) SegmentList() []byte {
if !AppConfig.GOPCache {
return nil
func (m *mergeWritingBuffer) Reserve(number int) {
utils.Assert(m.mwBlocks[m.index] != nil)
m.mwBlocks[m.index].Reserve(number)
}
func (m *mergeWritingBuffer) ReadSegmentsFromKeyFrameIndex(cb func([]byte)) {
if m.lastKeyFrameIndex < 0 || m.index == m.lastKeyFrameIndex {
return
}
head, _ := m.transStreamBuffer.Data()
return head[:m.segmentOffset]
for i := m.lastKeyFrameIndex; i < cap(m.mwBlocks); i++ {
if m.mwBlocks[i] == nil {
continue
}
data, _ := m.mwBlocks[i].Data()
cb(data)
}
//回调循环使用的头部数据
if m.index < m.lastKeyFrameIndex {
for i := 0; i < m.index; i++ {
data, _ := m.mwBlocks[i].Data()
cb(data)
}
}
}
func NewMergeWritingBuffer(existVideo bool) MergeWritingBuffer {
//开启GOP缓存, 输出流也缓存整个GOP
bufferSize := AppConfig.GOPBufferSize
if existVideo && !AppConfig.GOPCache {
bufferSize = 1024 * 1000
} else if !existVideo {
bufferSize = 48000 * 10
var blocks []collections.MemoryPool
if existVideo {
blocks = make([]collections.MemoryPool, AppConfig.WriteBufferNumber)
} else {
blocks = make([]collections.MemoryPool, 1)
}
if !existVideo || !AppConfig.GOPCache {
blocks[0] = collections.NewDirectMemoryPool(1024 * 100)
}
return &mergeWritingBuffer{
transStreamBuffer: NewDirectMemoryPool(bufferSize),
segmentOffset: 0,
prePacketTS: -1,
keyFrameBufferMaxLength: AppConfig.MergeWriteLatency * 1024 * 2,
nonKeyFrameBufferMaxLength: AppConfig.MergeWriteLatency * 1024 / 2,
mwBlocks: blocks,
startTS: -1,
lastKeyFrameIndex: -1,
existVideo: existVideo,
keyFrameMap: make(map[int]int, 5),
}
}