Merged in total-coeff-and-trailing-ones (pull request #243)

codec/h264/h264dec: parsing process for TotalCoeff and TrailingOnes

Approved-by: Alan Noble <anoble@gmail.com>
This commit is contained in:
Saxon Milton 2019-09-20 00:38:58 +00:00
commit 1fba7556dd
7 changed files with 538 additions and 0 deletions

View File

@ -26,12 +26,289 @@ LICENSE
package h264dec package h264dec
import ( import (
"encoding/csv"
"errors" "errors"
"fmt" "fmt"
"strconv"
"strings"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "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 // parseLevelPrefix parses the level_prefix variable as specified by the process
// outlined in section 9.2.2.1 in the specifications. // outlined in section 9.2.2.1 in the specifications.
func parseLevelPrefix(br *bits.BitReader) (int, error) { func parseLevelPrefix(br *bits.BitReader) (int, error) {

View File

@ -26,11 +26,94 @@ package h264dec
import ( import (
"bytes" "bytes"
"reflect"
"testing" "testing"
"bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "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) { func TestParseLevelPrefix(t *testing.T) {
tests := []struct { tests := []struct {
in string 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)
}
}
}

View File

@ -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,-,-`

View File

@ -9,6 +9,7 @@ package h264dec
import ( import (
"errors" "errors"
"math"
) )
// binToSlice is a helper function to convert a string of binary into a // 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 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 { func maxi(a, b int) int {
if a > b { if a > b {
return a return a

View File

@ -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)
}
}
}

View File

@ -40,6 +40,19 @@ const (
maxBSubMbType = 12 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 const MB_TYPE_INFERRED = 1000
var ( var (

View File

@ -46,6 +46,8 @@ type VideoStream struct {
type SliceContext struct { type SliceContext struct {
*NALUnit *NALUnit
*Slice *Slice
chromaArrayType int
nalType int
} }
type Slice struct { type Slice struct {