diff --git a/pkg/h264/dtsextractor.go b/pkg/h264/dtsextractor.go index e3908789..f17d3d92 100644 --- a/pkg/h264/dtsextractor.go +++ b/pkg/h264/dtsextractor.go @@ -68,7 +68,7 @@ func getPOC(buf []byte, sps *SPS) (uint32, error) { return uint32(picOrderCntLsb), nil } -func getNALUSPOC(nalus [][]byte, sps *SPS) (uint32, error) { +func findPOC(nalus [][]byte, sps *SPS) (uint32, error) { for _, nalu := range nalus { typ := NALUType(nalu[0] & 0x1F) if typ == NALUTypeIDR || typ == NALUTypeNonIDR { @@ -94,6 +94,70 @@ func getPOCDiff(poc1 uint32, poc2 uint32, sps *SPS) int32 { return diff } +func getSEIPicTimingDPBOutputDelay(buf []byte, sps *SPS) (uint32, bool) { + buf = AntiCompetitionRemove(buf) + pos := 1 + + for { + if pos >= (len(buf) - 1) { + return 0, false + } + + payloadType := 0 + for { + byt := buf[pos] + pos++ + payloadType += int(byt) + if byt != 0xFF { + break + } + } + + payloadSize := 0 + for { + byt := buf[pos] + pos++ + payloadSize += int(byt) + if byt != 0xFF { + break + } + } + + if payloadType == 1 { // timing info + br := bitio.NewReader(bytes.NewReader(buf[pos : pos+payloadSize])) + + // cpbRemovalDelay + _, err := br.ReadBits(sps.VUI.NalHRD.CpbRemovalDelayLengthMinus1 + 1) + if err != nil { + return 0, false + } + + tmp, err := br.ReadBits(sps.VUI.NalHRD.DpbOutputDelayLengthMinus1 + 1) + if err != nil { + return 0, false + } + dpbOutputDelay := uint32(tmp) + + return dpbOutputDelay, true + } + + pos += payloadSize + } +} + +func findSEIPicTimingDPBOutputDelay(nalus [][]byte, sps *SPS) (uint32, bool) { + for _, nalu := range nalus { + typ := NALUType(nalu[0] & 0x1F) + if typ == NALUTypeSEI { + ret, ok := getSEIPicTimingDPBOutputDelay(nalu, sps) + if ok { + return ret, true + } + } + } + return 0, false +} + // DTSExtractor is a utility that allows to extract NALU DTS from PTS. type DTSExtractor struct { sps []byte @@ -110,27 +174,22 @@ func NewDTSExtractor() *DTSExtractor { return &DTSExtractor{} } -func (d *DTSExtractor) extractInner( - nalus [][]byte, - pts time.Duration, -) (time.Duration, int32, error) { +func (d *DTSExtractor) extractInner(nalus [][]byte, pts time.Duration) (time.Duration, int32, error) { idrPresent := false for _, nalu := range nalus { typ := NALUType(nalu[0] & 0x1F) switch typ { - // parse SPS case NALUTypeSPS: if d.sps == nil || !bytes.Equal(d.sps, nalu) { var spsp SPS err := spsp.Unmarshal(nalu) if err != nil { - return 0, 0, err + return 0, 0, fmt.Errorf("invalid SPS: %v", err) } d.sps = append([]byte(nil), nalu...) d.spsp = &spsp - // in case of B-frames, we have to subtract from DTS the maximum number of reordered frames if d.spsp.VUI != nil && d.spsp.VUI.TimingInfo != nil && d.spsp.VUI.BitstreamRestriction != nil { d.ptsDTSOffset = time.Duration(math.Round(float64( @@ -141,7 +200,6 @@ func (d *DTSExtractor) extractInner( } } - // set IDR present flag case NALUTypeIDR: idrPresent = true } @@ -151,51 +209,68 @@ func (d *DTSExtractor) extractInner( return 0, 0, fmt.Errorf("SPS not received yet") } - if idrPresent || d.spsp.PicOrderCntType == 2 { - d.expectedPOC = 0 - return pts - d.ptsDTSOffset, 0, nil - } - - // compute expectedPOC immediately in order to store it even in case of errors - d.expectedPOC += 2 - d.expectedPOC &= ((1 << (d.spsp.Log2MaxPicOrderCntLsbMinus4 + 4)) - 1) - - poc, err := getNALUSPOC(nalus, d.spsp) - if err != nil { - return 0, 0, err - } - - pocDiff := getPOCDiff(poc, d.expectedPOC, d.spsp) - - if pocDiff == 0 { - return pts - d.ptsDTSOffset, 0, nil - } - - // special case to eliminate errors near 0 - if d.spsp.VUI != nil && d.spsp.VUI.TimingInfo != nil && d.spsp.VUI.BitstreamRestriction != nil && - pocDiff == -int32(d.spsp.VUI.BitstreamRestriction.MaxNumReorderFrames)*2 { - return pts, pocDiff, nil - } - - if d.prevPOCDiff == 0 { - if pocDiff == -2 { - return 0, 0, fmt.Errorf("invalid frame POC") + switch { + // DTS is computed from SEI + case d.spsp.VUI != nil && d.spsp.VUI.TimingInfo != nil && d.spsp.VUI.NalHRD != nil: + dpbOutputDelay, ok := findSEIPicTimingDPBOutputDelay(nalus, d.spsp) + if !ok { + return 0, 0, fmt.Errorf("SEI / pic_timing not found") } - return d.prevPTS - d.ptsDTSOffset + - time.Duration(math.Round(float64(pts-d.prevPTS)/float64(pocDiff/2+1))), pocDiff, nil - } + return pts - time.Duration(dpbOutputDelay)/2*time.Second* + time.Duration(d.spsp.VUI.TimingInfo.NumUnitsInTick)*2/time.Duration(d.spsp.VUI.TimingInfo.TimeScale), 0, nil - // pocDiff : prevPOCDiff = (pts - dts - ptsDTSOffset) : (prevPTS - prevDTS - ptsDTSOffset) - return pts - d.ptsDTSOffset + time.Duration(math.Round(float64(d.prevDTS-d.prevPTS+d.ptsDTSOffset)* - float64(pocDiff)/float64(d.prevPOCDiff))), pocDiff, nil + // DTS is always equal to PTS + case d.spsp.PicOrderCntType == 2: + return pts, 0, nil + + // DTS is computed by using POC, timing infos and max_num_reorder_frames + case d.spsp.VUI != nil && d.spsp.VUI.TimingInfo != nil && d.spsp.VUI.BitstreamRestriction != nil: + if idrPresent { + d.expectedPOC = 0 + return pts - d.ptsDTSOffset, 0, nil + } + + // compute expectedPOC immediately in order to store it even in case of errors + d.expectedPOC += 2 + d.expectedPOC &= ((1 << (d.spsp.Log2MaxPicOrderCntLsbMinus4 + 4)) - 1) + + poc, err := findPOC(nalus, d.spsp) + if err != nil { + return 0, 0, err + } + + pocDiff := getPOCDiff(poc, d.expectedPOC, d.spsp) + + if pocDiff == 0 { + return pts - d.ptsDTSOffset, 0, nil + } + + // special case to eliminate errors near 0 + if pocDiff == -int32(d.spsp.VUI.BitstreamRestriction.MaxNumReorderFrames)*2 { + return pts, pocDiff, nil + } + + if d.prevPOCDiff == 0 { + if pocDiff == -2 { + return 0, 0, fmt.Errorf("invalid frame POC") + } + + return d.prevPTS - d.ptsDTSOffset + + time.Duration(math.Round(float64(pts-d.prevPTS)/float64(pocDiff/2+1))), pocDiff, nil + } + + // pocDiff : prevPOCDiff = (pts - dts - ptsDTSOffset) : (prevPTS - prevDTS - ptsDTSOffset) + return pts - d.ptsDTSOffset + time.Duration(math.Round(float64(d.prevDTS-d.prevPTS+d.ptsDTSOffset)* + float64(pocDiff)/float64(d.prevPOCDiff))), pocDiff, nil + + default: + return 0, 0, fmt.Errorf("unable to compute H264 DTS") + } } -// Extract extracts the DTS of a NALU group. -func (d *DTSExtractor) Extract( - nalus [][]byte, - pts time.Duration, -) (time.Duration, error) { +// Extract extracts the DTS of a group of NALUs. +func (d *DTSExtractor) Extract(nalus [][]byte, pts time.Duration) (time.Duration, error) { dts, pocDiff, err := d.extractInner(nalus, pts) if err != nil { return 0, err diff --git a/pkg/h264/dtsextractor_test.go b/pkg/h264/dtsextractor_test.go index 06953636..19c3c977 100644 --- a/pkg/h264/dtsextractor_test.go +++ b/pkg/h264/dtsextractor_test.go @@ -8,137 +8,195 @@ import ( ) func TestDTSExtractor(t *testing.T) { - sequence := []struct { + type sequenceSample struct { nalus [][]byte pts time.Duration dts time.Duration + } + + for _, ca := range []struct { + name string + sequence []sequenceSample }{ { - [][]byte{ + "max_num_reorder_frames-based", + []sequenceSample{ { - 0x67, 0x64, 0x00, 0x28, 0xac, 0xd9, 0x40, 0x78, - 0x02, 0x27, 0xe5, 0xc0, 0x44, 0x00, 0x00, 0x03, - 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x28, 0x3c, - 0x60, 0xc6, 0x58, + [][]byte{ + { + 0x67, 0x64, 0x00, 0x28, 0xac, 0xd9, 0x40, 0x78, + 0x02, 0x27, 0xe5, 0xc0, 0x44, 0x00, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x28, 0x3c, + 0x60, 0xc6, 0x58, + }, + {0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0}, + { + 0x65, 0x88, 0x82, 0x00, 0x05, 0xbf, 0xfe, 0xf7, + 0xd3, 0x3f, 0xcc, 0xb2, 0xec, 0x9a, 0x24, 0xb5, + 0xe3, 0xa8, 0xf7, 0xa2, 0x9e, 0x26, 0x5f, 0x43, + 0x75, 0x25, 0x01, 0x9b, 0x96, 0xc4, 0xed, 0x3a, + 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x55, + 0xda, 0xf7, 0x10, 0xe5, 0xc4, 0x70, 0xe1, 0xfe, + 0x83, 0xc0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x1f, 0xa0, 0x00, 0x00, 0x05, 0x68, 0x00, + 0x00, 0x03, 0x01, 0xc6, 0x00, 0x00, 0x03, 0x01, + 0x0c, 0x00, 0x00, 0x03, 0x00, 0xb1, 0x00, 0x00, + 0x03, 0x00, 0x8f, 0x80, 0x00, 0x00, 0x8a, 0x80, + 0x00, 0x00, 0x9d, 0x00, 0x00, 0x03, 0x00, 0xb2, + 0x00, 0x00, 0x03, 0x01, 0x1c, 0x00, 0x00, 0x03, + 0x01, 0x7c, 0x00, 0x00, 0x03, 0x02, 0xf0, 0x00, + 0x00, 0x04, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0b, + 0x78, + }, + }, + 0, + -400 * time.Millisecond, }, - {0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0}, { - 0x65, 0x88, 0x82, 0x00, 0x05, 0xbf, 0xfe, 0xf7, - 0xd3, 0x3f, 0xcc, 0xb2, 0xec, 0x9a, 0x24, 0xb5, - 0xe3, 0xa8, 0xf7, 0xa2, 0x9e, 0x26, 0x5f, 0x43, - 0x75, 0x25, 0x01, 0x9b, 0x96, 0xc4, 0xed, 0x3a, - 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x55, - 0xda, 0xf7, 0x10, 0xe5, 0xc4, 0x70, 0xe1, 0xfe, - 0x83, 0xc0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x1f, 0xa0, 0x00, 0x00, 0x05, 0x68, 0x00, - 0x00, 0x03, 0x01, 0xc6, 0x00, 0x00, 0x03, 0x01, - 0x0c, 0x00, 0x00, 0x03, 0x00, 0xb1, 0x00, 0x00, - 0x03, 0x00, 0x8f, 0x80, 0x00, 0x00, 0x8a, 0x80, - 0x00, 0x00, 0x9d, 0x00, 0x00, 0x03, 0x00, 0xb2, - 0x00, 0x00, 0x03, 0x01, 0x1c, 0x00, 0x00, 0x03, - 0x01, 0x7c, 0x00, 0x00, 0x03, 0x02, 0xf0, 0x00, - 0x00, 0x04, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0b, - 0x78, + [][]byte{ + { + 0x41, 0x9a, 0x24, 0x6c, 0x41, 0x4f, 0xfe, 0xd6, + 0x8c, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x6d, 0x40, + }, + }, + 800 * time.Millisecond, + -200 * time.Millisecond, + }, + { + [][]byte{ + { + 0x41, 0x9e, 0x42, 0x78, 0x82, 0x1f, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, + 0x0f, + }, + }, + 400 * time.Millisecond, + 0, + }, + { + [][]byte{ + { + 0x01, 0x9e, 0x61, 0x74, 0x43, 0xff, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x9c, + }, + }, + 200 * time.Millisecond, + 200 * time.Millisecond, }, }, - 0, - -400 * time.Millisecond, }, { - [][]byte{ + "sei-based", + []sequenceSample{ { - 0x41, 0x9a, 0x24, 0x6c, 0x41, 0x4f, 0xfe, 0xd6, - 0x8c, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x6d, 0x40, + [][]byte{ + // SEI (buffering period) + {6, 0, 7, 128, 117, 48, 0, 0, 3, 0, 64, 128}, + // SEI (pic timing) + {6, 1, 4, 0, 0, 8, 16, 128}, + // SPS + { + 103, 100, 0, 42, 172, 44, 172, 7, + 128, 34, 126, 92, 5, 168, 8, 8, + 10, 0, 0, 7, 208, 0, 3, 169, + 129, 192, 0, 0, 76, 75, 0, 0, + 38, 37, 173, 222, 92, 20, + }, + }, + 0, + -16666666 * time.Nanosecond, + }, + { + [][]byte{ + // SEI + {6, 1, 4, 0, 2, 32, 16, 128}, + }, + 66666666 * time.Nanosecond, + 0, + }, + { + [][]byte{ + // SEI + {6, 1, 4, 0, 4, 0, 16, 128}, + }, + 16666666 * time.Nanosecond, + 16666666 * time.Nanosecond, + }, + { + [][]byte{ + // SEI + {6, 1, 4, 0, 6, 0, 16, 128}, + }, + 33333333 * time.Nanosecond, + 33333333 * time.Nanosecond, }, }, - 800 * time.Millisecond, - -200 * time.Millisecond, }, - { - [][]byte{ - { - 0x41, 0x9e, 0x42, 0x78, 0x82, 0x1f, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, - 0x0f, - }, - }, - 400 * time.Millisecond, - 0, - }, - { - [][]byte{ - { - 0x01, 0x9e, 0x61, 0x74, 0x43, 0xff, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x9c, - }, - }, - 200 * time.Millisecond, - 200 * time.Millisecond, - }, - } - - ex := NewDTSExtractor() - - for _, sample := range sequence { - dts, err := ex.Extract(sample.nalus, sample.pts) - require.NoError(t, err) - require.Equal(t, sample.dts, dts) + } { + t.Run(ca.name, func(t *testing.T) { + ex := NewDTSExtractor() + for _, sample := range ca.sequence { + dts, err := ex.Extract(sample.nalus, sample.pts) + require.NoError(t, err) + require.Equal(t, sample.dts, dts) + } + }) } } diff --git a/pkg/h264/sps.go b/pkg/h264/sps.go index b81b522e..d0097fa1 100644 --- a/pkg/h264/sps.go +++ b/pkg/h264/sps.go @@ -841,7 +841,7 @@ func (s SPS) Height() int { return int((2 - f) * (s.PicHeightInMbsMinus1 + 1) * 16) } -// FPS returns the frame per second of the video. +// FPS returns the frames per second of the video. func (s SPS) FPS() float64 { if s.VUI == nil || s.VUI.TimingInfo == nil { return 0