Skip to content

Commit

Permalink
Merge pull request #286 from Eyevinn/fix-avc-slice-header
Browse files Browse the repository at this point in the history
Fix avc ChromeArrayType
  • Loading branch information
tobbee authored Oct 14, 2023
2 parents 4f60fdc + 9b2cf73 commit e10c8a0
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 25 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- nothing yet
### Fixed

- SPS.ChromaArrayType method

### Added

- AccErrEBSPReader.NrBitsRead method

## [0.39.0] - 2023-10-13

Expand Down
24 changes: 15 additions & 9 deletions avc/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type SliceHeader struct {
ModificationOfPicNumsIDC uint32
AbsDiffPicNumMinus1 uint32
LongTermPicNum uint32
AbsDiffViewIdxMinus1 uint32
LumaLog2WeightDenom uint32
ChromaLog2WeightDenom uint32
DifferenceOfPicNumsMinus1 uint32
Expand All @@ -129,6 +130,7 @@ type SliceHeader struct {
AdaptiveRefPicMarkingModeFlag bool
}

// ParseSliceHeader parses AVC slice header following the syntax in ISO/IEC 14496-10 section 7.3.3
func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PPS) (*SliceHeader, error) {
sh := SliceHeader{}
buf := bytes.NewBuffer(nalu)
Expand Down Expand Up @@ -204,7 +206,7 @@ func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PP
}
}

// ref_pic_list_modification (nal unit type != 20)
// ref_pic_list_modification (nal unit type != 20 or 21) Section G.3.3.3.1.1
if sliceType != SLICE_I && sliceType != SLICE_SI {
sh.RefPicListModificationL0Flag = r.ReadFlag()
if sh.RefPicListModificationL0Flag {
Expand All @@ -216,6 +218,8 @@ func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PP
sh.AbsDiffPicNumMinus1 = uint32(r.ReadExpGolomb())
case 2:
sh.LongTermPicNum = uint32(r.ReadExpGolomb())
case 4, 5:
sh.AbsDiffViewIdxMinus1 = uint32(r.ReadExpGolomb())
case 3:
break refPicListL0Loop
}
Expand All @@ -236,6 +240,8 @@ func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PP
sh.AbsDiffPicNumMinus1 = uint32(r.ReadExpGolomb())
case 2:
sh.LongTermPicNum = uint32(r.ReadExpGolomb())
case 4, 5:
sh.AbsDiffViewIdxMinus1 = uint32(r.ReadExpGolomb())
case 3:
break refPicListL1Loop
}
Expand All @@ -249,9 +255,9 @@ func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PP

