Skip to content

Commit

Permalink
Merge pull request #321 from Eyevinn/hevc-sei1
Browse files Browse the repository at this point in the history
HEVC PicTiming SEI message support
  • Loading branch information
tobbee committed Jan 23, 2024
2 parents 577d234 + eb1143f commit a1ab453
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 38 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- W3C Common PSSH Box UUID
- HEVC PicTiming SEI message parsing
- JSON marshaling of AVC PicTiming SEI message

## [0.41.0] - 2024-01-12

Expand Down
15 changes: 8 additions & 7 deletions hevc/pps.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import (
"bytes"
"errors"
"fmt"
"github.com/Eyevinn/mp4ff/bits"
"io"

"github.com/Eyevinn/mp4ff/bits"
)

// HEVC PPS errors
var (
ErrNotPPS = errors.New("not an PPS NAL unit")
)

// This parser based on Rec. ITU-T H.265 v5 (02/2018) and ISO/IEC 23008-2 Ed. 5
Expand Down Expand Up @@ -169,11 +175,6 @@ type DeltaDlt struct {
DeltaValDiffMinusMin []uint
}

// HEVC PPS errors
var (
ErrNotPPS = errors.New("Not an PPS NAL unit")
)

// ParsePPSNALUnit - Parse AVC PPS NAL unit starting with NAL header
func ParsePPSNALUnit(data []byte, spsMap map[uint32]*SPS) (*PPS, error) {
var err error
Expand Down Expand Up @@ -317,7 +318,7 @@ func ParsePPSNALUnit(data []byte, spsMap map[uint32]*SPS) (*PPS, error) {
}
_ = r.Read(1)
if r.AccError() != io.EOF {
return nil, fmt.Errorf("Not at end after reading rbsp_trailing_bits")
return nil, fmt.Errorf("not at end after reading rbsp_trailing_bits")
}
return pps, nil
}
Expand Down
59 changes: 32 additions & 27 deletions hevc/sps.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func flagFrom(flags uint64, bitNr uint) bool {
return (flags & (1 << bitNr)) != 0
}

