mirror of
https://github.com/lkmio/lkm.git
synced 2025-10-04 23:02:43 +08:00
226 lines
5.6 KiB
Go
226 lines
5.6 KiB
Go
package stream
|
|
|
|
import (
|
|
"github.com/yangjiechina/avformat/utils"
|
|
"github.com/yangjiechina/lkm/collections"
|
|
)
|
|
|
|
// MergeWritingBuffer 实现针对RTMP/FLV/HLS等基于TCP传输流的合并写缓存
|
|
// 包含多个合并写块, 循环使用, 至少需要等到第二个I帧才开始循环. webrtcI帧间隔可能会高达几十秒,
|
|
// 容量根据write_timeout发送超时和合并写时间来计算, write_timeout/mw_latency.如果I帧间隔大于发送超时时间, 则需要创建新的块.
|
|
type MergeWritingBuffer interface {
|
|
Allocate(size int, ts int64, videoKey bool) []byte
|
|
|
|
// PeekCompletedSegment 返回当前完整切片, 如果不满, 返回nil.
|
|
PeekCompletedSegment() []byte
|
|
|
|
// FlushSegment 保存当前切片, 创建新的切片
|
|
FlushSegment() []byte
|
|
|
|
IsFull(ts int64) bool
|
|
|
|
// IsNewSegment 新切片, 还未写数据
|
|
IsNewSegment() bool
|
|
|
|
// Reserve 从当前切片中预留指定长度数据
|
|
Reserve(number int)
|
|
|
|
// ReadSegmentsFromKeyFrameIndex 从最近的关键帧读取切片
|
|
ReadSegmentsFromKeyFrameIndex(cb func([]byte))
|
|
}
|
|
|
|
type mergeWritingBuffer struct {
|
|
mwBlocks []collections.MemoryPool
|
|
|
|
//空闲合并写块
|
|
freeKeyFrameMWBlocks collections.LinkedList[collections.MemoryPool]
|
|
freeNoneKeyFrameMWBlocks collections.LinkedList[collections.MemoryPool]
|
|
|
|
index int //当前切片位于mwBlocks的索引
|
|
startTS int64 //当前切片的开始时间
|
|
duration int //当前切片时长
|
|
|
|
lastKeyFrameIndex int //最新关键帧所在切片的索引
|
|
existVideo bool //是否存在视频
|
|
|
|
keyFrameBufferMaxLength int
|
|
nonKeyFrameBufferMaxLength int
|
|
keyFrameMap map[int]int
|
|
}
|
|
|
|
func (m *mergeWritingBuffer) createMWBlock(videoKey bool) collections.MemoryPool {
|
|
if videoKey {
|
|
return collections.NewDirectMemoryPool(m.keyFrameBufferMaxLength)
|
|
} else {
|
|
return collections.NewDirectMemoryPool(m.nonKeyFrameBufferMaxLength)
|
|
}
|
|
}
|
|
|
|
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.duration < AppConfig.MergeWriteLatency {
|
|
return nil
|
|
}
|
|
|
|
return m.FlushSegment()
|
|
}
|
|
|
|
func (m *mergeWritingBuffer) IsFull(ts int64) bool {
|
|
if m.startTS == -1 {
|
|
return false
|
|
}
|
|
|
|
return int(ts-m.startTS) >= AppConfig.MergeWriteLatency
|
|
}
|
|
|
|
func (m *mergeWritingBuffer) IsNewSegment() bool {
|
|
if m.mwBlocks[m.index] == nil {
|
|
return true
|
|
}
|
|
|
|
data, _ := m.mwBlocks[m.index].Data()
|
|
return len(data) == 0
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
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{
|
|
keyFrameBufferMaxLength: AppConfig.MergeWriteLatency * 1024 * 2,
|
|
nonKeyFrameBufferMaxLength: AppConfig.MergeWriteLatency * 1024 / 2,
|
|
mwBlocks: blocks,
|
|
startTS: -1,
|
|
lastKeyFrameIndex: -1,
|
|
existVideo: existVideo,
|
|
keyFrameMap: make(map[int]int, 5),
|
|
}
|
|
}
|