diff --git a/codec/h264/h264dec/cavlc.go b/codec/h264/h264dec/cavlc.go index 9400ff43..e69a32e8 100644 --- a/codec/h264/h264dec/cavlc.go +++ b/codec/h264/h264dec/cavlc.go @@ -26,12 +26,289 @@ LICENSE package h264dec import ( + "encoding/csv" "errors" "fmt" + "strconv" + "strings" "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" ) +// TODO: find where these are defined in the specifications. +const ( + chromaDCLevel = iota + intra16x16DCLevel + intra16x16ACLevel + cbIntra16x16DCLevel + cbIntra16x16ACLevel + crIntra16x16DCLevel + crIntra16x16ACLevel + lumaLevel4x4 + cbLevel4x4 + crLevel4x4 +) + +// Initialize the CAVLC coeff_token mapping table. +func init() { + lines, err := csv.NewReader(strings.NewReader(coeffTokenTable)).ReadAll() + if err != nil { + panic(fmt.Sprintf("could not read lines from coeffTokenTable string, failed with error: %v", err)) + } + + coeffTokenMaps, err = formCoeffTokenMap(lines) + if err != nil { + panic(fmt.Sprintf("could not form coeff_token map, failed with err: %v", err)) + } +} + +// tokenMap maps coeff_token to values of TrailingOnes(coeff_token) and +// TotalCoeff(coeff_token) given as tokenMap[ number of leading zeros in +// coeff_token][ coeff_token val ][ 0 for trailing ones and 1 for totalCoef ] +type tokenMap map[int]map[int][2]int + +// The number of columns in the coeffTokenMap defined below. This is +// representative of the number of defined nC ranges in table 9-5. +const nColumns = 6 + +// coeffTokenMaps holds a representation of table 9-5 from the specifications, and +// is indexed as follows, coeffToken[ nC group ][ number of coeff_token leading +// zeros ][ value of coeff_token ][ 0 for TrailingOnes(coeff_token) and 1 for +// TotalCoef(coeff_token) ]. +var coeffTokenMaps [nColumns]tokenMap + +// formCoeffTokenMap populates the global [nColumns]tokenMap coeffTokenMaps +// representation of table 9-5 in the specifications using the coeffTokenTable +// const string defined in cavlctab.go. +func formCoeffTokenMap(lines [][]string) ([nColumns]tokenMap, error) { + var maps [nColumns]tokenMap + + for i := range maps { + maps[i] = make(tokenMap) + } + + for _, line := range lines { + trailingOnes, err := strconv.Atoi(line[0]) + if err != nil { + return maps, fmt.Errorf("could not convert trailingOnes string to int, failed with error: %v", err) + } + + totalCoeff, err := strconv.Atoi(line[1]) + if err != nil { + return maps, fmt.Errorf("could not convert totalCoeff string to int, failed with error: %v", err) + } + + // For each column in this row, therefore each nC category, load the + // coeff_token leading zeros and value into the map. + for j, v := range line[2:] { + if v[0] == '-' { + continue + } + + // Count the leading zeros. + var nZeros int + for _, c := range v { + if c == '1' { + break + } + + if c == '0' { + nZeros++ + } + } + + // This will be the value of the coeff_token (without leading zeros). + val, err := binToInt(v[nZeros:]) + if err != nil { + return maps, fmt.Errorf("could not get value of remaining binary, failed with error: %v", err) + } + + // Add the TrailingOnes(coeff_token) and TotalCoeff(coeff_token) values + // into the map for the coeff_token leading zeros and value. + if maps[j][nZeros] == nil { + maps[j][nZeros] = make(map[int][2]int) + } + maps[j][nZeros][val] = [2]int{trailingOnes, totalCoeff} + } + } + return maps, nil +} + +// TODO: put this somewhere more appropriate once context is understood. +type block struct { + usingInterMbPredMode bool + mbType int + totalCoef int +} + +// parseTotalCoeffAndTrailingOnes will use logic provided in section 9.2.1 of +// the specifications to obtain a value of nC, parse coeff_token from br and +// then use table 9-5 to find corresponding values of TrailingOnes(coeff_token) +// and TotalCoeff(coeff_token) which are then subsequently returned. +func parseTotalCoeffAndTrailingOnes(br *bits.BitReader, vid *VideoStream, ctx *SliceContext, usingMbPredMode bool, level, maxNumCoef, inBlockIdx int) (totalCoeff, trailingOnes, nC, outBlockIdx int, err error) { + if level == chromaDCLevel { + if ctx.chromaArrayType == 1 { + nC = -1 + } else { + nC = -2 + } + } else { + // Steps 1,2 and 3. + if level == intra16x16DCLevel || level == cbIntra16x16DCLevel || level == crIntra16x16DCLevel { + outBlockIdx = 0 + } + + // Step 4 derive blkA and blkB variables (blockA and blockB here). + var mbAddr, blk [2]block + switch level { + case intra16x16DCLevel, intra16x16ACLevel, lumaLevel4x4: + // TODO: clause 6.4.11.4 + panic("not implemented") + case cbIntra16x16DCLevel, cbIntra16x16ACLevel, cbLevel4x4: + // TODO: clause 6.4.11.6 + panic("not implemented") + case crIntra16x16DCLevel, crIntra16x16ACLevel, crLevel4x4: + // TODO: clause 6.4.11.6 + panic("not implemented") + default: + // TODO: clause 6.4.11.5 + panic("not implemented") + } + + var availableFlag [2]bool + var n [2]int + for i := range availableFlag { + // Step 5. + if !(!available(mbAddr[i]) || usingMbPredMode || vid.ConstrainedIntraPred || + mbAddr[i].usingInterMbPredMode || ctx.nalType == 2 || ctx.nalType == 3 || ctx.nalType == 4) { + availableFlag[i] = true + } + + // Step 6. + if availableFlag[i] { + switch { + case mbAddr[i].mbType == pSkip || mbAddr[i].mbType == bSkip || (mbAddr[i].mbType != iPCM && resTransformCoeffLevelsZero(blk[i])): + n[i] = 0 + case mbAddr[i].mbType == iPCM: + n[i] = 16 + default: + // TODO: how is this set ? + // "Otherwise, nN is set equal to the value TotalCoeff( coeff_token ) of + // the neighbouring block blkN." + // Do we need to run this same process for this block, or assume it's + // already happened? + n[i] = blk[i].totalCoef + } + } + } + + // Step 7. + switch { + case availableFlag[0] && availableFlag[1]: + nC = (n[0] + n[1] + 1) >> 1 + case availableFlag[0]: + nC = n[0] + case availableFlag[1]: + nC = n[1] + default: + nC = 0 + } + } + + trailingOnes, totalCoeff, _, err = readCoeffToken(br, nC) + if err != nil { + err = fmt.Errorf("could not get trailingOnes and totalCoeff vars, failed with error: %v", err) + return + } + return +} + +var ( + errInvalidNC = errors.New("invalid value of nC") + errBadToken = errors.New("could not find coeff_token value in map") +) + +// readCoeffToken will read the coeff_token from br and find a match in the +// coeff_token mapping table (table 9-5 in the specifications) given also nC. +// The resultant TrailingOnes(coeff_token) and TotalCoeff(coeff_token) are +// returned as well as the value of coeff_token. +func readCoeffToken(br *bits.BitReader, nC int) (trailingOnes, totalCoeff, coeffToken int, err error) { + // Get the number of leading zeros. + var b uint64 + nZeros := -1 + for ; b == 0; nZeros++ { + b, err = br.ReadBits(1) + if err != nil { + err = fmt.Errorf("could not read coeff_token leading zeros, failed with error: %v", err) + return + } + } + + // Get the column idx for the map. + var nCIdx int + switch { + case 0 <= nC && nC < 2: + nCIdx = 0 + case 2 <= nC && nC < 4: + nCIdx = 1 + case 4 <= nC && nC < 8: + nCIdx = 2 + case 8 <= nC: + nCIdx = 3 + case nC == -1: + nCIdx = 4 + case nC == -2: + nCIdx = 5 + default: + err = errInvalidNC + return + } + + // Get the value of coeff_token. + val := b + nBits := nZeros + for { + vars, ok := coeffTokenMaps[nCIdx][nZeros][int(val)] + if ok { + trailingOnes = vars[0] + totalCoeff = vars[1] + coeffToken = int(val) + return + } + + const maxCoeffTokenBits = 16 + if !ok && nBits == maxCoeffTokenBits { + err = errBadToken + return + } + + b, err = br.ReadBits(1) + if err != nil { + err = fmt.Errorf("could not read next bit of coeff_token, failed with error: %v", err) + return + } + + nBits++ + val <<= 1 + val |= b + } +} + +// TODO: implement this. From step 6 section 9.2.1. Will return true if "AC +// residual transform coefficient levels of the neighbouring block blkN are +// equal to 0 due to the corresponding bit of CodedBlockPatternLuma or +// CodedBlockPatternChroma being equal to 0" +func resTransformCoeffLevelsZero(b block) bool { + panic("not implemented") + return true +} + +// TODO: implement this +func available(b block) bool { + panic("not implemented") + return true +} + // parseLevelPrefix parses the level_prefix variable as specified by the process // outlined in section 9.2.2.1 in the specifications. func parseLevelPrefix(br *bits.BitReader) (int, error) { diff --git a/codec/h264/h264dec/cavlc_test.go b/codec/h264/h264dec/cavlc_test.go index a6740858..0abccbac 100644 --- a/codec/h264/h264dec/cavlc_test.go +++ b/codec/h264/h264dec/cavlc_test.go @@ -26,11 +26,94 @@ package h264dec import ( "bytes" + "reflect" "testing" "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" ) +func TestFormCoeffTokenMap(t *testing.T) { + tests := []struct { + in [][]string + want [nColumns]tokenMap + }{ + { + in: [][]string{ + {"0", "0", "1", "11", "1111", "0000 11", "01", "1"}, + {"0", "1", "0001 01", "0010 11", "0011 11", "0000 00", "0001 11", "0001 111"}, + }, + want: [nColumns]tokenMap{ + 0: { + 0: {1: {0, 0}}, + 3: {5: {0, 1}}, + }, + 1: { + 0: {3: {0, 0}}, + 2: {11: {0, 1}}, + }, + 2: { + 0: {15: {0, 0}}, + 2: {15: {0, 1}}, + }, + 3: { + 4: {3: {0, 0}}, + 6: {0: {0, 1}}, + }, + 4: { + 1: {1: {0, 0}}, + 3: {7: {0, 1}}, + }, + 5: { + 0: {1: {0, 0}}, + 3: {15: {0, 1}}, + }, + }, + }, + { + in: [][]string{ + {"0", "0", "1", "11", "1111", "0000 11", "01", "1"}, + {"0", "1", "0001 01", "0010 11", "0011 11", "-", "0001 11", "0001 111"}, + }, + want: [nColumns]tokenMap{ + 0: { + 0: {1: {0, 0}}, + 3: {5: {0, 1}}, + }, + 1: { + 0: {3: {0, 0}}, + 2: {11: {0, 1}}, + }, + 2: { + 0: {15: {0, 0}}, + 2: {15: {0, 1}}, + }, + 3: { + 4: {3: {0, 0}}, + }, + 4: { + 1: {1: {0, 0}}, + 3: {7: {0, 1}}, + }, + 5: { + 0: {1: {0, 0}}, + 3: {15: {0, 1}}, + }, + }, + }, + } + + for i, test := range tests { + m, err := formCoeffTokenMap(test.in) + if err != nil { + t.Errorf("did not expect error: %v for test: %d", err, i) + } + + if !reflect.DeepEqual(m, test.want) { + t.Errorf("did not get expected result for test: %d\nGot: %v\nWant: %v\n", i, m, test.want) + } + } +} + func TestParseLevelPrefix(t *testing.T) { tests := []struct { in string @@ -53,3 +136,55 @@ func TestParseLevelPrefix(t *testing.T) { } } } + +func TestReadCoeffToken(t *testing.T) { + tests := []struct { + // Input. + nC int + tokenBits string + + // Expected. + trailingOnes int + totalCoeff int + err error + }{ + { + nC: 5, + tokenBits: "0001001", + trailingOnes: 0, + totalCoeff: 6, + }, + { + nC: -1, + tokenBits: "0000000000111111111", + err: errBadToken, + }, + { + nC: -3, + tokenBits: "0001001", + err: errInvalidNC, + }, + } + + for i, test := range tests { + b, err := binToSlice(test.tokenBits) + if err != nil { + t.Errorf("converting bin string to slice failed with error: %v for test", err) + continue + } + + gotTrailingOnes, gotTotalCoeff, _, gotErr := readCoeffToken(bits.NewBitReader(bytes.NewReader(b)), test.nC) + if gotErr != test.err { + t.Errorf("did not get expected error for test: %d\nGot: %v\nWant: %v\n", i, gotErr, test.err) + continue + } + + if gotTrailingOnes != test.trailingOnes { + t.Errorf("did not get expected TrailingOnes(coeff_token) for test %d\nGot: %v\nWant: %v\n", i, gotTrailingOnes, test.trailingOnes) + } + + if gotTotalCoeff != test.totalCoeff { + t.Errorf("did not get expected TotalCoeff(coeff_token) for test %d\nGot: %v\nWant: %v\n", i, gotTotalCoeff, test.totalCoeff) + } + } +} diff --git a/codec/h264/h264dec/cavlctab.go b/codec/h264/h264dec/cavlctab.go new file mode 100644 index 00000000..2e85e644 --- /dev/null +++ b/codec/h264/h264dec/cavlctab.go @@ -0,0 +1,67 @@ +package h264dec + +// coefTokenTable is a CSV representation of table 9-5 from the specifications. +// This is used to populate a series of maps at initialisation for the +// retrieval of TotalCoeff and TrailingOnes values for a given coeff_token. +const coeffTokenTable = `0,0,1,11,1111,0000 11,01,1 +0,1,0001 01,0010 11,0011 11,0000 00,0001 11,0001 111 +1,1,01,10,1110,0000 01,1,01 +0,2,0000 0111,0001 11,0010 11,0001 00,0001 00,0001 110 +1,2,0001 00,0011 1,0111 1,0001 01,0001 10,0001 101 +2,2,001,011,1101,0001 10,001,001 +0,3,0000 0011 1,0000 111,0010 00,0010 00,0000 11,0000 0011 1 +1,3,0000 0110,0010 10,0110 0,0010 01,0000 011,0001 100 +2,3,0000 101,0010 01,0111 0,0010 10,0000 010,0001 011 +3,3,0001 1,0101,1100,0010 11,0001 01,0000 1 +0,4,0000 0001 11,0000 0111,0001 111,0011 00,0000 10,0000 0011 0 +1,4,0000 0011 0,0001 10,0101 0,0011 01,0000 0011,0000 0010 1 +2,4,0000 0101,0001 01,0101 1,0011 10,0000 0010,0001 010 +3,4,0000 11,0100,1011,0011 11,0000 000,0000 01 +0,5,0000 0000 111,0000 0100,0001 011,0100 00,-,0000 0001 11 +1,5,0000 0001 10,0000 110,0100 0,0100 01,-,0000 0001 10 +2,5,0000 0010 1,0000 101,0100 1,0100 10,-,0000 0010 0 +3,5,0000 100,0011 0,1010,0100 11,-,0001 001 +0,6,0000 0000 0111 1,0000 0011 1,0001 001,0101 00,-,0000 0000 111 +1,6,0000 0000 110,0000 0110,0011 10,0101 01,-,0000 0000 110 +2,6,0000 0001 01,0000 0101,0011 01,0101 10,-,0000 0001 01 +3,6,0000 0100,0010 00,1001,0101 11,-,0001 000 +0,7,0000 0000 0101 1,0000 0001 111,0001 000,0110 00,-,0000 0000 0111 +1,7,0000 0000 0111 0,0000 0011 0,0010 10,0110 01,-,0000 0000 0110 +2,7,0000 0000 101,0000 0010 1,0010 01,0110 10,-,0000 0000 101 +3,7,0000 0010 0,0001 00,1000,0110 11,-,0000 0001 00 +0,8,0000 0000 0100 0,0000 0001 011,0000 1111,0111 00,-,0000 0000 0011 1 +1,8,0000 0000 0101 0,0000 0001 110,0001 110,0111 01,-,0000 0000 0101 +2,8,0000 0000 0110 1,0000 0001 101,0001 101,0111 10,-,0000 0000 0100 +3,8,0000 0001 00,0000 100,0110 1,0111 11,-,0000 0000 100 +0,9,0000 0000 0011 11,0000 0000 1111,0000 1011,1000 00,-,- +1,9,0000 0000 0011 10,0000 0001 010,0000 1110,1000 01,-,- +2,9,0000 0000 0100 1,0000 0001 001,0001 010,1000 10,-,- +3,9,0000 0000 100,0000 0010 0,0011 00,1000 11,-,- +0,10,0000 0000 0010 11,0000 0000 1011,0000 0111 1,1001 00,-,- +1,10,0000 0000 0010 10,0000 0000 1110,0000 1010,1001 01,-,- +2,10,0000 0000 0011 01,0000 0000 1101,0000 1101,1001 10,-,- +3,10,0000 0000 0110 0,0000 0001 100,0001 100,1001 11,-,- +0,11,0000 0000 0001 111,0000 0000 1000,0000 0101 1,1010 00,-,- +1,11,0000 0000 0001 110,0000 0000 1010,0000 0111 0,1010 01,-,- +2,11,0000 0000 0010 01,0000 0000 1001,0000 1001,1010 10,-,- +3,11,0000 0000 0011 00,0000 0001 000,0000 1100,1010 11,-,- +0,12,0000 0000 0001 011,0000 0000 0111 1,0000 0100 0,1011 00,-,- +1,12,0000 0000 0001 010,0000 0000 0111 0,0000 0101 0,1011 01,-,- +2,12,0000 0000 0001 101,0000 0000 0110 1,0000 0110 1,1011 10,-,- +3,12,0000 0000 0010 00,0000 0000 1100,0000 1000,1011 11,-,- +0,13,0000 0000 0000 1111,0000 0000 0101 1,0000 0011 01,1100 00,-,- +1,13,0000 0000 0000 001,0000 0000 0101 0,0000 0011 1,1100 01,-,- +2,13,0000 0000 0001 001,0000 0000 0100 1,0000 0100 1,1100 10,-,- +3,13,0000 0000 0001 100,0000 0000 0110 0,0000 0110 0,1100 11,-,- +0,14,0000 0000 0000 1011,0000 0000 0011 1,0000 0010 01,1101 00,-,- +1,14,0000 0000 0000 1110,0000 0000 0010 11,0000 0011 00,1101 01,-,- +2,14,0000 0000 0000 1101,0000 0000 0011 0,0000 0010 11,1101 10,-,- +3,14,0000 0000 0001 000,0000 0000 0100 0,0000 0010 10,1101 11,-,- +0,15,0000 0000 0000 0111,0000 0000 0010 01,0000 0001 01,1110 00,-,- +1,15,0000 0000 0000 1010,0000 0000 0010 00,0000 0010 00,1110 01,-,- +2,15,0000 0000 0000 1001,0000 0000 0010 10,0000 0001 11,1110 10,-,- +3,15,0000 0000 0000 1100,0000 0000 0000 1,0000 0001 10,1110 11,-,- +0,16,0000 0000 0000 0100,0000 0000 0001 11,0000 0000 01,1111 00,-,- +1,16,0000 0000 0000 0110,0000 0000 0001 10,0000 0001 00,1111 01,-,- +2,16,0000 0000 0000 0101,0000 0000 0001 01,0000 0000 11,1111 10,-,- +3,16,0000 0000 0000 1000,0000 0000 0001 00,0000 0000 10,1111 11,-,-` diff --git a/codec/h264/h264dec/helpers.go b/codec/h264/h264dec/helpers.go index 644b80d9..92124cec 100644 --- a/codec/h264/h264dec/helpers.go +++ b/codec/h264/h264dec/helpers.go @@ -9,6 +9,7 @@ package h264dec import ( "errors" + "math" ) // binToSlice is a helper function to convert a string of binary into a @@ -42,6 +43,21 @@ func binToSlice(s string) ([]byte, error) { return bytes, nil } +// binToInt converts a binary string provided as a string and returns as an int. +// White spaces are ignored. +func binToInt(s string) (int, error) { + var sum int + var nSpace int + for i := len(s) - 1; i >= 0; i-- { + if s[i] == ' ' { + nSpace++ + continue + } + sum += int(math.Pow(2, float64(len(s)-1-i-nSpace))) * int(s[i]-'0') + } + return sum, nil +} + func maxi(a, b int) int { if a > b { return a diff --git a/codec/h264/h264dec/helpers_test.go b/codec/h264/h264dec/helpers_test.go new file mode 100644 index 00000000..53806f80 --- /dev/null +++ b/codec/h264/h264dec/helpers_test.go @@ -0,0 +1,28 @@ +package h264dec + +import "testing" + +func TestBinToInt(t *testing.T) { + tests := []struct { + in string + want int + }{ + {in: "101", want: 5}, + {in: "1", want: 1}, + {in: "00000", want: 0}, + {in: "", want: 0}, + {in: "1111", want: 15}, + {in: "1 111", want: 15}, + } + + for i, test := range tests { + n, err := binToInt(test.in) + if err != nil { + t.Errorf("did not expect error: %v from binToInt", err) + } + + if n != test.want { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, n, test.want) + } + } +} diff --git a/codec/h264/h264dec/mbtype.go b/codec/h264/h264dec/mbtype.go index eb716c17..2cd124d2 100644 --- a/codec/h264/h264dec/mbtype.go +++ b/codec/h264/h264dec/mbtype.go @@ -40,6 +40,19 @@ const ( maxBSubMbType = 12 ) +// Inferred macroblock types. +// TODO: specification reference. +const ( + pSkip = -1 + bSkip = -1 +) + +// TODO: complete +// I slice mb types. +const ( + iPCM = 25 +) + const MB_TYPE_INFERRED = 1000 var ( diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 4952cfd9..5ffcf5fd 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -46,6 +46,8 @@ type VideoStream struct { type SliceContext struct { *NALUnit *Slice + chromaArrayType int + nalType int } type Slice struct {