if pps.WeightedPredFlag && (sliceType == SLICE_P || sliceType == SLICE_SP) ||
(pps.WeightedBipredIDC == 1 && sliceType == SLICE_B) {
// pred_weight_table
// pred_weight_table, section 7.3.3.2
sh.LumaLog2WeightDenom = uint32(r.ReadExpGolomb())
if sps.ChromaArrayType() != 0 {
if sps.ChromaArrayType() != 0 { // chroma_idc != 0 in Bento4
sh.ChromaLog2WeightDenom = uint32(r.ReadExpGolomb())
}

Expand Down Expand Up @@ -281,9 +287,9 @@ func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PP
_ = r.ReadExpGolomb() // luma_weight_l1[i] = SignedGolomb()
_ = r.ReadExpGolomb() // luma_offset_l1[i] = SignedGolomb()
}
if sps.ChromaFormatIDC != 0 {
chromaWeightL0Flag := r.ReadFlag()
if chromaWeightL0Flag {
if sps.ChromaArrayType() != 0 {
chromaWeightL1Flag := r.ReadFlag()
if chromaWeightL1Flag {
// Just parse, don't store this
for j := 0; j < 2; j++ {
_ = r.ReadSignedGolomb() // chroma_weight_l1[i][j] = SignedGolomb()
Expand Down Expand Up @@ -350,12 +356,12 @@ func ParseSliceHeader(nalu []byte, spsMap map[uint32]*SPS, ppsMap map[uint32]*PP
pps.SliceGroupMapType >= 3 &&
pps.SliceGroupMapType <= 5 {
picSizeInMapUnits := pps.PicSizeInMapUnitsMinus1 + 1
sliceGroupChangeRage := pps.SliceGroupChangeRateMinus1 + 1
nrBits := int(math.Ceil(math.Log2(float64(picSizeInMapUnits/sliceGroupChangeRage + 1))))
sliceGroupChangeRate := pps.SliceGroupChangeRateMinus1 + 1
nrBits := int(math.Ceil(math.Log2(float64(picSizeInMapUnits/sliceGroupChangeRate + 1))))
sh.SliceGroupChangeCycle = uint32(r.Read(nrBits))
}

// compute the size in bytes. The last byte may not be fully read
// compute the size in bytes. The last byte may not be fully parsed
sh.Size = uint32(r.NrBytesRead())
return &sh, nil
}
29 changes: 29 additions & 0 deletions avc/slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,32 @@ func TestParseSliceHeader_TwoFrames(t *testing.T) {
t.Errorf("Got NON_IDR Slice Header: %+v\n Diff is: %v", *gotNonIdrHdr, diff)
}
}

func TestParseSliceHeaderLength(t *testing.T) {
spsHex := "6764001eacd940a02ff9610000030001000003003c8f162d96"
ppsHex := "68ebecb22c"
naluStartHex := "419a6649e10f2653022fff8700000302c8a32d32"
spsData, _ := hex.DecodeString(spsHex)
sps, err := ParseSPSNALUnit(spsData, true)
if err != nil {
t.Error(err)
}
spsMap := make(map[uint32]*SPS, 1)
spsMap[uint32(sps.ParameterID)] = sps
ppsData, _ := hex.DecodeString(ppsHex)
pps, err := ParsePPSNALUnit(ppsData, spsMap)
if err != nil {
t.Error(err)
}
ppsMap := make(map[uint32]*PPS, 1)
ppsMap[uint32(pps.PicParameterSetID)] = pps
naluStart, _ := hex.DecodeString(naluStartHex)
sh, err := ParseSliceHeader(naluStart, spsMap, ppsMap)
if err != nil {
t.Error(err)
}
wantedSliceHeaderSize := uint32(11)
if sh.Size != wantedSliceHeaderSize {
t.Errorf("got %d want %d", sh.Size, wantedSliceHeaderSize)
}
}
4 changes: 2 additions & 2 deletions avc/sps.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ func (s *SPS) PicStructPresent() bool {
return s.VUI.PicStructPresentFlag
}

// ChromaArrayType as defined in Section 7.4.2.1.1
// ChromaArrayType as defined in Section 7.4.2.1.1 under separate_colour_plane_flag
func (s *SPS) ChromaArrayType() byte {
if s.SeparateColourPlaneFlag {
if !s.SeparateColourPlaneFlag {
return byte(s.ChromaFormatIDC)
}
return 0
Expand Down
9 changes: 9 additions & 0 deletions bits/aeebspreader.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ func (r *AccErrEBSPReader) NrBytesRead() int {
return r.pos + 1 // Starts at -1
}

// NrBitsRead - how many bits read into parser
func (r *AccErrEBSPReader) NrBitsRead() int {
nrBits := r.NrBytesRead() * 8
if r.NrBitsReadInCurrentByte() != 8 {
nrBits += r.NrBitsReadInCurrentByte() - 8
}
return nrBits
}

// NrBitsReadInCurrentByte - how many bits have been read
func (r *AccErrEBSPReader) NrBitsReadInCurrentByte() int {
return 8 - r.n
Expand Down
30 changes: 17 additions & 13 deletions bits/aeebspreader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import (

func TestAccErrEBSPReader(t *testing.T) {
testCases := []struct {
hexData string
readBits int
expectedValue uint
expectedNrBytesRead int
expectedNrBitsRead int
hexData string
readBits int
expectedValue uint
expectedNrBytesRead int
expectedNrBitsRead int
expectedNrBitsReadInCurrentByte int
}{
{"00", 0, 0, 0, 8},
{"0001", 1, 0, 1, 1},
{"0001", 8, 0x00, 1, 8},
{"0010", 12, 0x001, 2, 4},
{"0102", 16, 0x0102, 2, 8},
{"00000304", 24, 0x000004, 4, 8},
{"00", 0, 0, 0, 0, 8},
{"0001", 1, 0, 1, 1, 1},
{"0001", 8, 0x00, 1, 8, 8},
{"0010", 12, 0x001, 2, 12, 4},
{"0102", 16, 0x0102, 2, 16, 8},
{"00000304", 24, 0x000004, 4, 32, 8}, // Note the start-code emulation 0x03
}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
Expand All @@ -41,8 +42,11 @@ func TestAccErrEBSPReader(t *testing.T) {
if r.NrBytesRead() != tc.expectedNrBytesRead {
t.Errorf("expected %d bytes read, got %d", tc.expectedNrBytesRead, r.NrBytesRead())
}
if r.NrBitsReadInCurrentByte() != tc.expectedNrBitsRead {
t.Errorf("expected %d bits read, got %d", tc.expectedNrBitsRead, r.NrBitsReadInCurrentByte())
if r.NrBitsRead() != tc.expectedNrBitsRead {
t.Errorf("expected %d bits read, got %d", tc.expectedNrBitsRead, r.NrBitsRead())
}
if r.NrBitsReadInCurrentByte() != tc.expectedNrBitsReadInCurrentByte {
t.Errorf("expected %d bits read in current byte, got %d", tc.expectedNrBitsReadInCurrentByte, r.NrBitsReadInCurrentByte())
}
})
}
Expand Down

0 comments on commit e10c8a0

Please sign in to comment.