// parseProfileTierLevel follows ISO/IEC 23008-2 Section 7.3.3
func parseProfileTierLevel(r *bits.AccErrEBSPReader, profilePresentFlag bool, maxNumSubLayersMinus1 byte) ProfileTierLevel {
ptl := ProfileTierLevel{}
if profilePresentFlag {
Expand All @@ -114,33 +115,32 @@ func parseProfileTierLevel(r *bits.AccErrEBSPReader, profilePresentFlag bool, ma
ptl.GeneralInterlacedSourceFlag = flagFrom(ptl.GeneralConstraintIndicatorFlags, 46)
ptl.GeneralNonPackedConstraintFlag = flagFrom(ptl.GeneralConstraintIndicatorFlags, 45)
ptl.GeneralFrameOnlyConstraintFlag = flagFrom(ptl.GeneralConstraintIndicatorFlags, 44)

ptl.GeneralLevelIDC = byte(r.Read(8))
if maxNumSubLayersMinus1 > 0 {
ptl.SubLayers = make([]SubLayer, maxNumSubLayersMinus1)
for i := byte(0); i < maxNumSubLayersMinus1; i++ {
ptl.SubLayers[i].ProfilePresentFlag = r.ReadFlag()
ptl.SubLayers[i].LevelPresentFlag = r.ReadFlag()
}
ptl.GeneralLevelIDC = byte(r.Read(8))
if maxNumSubLayersMinus1 > 0 {
ptl.SubLayers = make([]SubLayer, maxNumSubLayersMinus1)
for i := byte(0); i < maxNumSubLayersMinus1; i++ {
ptl.SubLayers[i].ProfilePresentFlag = r.ReadFlag()
ptl.SubLayers[i].LevelPresentFlag = r.ReadFlag()
}
if maxNumSubLayersMinus1 < 8 {
nrReservedZeroBits := 2 * (8 - int(maxNumSubLayersMinus1))
_ = r.Read(nrReservedZeroBits)
}
for i := byte(0); i < maxNumSubLayersMinus1; i++ {
if ptl.SubLayers[i].ProfilePresentFlag {
ptl.SubLayers[i].ProfileSpace = byte(r.Read(2))
ptl.SubLayers[i].TierFlag = r.ReadFlag()
ptl.SubLayers[i].ProfileIDC = byte(r.Read(5))
ptl.SubLayers[i].ProfileCompatibilityFlags = uint32(r.Read(32))
ptl.SubLayers[i].ConstraintFlags = uint64(r.Read(48)) // Including 4 flags from ProgressiveSourceFlag and forward
ptl.SubLayers[i].ProgressiveSourceFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 47)
ptl.SubLayers[i].InterlacedSourceFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 46)
ptl.SubLayers[i].NonPackedConstraintFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 45)
ptl.SubLayers[i].FrameOnlyConstraintFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 44)
}
if maxNumSubLayersMinus1 < 8 {
nrReservedZeroBits := 2 * (8 - int(maxNumSubLayersMinus1))
_ = r.Read(nrReservedZeroBits)
}
for i := byte(0); i < maxNumSubLayersMinus1; i++ {
if ptl.SubLayers[i].ProfilePresentFlag {
ptl.SubLayers[i].ProfileSpace = byte(r.Read(2))
ptl.SubLayers[i].TierFlag = r.ReadFlag()
ptl.SubLayers[i].ProfileIDC = byte(r.Read(5))
ptl.SubLayers[i].ProfileCompatibilityFlags = uint32(r.Read(32))
ptl.SubLayers[i].ConstraintFlags = uint64(r.Read(48)) // Including 4 flags from ProgressiveSourceFlag and forward
ptl.SubLayers[i].ProgressiveSourceFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 47)
ptl.SubLayers[i].InterlacedSourceFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 46)
ptl.SubLayers[i].NonPackedConstraintFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 45)
ptl.SubLayers[i].FrameOnlyConstraintFlag = flagFrom(ptl.SubLayers[i].ConstraintFlags, 44)
}
if ptl.SubLayers[i].LevelPresentFlag {
ptl.SubLayers[i].LayerIDC = byte(r.Read(8))
}
if ptl.SubLayers[i].LevelPresentFlag {
ptl.SubLayers[i].LayerIDC = byte(r.Read(8))
}
}
}
Expand Down Expand Up @@ -214,6 +214,11 @@ type HrdParameters struct {
SubLayerHrd []SubLayerHrd
}

// CpbDpbDelaysPresentFlag is defined in ISO/IEC 23008-2 Section E.3.2.
func (h *HrdParameters) CpbDpbDelaysPresentFlag() bool {
return h.NalHrdParametersPresentFlag || h.VclHrdParametersPresentFlag
}

type SubLayerHrd struct {
FixedPicRateGeneralFlag bool
FixedPicRateWithinCvsFlag bool
Expand Down Expand Up @@ -469,7 +474,7 @@ func ParseSPSNALUnit(data []byte) (*SPS, error) {
}
_ = r.Read(1)
if r.AccError() != io.EOF {
return nil, fmt.Errorf("Not at end after reading rbsp_trailing_bits")
return nil, fmt.Errorf("not at end after reading rbsp_trailing_bits")
}

return sps, nil
Expand Down
19 changes: 15 additions & 4 deletions sei/sei1.go → sei/sei1_avc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sei

import (
"bytes"
"encoding/json"
"fmt"

"github.com/Eyevinn/mp4ff/bits"
Expand All @@ -11,11 +12,11 @@ import (
// The corresponding SEI 1 for HEVC is very different.
type PicTimingAvcSEI struct {
// CbpDbpDelay is optional and triggered by VUI HRD data
CbpDbpDelay *CbpDbpDelay
CbpDbpDelay *CbpDbpDelay `json:"-"`
// TimeOffsetLength is 5 bits and comes from SPS HRD if present
TimeOffsetLength uint8
PictStruct uint8
Clocks []ClockTSAvc
TimeOffsetLength uint8 `json:"-"`
PictStruct uint8 `json:"pict_struct"`
Clocks []ClockTSAvc `json:"clocks"`
}

// CbpDbpDelay carries the optional data on CpbDpbDelay.
Expand Down Expand Up @@ -150,6 +151,16 @@ func (c ClockTSAvc) String() string {
return fmt.Sprintf("%02d:%02d:%02d:%02d offset=%d", c.Hours, c.Minutes, c.Seconds, c.NFrames, c.TimeOffsetValue)
}

func (c *ClockTSAvc) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Time string `json:"time"`
Offset int `json:"offset"`
}{
Time: fmt.Sprintf("%02d:%02d:%02d:%02d", c.Hours, c.Minutes, c.Seconds, c.NFrames),
Offset: c.TimeOffsetValue,
})
}

