Skip to content

Commit

Permalink
Merge pull request #59 from edgeware/hevc-fix
Browse files Browse the repository at this point in the history
Fix HEVCDecoderConfigurationRecord
  • Loading branch information
tobbee committed Jan 19, 2021
2 parents 3316b38 + 9ad9b4f commit 599ee1f
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 15 deletions.
2 changes: 1 addition & 1 deletion bits/slicereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"fmt"
)

var SliceReadError = fmt.Errorf("Read too far in SliceReaderß")
var SliceReadError = fmt.Errorf("Read too far in SliceReader")

// SliceReader - read integers and other data from a slice.
// Accumulates error, and the first error can be retrived.
Expand Down
4 changes: 4 additions & 0 deletions hevc/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
Package hevc - parsing of HEVC(H.265) NAL unit headers, slice headers, VPS, SPS, and PPS.
*/
package hevc
63 changes: 54 additions & 9 deletions hevc/hevcdecoderconfigurationrecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var ErrLengthSize = errors.New("Can only handle 4byte NALU length size")
type HEVCDecConfRec struct {
ConfigurationVersion byte
GeneralProfileSpace byte
GeneralTierFlag byte
GeneralTierFlag bool
GeneralProfileIDC byte
GeneralProfileCompatibilityFlags uint32
GeneralConstraintIndicatorFlags uint64
Expand All @@ -34,43 +34,83 @@ type HEVCDecConfRec struct {
NaluArrays []NaluArray
}

// NaluArray - HEVC NALU array including complete bit and type
type NaluArray struct {
completeAndType byte
Nalus [][]byte
}

func (n *NaluArray) NewNaluArray(complete byte, naluType NaluType, nalus [][]byte) *NaluArray {
// NewNaluArray - create an HEVC NaluArray
func NewNaluArray(complete bool, naluType NaluType, nalus [][]byte) *NaluArray {
var completeBit byte
if complete {
completeBit = 0x80
}
return &NaluArray{
completeAndType: complete<<7 | byte(naluType),
completeAndType: completeBit | byte(naluType),
Nalus: nalus,
}
}

// NaluType - return NaluType for NaluArray
func (n *NaluArray) NaluType() NaluType {
return NaluType(n.completeAndType & 0x3f)
}

// Complete - return 0x1 if complete
func (n *NaluArray) Complete() byte {
return n.completeAndType >> 7
}

// CreateHEVCDecConfRec - extract information from vps, sps, pps and fill HEVCDecConfRec with that
func CreateHEVCDecConfRec(vpsNalus, spsNalus, ppsNalus [][]byte, vpsComplete, spsComplete, ppsComplete bool) (HEVCDecConfRec, error) {
sps, err := ParseSPSNALUnit(spsNalus[0])
if err != nil {
return HEVCDecConfRec{}, err
}
var naluArrays []NaluArray
naluArrays = append(naluArrays, *NewNaluArray(vpsComplete, NALU_VPS, vpsNalus))
naluArrays = append(naluArrays, *NewNaluArray(spsComplete, NALU_SPS, spsNalus))
naluArrays = append(naluArrays, *NewNaluArray(ppsComplete, NALU_PPS, ppsNalus))
ptf := sps.ProfileTierLevel
return HEVCDecConfRec{
ConfigurationVersion: 1,
GeneralProfileSpace: ptf.GeneralProfileSpace,
GeneralTierFlag: ptf.GeneralTierFlag,
GeneralProfileIDC: ptf.GeneralProfileIDC,
GeneralProfileCompatibilityFlags: ptf.GeneralProfileCompatibilityFlags,
GeneralConstraintIndicatorFlags: ptf.GeneralConstraintIndicatorFlags,
GeneralLevelIDC: ptf.GeneralLevelIDC,
MinSpatialSegmentationIDC: 0, // Set as default value
ParallellismType: 0, // Set as default value
ChromaFormatIDC: sps.ChromaFormatIDC,
BitDepthLumaMinus8: sps.BitDepthLumaMinus8,
BitDepthChromaMinus8: sps.BitDepthChromaMinus8,
AvgFrameRate: 0, // Set as default value
ConstantFrameRate: 0, // Set as default value
NumTemporalLayers: 0, // Set as default value
TemporalIDNested: 0, // Set as default value
LengthSizeMinusOne: 3, // only support 4-byte length
NaluArrays: naluArrays, // VPS, SPS, PPS nalus with complete flag
}, nil
}

// DecodeHEVCDecConfRec - decode an HEVCDecConfRec
func DecodeHEVCDecConfRec(r io.Reader) (HEVCDecConfRec, error) {

data, err := ioutil.ReadAll(r)
if err != nil {
return HEVCDecConfRec{}, err
}
hdcr := HEVCDecConfRec{}
sr := bits.NewSliceReader(data)
configurationVersion := sr.ReadUint8()
if configurationVersion != 1 {
hdcr.ConfigurationVersion = sr.ReadUint8()
if hdcr.ConfigurationVersion != 1 {
return HEVCDecConfRec{}, fmt.Errorf("HEVC decoder configuration record version %d unknown",
configurationVersion)
hdcr.ConfigurationVersion)
}
aByte := sr.ReadUint8()
hdcr.GeneralProfileSpace = (aByte >> 6) & 0x3
hdcr.GeneralTierFlag = (aByte >> 5) & 0x1
hdcr.GeneralTierFlag = (aByte>>5)&0x1 == 0x1
hdcr.GeneralProfileIDC = aByte & 0x1f
hdcr.GeneralProfileCompatibilityFlags = sr.ReadUint32()
hdcr.GeneralConstraintIndicatorFlags = (uint64(sr.ReadUint32()) << 16) | uint64(sr.ReadUint16())
Expand Down Expand Up @@ -121,7 +161,11 @@ func (h *HEVCDecConfRec) Size() uint64 {
func (h *HEVCDecConfRec) Encode(w io.Writer) error {
aw := bits.NewAccErrByteWriter(w)
aw.WriteUint8(h.ConfigurationVersion)
aw.WriteUint8(h.GeneralProfileSpace<<6 | h.GeneralTierFlag<<5 | h.GeneralProfileIDC)
var generalTierFlagBit byte
if h.GeneralTierFlag {
generalTierFlagBit = 1 << 5
}
aw.WriteUint8(h.GeneralProfileSpace<<6 | generalTierFlagBit | h.GeneralProfileIDC)
aw.WriteUint32(h.GeneralProfileCompatibilityFlags)
aw.WriteUint48(h.GeneralConstraintIndicatorFlags)
aw.WriteUint8(h.GeneralLevelIDC)
Expand All @@ -137,6 +181,7 @@ func (h *HEVCDecConfRec) Encode(w io.Writer) error {
aw.WriteUint8(array.completeAndType)
aw.WriteUint16(uint16(len(array.Nalus)))
for _, nalu := range array.Nalus {
aw.WriteUint16(uint16(len(nalu)))
aw.WriteSlice(nalu)
}
}
Expand Down
7 changes: 2 additions & 5 deletions hevc/sps.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type ProfileTierLevel struct {
GeneralTierFlag bool
GeneralProfileIDC byte
GeneralProfileCompatibilityFlags uint32
GeneralConstraintIndicatorFlags uint64 // 48 bits
GeneralProgressiveSourceFlag bool
GeneralInterlacedSourceFlag bool
GeneralNonPackedConstraintFlag bool
Expand Down Expand Up @@ -94,11 +95,7 @@ func ParseSPSNALUnit(data []byte) (*SPS, error) {
sps.ProfileTierLevel.GeneralTierFlag = r.ReadFlag()
sps.ProfileTierLevel.GeneralProfileIDC = byte(r.Read(5))
sps.ProfileTierLevel.GeneralProfileCompatibilityFlags = uint32(r.Read(32))
sps.ProfileTierLevel.GeneralProgressiveSourceFlag = r.ReadFlag()
sps.ProfileTierLevel.GeneralInterlacedSourceFlag = r.ReadFlag()
sps.ProfileTierLevel.GeneralNonPackedConstraintFlag = r.ReadFlag()
sps.ProfileTierLevel.GeneralFrameOnlyConstraintFlag = r.ReadFlag()
_ = r.Read(44) // constraint flags + general_inbld_flag/reserved bit
sps.ProfileTierLevel.GeneralConstraintIndicatorFlags = uint64(r.Read(48))
sps.ProfileTierLevel.GeneralLevelIDC = byte(r.Read(8))
if sps.MaxSubLayersMinus1 != 0 {
return sps, nil // Cannot parse any further
Expand Down
13 changes: 13 additions & 0 deletions mp4/hvcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mp4

import (
"encoding/hex"
"fmt"
"io"

"github.com/edgeware/mp4ff/hevc"
Expand All @@ -13,6 +14,17 @@ type HvcCBox struct {
hevc.HEVCDecConfRec
}

// CreateHvcC- Create an hvcC box based on VPS, SPS and PPS and signal completeness
func CreateHvcC(vpsNalus, spsNalus, ppsNalus [][]byte, vpsComplete, spsComplete, ppsComplete bool) (*HvcCBox, error) {
hevcDecConfRec, err := hevc.CreateHEVCDecConfRec(vpsNalus, spsNalus, ppsNalus,
vpsComplete, spsComplete, ppsComplete)
if err != nil {
return nil, fmt.Errorf("CreateHEVCDecConfRec: %w", err)
}

return &HvcCBox{hevcDecConfRec}, nil
}

// DecodeHvcC - box-specific decode
func DecodeHvcC(hdr *boxHeader, startPos uint64, r io.Reader) (Box, error) {
hevcDecConfRec, err := hevc.DecodeHEVCDecConfRec(r)
Expand Down Expand Up @@ -41,6 +53,7 @@ func (b *HvcCBox) Encode(w io.Writer) error {
return b.HEVCDecConfRec.Encode(w)
}

// Info - box-specific Info
func (b *HvcCBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string) error {
bd := newInfoDumper(w, indent, b, -1)
hdcr := b.HEVCDecConfRec
Expand Down
32 changes: 32 additions & 0 deletions mp4/hvcc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package mp4

import (
"encoding/hex"
"testing"
)

const (
vpsHex = "40010c01ffff022000000300b0000003000003007b18b024"
spsHex = "420101022000000300b0000003000003007ba0078200887db6718b92448053888892cf24a69272c9124922dc91aa48fca223ff000100016a02020201"
ppsHex = "4401c0252f053240"
)

func TestHvcC(t *testing.T) {
vpsNalu, err := hex.DecodeString(vpsHex)
if err != nil {
t.Error(err)
}
spsNalu, err := hex.DecodeString(spsHex)
if err != nil {
t.Error(err)
}
ppsNalu, err := hex.DecodeString(ppsHex)
if err != nil {
t.Error(err)
}
hvcC, err := CreateHvcC([][]byte{vpsNalu}, [][]byte{spsNalu}, [][]byte{ppsNalu}, true, true, true)
if err != nil {
t.Error(err)
}
boxDiffAfterEncodeAndDecode(t, hvcC)
}

0 comments on commit 599ee1f

Please sign in to comment.