From a4130404fd773837dfa51be16359f75f8878e7ee Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 11 Aug 2019 22:03:25 +0930 Subject: [PATCH] codec/h264/h264dec: added truncUnaryBinarization and unaryExpGolombBinarization functions both with testing --- codec/h264/h264dec/cabac.go | 80 ++++++++++++++++++++++++++++++ codec/h264/h264dec/cabac_test.go | 83 ++++++++++++++++++++++++++++++++ codec/h264/h264dec/helpers.go | 17 ++++++- 3 files changed, 179 insertions(+), 1 deletion(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 9e353b5f..2fd48367 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -751,3 +751,83 @@ func unaryBinarization(v int) ([]int, error) { } return r, nil } + +// Error used by truncUnaryBinarization. +var errInvalidSyntaxVal = errors.New("syntax value cannot be greater than cMax") + +// truncUnaryBinarization returns the runcated unary binarization of a syntax +// element v given a cMax as specified in section 9.3.2.2 of the specifications. +func truncUnaryBinarization(v, cMax int) ([]int, error) { + if v < 0 { + return nil, errNegativeSyntaxVal + } + + if v > cMax { + return nil, errInvalidSyntaxVal + } + + if v == cMax { + b, _ := unaryBinarization(v) + return b[:len(b)-1], nil + } + return unaryBinarization(v) +} + +// Error used by unaryExpGolombBinarization. +var errInvalidUCoff = errors.New("uCoff cannot be less than or equal to zero") + +// unaryExpGolombBinarization returns the concatendated unary/k-th order +// Exp-Golomb (UEGk) binarization of a syntax element using the process defined +// in section 9.3.2.3 of the specifications. +func unaryExpGolombBinarization(v, uCoff, k int, signedValFlag bool) ([]int, error) { + if uCoff <= 0 { + return nil, errInvalidUCoff + } + + prefix, err := truncUnaryBinarization(mini(uCoff, absi(v)), uCoff) + if err != nil { + return nil, err + } + + return append(prefix, suffix(v, uCoff, k, signedValFlag)...), nil +} + +// suffix returns the suffix part of a unary k-th Exp-Golomb Binarization +// using the the algorithm as described by pseudo code 9-6 in section 9.3.2.3. +// TODO: could probably reduce allocations. +func suffix(v, uCoff, k int, signedValFlag bool) []int { + var suffix []int + put := func(p int) { suffix = append(suffix, p) } + + if absi(v) >= uCoff { + sufS := absi(v) - uCoff + var stop bool + + for { + if sufS >= (1 << uint(k)) { + put(1) + sufS = sufS - (1 << uint(k)) + k++ + } else { + put(0) + for k = k - 1; k >= 0; k-- { + put((sufS >> uint(k)) & 1) + } + stop = true + } + if stop { + break + } + } + } + + if signedValFlag && v != 0 { + if v > 0 { + put(0) + } else { + put(1) + } + } + + return suffix +} diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 9bc3a35a..990ba780 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -214,3 +214,86 @@ func TestUnaryBinarization(t *testing.T) { } } } + +func TestTruncUnaryBinarization(t *testing.T) { + tests := []struct { + v int + cMax int + want []int + err error + }{ + {v: 0, cMax: 10, want: []int{0}, err: nil}, + {v: 1, cMax: 10, want: []int{1, 0}, err: nil}, + {v: 2, cMax: 10, want: []int{1, 1, 0}, err: nil}, + {v: 0, cMax: 0, want: []int{}, err: nil}, + {v: 4, cMax: 4, want: []int{1, 1, 1, 1}, err: nil}, + {v: 1, cMax: 10, want: []int{1, 0}, err: nil}, + {v: 2, cMax: 10, want: []int{1, 1, 0}, err: nil}, + {v: -3, cMax: 10, want: nil, err: errNegativeSyntaxVal}, + {v: 5, cMax: 4, want: nil, err: errInvalidSyntaxVal}, + } + + for i, test := range tests { + got, err := truncUnaryBinarization(test.v, test.cMax) + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v", i, err, test.err) + } + + if !reflect.DeepEqual(test.want, got) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestUEGkSuffix(t *testing.T) { + // Data from https://patents.google.com/patent/US20070092150 + tests := []struct { + v, uCoff, k int + signedValFlag bool + want []int + }{ + 0: {v: 14, uCoff: 14, want: []int{0}}, + 1: {v: 15, uCoff: 14, want: []int{1, 0, 0}}, + 2: {v: 16, uCoff: 14, want: []int{1, 0, 1}}, + 3: {v: 17, uCoff: 14, want: []int{1, 1, 0, 0, 0}}, + 4: {v: 18, uCoff: 14, want: []int{1, 1, 0, 0, 1}}, + 5: {v: 19, uCoff: 14, want: []int{1, 1, 0, 1, 0}}, + 6: {v: 20, uCoff: 14, want: []int{1, 1, 0, 1, 1}}, + 7: {v: 21, uCoff: 14, want: []int{1, 1, 1, 0, 0, 0, 0}}, + 8: {v: 22, uCoff: 14, want: []int{1, 1, 1, 0, 0, 0, 1}}, + 9: {v: 23, uCoff: 14, want: []int{1, 1, 1, 0, 0, 1, 0}}, + 10: {v: 24, uCoff: 14, want: []int{1, 1, 1, 0, 0, 1, 1}}, + 11: {v: 25, uCoff: 14, want: []int{1, 1, 1, 0, 1, 0, 0}}, + } + + for i, test := range tests { + got := suffix(test.v, test.uCoff, test.k, test.signedValFlag) + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestUnaryExpGolombBinarization(t *testing.T) { + tests := []struct { + v, uCoff, k int + signedValFlag bool + want []int + }{ + 0: {v: 7, uCoff: 14, want: []int{1, 1, 1, 1, 1, 1, 1, 0}}, + 1: {v: 17, uCoff: 14, want: []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}}, + 2: {v: 15, uCoff: 14, signedValFlag: true, want: []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}}, + 3: {v: -15, uCoff: 14, signedValFlag: true, want: []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1}}, + } + + for i, test := range tests { + got, err := unaryExpGolombBinarization(test.v, test.uCoff, test.k, test.signedValFlag) + if err != nil { + t.Errorf("did not expect error %v for test %d", err, i) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} diff --git a/codec/h264/h264dec/helpers.go b/codec/h264/h264dec/helpers.go index 22e9a5eb..d9780c29 100644 --- a/codec/h264/h264dec/helpers.go +++ b/codec/h264/h264dec/helpers.go @@ -7,7 +7,10 @@ AUTHORS */ package h264dec -import "errors" +import ( + "errors" + "math" +) // binToSlice is a helper function to convert a string of binary into a // corresponding byte slice, e.g. "0100 0001 1000 1100" => {0x41,0x8c}. @@ -39,3 +42,15 @@ func binToSlice(s string) ([]byte, error) { } return bytes, nil } + +func maxi(a, b int) int { + return int(math.Max(float64(a), float64(b))) +} + +func mini(a, b int) int { + return int(math.Min(float64(a), float64(b))) +} + +func absi(i int) int { + return int(math.Abs(float64(i))) +}