// CreatePTClockTS creates a clock timestamp.
func CreateClockTSAvc(timeOffsetLen byte) ClockTSAvc {
return ClockTSAvc{
Expand Down
103 changes: 103 additions & 0 deletions sei/sei1_hevc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package sei

import (
"bytes"
"fmt"

"github.com/Eyevinn/mp4ff/bits"
)

// PicTimingHevcSEI carries the data of an SEI 1 PicTiming message for HEVC.
// The corresponding SEI 1 for AVC is very different. Time code is in SEI 136 for HEVC.
// Defined in ISO/IEC 23008-2 Ed 5. Section D.2.3 (page 372) and D.3.2.3 (page 405)
type PicTimingHevcSEI struct {
ExternalParams HEVCPicTimingParams `json:"-"`
FrameFieldInfo *HEVCFrameFieldInfo `json:"FrameFieldInfo,omitempty"`
AuCpbRemovalDelayMinus1 uint32 `json:"AuCpbRemovalDelayMinus1,omitempty"`
PicDpbOutputDelay uint32 `json:"PicDpbOutputDelay,omitempty"`
PicDpbOutputDuDelay uint32 `json:"PicDpbOutputDuDelay,omitempty"`
NumDecodingUnitsMinus1 uint32 `json:"NumDecodingUnitsMinus1,omitempty"`
DuCommonCpbRemovalDelayFlag bool `json:"DuCommonCpbRemovalDelayFlag,omitempty"`
DuCommonCpbRemovalDelayIncrementMinus1 uint32 `json:"DuCommonCpbRemovalDelayIncrementMinus1,omitempty"`
NumNalusInDuMinus1 []uint32 `json:"NumNalusInDuMinus1,omitempty"`
DuCpbRemovalDelayIncrementMinus1 []uint32 `json:"DuCpbRemovalDelayIncrementMinus1,omitempty"`
payload []byte `json:"-"`
}

type HEVCPicTimingParams struct {
FrameFieldInfoPresentFlag bool
CpbDpbDelaysPresentFlag bool
SubPicHrdParamsPresentFlag bool
SubPicCpbParamsInPicTimingSeiFlag bool
AuCbpRemovalDelayLengthMinus1 uint8
DpbOutputDelayLengthMinus1 uint8
DpbOutputDelayDuLengthMinus1 uint8
DuCpbRemovalDelayIncrementLengthMinus1 uint8
}

type HEVCFrameFieldInfo struct {
PicStruct uint8 // 4 bits
SourceScanType uint8 // 2 bits
DuplicateFlag bool `json:"DuplicateFlag,omitempty"` // 1bit
}

func DecodePicTimingHevcSEI(sd *SEIData, exPar HEVCPicTimingParams) (SEIMessage, error) {
buf := bytes.NewBuffer(sd.Payload())
br := bits.NewAccErrEBSPReader(buf)
pt := PicTimingHevcSEI{
payload: sd.Payload(),
}
if exPar.FrameFieldInfoPresentFlag {
frameFieldInfo := &HEVCFrameFieldInfo{}
frameFieldInfo.PicStruct = uint8(br.Read(4))
frameFieldInfo.SourceScanType = uint8(br.Read(2))
frameFieldInfo.DuplicateFlag = br.ReadFlag()
pt.FrameFieldInfo = frameFieldInfo
}
if exPar.CpbDpbDelaysPresentFlag {
pt.AuCpbRemovalDelayMinus1 = uint32(br.Read(int(exPar.AuCbpRemovalDelayLengthMinus1) + 1))
pt.PicDpbOutputDelay = uint32(br.Read(int(exPar.DpbOutputDelayLengthMinus1) + 1))
if exPar.SubPicHrdParamsPresentFlag {
pt.PicDpbOutputDuDelay = uint32(br.Read(int(exPar.DpbOutputDelayDuLengthMinus1) + 1))
if exPar.SubPicCpbParamsInPicTimingSeiFlag {
pt.NumDecodingUnitsMinus1 = uint32(br.ReadExpGolomb())
pt.DuCommonCpbRemovalDelayFlag = br.ReadFlag()
if pt.DuCommonCpbRemovalDelayFlag {
pt.DuCommonCpbRemovalDelayIncrementMinus1 = uint32(br.Read(int(exPar.DuCpbRemovalDelayIncrementLengthMinus1) + 1))
}
for i := uint32(0); i <= pt.NumDecodingUnitsMinus1; i++ {
pt.NumNalusInDuMinus1[i] = uint32(br.ReadExpGolomb())
if !pt.DuCommonCpbRemovalDelayFlag && i < pt.NumDecodingUnitsMinus1 {
pt.DuCpbRemovalDelayIncrementMinus1[i] = uint32(br.Read(int(exPar.DuCpbRemovalDelayIncrementLengthMinus1) + 1))
}
}
}
}
}
return &pt, br.AccError()
}

// Type returns the SEI payload type.
func (s *PicTimingHevcSEI) Type() uint {
return SEIPicTimingType
}

// Payload returns the SEI raw rbsp payload.
func (s *PicTimingHevcSEI) Payload() []byte {
return s.payload
}

// String returns string representation of PicTiming SEI1.
func (s *PicTimingHevcSEI) String() string {
msgType := SEIType(s.Type())
msg := fmt.Sprintf("%s: ", msgType)
if s.FrameFieldInfo != nil {
msg += fmt.Sprintf("FrameFieldInfo: %+v, ", s.FrameFieldInfo)
}
return msg
}

// Size is size in bytes of raw SEI message rbsp payload.
func (s *PicTimingHevcSEI) Size() uint {
return uint(len(s.payload))
}
67 changes: 67 additions & 0 deletions sei/sei1_hevc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package sei

import (
"bytes"
"encoding/hex"
"testing"

"github.com/go-test/deep"
)

func TestHEVCSETI1PicTiming(t *testing.T) {

cases := []struct {
name string
naluPayloadHex string
extParams HEVCPicTimingParams
expected PicTimingHevcSEI
expNonFatalErr error
}{
{
name: "HEVC_SETI1PicTiming",
naluPayloadHex: "01071000001a0000030180",
extParams: HEVCPicTimingParams{
FrameFieldInfoPresentFlag: true,
CpbDpbDelaysPresentFlag: true,
SubPicHrdParamsPresentFlag: false,
SubPicCpbParamsInPicTimingSeiFlag: false,
AuCbpRemovalDelayLengthMinus1: 23,
DpbOutputDelayLengthMinus1: 0,
DpbOutputDelayDuLengthMinus1: 23,
DuCpbRemovalDelayIncrementLengthMinus1: 0,
},
expected: PicTimingHevcSEI{
FrameFieldInfo: &HEVCFrameFieldInfo{
PicStruct: 1,
SourceScanType: 0,
DuplicateFlag: false,
},
},
expNonFatalErr: nil,
},
}

for _, tc := range cases {
seiNaluPayload, _ := hex.DecodeString(tc.naluPayloadHex)
r := bytes.NewReader(seiNaluPayload)
seis, err := ExtractSEIData(r)
if err != nil && err != tc.expNonFatalErr {
t.Error(err)
}
if len(seis) != 1 {
t.Errorf("%s: Not %d but %d sei messages found", tc.name, 1, len(seis))
}
seiMessage, err := DecodePicTimingHevcSEI(&seis[0], tc.extParams)
if err != nil {
t.Error(err)
}
if seiMessage.Type() != SEIPicTimingType {
t.Errorf("%s: got SEI type %d instead of %d", tc.name, seiMessage.Type(), SEIPicTimingType)
}
seiPT := seiMessage.(*PicTimingHevcSEI)
diff := deep.Equal(seiPT.FrameFieldInfo, tc.expected.FrameFieldInfo)
if diff != nil {
t.Errorf("%s: %v %s", tc.name, diff, "frame field info mismatch")
}
}
}

0 comments on commit a1ab453

Please sign in to comment.