Files
monibuca/pkg/format/annexb.go
langhuihui 8a9fffb987 refactor: frame converter and mp4 track improvements
- Refactor frame converter implementation
- Update mp4 track to use ICodex
- General refactoring and code improvements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 19:55:37 +08:00

291 lines
7.5 KiB
Go

package format
import (
"bytes"
"fmt"
"io"
"slices"
"github.com/deepch/vdk/codec/h264parser"
"github.com/deepch/vdk/codec/h265parser"
"m7s.live/v5/pkg"
"m7s.live/v5/pkg/codec"
"m7s.live/v5/pkg/util"
)
type AnnexB struct {
pkg.Sample
}
func (a *AnnexB) CheckCodecChange() (err error) {
if !a.HasRaw() || a.ICodecCtx == nil {
err = a.Demux()
if err != nil {
return
}
}
if a.ICodecCtx == nil {
return pkg.ErrSkip
}
var vps, sps, pps []byte
a.IDR = false
for nalu := range a.Raw.(*pkg.Nalus).RangePoint {
if a.FourCC() == codec.FourCC_H265 {
switch codec.ParseH265NALUType(nalu.Buffers[0][0]) {
case h265parser.NAL_UNIT_VPS:
vps = nalu.ToBytes()
case h265parser.NAL_UNIT_SPS:
sps = nalu.ToBytes()
case h265parser.NAL_UNIT_PPS:
pps = nalu.ToBytes()
case h265parser.NAL_UNIT_CODED_SLICE_BLA_W_LP,
h265parser.NAL_UNIT_CODED_SLICE_BLA_W_RADL,
h265parser.NAL_UNIT_CODED_SLICE_BLA_N_LP,
h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL,
h265parser.NAL_UNIT_CODED_SLICE_IDR_N_LP,
h265parser.NAL_UNIT_CODED_SLICE_CRA:
a.IDR = true
}
} else {
switch codec.ParseH264NALUType(nalu.Buffers[0][0]) {
case codec.NALU_SPS:
sps = nalu.ToBytes()
case codec.NALU_PPS:
pps = nalu.ToBytes()
case codec.NALU_IDR_Picture:
a.IDR = true
}
}
}
if a.FourCC() == codec.FourCC_H265 {
if vps != nil && sps != nil && pps != nil {
var codecData h265parser.CodecData
codecData, err = h265parser.NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps)
if err != nil {
return
}
if !bytes.Equal(codecData.Record, a.ICodecCtx.(*codec.H265Ctx).Record) {
a.ICodecCtx = &codec.H265Ctx{
CodecData: codecData,
}
}
}
if a.ICodecCtx.(*codec.H265Ctx).Record == nil {
err = pkg.ErrSkip
}
} else {
if sps != nil && pps != nil {
var codecData h264parser.CodecData
codecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps)
if err != nil {
return
}
if !bytes.Equal(codecData.Record, a.ICodecCtx.(*codec.H264Ctx).Record) {
a.ICodecCtx = &codec.H264Ctx{
CodecData: codecData,
}
}
}
if a.ICodecCtx.(*codec.H264Ctx).Record == nil {
err = pkg.ErrSkip
}
}
return
}
// String implements pkg.IAVFrame.
func (a *AnnexB) String() string {
return fmt.Sprintf("%d %d", a.Timestamp, a.Memory.Size)
}
// Demux implements pkg.IAVFrame.
func (a *AnnexB) Demux() (err error) {
nalus := a.GetNalus()
var lastFourBytes [4]byte
var b byte
var shallow util.Memory
shallow.Push(a.Buffers...)
reader := shallow.NewReader()
gotNalu := func() {
nalu := nalus.GetNextPointer()
for buf := range reader.ClipFront {
nalu.PushOne(buf)
}
if a.ICodecCtx == nil {
naluType := codec.ParseH264NALUType(nalu.Buffers[0][0])
switch naluType {
case codec.NALU_Non_IDR_Picture,
codec.NALU_IDR_Picture,
codec.NALU_SEI,
codec.NALU_SPS,
codec.NALU_PPS,
codec.NALU_Access_Unit_Delimiter:
a.ICodecCtx = &codec.H264Ctx{}
}
}
}
for {
b, err = reader.ReadByte()
if err == nil {
copy(lastFourBytes[:], lastFourBytes[1:])
lastFourBytes[3] = b
var startCode = 0
if lastFourBytes == codec.NALU_Delimiter2 {
startCode = 4
} else if [3]byte(lastFourBytes[1:]) == codec.NALU_Delimiter1 {
startCode = 3
}
if startCode > 0 && reader.Offset() >= 3 {
if reader.Offset() == 3 {
startCode = 3
}
reader.Unread(startCode)
if reader.Offset() > 0 {
gotNalu()
}
reader.Skip(startCode)
for range reader.ClipFront {
}
}
} else if err == io.EOF {
if reader.Offset() > 0 {
gotNalu()
}
err = nil
break
}
}
return
}
func (a *AnnexB) Mux(fromBase *pkg.Sample) (err error) {
if a.ICodecCtx == nil {
a.ICodecCtx = fromBase.GetBase()
}
a.InitRecycleIndexes(0)
delimiter2 := codec.NALU_Delimiter2[:]
a.PushOne(delimiter2)
if fromBase.IDR {
switch ctx := fromBase.GetBase().(type) {
case *codec.H264Ctx:
a.Push(ctx.SPS(), delimiter2, ctx.PPS(), delimiter2)
case *codec.H265Ctx:
a.Push(ctx.SPS(), delimiter2, ctx.PPS(), delimiter2, ctx.VPS(), delimiter2)
}
}
for i, nalu := range *fromBase.Raw.(*pkg.Nalus) {
if i > 0 {
a.PushOne(codec.NALU_Delimiter1[:])
}
a.Push(nalu.Buffers...)
}
return
}
func (a *AnnexB) Parse(reader *pkg.AnnexBReader) (hasFrame bool, err error) {
nalus := a.BaseSample.GetNalus()
for !hasFrame {
nalu := nalus.GetNextPointer()
reader.ReadNALU(&a.Memory, nalu)
if nalu.Size == 0 {
nalus.Reduce()
return
}
tryH264Type := codec.ParseH264NALUType(nalu.Buffers[0][0])
h265Type := codec.ParseH265NALUType(nalu.Buffers[0][0])
if a.ICodecCtx == nil {
a.ICodecCtx = &codec.H26XCtx{}
}
switch ctx := a.ICodecCtx.(type) {
case *codec.H26XCtx:
if tryH264Type == codec.NALU_SPS {
ctx.SPS = nalu.ToBytes()
nalus.Reduce()
a.Recycle()
} else if tryH264Type == codec.NALU_PPS {
ctx.PPS = nalu.ToBytes()
nalus.Reduce()
a.Recycle()
} else if h265Type == h265parser.NAL_UNIT_VPS {
ctx.VPS = nalu.ToBytes()
nalus.Reduce()
a.Recycle()
} else if h265Type == h265parser.NAL_UNIT_SPS {
ctx.SPS = nalu.ToBytes()
nalus.Reduce()
a.Recycle()
} else if h265Type == h265parser.NAL_UNIT_PPS {
ctx.PPS = nalu.ToBytes()
nalus.Reduce()
a.Recycle()
} else {
if ctx.SPS != nil && ctx.PPS != nil && tryH264Type == codec.NALU_IDR_Picture {
var codecData h264parser.CodecData
codecData, err = h264parser.NewCodecDataFromSPSAndPPS(ctx.SPS, ctx.PPS)
if err != nil {
return
}
a.ICodecCtx = &codec.H264Ctx{
CodecData: codecData,
}
*nalus = slices.Insert(*nalus, 0, util.NewMemory(ctx.SPS), util.NewMemory(ctx.PPS))
delimiter2 := codec.NALU_Delimiter2[:]
a.Buffers = slices.Insert(a.Buffers, 0, delimiter2, ctx.SPS, delimiter2, ctx.PPS)
a.Size += 8 + len(ctx.SPS) + len(ctx.PPS)
} else if ctx.VPS != nil && ctx.SPS != nil && ctx.PPS != nil && h265Type == h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL {
var codecData h265parser.CodecData
codecData, err = h265parser.NewCodecDataFromVPSAndSPSAndPPS(ctx.VPS, ctx.SPS, ctx.PPS)
if err != nil {
return
}
a.ICodecCtx = &codec.H265Ctx{
CodecData: codecData,
}
*nalus = slices.Insert(*nalus, 0, util.NewMemory(ctx.VPS), util.NewMemory(ctx.SPS), util.NewMemory(ctx.PPS))
delimiter2 := codec.NALU_Delimiter2[:]
a.Buffers = slices.Insert(a.Buffers, 0, delimiter2, ctx.VPS, delimiter2, ctx.SPS, delimiter2, ctx.PPS)
a.Size += 24 + len(ctx.VPS) + len(ctx.SPS) + len(ctx.PPS)
} else {
nalus.Reduce()
a.Recycle()
}
}
case *codec.H264Ctx:
switch tryH264Type {
case codec.NALU_IDR_Picture:
a.IDR = true
hasFrame = true
case codec.NALU_Non_IDR_Picture:
a.IDR = false
hasFrame = true
}
case *codec.H265Ctx:
switch h265Type {
case h265parser.NAL_UNIT_CODED_SLICE_BLA_W_LP,
h265parser.NAL_UNIT_CODED_SLICE_BLA_W_RADL,
h265parser.NAL_UNIT_CODED_SLICE_BLA_N_LP,
h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL,
h265parser.NAL_UNIT_CODED_SLICE_IDR_N_LP,
h265parser.NAL_UNIT_CODED_SLICE_CRA:
a.IDR = true
hasFrame = true
case h265parser.NAL_UNIT_CODED_SLICE_TRAIL_N,
h265parser.NAL_UNIT_CODED_SLICE_TRAIL_R,
h265parser.NAL_UNIT_CODED_SLICE_TSA_N,
h265parser.NAL_UNIT_CODED_SLICE_TSA_R,
h265parser.NAL_UNIT_CODED_SLICE_STSA_N,
h265parser.NAL_UNIT_CODED_SLICE_STSA_R,
h265parser.NAL_UNIT_CODED_SLICE_RADL_N,
h265parser.NAL_UNIT_CODED_SLICE_RADL_R,
h265parser.NAL_UNIT_CODED_SLICE_RASL_N,
h265parser.NAL_UNIT_CODED_SLICE_RASL_R:
a.IDR = false
hasFrame = true
}
}
}
return
}