// Copyright 2019, Chef. All rights reserved. // https://github.com/q191201771/lal // // Use of this source code is governed by a MIT-style license // that can be found in the License file. // // Author: Chef (191201771@qq.com) package logic import ( "github.com/q191201771/lal/pkg/base" "github.com/q191201771/lal/pkg/remux" "github.com/q191201771/lal/pkg/rtmp" "github.com/q191201771/naza/pkg/nazalog" ) // TODO(chef): refactor 本文件移出package logic // 考虑以下两种场景: // - 只有上行,没有下行,没有必要做rtmp chunk切片的操作 // - 有多个下行,只需要做一次rtmp chunk切片 // 所以这一步做了懒处理 type LazyChunkDivider struct { message []byte header *base.RtmpHeader chunks []byte } func (lcd *LazyChunkDivider) Init(message []byte, header *base.RtmpHeader) { lcd.message = message lcd.header = header } func (lcd *LazyChunkDivider) Get() []byte { if lcd.chunks == nil { lcd.chunks = rtmp.Message2Chunks(lcd.message, lcd.header) } return lcd.chunks } // 懒转换 type LazyRtmpMsg2FlvTag struct { msg base.RtmpMsg tag []byte } func (l *LazyRtmpMsg2FlvTag) Init(msg base.RtmpMsg) { l.msg = msg } func (l *LazyRtmpMsg2FlvTag) Get() []byte { if l.tag == nil { l.tag = remux.RtmpMsg2FlvTag(l.msg).Raw } return l.tag } // --------------------------------------------------------------------------------------------------------------------- // 提供两个功能: // 1. 缓存Metadata, VideoSeqHeader, AacSeqHeader // 2. 缓存音视频GOP数据 // // 以下,只讨论GopCache的第2点功能 // // 音频和视频都会缓存 // // GopCache也可能不缓存GOP数据,见NewGopCache函数的gopNum参数说明 // // 以下,我们只讨论gopNum > 0(也即gopSize > 1)的情况 // // GopCache为空时,只有输入了关键帧,才能开启GOP缓存,非关键帧以及音频数据不会被缓存 // 因此,单音频的流是ok的,相当于不缓存任何数据 // // GopCache不为空时,输入关键帧触发生成新的GOP元素,其他情况则往最后一个GOP元素一直追加 // // first用于读取第一个GOP(可能不完整),last的前一个用于写入当前GOP // // 最近不完整的GOP也会被缓存,见NewGopCache函数的gopNum参数说明 // // ----- // gopNum = 1 // gopSize = 2 // // first | first | first | 在后面两个状态间转换,就不画了 // | | | | | | // 0 1 | 0 1 | 0 1 | // * * | * * | * * | // | | | | | | // last | last | last | // | | | // (empty) | (full) | (full) | // GetGopCount: 0 | 1 | 1 | // ----- // // type GopCache struct { t string uniqueKey string Metadata []byte VideoSeqHeader []byte AacSeqHeader []byte gopRing []Gop gopRingFirst int gopRingLast int gopSize int } // @param gopNum: gop缓存大小 // 如果为0,则不缓存音频数据,也即GOP缓存功能不生效 // 如果>0,则缓存个完整GOP,另外还可能有半个最近不完整的GOP // func NewGopCache(t string, uniqueKey string, gopNum int) *GopCache { return &GopCache{ t: t, uniqueKey: uniqueKey, gopSize: gopNum + 1, gopRing: make([]Gop, gopNum+1, gopNum+1), gopRingFirst: 0, gopRingLast: 0, } } type LazyGet func() []byte func (gc *GopCache) Feed(msg base.RtmpMsg, lg LazyGet) { switch msg.Header.MsgTypeId { case base.RtmpTypeIdMetadata: gc.Metadata = lg() nazalog.Debugf("[%s] cache %s metadata. size:%d", gc.uniqueKey, gc.t, len(gc.Metadata)) return case base.RtmpTypeIdAudio: if msg.IsAacSeqHeader() { gc.AacSeqHeader = lg() nazalog.Debugf("[%s] cache %s aac seq header. size:%d", gc.uniqueKey, gc.t, len(gc.AacSeqHeader)) return } case base.RtmpTypeIdVideo: if msg.IsVideoKeySeqHeader() { gc.VideoSeqHeader = lg() nazalog.Debugf("[%s] cache %s video seq header. size:%d", gc.uniqueKey, gc.t, len(gc.VideoSeqHeader)) return } } if gc.gopSize > 1 { if msg.IsVideoKeyNalu() { gc.feedNewGop(msg, lg()) } else { gc.feedLastGop(msg, lg()) } } } // 获取GOP数量,注意,最后一个可能是不完整的 func (gc *GopCache) GetGopCount() int { return (gc.gopRingLast + gc.gopSize - gc.gopRingFirst) % gc.gopSize } func (gc *GopCache) GetGopDataAt(pos int) [][]byte { if pos >= gc.GetGopCount() || pos < 0 { return nil } return gc.gopRing[(pos+gc.gopRingFirst)%gc.gopSize].data } func (gc *GopCache) Clear() { gc.Metadata = nil gc.VideoSeqHeader = nil gc.AacSeqHeader = nil gc.gopRingLast = 0 gc.gopRingFirst = 0 } // 往最后一个GOP元素追加一个msg // 注意,如果GopCache为空,则不缓存msg func (gc *GopCache) feedLastGop(msg base.RtmpMsg, b []byte) { if !gc.isGopRingEmpty() { gc.gopRing[(gc.gopRingLast-1+gc.gopSize)%gc.gopSize].Feed(msg, b) } } // 生成一个最新的GOP元素,并往里追加一个msg func (gc *GopCache) feedNewGop(msg base.RtmpMsg, b []byte) { if gc.isGopRingFull() { gc.gopRingFirst = (gc.gopRingFirst + 1) % gc.gopSize } gc.gopRing[gc.gopRingLast].Clear() gc.gopRing[gc.gopRingLast].Feed(msg, b) gc.gopRingLast = (gc.gopRingLast + 1) % gc.gopSize } func (gc *GopCache) isGopRingFull() bool { return (gc.gopRingLast+1)%gc.gopSize == gc.gopRingFirst } func (gc *GopCache) isGopRingEmpty() bool { return gc.gopRingFirst == gc.gopRingLast } type Gop struct { data [][]byte } func (g *Gop) Feed(msg base.RtmpMsg, b []byte) { g.data = append(g.data, b) } func (g *Gop) Clear() { g.data = g.data[:0] }