Files
lkm/rtmp/rtmp_stream.go
yangjiechina f7552240d0 封装hls
2024-03-02 18:41:00 +08:00

294 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rtmp
import (
"github.com/yangjiechina/avformat/libflv"
"github.com/yangjiechina/avformat/librtmp"
"github.com/yangjiechina/avformat/utils"
"github.com/yangjiechina/live-server/stream"
)
type TransStream struct {
stream.TransStreamImpl
chunkSize int
//sequence header
header []byte
headerSize int
muxer *libflv.Muxer
//只存在音频流
onlyAudio bool
audioChunk librtmp.Chunk
videoChunk librtmp.Chunk
//只需要缓存一组GOP+第2组GOP的第一个合并写切片
//当缓存到第2组GOP的第二个合并写切片时将上一个GOP缓存释放掉
//使用2块内存池分别缓存2个GOP保证内存连续一次发送
//不开启GOP缓存和只有音频包的情况下创建使用一个MemoryPool
memoryPool [2]stream.MemoryPool
//当前合并写切片的缓存时长
segmentDuration int
//当前合并写切片位于memoryPool的开始偏移量
segmentOffset int
//前一个包的时间戳
prePacketTS int64
firstVideoPacket bool
//发送未完整切片的Sinks
//当AddSink时还未缓存到一组切片有多少先发多少. 后续切片未满之前的生成的rtmp包都将直接发送给sink.
//只要满了一组切片后这些sink都不单独发包, 统一发送切片.
incompleteSinks []stream.ISink
}
func NewTransStream(chunkSize int) stream.ITransStream {
transStream := &TransStream{chunkSize: chunkSize, TransStreamImpl: stream.TransStreamImpl{Sinks: make(map[stream.SinkId]stream.ISink, 64)}}
return transStream
}
func (t *TransStream) Input(packet utils.AVPacket) {
utils.Assert(t.TransStreamImpl.Completed)
var data []byte
var chunk *librtmp.Chunk
var videoPkt bool
var videoKey bool
var length int
//rtmp chunk消息体的数据大小
var payloadSize int
if utils.AVMediaTypeAudio == packet.MediaType() {
data = packet.Data()
length = len(data)
chunk = &t.audioChunk
payloadSize += 2 + length
} else if utils.AVMediaTypeVideo == packet.MediaType() {
//首帧必须要视频关键帧
if !t.firstVideoPacket {
if !packet.KeyFrame() {
return
}
t.firstVideoPacket = true
}
videoPkt = true
videoKey = packet.KeyFrame()
data = packet.AVCCPacketData()
length = len(data)
chunk = &t.videoChunk
payloadSize += 5 + length
}
//即不开启GOP缓存又不合并发送. 直接使用AVPacket的预留头封装发送
if !stream.AppConfig.GOPCache || t.onlyAudio {
//首帧视频帧必须要关键帧
return
}
if videoKey {
tmp := t.memoryPool[0]
head, _ := tmp.Data()
if len(head) > t.segmentOffset {
for _, sink := range t.Sinks {
sink.Input(head[t.segmentOffset:])
}
}
t.memoryPool[0].Clear()
//交替使用缓存
t.memoryPool[0] = t.memoryPool[1]
t.memoryPool[1] = tmp
t.segmentDuration = 0
t.segmentOffset = 0
}
//分配内存
t.memoryPool[0].Mark()
allocate := t.memoryPool[0].Allocate(12 + payloadSize + (payloadSize / t.chunkSize))
//写chunk头
chunk.Length = payloadSize
chunk.Timestamp = uint32(packet.Pts())
n := chunk.ToBytes(allocate)
utils.Assert(n == 12)
//写flv
ct := packet.Pts() - packet.Dts()
if videoPkt {
n += t.muxer.WriteVideoData(allocate[12:], uint32(ct), packet.KeyFrame(), false)
} else {
n += t.muxer.WriteAudioData(allocate[12:], false)
}
first := true
for length > 0 {
var min int
if first {
min = utils.MinInt(length, t.chunkSize-5)
first = false
} else {
min = utils.MinInt(length, t.chunkSize)
}
copy(allocate[n:], data[:min])
n += min
length -= min
data = data[min:]
//写一个ChunkType3用作分割
if length > 0 {
if videoPkt {
allocate[n] = (0x3 << 6) | byte(librtmp.ChunkStreamIdVideo)
} else {
allocate[n] = (0x3 << 6) | byte(librtmp.ChunkStreamIdAudio)
}
n++
}
}
rtmpData := t.memoryPool[0].Fetch()[:n]
t.segmentDuration += int(packet.Pts() - t.prePacketTS)
t.prePacketTS = packet.Pts()
//给不完整切片的Sink补齐包
if len(t.incompleteSinks) > 0 {
for _, sink := range t.incompleteSinks {
sink.Input(rtmpData)
}
if t.segmentDuration >= stream.AppConfig.MergeWriteLatency {
head, tail := t.memoryPool[0].Data()
utils.Assert(len(tail) == 0)
t.segmentOffset = len(head)
t.segmentDuration = 0
t.incompleteSinks = nil
}
return
}
if t.segmentDuration < stream.AppConfig.MergeWriteLatency {
return
}
head, tail := t.memoryPool[0].Data()
utils.Assert(len(tail) == 0)
for _, sink := range t.Sinks {
sink.Input(head[t.segmentOffset:])
}
t.segmentOffset = len(head)
t.segmentDuration = 0
//当缓存到第2组GOP的第二个合并写切片时将上一个GOP缓存释放掉
if t.segmentOffset > len(head) && t.memoryPool[1] != nil && !t.memoryPool[1].Empty() {
t.memoryPool[1].Clear()
}
}
func (t *TransStream) AddSink(sink stream.ISink) {
utils.Assert(t.headerSize > 0)
t.TransStreamImpl.AddSink(sink)
//发送sequence header
sink.Input(t.header[:t.headerSize])
if !stream.AppConfig.GOPCache || t.onlyAudio {
return
}
//发送当前内存池已有的合并写切片
if t.segmentOffset > 0 {
data, tail := t.memoryPool[0].Data()
utils.Assert(len(data) > 0)
utils.Assert(len(tail) == 0)
sink.Input(data[:t.segmentOffset])
return
}
//发送上一组GOP
if t.memoryPool[1] != nil && !t.memoryPool[1].Empty() {
data, tail := t.memoryPool[0].Data()
utils.Assert(len(data) > 0)
utils.Assert(len(tail) == 0)
sink.Input(data)
return
}
//不足一个合并写切片, 有多少发多少
data, tail := t.memoryPool[0].Data()
utils.Assert(len(tail) == 0)
if len(data) > 0 {
sink.Input(data)
t.incompleteSinks = append(t.incompleteSinks, sink)
}
}
func (t *TransStream) WriteHeader() error {
utils.Assert(t.Tracks != nil)
utils.Assert(!t.TransStreamImpl.Completed)
var audioStream utils.AVStream
var videoStream utils.AVStream
var audioCodecId utils.AVCodecID
var videoCodecId utils.AVCodecID
for _, track := range t.Tracks {
if utils.AVMediaTypeAudio == track.Type() {
audioStream = track
audioCodecId = audioStream.CodecId()
t.audioChunk = librtmp.NewAudioChunk()
} else if utils.AVMediaTypeVideo == track.Type() {
videoStream = track
videoCodecId = videoStream.CodecId()
t.videoChunk = librtmp.NewVideoChunk()
}
}
utils.Assert(audioStream != nil || videoStream != nil)
//初始化
t.TransStreamImpl.Completed = true
t.header = make([]byte, 1024)
t.muxer = libflv.NewMuxer(audioCodecId, videoCodecId, 0, 0, 0)
if stream.AppConfig.GOPCache {
//创建2块内存
t.memoryPool[0] = stream.NewMemoryPoolWithDirect(1024*4000, true)
t.memoryPool[1] = stream.NewMemoryPoolWithDirect(1024*4000, true)
} else {
}
var n int
if audioStream != nil {
n += t.muxer.WriteAudioData(t.header[12:], true)
extra := audioStream.Extra()
copy(t.header[n+12:], extra)
n += len(extra)
t.audioChunk.Length = n
t.audioChunk.ToBytes(t.header)
n += 12
}
if videoStream != nil {
tmp := n
n += t.muxer.WriteVideoData(t.header[n+12:], 0, false, true)
extra := videoStream.Extra()
copy(t.header[n+12:], extra)
n += len(extra)
t.videoChunk.Length = 5 + len(extra)
t.videoChunk.ToBytes(t.header[tmp:])
n += 12
}
t.headerSize = n
return nil
}