From 1c46c98f8cfa782f4ab5e8b7a9ff823db2be7779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbjo=CC=88rn=20Einarsson?= Date: Mon, 18 Jan 2021 18:35:16 +0100 Subject: [PATCH 1/4] feat: CreateHvcC function fills in hvcC fields from SPS data --- hevc/doc.go | 4 ++ hevc/hevcdecoderconfigurationrecord.go | 52 +++++++++++++++++++++++--- hevc/sps.go | 7 +--- mp4/hvcc.go | 12 ++++++ 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 hevc/doc.go 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..c0723ae4 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 @@ -39,9 +39,13 @@ type NaluArray struct { Nalus [][]byte } -func (n *NaluArray) NewNaluArray(complete byte, naluType NaluType, nalus [][]byte) *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, } } @@ -54,9 +58,41 @@ 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, spsNalus)) + 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 @@ -70,7 +106,7 @@ func DecodeHEVCDecConfRec(r io.Reader) (HEVCDecConfRec, error) { } 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 +157,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) 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..4629d955 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) From 479783e6ec3660012cb43cb0e859f13aa31bd185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbjo=CC=88rn=20Einarsson?= Date: Tue, 19 Jan 2021 11:11:18 +0100 Subject: [PATCH 2/4] fix: hevcdecoderconfigurationrecord including test --- hevc/hevcdecoderconfigurationrecord.go | 9 ++++---- mp4/hvcc_test.go | 32 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 mp4/hvcc_test.go diff --git a/hevc/hevcdecoderconfigurationrecord.go b/hevc/hevcdecoderconfigurationrecord.go index c0723ae4..9ddf1008 100644 --- a/hevc/hevcdecoderconfigurationrecord.go +++ b/hevc/hevcdecoderconfigurationrecord.go @@ -67,7 +67,7 @@ func CreateHEVCDecConfRec(vpsNalus, spsNalus, ppsNalus [][]byte, vpsComplete, sp 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, spsNalus)) + naluArrays = append(naluArrays, *NewNaluArray(ppsComplete, NALU_PPS, ppsNalus)) ptf := sps.ProfileTierLevel return HEVCDecConfRec{ ConfigurationVersion: 1, @@ -99,10 +99,10 @@ func DecodeHEVCDecConfRec(r io.Reader) (HEVCDecConfRec, error) { } 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 @@ -177,6 +177,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/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) +} From e2995b5981b36296e1b90e312b0534e0ca329098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbjo=CC=88rn=20Einarsson?= Date: Tue, 19 Jan 2021 11:10:02 +0100 Subject: [PATCH 3/4] fix: typo --- bits/slicereader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 9ad9b4f7dae8d2dcc8976bbf39c3f6e9cecac491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbjo=CC=88rn=20Einarsson?= Date: Tue, 19 Jan 2021 11:21:59 +0100 Subject: [PATCH 4/4] doc: Add func comments for HEVC --- hevc/hevcdecoderconfigurationrecord.go | 4 ++++ mp4/hvcc.go | 1 + 2 files changed, 5 insertions(+) diff --git a/hevc/hevcdecoderconfigurationrecord.go b/hevc/hevcdecoderconfigurationrecord.go index 9ddf1008..c25c912f 100644 --- a/hevc/hevcdecoderconfigurationrecord.go +++ b/hevc/hevcdecoderconfigurationrecord.go @@ -34,11 +34,13 @@ type HEVCDecConfRec struct { NaluArrays []NaluArray } +// NaluArray - HEVC NALU array including complete bit and type type NaluArray struct { completeAndType byte Nalus [][]byte } +// NewNaluArray - create an HEVC NaluArray func NewNaluArray(complete bool, naluType NaluType, nalus [][]byte) *NaluArray { var completeBit byte if complete { @@ -50,10 +52,12 @@ func NewNaluArray(complete bool, naluType NaluType, nalus [][]byte) *NaluArray { } } +// 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 } diff --git a/mp4/hvcc.go b/mp4/hvcc.go index 4629d955..84cf8566 100644 --- a/mp4/hvcc.go +++ b/mp4/hvcc.go @@ -53,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