diff --git a/bits/slicereader.go b/bits/slicereader.go index 4bd9abe7..7167ea65 100644 --- a/bits/slicereader.go +++ b/bits/slicereader.go @@ -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. diff --git a/hevc/doc.go b/hevc/doc.go new file mode 100644 index 00000000..9327ab19 --- /dev/null +++ b/hevc/doc.go @@ -0,0 +1,4 @@ +/* +Package hevc - parsing of HEVC(H.265) NAL unit headers, slice headers, VPS, SPS, and PPS. +*/ +package hevc diff --git a/hevc/hevcdecoderconfigurationrecord.go b/hevc/hevcdecoderconfigurationrecord.go index ea8ef92f..c25c912f 100644 --- a/hevc/hevcdecoderconfigurationrecord.go +++ b/hevc/hevcdecoderconfigurationrecord.go @@ -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 @@ -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()) @@ -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) @@ -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) } } diff --git a/hevc/sps.go b/hevc/sps.go index 9ffe1bd3..260467c5 100644 --- a/hevc/sps.go +++ b/hevc/sps.go @@ -50,6 +50,7 @@ type ProfileTierLevel struct { GeneralTierFlag bool GeneralProfileIDC byte GeneralProfileCompatibilityFlags uint32 + GeneralConstraintIndicatorFlags uint64 // 48 bits GeneralProgressiveSourceFlag bool GeneralInterlacedSourceFlag bool GeneralNonPackedConstraintFlag bool @@ -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 diff --git a/mp4/hvcc.go b/mp4/hvcc.go index 0aeae850..84cf8566 100644 --- a/mp4/hvcc.go +++ b/mp4/hvcc.go @@ -2,6 +2,7 @@ package mp4 import ( "encoding/hex" + "fmt" "io" "github.com/edgeware/mp4ff/hevc" @@ -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) @@ -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 diff --git a/mp4/hvcc_test.go b/mp4/hvcc_test.go new file mode 100644 index 00000000..787d0013 --- /dev/null +++ b/mp4/hvcc_test.go @@ -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) +}