mirror of
https://github.com/lkmio/lkm.git
synced 2025-10-04 14:52:44 +08:00
封装http-flv
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/yangjiechina/avformat/libflv"
|
||||
"github.com/yangjiechina/avformat/librtmp"
|
||||
"github.com/yangjiechina/avformat/utils"
|
||||
@@ -9,41 +8,19 @@ import (
|
||||
)
|
||||
|
||||
type TransStream struct {
|
||||
stream.TransStreamImpl
|
||||
stream.CacheTransStream
|
||||
chunkSize int
|
||||
//sequence header
|
||||
header []byte
|
||||
headerSize int
|
||||
muxer *libflv.Muxer
|
||||
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)}}
|
||||
transStream := &TransStream{chunkSize: chunkSize}
|
||||
return transStream
|
||||
}
|
||||
|
||||
@@ -64,15 +41,6 @@ func (t *TransStream) Input(packet utils.AVPacket) error {
|
||||
chunk = &t.audioChunk
|
||||
payloadSize += 2 + length
|
||||
} else if utils.AVMediaTypeVideo == packet.MediaType() {
|
||||
//首帧必须要视频关键帧
|
||||
if !t.firstVideoPacket {
|
||||
if !packet.KeyFrame() {
|
||||
return fmt.Errorf("the first video frame must be a keyframe")
|
||||
}
|
||||
|
||||
t.firstVideoPacket = true
|
||||
}
|
||||
|
||||
videoPkt = true
|
||||
videoKey = packet.KeyFrame()
|
||||
data = packet.AVCCPacketData()
|
||||
@@ -81,33 +49,16 @@ func (t *TransStream) Input(packet utils.AVPacket) error {
|
||||
payloadSize += 5 + length
|
||||
}
|
||||
|
||||
//即不开启GOP缓存又不合并发送. 直接使用AVPacket的预留头封装发送
|
||||
if !stream.AppConfig.GOPCache || t.onlyAudio {
|
||||
//首帧视频帧必须要关键帧
|
||||
return nil
|
||||
}
|
||||
|
||||
if videoKey {
|
||||
tmp := t.memoryPool[0]
|
||||
tmp := t.StreamBuffers[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.SendPacket(head[t.SegmentOffset:])
|
||||
t.SwapStreamBuffer()
|
||||
}
|
||||
|
||||
//分配内存
|
||||
t.memoryPool[0].Mark()
|
||||
allocate := t.memoryPool[0].Allocate(12 + payloadSize + (payloadSize / t.chunkSize))
|
||||
t.StreamBuffers[0].Mark()
|
||||
allocate := t.StreamBuffers[0].Allocate(12 + payloadSize + (payloadSize / t.chunkSize))
|
||||
|
||||
//写chunk头
|
||||
chunk.Length = payloadSize
|
||||
@@ -119,77 +70,19 @@ func (t *TransStream) Input(packet utils.AVPacket) error {
|
||||
ct := packet.Pts() - packet.Dts()
|
||||
if videoPkt {
|
||||
n += t.muxer.WriteVideoData(allocate[12:], uint32(ct), packet.KeyFrame(), false)
|
||||
n += chunk.WriteData(allocate[n:], data, t.chunkSize)
|
||||
} else {
|
||||
n += t.muxer.WriteAudioData(allocate[12:], false)
|
||||
n += chunk.WriteData(allocate[n:], data, t.chunkSize)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
_ = t.StreamBuffers[0].Fetch()[:n]
|
||||
if t.Full(packet.Pts()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.segmentDuration < stream.AppConfig.MergeWriteLatency {
|
||||
return nil
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
head, _ := t.StreamBuffers[0].Data()
|
||||
t.SendPacketWithOffset(head[:], t.SegmentOffset)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -200,36 +93,22 @@ func (t *TransStream) AddSink(sink stream.ISink) error {
|
||||
//发送sequence header
|
||||
sink.Input(t.header[:t.headerSize])
|
||||
|
||||
if !stream.AppConfig.GOPCache || t.onlyAudio {
|
||||
return nil
|
||||
}
|
||||
|
||||
//发送当前内存池已有的合并写切片
|
||||
if t.segmentOffset > 0 {
|
||||
data, tail := t.memoryPool[0].Data()
|
||||
if t.SegmentOffset > 0 {
|
||||
data, _ := t.StreamBuffers[0].Data()
|
||||
utils.Assert(len(data) > 0)
|
||||
utils.Assert(len(tail) == 0)
|
||||
sink.Input(data[:t.segmentOffset])
|
||||
sink.Input(data[:t.SegmentOffset])
|
||||
return nil
|
||||
}
|
||||
|
||||
//发送上一组GOP
|
||||
if t.memoryPool[1] != nil && !t.memoryPool[1].Empty() {
|
||||
data, tail := t.memoryPool[0].Data()
|
||||
if t.StreamBuffers[1] != nil && !t.StreamBuffers[1].Empty() {
|
||||
data, _ := t.StreamBuffers[0].Data()
|
||||
utils.Assert(len(data) > 0)
|
||||
utils.Assert(len(tail) == 0)
|
||||
sink.Input(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
//不足一个合并写切片, 有多少发多少
|
||||
data, tail := t.memoryPool[0].Data()
|
||||
utils.Assert(len(tail) == 0)
|
||||
if len(data) > 0 {
|
||||
sink.Input(data)
|
||||
t.incompleteSinks = append(t.incompleteSinks, sink)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -237,6 +116,8 @@ func (t *TransStream) WriteHeader() error {
|
||||
utils.Assert(t.Tracks != nil)
|
||||
utils.Assert(!t.TransStreamImpl.Completed)
|
||||
|
||||
t.Init()
|
||||
|
||||
var audioStream utils.AVStream
|
||||
var videoStream utils.AVStream
|
||||
var audioCodecId utils.AVCodecID
|
||||
@@ -259,16 +140,16 @@ func (t *TransStream) WriteHeader() error {
|
||||
//初始化
|
||||
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 {
|
||||
|
||||
t.muxer = libflv.NewMuxer()
|
||||
if utils.AVCodecIdNONE != audioCodecId {
|
||||
t.muxer.AddAudioTrack(audioCodecId, 0, 0, 0)
|
||||
}
|
||||
|
||||
if utils.AVCodecIdNONE != videoCodecId {
|
||||
t.muxer.AddVideoTrack(videoCodecId)
|
||||
}
|
||||
|
||||
//统一生成rtmp拉流需要的数据头(chunk+sequence header)
|
||||
var n int
|
||||
if audioStream != nil {
|
||||
n += t.muxer.WriteAudioData(t.header[12:], true)
|
||||
|
Reference in New Issue
Block a user