Files
ipchub/media/cache/h264cache.go
2021-01-19 09:50:04 +08:00

206 lines
6.2 KiB
Go
Executable File

// Copyright (c) 2019,CAOHONGJU All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cache
import (
"sync"
"github.com/cnotch/ipchub/av/codec/h264"
"github.com/cnotch/ipchub/av/format/rtp"
"github.com/cnotch/queue"
)
// H264Cache 画面组缓存(Group of Pictures).
type H264Cache struct {
cacheGop bool
l sync.RWMutex
gop queue.Queue
sps *rtp.Packet // 序列参数集包
pps *rtp.Packet // 图像参数集包
}
// NewH264Cache 创建 H264 缓存
func NewH264Cache(cacheGop bool) *H264Cache {
return &H264Cache{
cacheGop: cacheGop,
}
}
// CachePack 向H264Cache中缓存包
func (cache *H264Cache) CachePack(pack Pack) bool {
rtppack := pack.(*rtp.Packet)
if rtppack.Channel != rtp.ChannelVideo {
return false
}
// 判断是否是参数和关键帧包
sps, pps, islice := cache.getPalyloadType(rtppack.Payload())
cache.l.Lock()
defer cache.l.Unlock()
if sps { // 新序列参数,重置图像参数和 GopCache
cache.sps = rtppack
return false
}
if pps { // 新图像参数,重置 GopCahce
cache.pps = rtppack
return false
}
if cache.cacheGop { // 需要缓存 GOP
if islice { // 关键帧
cache.gop.Reset()
cache.gop.Push(rtppack)
} else if cache.gop.Len() > 0 { // 必须关键帧作为cache的第一个包
cache.gop.Push(rtppack)
}
}
return islice
}
// Reset 重置H264Cache缓存
func (cache *H264Cache) Reset() {
cache.l.Lock()
defer cache.l.Unlock()
cache.sps = nil
cache.pps = nil
cache.gop.Reset()
}
// PushTo 入列到指定的队列
func (cache *H264Cache) PushTo(q *queue.SyncQueue) int {
bytes := 0
cache.l.RLock()
defer cache.l.RUnlock()
// 写参数包
if cache.sps != nil {
q.Queue().Push(cache.sps)
bytes += cache.sps.Size()
}
if cache.pps != nil {
q.Queue().Push(cache.pps)
bytes += cache.pps.Size()
}
// 如果必要,写 GopCache
if cache.cacheGop {
packs := cache.gop.Elems()
q.Queue().PushN(packs) // 启动阶段调用,无需加锁
for _, p := range packs {
bytes += p.(Pack).Size()
}
}
return bytes
}
func (cache *H264Cache) getPalyloadType(payload []byte) (sps, pps, islice bool) {
if len(payload) < 3 {
return
}
// +---------------+
// |0|1|2|3|4|5|6|7|
// +-+-+-+-+-+-+-+-+
// |F|NRI| Type |
// +---------------+
naluTypeInRtp := payload[0] & 0x1F
switch naluTypeInRtp {
case h264.NalStapaInRtp, h264.NalStapbInRtp, h264.NalMtap16InRtp, h264.NalMtap24InRtp:
// 组合包
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | RTP Header |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | NALU 1 Data |
// : :
// + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | | NALU 2 Size | NALU 2 HDR |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | NALU 2 Data |
// : :
// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | :...OPTIONAL RTP padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
off := 1
// 循环读取被封装的NAL
for {
// nal长度
nalSize := ((uint16(payload[off])) << 8) | uint16(payload[off+1])
if nalSize < 1 {
return
}
off += 2
realNALU := byte(payload[off] & 0x1f)
cache.nalType(realNALU, &sps, &pps, &islice) // 当前NAL类型
off += int(nalSize)
if off >= len(payload) { // 扫描完成
break
}
}
case h264.NalFuAInRtp, h264.NalFuBInRtp:
// 分片包
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | FU indicator | FU header | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
// | |
// | FU payload |
// | |
// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | :...OPTIONAL RTP padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// +---------------+
// |0|1|2|3|4|5|6|7|
// +-+-+-+-+-+-+-+-+
// |S|E|R| Type |
// +---------------+
fuHeader := payload[1]
if (fuHeader>>7)&1 == 1 { // 仅对第一个分片进行检测
// Start
realNALU := byte(fuHeader & 0x1f)
cache.nalType(realNALU, &sps, &pps, &islice)
}
default:
// h264 原生 nal 包
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |F|NRI| type | |
// +-+-+-+-+-+-+-+-+ |
// | |
// | Bytes 2..n of a Single NAL unit |
// | |
// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | :...OPTIONAL RTP padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
cache.nalType(naluTypeInRtp, &sps, &pps, &islice)
}
return
}
func (cache *H264Cache) nalType(nalType byte, sps, pps, islice *bool) {
switch nalType {
case h264.NalSps:
*sps = true
case h264.NalPps:
*pps = true
case h264.NalIdrSlice:
*islice = true
}
return
}