From 48955967e039b751e6972d4bfb55aa483b921cf4 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 31 Jul 2019 22:53:26 +0930 Subject: [PATCH 01/58] codec/h264/h264dec/nalunit_test.go: added TestNewMVCExtension --- codec/h264/h264dec/nalunit_test.go | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 codec/h264/h264dec/nalunit_test.go diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go new file mode 100644 index 00000000..2519fafa --- /dev/null +++ b/codec/h264/h264dec/nalunit_test.go @@ -0,0 +1,52 @@ +package h264dec + +import ( + "bytes" + "reflect" + "testing" + + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" +) + +func TestNewMVCExtension(t *testing.T) { + tests := []struct { + in string + want MVCExtension + err error + }{ + { + in: "0" + // u(1) non_idr_flag = false + "00 0010" + // u(6) priority_id = 2 + "00 0001 1000" + // u(10) view_id = 24 + "100" + // u(3) temporal_id = 4 + "1" + // u(1) anchor_pic_flag = true + "0" + // u(1) inter_view_flag = false + "1", // u(1) reserved_one_bit = 1 + want: MVCExtension{ + NonIdrFlag: false, + PriorityID: 2, + ViewID: 24, + TemporalID: 4, + AnchorPicFlag: true, + InterViewFlag: false, + ReservedOneBit: 1, + }, + }, + } + + for i, test := range tests { + inBytes, err := binToSlice(test.in) + if err != nil { + t.Fatalf("did not expect error %v from binToSlice for test %d", err, i) + } + + got, err := NewMVCExtension(bits.NewBitReader(bytes.NewReader(inBytes))) + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) + } + + 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) + } + } +} From 96377f8788411c589cc469ee9611bc399e023559 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 1 Aug 2019 00:15:07 +0930 Subject: [PATCH 02/58] codec/h264/h264dec/nalunit_test.go: added TestNewThreeDAVCExtension --- codec/h264/h264dec/nalunit_test.go | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go index 2519fafa..f3828880 100644 --- a/codec/h264/h264dec/nalunit_test.go +++ b/codec/h264/h264dec/nalunit_test.go @@ -50,3 +50,44 @@ func TestNewMVCExtension(t *testing.T) { } } } + +func TestNewThreeDAVCExtension(t *testing.T) { + tests := []struct { + in string + want ThreeDAVCExtension + err error + }{ + { + in: "0001 0000" + // u(8) view_idx = 16 + "1" + // u(1) depth_flag = true + "0" + // u(1) non_idr_flag = false + "010" + // u(1) temporal_id = 2 + "1" + // u(1) anchor_pic_flag = true + "1", // u(1) inter_view_flag = true + want: ThreeDAVCExtension{ + ViewIdx: 16, + DepthFlag: true, + NonIdrFlag: false, + TemporalID: 2, + AnchorPicFlag: true, + InterViewFlag: true, + }, + }, + } + + for i, test := range tests { + inBytes, err := binToSlice(test.in) + if err != nil { + t.Fatalf("did not expect error %v from binToSlice for test %d", err, i) + } + + got, err := NewThreeDAVCExtension(bits.NewBitReader(bytes.NewReader(inBytes))) + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) + } + + 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) + } + } +} From aab3473abfba0f178e4beeab373c803df99c12ff Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 1 Aug 2019 00:32:54 +0930 Subject: [PATCH 03/58] codec/h264/h264dec: fixed bug in tests --- codec/h264/h264dec/nalunit_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go index f3828880..930fd2b3 100644 --- a/codec/h264/h264dec/nalunit_test.go +++ b/codec/h264/h264dec/nalunit_test.go @@ -21,7 +21,8 @@ func TestNewMVCExtension(t *testing.T) { "100" + // u(3) temporal_id = 4 "1" + // u(1) anchor_pic_flag = true "0" + // u(1) inter_view_flag = false - "1", // u(1) reserved_one_bit = 1 + "1" + // u(1) reserved_one_bit = 1 + "0 00000000", // Some padding want: MVCExtension{ NonIdrFlag: false, PriorityID: 2, @@ -63,7 +64,8 @@ func TestNewThreeDAVCExtension(t *testing.T) { "0" + // u(1) non_idr_flag = false "010" + // u(1) temporal_id = 2 "1" + // u(1) anchor_pic_flag = true - "1", // u(1) inter_view_flag = true + "1" + // u(1) inter_view_flag = true + "000", // Some padding want: ThreeDAVCExtension{ ViewIdx: 16, DepthFlag: true, From d5776c4496d84decba68bf0a73eb12b887a9c3ff Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 1 Aug 2019 00:57:38 +0930 Subject: [PATCH 04/58] codec/h264/h264dec: added TestNewSVCExtension --- codec/h264/h264dec/nalunit_test.go | 52 +++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go index 930fd2b3..b294760e 100644 --- a/codec/h264/h264dec/nalunit_test.go +++ b/codec/h264/h264dec/nalunit_test.go @@ -22,7 +22,7 @@ func TestNewMVCExtension(t *testing.T) { "1" + // u(1) anchor_pic_flag = true "0" + // u(1) inter_view_flag = false "1" + // u(1) reserved_one_bit = 1 - "0 00000000", // Some padding + "0", // Some padding want: MVCExtension{ NonIdrFlag: false, PriorityID: 2, @@ -93,3 +93,53 @@ func TestNewThreeDAVCExtension(t *testing.T) { } } } + +func TestSVCExtension(t *testing.T) { + tests := []struct { + in string + want SVCExtension + err error + }{ + { + in: "0" + // u(1) idr_flag = false + "10 0000" + // u(6) priority_id = 32 + "0" + // u(1) no_inter_layer_pred_flag = false + "001" + // u(3) dependency_id = 1 + "1000" + // u(4) quality_id = 8 + "010" + // u(3) temporal_id = 2 + "1" + // u(1) use_ref_base_pic_flag = true + "0" + // u(1) discardable_flag = false + "0" + // u(1) output_flag = false + "11" + // ReservedThree2Bits + "0", // padding + want: SVCExtension{ + IdrFlag: false, + PriorityID: 32, + NoInterLayerPredFlag: false, + DependencyID: 1, + QualityID: 8, + TemporalID: 2, + UseRefBasePicFlag: true, + DiscardableFlag: false, + OutputFlag: false, + ReservedThree2Bits: 3, + }, + }, + } + + for i, test := range tests { + inBytes, err := binToSlice(test.in) + if err != nil { + t.Fatalf("did not expect error %v from binToSlice for test %d", err, i) + } + + got, err := NewSVCExtension(bits.NewBitReader(bytes.NewReader(inBytes))) + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) + } + + 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) + } + } +} From 5185a70bbef844f3834e256badd3c9958c7f1eb4 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 1 Aug 2019 02:28:29 +0930 Subject: [PATCH 05/58] codec/h264/h264dec: added TestNewNALUnit but broken, need to fix --- codec/h264/h264dec/nalunit.go | 1 + codec/h264/h264dec/nalunit_test.go | 113 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index f1535855..dd0248a1 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -260,6 +260,7 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { } for moreRBSPData(br) { + fmt.Println("here") next3Bytes, err := br.PeekBits(24) // If PeekBits cannot get 3 bytes, but there still might be 2 bytes left in diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go index b294760e..419c6c5f 100644 --- a/codec/h264/h264dec/nalunit_test.go +++ b/codec/h264/h264dec/nalunit_test.go @@ -143,3 +143,116 @@ func TestSVCExtension(t *testing.T) { } } } + +func TestNewNALUnit(t *testing.T) { + tests := []struct { + in string + want NALUnit + err error + }{ + { + in: "0" + // f(1) forbidden_zero_bit = 0 + "01" + // u(2) nal_ref_idc = 1 + "0 1110" + // u(5) nal_unit_type = 14 + "1" + // u(1) svc_extension_flag = true + + // svc extension + "0" + // u(1) idr_flag = false + "10 0000" + // u(6) priority_id = 32 + "0" + // u(1) no_inter_layer_pred_flag = false + "001" + // u(3) dependency_id = 1 + "1000" + // u(4) quality_id = 8 + "010" + // u(3) temporal_id = 2 + "1" + // u(1) use_ref_base_pic_flag = true + "0" + // u(1) discardable_flag = false + "0" + // u(1) output_flag = false + "11" + // ReservedThree2Bits + + // rbsp bytes + "0000 0001" + + "0000 0010" + + "0000 0100" + + "0000 1000" + + "1000 0000", + + want: NALUnit{ + ForbiddenZeroBit: 0, + RefIdc: 1, + Type: 14, + SVCExtensionFlag: true, + SVCExtension: &SVCExtension{ + IdrFlag: false, + PriorityID: 32, + NoInterLayerPredFlag: false, + DependencyID: 1, + QualityID: 8, + TemporalID: 2, + UseRefBasePicFlag: true, + DiscardableFlag: false, + OutputFlag: false, + ReservedThree2Bits: 3, + }, + + RBSP: []byte{ + 0x01, + 0x02, + 0x04, + 0x08, + 0x80, + }, + }, + }, + } + + for i, test := range tests { + inBytes, err := binToSlice(test.in) + if err != nil { + t.Fatalf("did not expect error %v from binToSlice for test %d", err, i) + } + + got, err := NewNALUnit(bits.NewBitReader(bytes.NewReader(inBytes))) + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) + } + + if !nalEqual(*got, test.want) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, *got, test.want) + } + } +} + +func nalEqual(a, b NALUnit) bool { + aCopy := a + bCopy := b + for _, n := range [](*NALUnit){&aCopy, &bCopy} { + n.SVCExtension = nil + n.MVCExtension = nil + n.ThreeDAVCExtension = nil + } + + if !reflect.DeepEqual(aCopy, bCopy) { + return false + } + + if (a.SVCExtension == nil || b.SVCExtension == nil) && + (a.SVCExtension != b.SVCExtension) { + return false + } + + if (a.MVCExtension == nil || b.MVCExtension == nil) && + (a.MVCExtension != b.MVCExtension) { + return false + } + + if (a.ThreeDAVCExtension == nil || b.ThreeDAVCExtension == nil) && + (a.ThreeDAVCExtension != b.ThreeDAVCExtension) { + return false + } + + if !reflect.DeepEqual(a.SVCExtension, b.SVCExtension) || + !reflect.DeepEqual(a.MVCExtension, b.MVCExtension) || + !reflect.DeepEqual(a.ThreeDAVCExtension, b.ThreeDAVCExtension) { + return false + } + return true +} From 311c44f55ce105915dd1c16eb5b12b657b6c761e Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 1 Aug 2019 02:37:34 +0930 Subject: [PATCH 06/58] codec/h264/h264dec: fixed problem with test --- codec/h264/h264dec/nalunit.go | 3 +-- codec/h264/h264dec/nalunit_test.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index dd0248a1..e8ae3e3f 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -260,14 +260,13 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { } for moreRBSPData(br) { - fmt.Println("here") next3Bytes, err := br.PeekBits(24) // If PeekBits cannot get 3 bytes, but there still might be 2 bytes left in // the source, we will get an io.EOF; we wish to ignore this and continue. // The call to moreRBSPData will determine when we have reached the end of // the NAL unit. - if err != nil && errors.Cause(err) != io.EOF { + if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF { return nil, errors.Wrap(err, "could not Peek next 3 bytes") } diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go index 419c6c5f..0099b8ba 100644 --- a/codec/h264/h264dec/nalunit_test.go +++ b/codec/h264/h264dec/nalunit_test.go @@ -173,7 +173,7 @@ func TestNewNALUnit(t *testing.T) { "0000 0010" + "0000 0100" + "0000 1000" + - "1000 0000", + "1000 0000", // trailing bits want: NALUnit{ ForbiddenZeroBit: 0, @@ -198,7 +198,6 @@ func TestNewNALUnit(t *testing.T) { 0x02, 0x04, 0x08, - 0x80, }, }, }, @@ -221,6 +220,7 @@ func TestNewNALUnit(t *testing.T) { } } +// nalEqual returns true if two NALUnits are equal. func nalEqual(a, b NALUnit) bool { aCopy := a bCopy := b From 344d37cd29169e9ddea77a644118f31f462e125e Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 1 Aug 2019 02:41:20 +0930 Subject: [PATCH 07/58] codec/h264/h264dec/nalunit_test.go: added file header --- codec/h264/h264dec/nalunit_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go index 0099b8ba..d9925fdc 100644 --- a/codec/h264/h264dec/nalunit_test.go +++ b/codec/h264/h264dec/nalunit_test.go @@ -1,3 +1,10 @@ +/* +DESCRIPTION + nalunit_test.go provides testing for NAL unit parsing utilities in nalunit.go. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) +*/ package h264dec import ( From a94109e2867f44ac6db1fc50e08c4f3fd3e7fa48 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 4 Aug 2019 12:59:13 +0930 Subject: [PATCH 08/58] codec/h264/h264dec: fixed newRefPicListModification --- codec/h264/h264dec/slice.go | 109 ++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 18fb7a69..63af7da3 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -37,11 +37,10 @@ type Slice struct { // (defined in 7.3.3.1 of specifications) and a ref_pic_list_mvc_modification // (defined in H.7.3.3.1.1 of specifications). type RefPicListModification struct { - RefPicListModificationFlagL0 bool - ModificationOfPicNums int - AbsDiffPicNumMinus1 int - LongTermPicNum int - RefPicListModificationFlagL1 bool + RefPicListModificationFlag [2]bool + ModificationOfPicNums [2][]int + AbsDiffPicNumMinus1 [2][]int + LongTermPicNum [2][]int } // TODO: need to complete this. @@ -55,68 +54,57 @@ func NewRefPicListMVCModifiation(br *bits.BitReader) (*RefPicListModification, e // NewRefPicListModification parses elements of a ref_pic_list_modification // following the syntax structure defined in section 7.3.3.1, and returns as // a new RefPicListModification. -func NewRefPicListModification(br *bits.BitReader, h *SliceHeader) (*RefPicListModification, error) { +func NewRefPicListModification(br *bits.BitReader, p *PPS, s *SliceHeader) (*RefPicListModification, error) { r := &RefPicListModification{} + r.ModificationOfPicNums[0] = make([]int, p.NumRefIdxL0DefaultActiveMinus1+2) + r.ModificationOfPicNums[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) + r.AbsDiffPicNumMinus1[0] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) + r.AbsDiffPicNumMinus1[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) + r.LongTermPicNum[0] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) + r.LongTermPicNum[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) + fr := newFieldReader(br) + // 7.3.3.1 - if h.SliceType%5 != 2 && h.SliceType%5 != 4 { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL0") - } - r.RefPicListModificationFlagL0 = b == 1 + if s.SliceType%5 != 2 && s.SliceType%5 != 4 { + r.RefPicListModificationFlag[0] = fr.readBits(1) == 1 - if r.RefPicListModificationFlagL0 { - for r.ModificationOfPicNums != 3 { - r.ModificationOfPicNums, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") + if r.RefPicListModificationFlag[0] { + for i := 0; ; i++ { + r.ModificationOfPicNums[0][i] = fr.readUe() + + if r.ModificationOfPicNums[0][i] == 0 || r.ModificationOfPicNums[0][i] == 1 { + r.AbsDiffPicNumMinus1[0][i] = fr.readUe() + } else if r.ModificationOfPicNums[0][i] == 2 { + r.LongTermPicNum[0][i] = fr.readUe() } - if r.ModificationOfPicNums == 0 || r.ModificationOfPicNums == 1 { - r.AbsDiffPicNumMinus1, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") - } - } else if r.ModificationOfPicNums == 2 { - r.LongTermPicNum, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LongTermPicNum") - } - } - } - } - - } - if h.SliceType%5 == 1 { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL1") - } - r.RefPicListModificationFlagL1 = b == 1 - - if r.RefPicListModificationFlagL1 { - for r.ModificationOfPicNums != 3 { - r.ModificationOfPicNums, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") - } - - if r.ModificationOfPicNums == 0 || r.ModificationOfPicNums == 1 { - r.AbsDiffPicNumMinus1, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") - } - } else if r.ModificationOfPicNums == 2 { - r.LongTermPicNum, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LongTermPicNum") - } + if r.ModificationOfPicNums[0][i] == 3 { + break } } } } - // refPicListModification() - return nil, nil + + if s.SliceType%5 == 1 { + r.RefPicListModificationFlag[1] = fr.readBits(1) == 1 + + if r.RefPicListModificationFlag[1] { + for i := 0; ; i++ { + r.ModificationOfPicNums[1][i] = fr.readUe() + + if r.ModificationOfPicNums[1][i] == 0 || r.ModificationOfPicNums[1][i] == 1 { + r.AbsDiffPicNumMinus1[1][i] = fr.readUe() + } else if r.ModificationOfPicNums[1][i] == 2 { + r.LongTermPicNum[1][i] = fr.readUe() + } + + if r.ModificationOfPicNums[1][i] == 3 { + break + } + } + } + } + return r, nil } // PredWeightTable provides elements of a pred_weight_table syntax structure @@ -262,6 +250,7 @@ type DecRefPicMarking struct { AdaptiveRefPicMarkingModeFlag bool MemoryManagementControlOperation int DifferenceOfPicNumsMinus1 int + LongTermPicNum int LongTermFrameIdx int MaxLongTermFrameIdxPlus1 int } @@ -303,7 +292,7 @@ func NewDecRefPicMarking(br *bits.BitReader, idrPic bool, h *SliceHeader) (*DecR } } if d.MemoryManagementControlOperation == 2 { - h.RefPicListModification.LongTermPicNum, err = readUe(br) + d.LongTermPicNum, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermPicNum") } @@ -1345,7 +1334,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh // H.7.3.3.1.1 // refPicListMvcModifications() } else { - header.RefPicListModification, err = NewRefPicListModification(br, &header) + header.RefPicListModification, err = NewRefPicListModification(br, pps, &header) if err != nil { return nil, errors.Wrap(err, "could not parse RefPicListModification") } From 21603750aa6f88b6d61e9881f21177d84a4da10c Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 4 Aug 2019 13:40:53 +0930 Subject: [PATCH 09/58] codec/h264/h264dec/slice_test.go: added TestNewRefPicListModification with a single test --- codec/h264/h264dec/slice.go | 4 +- codec/h264/h264dec/slice_test.go | 69 +++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 63af7da3..6930c89c 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -58,9 +58,9 @@ func NewRefPicListModification(br *bits.BitReader, p *PPS, s *SliceHeader) (*Ref r := &RefPicListModification{} r.ModificationOfPicNums[0] = make([]int, p.NumRefIdxL0DefaultActiveMinus1+2) r.ModificationOfPicNums[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) - r.AbsDiffPicNumMinus1[0] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) + r.AbsDiffPicNumMinus1[0] = make([]int, p.NumRefIdxL0DefaultActiveMinus1+2) r.AbsDiffPicNumMinus1[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) - r.LongTermPicNum[0] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) + r.LongTermPicNum[0] = make([]int, p.NumRefIdxL0DefaultActiveMinus1+2) r.LongTermPicNum[1] = make([]int, p.NumRefIdxL1DefaultActiveMinus1+2) fr := newFieldReader(br) diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index e7988a7e..59f8314b 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -1,6 +1,12 @@ package h264dec -import "testing" +import ( + "bytes" + "reflect" + "testing" + + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" +) var subWidthCTests = []struct { in SPS @@ -47,3 +53,64 @@ func TestSubHeightC(t *testing.T) { } } } + +func TestNewRefPicListModification(t *testing.T) { + tests := []struct { + in string + s SliceHeader + p PPS + want RefPicListModification + }{ + { + in: "1" + // u(1) ref_pic_list_modification_flag_l0=true + // First modification for list0 + "1" + // ue(v) modification_of_pic_nums_idc[0][0] = 0 + "010" + // ue(v) abs_diff_pic_num_minus1[0][0] = 1 + + // Second modification for list0 + "010" + // ue(v) modification_of_pic_nums_idc[0][1] = 1 + "011" + // ue(v) abs_diff_pic_num_minus1[0][1] = 2 + + // Third modification for list0 + "011" + // ue(v) modification_of_pic_nums_idc[0][2] = 2 + "010" + // ue(v) long_term_pic_num = 1 + + // Fourth modification does not exist + "00100" + // ue(v) modification_of_pic_nums_idc[0][3] = 3 + + // Padding bits + "00", + + s: SliceHeader{ + SliceType: 3, + }, + + p: PPS{ + NumRefIdxL0DefaultActiveMinus1: 2, + }, + + want: RefPicListModification{ + RefPicListModificationFlag: [2]bool{true, false}, + ModificationOfPicNums: [2][]int{{0, 1, 2, 3}, {0, 0}}, + AbsDiffPicNumMinus1: [2][]int{{1, 2, 0, 0}, {0, 0}}, + LongTermPicNum: [2][]int{{0, 0, 1, 0}, {0, 0}}, + }, + }, + } + + for i, test := range tests { + inBytes, err := binToSlice(test.in) + if err != nil { + t.Fatalf("unexpected error %v for binToSlice in test %d", err, i) + } + + got, err := NewRefPicListModification(bits.NewBitReader(bytes.NewReader(inBytes)), &test.p, &test.s) + if err != nil { + t.Fatalf("unexpected error %v for NewRefPicListModification in 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) + } + } +} From 34ce81370d8fe327113a8e4dba7fdb1e31872850 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 4 Aug 2019 13:45:28 +0930 Subject: [PATCH 10/58] codec/h264/h264dec: added file headers to slice.go and slice_test.go --- codec/h264/h264dec/slice.go | 9 +++++++++ codec/h264/h264dec/slice_test.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 6930c89c..024ea13b 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -1,3 +1,12 @@ +/* +DESCRIPTION + slice.go provides parsing functionality for slice RBSP. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + mrmod +*/ + package h264dec import ( diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index 59f8314b..b5de6a6a 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -1,3 +1,12 @@ +/* +DESCRIPTION + slice_test.go provides testing for parsing utilities found in slice.go. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + Shawn Smith , The Australian Ocean Laboratory (AusOcean) +*/ + package h264dec import ( From b6eb39f4e0962be5b44a703b3cf91ff55dcf686e Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 5 Aug 2019 00:18:15 +0930 Subject: [PATCH 11/58] codec/h264/h264dec: started added TestNewPredWeightTable --- codec/h264/h264dec/slice_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index b5de6a6a..375092c9 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -123,3 +123,21 @@ func TestNewRefPicListModification(t *testing.T) { } } } + +func TestNewPredWeightTable(t *testing.T){ + tests := []struct{ + in string + s SliceHeader + want PredWeightTable + }{ + { + in: "011" + // ue(v) luma_log2_weight_denom = 2 + "00100" + // ue(v) chroma_log2_weigght_denom = 3 + + "1" + // u(1) luma_weight_l0_flag = true + "011" + // se(v) luma_weight_l0[0] = -1 + "010" + // se(v) luma_offset_l0[0] = 1 + "1" + // u(1) chroma_weight_l0_flag = true + }, + } +} From c2ac8cff24a1b3631b6b4b39d0a710d34a46d06e Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 5 Aug 2019 01:17:19 +0930 Subject: [PATCH 12/58] codec/h264/h264dec: added TestNewPredWeightTable --- codec/h264/h264dec/helpers.go | 4 +- codec/h264/h264dec/slice.go | 35 ++++----- codec/h264/h264dec/slice_test.go | 123 +++++++++++++++++++++++++++---- 3 files changed, 129 insertions(+), 33 deletions(-) diff --git a/codec/h264/h264dec/helpers.go b/codec/h264/h264dec/helpers.go index 22e9a5eb..947d3895 100644 --- a/codec/h264/h264dec/helpers.go +++ b/codec/h264/h264dec/helpers.go @@ -19,7 +19,7 @@ func binToSlice(s string) ([]byte, error) { bytes []byte ) - for _, c := range s { + for i, c := range s { switch c { case ' ': continue @@ -31,7 +31,7 @@ func binToSlice(s string) ([]byte, error) { } a >>= 1 - if a == 0 { + if a == 0 || i == (len(s)-1) { bytes = append(bytes, cur) cur = 0 a = 0x80 diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 024ea13b..946a90d9 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -30,6 +30,8 @@ type VideoStream struct { SPS *SPS PPS *PPS Slices []*SliceContext + + ChromaArrayType int } type SliceContext struct { *NALUnit @@ -121,7 +123,6 @@ func NewRefPicListModification(br *bits.BitReader, p *PPS, s *SliceHeader) (*Ref type PredWeightTable struct { LumaLog2WeightDenom int ChromaLog2WeightDenom int - ChromaArrayType int LumaWeightL0Flag bool LumaWeightL0 []int LumaOffsetL0 []int @@ -139,7 +140,7 @@ type PredWeightTable struct { // NewPredWeightTable parses elements of a pred_weight_table following the // syntax structure defined in section 7.3.3.2, and returns as a new // PredWeightTable. -func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, error) { +func NewPredWeightTable(br *bits.BitReader, h *SliceHeader, chromaArrayType int) (*PredWeightTable, error) { p := &PredWeightTable{} var err error @@ -148,7 +149,7 @@ func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, e return nil, errors.Wrap(err, "could not parse LumaLog2WeightDenom") } - if p.ChromaArrayType != 0 { + if chromaArrayType != 0 { p.ChromaLog2WeightDenom, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaLog2WeightDenom") @@ -174,7 +175,7 @@ func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, e } p.LumaOffsetL0 = append(p.LumaOffsetL0, se) } - if p.ChromaArrayType != 0 { + if chromaArrayType != 0 { b, err := br.ReadBits(1) if err != nil { return nil, errors.Wrap(err, "could not read ChromaWeightL0Flag") @@ -221,7 +222,7 @@ func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, e } p.LumaOffsetL1 = append(p.LumaOffsetL1, se) } - if p.ChromaArrayType != 0 { + if chromaArrayType != 0 { b, err := br.ReadBits(1) if err != nil { return nil, errors.Wrap(err, "could not read ChromaWeightL1Flag") @@ -529,7 +530,7 @@ func NumMbPart(nalUnit *NALUnit, sps *SPS, header *SliceHeader, data *SliceData) return numMbPart } -func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { +func MbPred(chromaArrayType int, sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { var cabac *CABAC sliceType := sliceTypeMap[sliceContext.Slice.Header.SliceType] mbPartPredMode, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, 0) @@ -630,7 +631,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { } } - if sliceContext.Slice.Header.ChromaArrayType == 1 || sliceContext.Slice.Header.ChromaArrayType == 2 { + if chromaArrayType == 1 || chromaArrayType == 2 { if sliceContext.PPS.EntropyCodingMode == 1 { // TODO: ue(v) or ae(v) binarization := NewBinarization( @@ -882,7 +883,7 @@ func MbaffFrameFlag(sps *SPS, header *SliceHeader) int { return 0 } -func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, error) { +func NewSliceData(chromaArrayType int, sliceContext *SliceContext, br *bits.BitReader) (*SliceData, error) { var cabac *CABAC var err error sliceContext.Slice.Data = &SliceData{BitReader: br} @@ -1117,7 +1118,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e } } // TODO: fix nil argument for. - MbPred(sliceContext, br, nil) + MbPred(chromaArrayType, sliceContext, br, nil) } m, err = MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) if err != nil { @@ -1136,7 +1137,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e } else { me, _ := readMe( br, - uint(sliceContext.Slice.Header.ChromaArrayType), + uint(chromaArrayType), // TODO: fix this //MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0))) 0) @@ -1213,10 +1214,10 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e func (c *SliceContext) Update(header *SliceHeader, data *SliceData) { c.Slice = &Slice{Header: header, Data: data} } -func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, showPacket bool) (*SliceContext, error) { +func NewSliceContext(vid *VideoStream, nalUnit *NALUnit, rbsp []byte, showPacket bool) (*SliceContext, error) { var err error - sps := videoStream.SPS - pps := videoStream.PPS + sps := vid.SPS + pps := vid.PPS logger.Printf("debug: %s RBSP %d bytes %d bits == \n", NALUnitType[int(nalUnit.Type)], len(rbsp), len(rbsp)*8) logger.Printf("debug: \t%#v\n", rbsp[0:8]) var idrPic bool @@ -1225,9 +1226,9 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh } header := SliceHeader{} if sps.UseSeparateColorPlane { - header.ChromaArrayType = 0 + vid.ChromaArrayType = 0 } else { - header.ChromaArrayType = sps.ChromaFormat + vid.ChromaArrayType = sps.ChromaFormat } br := bits.NewBitReader(bytes.NewReader(rbsp)) @@ -1350,7 +1351,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh } if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") { - header.PredWeightTable, err = NewPredWeightTable(br, &header) + header.PredWeightTable, err = NewPredWeightTable(br, &header, vid.ChromaArrayType) if err != nil { return nil, errors.Wrap(err, "could not parse PredWeightTable") } @@ -1420,7 +1421,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh Header: &header, }, } - sliceContext.Slice.Data, err = NewSliceData(sliceContext, br) + sliceContext.Slice.Data, err = NewSliceData(vid.ChromaArrayType,sliceContext, br) if err != nil { return nil, errors.Wrap(err, "could not create slice data") } diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index 375092c9..522a9eb0 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -85,10 +85,7 @@ func TestNewRefPicListModification(t *testing.T) { "010" + // ue(v) long_term_pic_num = 1 // Fourth modification does not exist - "00100" + // ue(v) modification_of_pic_nums_idc[0][3] = 3 - - // Padding bits - "00", + "00100", // ue(v) modification_of_pic_nums_idc[0][3] = 3 s: SliceHeader{ SliceType: 3, @@ -124,20 +121,118 @@ func TestNewRefPicListModification(t *testing.T) { } } -func TestNewPredWeightTable(t *testing.T){ - tests := []struct{ - in string - s SliceHeader - want PredWeightTable +func TestNewPredWeightTable(t *testing.T) { + tests := []struct { + in string + sliceHeader SliceHeader + chromaArrayType int // ChromaArrayType + want PredWeightTable }{ { in: "011" + // ue(v) luma_log2_weight_denom = 2 - "00100" + // ue(v) chroma_log2_weigght_denom = 3 + "00100" + // ue(v) chroma_log2_weigght_denom = 3 - "1" + // u(1) luma_weight_l0_flag = true - "011" + // se(v) luma_weight_l0[0] = -1 - "010" + // se(v) luma_offset_l0[0] = 1 - "1" + // u(1) chroma_weight_l0_flag = true + // list0 + // i = 0 + "1" + // u(1) luma_weight_l0_flag = true + "011" + // se(v) luma_weight_l0[0] = -1 + "010" + // se(v) luma_offset_l0[0] = 1 + "1" + // u(1) chroma_weight_l0_flag = true + + // i = 0, j = 0 + "010" + // se(v) chroma_weight_l0[0][0] = 1 + "010" + // se(v) chroma_offset_l0[0][0] = 1 + + // i = 0, j = 1 + "010" + // se(v) chroma_weight_l0[0][1] = 1 + "010" + // se(v) chroma_offset_l0[0][1] = 1 + + // i = 1 + "1" + // u(1) luma_weight_l0_flag = true + "011" + // se(v) luma_weight_l0[1] = -1 + "00100" + // se(v) luma_offset_l0[1] = 2 + "1" + // u(1) chroma_weight_l0_flag = true + + // i = 1, j = 0 + "011" + // se(v) chroma_weight_l0[1][0] = -1 + "00101" + // se(v) chroma_offset_l0[1][0] = -2 + + // i = 1, j = 1 + "011" + // se(v) chroma_weight_l0[1][1] = -1 + "011" + // se(v) chroma_offset_l0[1][1] = -1 + + // list1 + // i = 0 + "1" + // u(1) luma_weight_l1_flag = true + "011" + // se(v) luma_weight_l1[0] = -1 + "010" + // se(v) luma_offset_l1[0] = 1 + "1" + // u(1) chroma_weight_l1_flag = true + + // i = 0, j = 0 + "010" + // se(v) chroma_weight_l1[0][0] = 1 + "010" + // se(v) chroma_offset_l1[0][0] = 1 + + // i = 0, j = 1 + "010" + // se(v) chroma_weight_l1[0][1] = 1 + "010" + // se(v) chroma_offset_l1[0][1] = 1 + + // i = 1 + "1" + // u(1) luma_weight_l1_flag = true + "011" + // se(v) luma_weight_l1[1] = -1 + "00100" + // se(v) luma_offset_l1[1] = 2 + "1" + // u(1) chroma_weight_l1_flag = true + + // i = 1, j = 0 + "011" + // se(v) chroma_weight_l0[1][0] = -1 + "00101" + // se(v) chroma_offset_l0[1][0] = -2 + + // i = 1, j = 1 + "011" + // se(v) chroma_weight_l0[1][1] = -1 + "011", // se(v) chroma_offset_l0[1][1] = -1 + + sliceHeader: SliceHeader{ + NumRefIdxL0ActiveMinus1: 1, + NumRefIdxL1ActiveMinus1: 1, + SliceType: 1, + }, + + chromaArrayType: 1, + + want: PredWeightTable{ + LumaLog2WeightDenom: 2, + ChromaLog2WeightDenom: 3, + + LumaWeightL0Flag: true, + LumaWeightL0: []int{-1, -1}, + LumaOffsetL0: []int{1, 2}, + ChromaWeightL0Flag: true, + ChromaWeightL0: [][]int{{1, 1}, {-1, -1}}, + ChromaOffsetL0: [][]int{{1, 1}, {-2, -1}}, + + LumaWeightL1Flag: true, + LumaWeightL1: []int{-1, -1}, + LumaOffsetL1: []int{1, 2}, + ChromaWeightL1Flag: true, + ChromaWeightL1: [][]int{{1, 1}, {-1, -1}}, + ChromaOffsetL1: [][]int{{1, 1}, {-2, -1}}, + }, }, } + + for i, test := range tests { + inBytes, err := binToSlice(test.in) + if err != nil { + t.Fatalf("unexpected error %v for binToSlice in test %d", err, i) + } + + got, err := NewPredWeightTable(bits.NewBitReader(bytes.NewReader(inBytes)), + &test.sliceHeader, test.chromaArrayType) + if err != nil { + t.Fatalf("unexpected error %v for NewPredWeightTable in 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) + } + } } From 95a5ce0ac0835180dfef045cc756c3ee2e6e575c Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 5 Aug 2019 10:40:20 +0930 Subject: [PATCH 13/58] codec/h264/h264dec: wrote TestDecRefPicMarking and fixed bugs --- codec/h264/h264dec/slice.go | 41 ++++++++++------ codec/h264/h264dec/slice_test.go | 80 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 15 deletions(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 946a90d9..5f819601 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -258,6 +258,10 @@ type DecRefPicMarking struct { NoOutputOfPriorPicsFlag bool LongTermReferenceFlag bool AdaptiveRefPicMarkingModeFlag bool + elements []drpmElement +} + +type drpmElement struct { MemoryManagementControlOperation int DifferenceOfPicNumsMinus1 int LongTermPicNum int @@ -268,7 +272,7 @@ type DecRefPicMarking struct { // NewDecRefPicMarking parses elements of a dec_ref_pic_marking following the // syntax structure defined in section 7.3.3.3, and returns as a new // DecRefPicMarking. -func NewDecRefPicMarking(br *bits.BitReader, idrPic bool, h *SliceHeader) (*DecRefPicMarking, error) { +func NewDecRefPicMarking(br *bits.BitReader, idrPic bool) (*DecRefPicMarking, error) { d := &DecRefPicMarking{} if idrPic { b, err := br.ReadBits(1) @@ -290,35 +294,42 @@ func NewDecRefPicMarking(br *bits.BitReader, idrPic bool, h *SliceHeader) (*DecR d.AdaptiveRefPicMarkingModeFlag = b == 1 if d.AdaptiveRefPicMarkingModeFlag { - d.MemoryManagementControlOperation, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") - } - for d.MemoryManagementControlOperation != 0 { - if d.MemoryManagementControlOperation == 1 || d.MemoryManagementControlOperation == 3 { - d.DifferenceOfPicNumsMinus1, err = readUe(br) + for i := 0; ; i++ { + d.elements = append(d.elements,drpmElement{}) + + d.elements[i].MemoryManagementControlOperation, err = readUe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") + } + + if d.elements[i].MemoryManagementControlOperation == 1 || d.elements[i].MemoryManagementControlOperation == 3 { + d.elements[i].DifferenceOfPicNumsMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") } } - if d.MemoryManagementControlOperation == 2 { - d.LongTermPicNum, err = readUe(br) + if d.elements[i].MemoryManagementControlOperation == 2 { + d.elements[i].LongTermPicNum, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermPicNum") } } - if d.MemoryManagementControlOperation == 3 || d.MemoryManagementControlOperation == 6 { - d.LongTermFrameIdx, err = readUe(br) + if d.elements[i].MemoryManagementControlOperation == 3 || d.elements[i].MemoryManagementControlOperation == 6 { + d.elements[i].LongTermFrameIdx, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermFrameIdx") } } - if d.MemoryManagementControlOperation == 4 { - d.MaxLongTermFrameIdxPlus1, err = readUe(br) + if d.elements[i].MemoryManagementControlOperation == 4 { + d.elements[i].MaxLongTermFrameIdxPlus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MaxLongTermFrameIdxPlus1") } } + + if d.elements[i].MemoryManagementControlOperation == 0 { + break + } } } } @@ -1358,7 +1369,7 @@ func NewSliceContext(vid *VideoStream, nalUnit *NALUnit, rbsp []byte, showPacket } if nalUnit.RefIdc != 0 { // devRefPicMarking() - header.DecRefPicMarking, err = NewDecRefPicMarking(br, idrPic, &header) + header.DecRefPicMarking, err = NewDecRefPicMarking(br, idrPic) if err != nil { return nil, errors.Wrap(err, "could not parse DecRefPicMarking") } diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index 522a9eb0..f0914a4c 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -236,3 +236,83 @@ func TestNewPredWeightTable(t *testing.T) { } } } + +func TestDecRefPicMarking(t *testing.T) { + tests := []struct { + in string + idrPic bool + want DecRefPicMarking + }{ + { + in: "0" + // u(1) no_output_of_prior_pics_flag = false + "1", // u(1) long_term_reference_flag = true + idrPic: true, + want: DecRefPicMarking{ + NoOutputOfPriorPicsFlag: false, + LongTermReferenceFlag: true, + }, + }, + { + in: "1" + // u(1) adaptive_ref_pic_marking_mode_flag = true + + "010" + // ue(v) memory_management_control_operation = 1 + "011" + // ue(v) difference_of_pic_nums_minus1 = 2 + + "00100" + // ue(v) memory_management_control_operation = 3 + "1" + // ue(v) difference_of_pic_nums_minus1 = 0 + "011" + // ue(v) long_term_frame_idx = 2 + + "011" + // ue(v) memory_management_control_operation = 2 + "00100" + // ue(v) long_term_pic_num = 3 + + "00101" + // ue(v) memory_management_control_operation = 4 + "010" + // ue(v) max_long_term_frame_idx_plus1 = 1 + + "1", // ue(v) memory_management_control_operation = 0 + + idrPic: false, + + want: DecRefPicMarking{ + AdaptiveRefPicMarkingModeFlag: true, + elements: []drpmElement{ + { + MemoryManagementControlOperation: 1, + DifferenceOfPicNumsMinus1: 2, + }, + { + MemoryManagementControlOperation: 3, + DifferenceOfPicNumsMinus1: 0, + LongTermFrameIdx: 2, + }, + { + MemoryManagementControlOperation: 2, + LongTermPicNum: 3, + }, + { + MemoryManagementControlOperation: 4, + MaxLongTermFrameIdxPlus1: 1, + }, + { + MemoryManagementControlOperation: 0, + }, + }, + }, + }, + } + + for i, test := range tests { + inBytes, err := binToSlice(test.in) + if err != nil { + t.Fatalf("unexpected error %v for binToSlice in test %d", err, i) + } + + got, err := NewDecRefPicMarking(bits.NewBitReader(bytes.NewReader(inBytes)), test.idrPic) + if err != nil { + t.Fatalf("unexpected error %v for NewPredWeightTable in 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) + } + } +} From 75b7a2946f625bcfe9066c8eec87aef4fffbc30b Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 8 Aug 2019 13:20:02 +0930 Subject: [PATCH 14/58] revid: made MTS and RTMP ringbuffer sizes configurable via vars and added to revid config --- revid/config.go | 83 +++++++++++++++++++++++++++---------------- revid/revid.go | 57 +++++++++++++++++++++-------- revid/senders.go | 4 +-- revid/senders_test.go | 6 ++-- 4 files changed, 100 insertions(+), 50 deletions(-) diff --git a/revid/config.go b/revid/config.go index cf108db6..a995d238 100644 --- a/revid/config.go +++ b/revid/config.go @@ -90,32 +90,40 @@ const ( // Default config settings const ( - defaultInput = Raspivid - defaultOutput = HTTP - defaultFrameRate = 25 - defaultWriteRate = 25 - defaultWidth = 1280 - defaultHeight = 720 - defaultIntraRefreshPeriod = 100 - defaultTimeout = 0 - defaultQuantization = 40 - defaultBitrate = 400000 - defaultFramesPerClip = 1 - httpFramesPerClip = 560 - defaultInputCodec = codecutil.H264 - defaultVerbosity = logger.Error - defaultRtpAddr = "localhost:6970" - defaultBurstPeriod = 10 // Seconds - defaultRotation = 0 // Degrees + // General revid defaults. + defaultInput = Raspivid + defaultOutput = HTTP + defaultFrameRate = 25 + defaultWriteRate = 25 + defaultTimeout = 0 + defaultInputCodec = codecutil.H264 + defaultVerbosity = logger.Error + defaultRtpAddr = "localhost:6970" + defaultBurstPeriod = 10 // Seconds + + // Raspivid video defaults. defaultBrightness = 50 defaultExposure = "auto" defaultAutoWhiteBalance = "auto" + defaultRotation = 0 // Degrees + defaultWidth = 1280 + defaultHeight = 720 + defaultIntraRefreshPeriod = 100 + defaultQuantization = 40 + defaultBitrate = 400000 + // Audio defaults. defaultAudioInputCodec = codecutil.ADPCM defaultSampleRate = 48000 defaultBitDepth = 16 defaultChannels = 1 defaultRecPeriod = 1.0 + + // Ringbuffer defaults. + defaultMTSRBSize = 1000 + defaultMTSRBElementSize = 100000 + defaultRTMPRBSize = 500 + defaultRTMPRBElementSize = 200000 ) // Config provides parameters relevant to a revid instance. A new config must @@ -166,10 +174,6 @@ type Config struct { // bitrate, if configurable with the chosen input. Raspivid supports quantization. Quantize bool - // FramesPerClip defines the number of packetization units to pack into a clip - // per HTTP send. - FramesPerClip uint - // RTMPURL specifies the Rtmp output destination URL. This must be defined if // RTMP is to be used as an output. RTMPURL string @@ -236,10 +240,16 @@ type Config struct { BurstPeriod uint // BurstPeriod defines the revid burst period in seconds. Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input. Height uint // Height defines the input video height Raspivid input. - Width uint // Width defines the input video width Raspivid input. + Width uint // Width defines the input video width Raspivid input. Bitrate uint // Bitrate specifies the input bitrate for Raspivid input. FlipHorizontal bool // FlipHorizontal flips video horizontally for Raspivid input. FlipVertical bool // FlipVertial flips video vertically for Raspivid input. + + // Ring buffer sizes. + RTMPRBSize int // The number of elements in the RTMP sender ringbuffer. + RTMPRBElementSize int // The element size in bytes of the RTMP sender RingBuffer. + MTSRBSize int // The number of elements in the MTS sender ringbuffer. + MTSRBElementSize int // The element size in bytes of the MTS sender RingBuffer. } // Validate checks for any errors in the config fields and defaults settings @@ -310,11 +320,7 @@ func (c *Config) Validate(r *Revid) error { // c.FramesPerClip = httpFramesPerClip break } - c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for rtmp out", "framesPerClip", defaultFramesPerClip) - c.FramesPerClip = defaultFramesPerClip case HTTP, RTP: - c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for http out", "framesPerClip", httpFramesPerClip) - c.FramesPerClip = httpFramesPerClip default: return errors.New("bad output type defined in config") } @@ -326,11 +332,6 @@ func (c *Config) Validate(r *Revid) error { c.BurstPeriod = defaultBurstPeriod } - if c.FramesPerClip < 1 { - c.Logger.Log(logger.Info, pkg+"no FramesPerClip defined, defaulting", "framesPerClip", defaultFramesPerClip) - c.FramesPerClip = defaultFramesPerClip - } - if c.Rotation > 359 { c.Logger.Log(logger.Warning, pkg+"bad rotate angle, defaulting", "angle", defaultRotation) c.Rotation = defaultRotation @@ -425,6 +426,26 @@ func (c *Config) Validate(r *Revid) error { return errors.New(pkg + "bad auto white balance setting in config") } + if c.RTMPRBSize <= 0 { + c.Logger.Log(logger.Info, pkg+"RTMPRBSize bad or unset, defaulting", "RTMPRBSize", defaultRTMPRBSize) + c.RTMPRBSize = defaultRTMPRBSize + } + + if c.RTMPRBElementSize <= 0 { + c.Logger.Log(logger.Info, pkg+"RTMPRBElementSize bad or unset, defaulting", "RTMPRBElementSize", defaultRTMPRBElementSize) + c.RTMPRBElementSize = defaultRTMPRBElementSize + } + + if c.MTSRBSize <= 0 { + c.Logger.Log(logger.Info, pkg+"MTSRBSize bad or unset, defaulting", "MTSRBSize", defaultMTSRBSize) + c.MTSRBSize = defaultMTSRBSize + } + + if c.MTSRBElementSize <= 0 { + c.Logger.Log(logger.Info, pkg+"MTSRBElementSize bad or unset, defaulting", "MTSRBElementSize", defaultMTSRBElementSize) + c.MTSRBElementSize = defaultMTSRBElementSize + } + return nil } diff --git a/revid/revid.go b/revid/revid.go index c3664e4a..7075ce8f 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -54,18 +54,6 @@ import ( "bitbucket.org/ausocean/utils/logger" ) -// mtsSender ringBuffer sizes. -const ( - mtsRBSize = 1000 - mtsRBElementSize = 100000 -) - -// rtmpSender ringBuffer sizes. -const ( - rtmpRBSize = 500 - rtmpRBElementSize = 200000 -) - // RTMP connection properties. const ( rtmpConnectionMaxTries = 5 @@ -240,7 +228,13 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. for _, out := range r.config.Outputs { switch out { case HTTP: - w = newMtsSender(newHttpSender(r.ns, r.config.Logger.Log), r.config.Logger.Log, mtsRBSize, mtsRBElementSize, 0) + w = newMtsSender( + newHttpSender(r.ns, r.config.Logger.Log), + r.config.Logger.Log, + r.config.MTSRBSize, + r.config.MTSRBElementSize, + 0, + ) mtsSenders = append(mtsSenders, w) case RTP: w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate) @@ -255,7 +249,14 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. } mtsSenders = append(mtsSenders, w) case RTMP: - w, err := newRtmpSender(r.config.RTMPURL, rtmpConnectionTimeout, rtmpConnectionMaxTries, r.config.Logger.Log) + w, err := newRtmpSender( + r.config.RTMPURL, + rtmpConnectionTimeout, + rtmpConnectionMaxTries, + r.config.RTMPRBSize, + r.config.RTMPRBElementSize, + r.config.Logger.Log, + ) if err != nil { r.config.Logger.Log(logger.Warning, pkg+"rtmp connect error", "error", err.Error()) } @@ -504,6 +505,34 @@ func (r *Revid) Update(vars map[string]string) error { default: r.config.Logger.Log(logger.Warning, pkg+"invalid Logging param", "value", value) } + case "RTMPRBSize": + v, err := strconv.Atoi(value) + if err != nil || v < 0 { + r.config.Logger.Log(logger.Warning, pkg+"invalid RTMPRBSize var", "value", value) + break + } + r.config.RTMPRBSize = v + case "RTMPRBElementSize": + v, err := strconv.Atoi(value) + if err != nil || v < 0 { + r.config.Logger.Log(logger.Warning, pkg+"invalid RTMPRBElementSize var", "value", value) + break + } + r.config.RTMPRBElementSize = v + case "MTSRBSize": + v, err := strconv.Atoi(value) + if err != nil || v < 0 { + r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBSize var", "value", value) + break + } + r.config.MTSRBElementSize = v + case "MTSRBElementSize": + v, err := strconv.Atoi(value) + if err != nil || v < 0 { + r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBElementSize var", "value", value) + break + } + r.config.MTSRBElementSize = v } } r.config.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.config)) diff --git a/revid/senders.go b/revid/senders.go index ea1f8447..7de06342 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -261,7 +261,7 @@ type rtmpSender struct { wg sync.WaitGroup } -func newRtmpSender(url string, timeout uint, retries int, log func(lvl int8, msg string, args ...interface{})) (*rtmpSender, error) { +func newRtmpSender(url string, timeout uint, retries, rbSize, rbElementSize int, log func(lvl int8, msg string, args ...interface{})) (*rtmpSender, error) { var conn *rtmp.Conn var err error for n := 0; n < retries; n++ { @@ -280,7 +280,7 @@ func newRtmpSender(url string, timeout uint, retries int, log func(lvl int8, msg timeout: timeout, retries: retries, log: log, - ring: ring.NewBuffer(rtmpRBSize, rtmpRBElementSize, 0), + ring: ring.NewBuffer(rbSize, rbElementSize, 0), done: make(chan struct{}), } s.wg.Add(1) diff --git a/revid/senders_test.go b/revid/senders_test.go index eeba6a2b..d92f19f4 100644 --- a/revid/senders_test.go +++ b/revid/senders_test.go @@ -133,7 +133,7 @@ func TestMtsSenderSegment(t *testing.T) { // Create ringBuffer, sender, sender and the MPEGTS encoder. const numberOfClips = 11 dst := &destination{t: t, done: make(chan struct{}), doneAt: numberOfClips} - sender := newMtsSender(dst, (*dummyLogger)(t).log, mtsRBSize, mtsRBElementSize, 0) + sender := newMtsSender(dst, (*dummyLogger)(t).log, defaultMTSRBSize, defaultMTSRBElementSize, 0) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) // Turn time based PSI writing off for encoder. @@ -211,7 +211,7 @@ func TestMtsSenderFailedSend(t *testing.T) { // Create destination, the mtsSender and the mtsEncoder const clipToFailAt = 3 dst := &destination{t: t, testFails: true, failAt: clipToFailAt, done: make(chan struct{})} - sender := newMtsSender(dst, (*dummyLogger)(t).log, mtsRBSize, mtsRBElementSize, 0) + sender := newMtsSender(dst, (*dummyLogger)(t).log, defaultMTSRBSize, defaultMTSRBElementSize, 0) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) // Turn time based PSI writing off for encoder and send PSI every 10 packets. @@ -291,7 +291,7 @@ func TestMtsSenderDiscontinuity(t *testing.T) { // Create destination, the mtsSender and the mtsEncoder. const clipToDelay = 3 dst := &destination{t: t, sendDelay: 10 * time.Millisecond, delayAt: clipToDelay, done: make(chan struct{})} - sender := newMtsSender(dst, (*dummyLogger)(t).log, 1, mtsRBElementSize, 0) + sender := newMtsSender(dst, (*dummyLogger)(t).log, 1, defaultMTSRBElementSize, 0) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) // Turn time based PSI writing off for encoder. From 61274a18d512eeb01b746e5741b020ac15f1b9d0 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 8 Aug 2019 13:25:24 +0930 Subject: [PATCH 15/58] revid-cli: removed reference to framesPerClip which doesn't exist anymore --- cmd/revid-cli/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 0c8a6af9..ff53a176 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -122,7 +122,6 @@ func handleFlags() revid.Config { httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts") verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No") horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No") - framesPerClipPtr = flag.Uint("FramesPerClip", 0, "Number of frames per clip sent") bitratePtr = flag.Uint("Bitrate", 0, "Bitrate of recorded video") heightPtr = flag.Uint("Height", 0, "Height in pixels") widthPtr = flag.Uint("Width", 0, "Width in pixels") @@ -241,7 +240,6 @@ func handleFlags() revid.Config { cfg.Rotation = *rotationPtr cfg.FlipHorizontal = *horizontalFlipPtr cfg.FlipVertical = *verticalFlipPtr - cfg.FramesPerClip = *framesPerClipPtr cfg.RTMPURL = *rtmpUrlPtr cfg.Bitrate = *bitratePtr cfg.OutputPath = *outputPathPtr From d32eac7394c274a286dff1bb23ae5bb928770c33 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 8 Aug 2019 14:39:55 +0930 Subject: [PATCH 16/58] Moved audio support to OS-specific files. --- revid/audio_linux.go | 51 ++++++++++++++++++++++++++++++++++++++++++++ revid/revid.go | 46 --------------------------------------- 2 files changed, 51 insertions(+), 46 deletions(-) create mode 100644 revid/audio_linux.go diff --git a/revid/audio_linux.go b/revid/audio_linux.go new file mode 100644 index 00000000..3224ce68 --- /dev/null +++ b/revid/audio_linux.go @@ -0,0 +1,51 @@ +package revid + +import ( + "bitbucket.org/ausocean/av/container/mts" + "bitbucket.org/ausocean/av/input/audio" +) + +// startAudioDevice is used to start capturing audio from an audio device and processing it. +// It returns a function that can be used to stop the device and any errors that occur. +func (r *Revid) startAudioDevice() (func() error, error) { + // Create audio device. + ac := &audio.Config{ + SampleRate: r.config.SampleRate, + Channels: r.config.Channels, + RecPeriod: r.config.RecPeriod, + BitDepth: r.config.BitDepth, + Codec: r.config.InputCodec, + } + mts.Meta.Add("sampleRate", strconv.Itoa(r.config.SampleRate)) + mts.Meta.Add("channels", strconv.Itoa(r.config.Channels)) + mts.Meta.Add("period", fmt.Sprintf("%.6f", r.config.RecPeriod)) + mts.Meta.Add("bitDepth", strconv.Itoa(r.config.BitDepth)) + switch r.config.InputCodec { + case codecutil.PCM: + mts.Meta.Add("codec", "pcm") + case codecutil.ADPCM: + mts.Meta.Add("codec", "adpcm") + default: + r.config.Logger.Log(logger.Fatal, pkg+"no audio codec set in config") + } + + ai, err := audio.NewDevice(ac, r.config.Logger) + if err != nil { + r.config.Logger.Log(logger.Fatal, pkg+"failed to create audio device", "error", err.Error()) + } + + // Start audio device + err = ai.Start() + if err != nil { + r.config.Logger.Log(logger.Fatal, pkg+"failed to start audio device", "error", err.Error()) + } + + // Process output from audio device. + r.config.ChunkSize = ai.ChunkSize() + r.wg.Add(1) + go r.processFrom(ai, time.Duration(float64(time.Second)/r.config.WriteRate)) + return func() error { + ai.Stop() + return nil + }, nil +} diff --git a/revid/revid.go b/revid/revid.go index 410db7a2..75c9104e 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -45,7 +45,6 @@ import ( "bitbucket.org/ausocean/av/codec/h265" "bitbucket.org/ausocean/av/container/flv" "bitbucket.org/ausocean/av/container/mts" - "bitbucket.org/ausocean/av/input/audio" "bitbucket.org/ausocean/av/protocol/rtcp" "bitbucket.org/ausocean/av/protocol/rtp" "bitbucket.org/ausocean/av/protocol/rtsp" @@ -625,51 +624,6 @@ func (r *Revid) setupInputForFile() (func() error, error) { return func() error { return f.Close() }, nil } -// startAudioDevice is used to start capturing audio from an audio device and processing it. -// It returns a function that can be used to stop the device and any errors that occur. -func (r *Revid) startAudioDevice() (func() error, error) { - // Create audio device. - ac := &audio.Config{ - SampleRate: r.config.SampleRate, - Channels: r.config.Channels, - RecPeriod: r.config.RecPeriod, - BitDepth: r.config.BitDepth, - Codec: r.config.InputCodec, - } - mts.Meta.Add("sampleRate", strconv.Itoa(r.config.SampleRate)) - mts.Meta.Add("channels", strconv.Itoa(r.config.Channels)) - mts.Meta.Add("period", fmt.Sprintf("%.6f", r.config.RecPeriod)) - mts.Meta.Add("bitDepth", strconv.Itoa(r.config.BitDepth)) - switch r.config.InputCodec { - case codecutil.PCM: - mts.Meta.Add("codec", "pcm") - case codecutil.ADPCM: - mts.Meta.Add("codec", "adpcm") - default: - r.config.Logger.Log(logger.Fatal, pkg+"no audio codec set in config") - } - - ai, err := audio.NewDevice(ac, r.config.Logger) - if err != nil { - r.config.Logger.Log(logger.Fatal, pkg+"failed to create audio device", "error", err.Error()) - } - - // Start audio device - err = ai.Start() - if err != nil { - r.config.Logger.Log(logger.Fatal, pkg+"failed to start audio device", "error", err.Error()) - } - - // Process output from audio device. - r.config.ChunkSize = ai.ChunkSize() - r.wg.Add(1) - go r.processFrom(ai, time.Duration(float64(time.Second)/r.config.WriteRate)) - return func() error { - ai.Stop() - return nil - }, nil -} - // startRTSPCamera uses RTSP to request an RTP stream from an IP camera. An RTP // client is created from which RTP packets containing either h264/h265 can read // by the selected lexer. From 2597556e6d5f8b96188c1bbc8d6296e2f2ca9ade Mon Sep 17 00:00:00 2001 From: Alan Noble Date: Thu, 8 Aug 2019 14:57:09 +0930 Subject: [PATCH 17/58] Add missing packages for Linux. --- revid/audio_linux.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/revid/audio_linux.go b/revid/audio_linux.go index 3224ce68..4df3500b 100644 --- a/revid/audio_linux.go +++ b/revid/audio_linux.go @@ -1,8 +1,14 @@ package revid import ( + "fmt" + "strconv" + "time" + + "bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/av/container/mts" "bitbucket.org/ausocean/av/input/audio" + "bitbucket.org/ausocean/utils/logger" ) // startAudioDevice is used to start capturing audio from an audio device and processing it. From dec7bd4870d42d5ca232c7c1fc42682a811dd3b2 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 8 Aug 2019 15:01:16 +0930 Subject: [PATCH 18/58] Initial revision. --- revid/audio_windows.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 revid/audio_windows.go diff --git a/revid/audio_windows.go b/revid/audio_windows.go new file mode 100644 index 00000000..d8b1c95e --- /dev/null +++ b/revid/audio_windows.go @@ -0,0 +1,7 @@ +// startAudioDevice is used to start capturing audio from an audio device +// TODO: Implement on Windows. +package revid + +func (r *Revid) startAudioDevice() (func() error, error) { + panic("Audio not implemented on Windows") +} From ab6c789c34be349464dc861b61f3e3de764dee06 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 8 Aug 2019 15:23:09 +0930 Subject: [PATCH 19/58] Added license. --- revid/audio_linux.go | 18 ++++++++++++++++++ revid/audio_windows.go | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/revid/audio_linux.go b/revid/audio_linux.go index 4df3500b..831ef077 100644 --- a/revid/audio_linux.go +++ b/revid/audio_linux.go @@ -1,3 +1,21 @@ +/* +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean) + + This is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + package revid import ( diff --git a/revid/audio_windows.go b/revid/audio_windows.go index d8b1c95e..bdc61da8 100644 --- a/revid/audio_windows.go +++ b/revid/audio_windows.go @@ -1,3 +1,21 @@ +/* +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean) + + This is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + // startAudioDevice is used to start capturing audio from an audio device // TODO: Implement on Windows. package revid From 67e50295c92b623da855dbac14800e922972c8e8 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 9 Aug 2019 15:34:29 +0930 Subject: [PATCH 20/58] revid: fixed assignment of MTSRBSize --- revid/revid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/revid/revid.go b/revid/revid.go index 7075ce8f..8da1854a 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -525,7 +525,7 @@ func (r *Revid) Update(vars map[string]string) error { r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBSize var", "value", value) break } - r.config.MTSRBElementSize = v + r.config.MTSRBSize = v case "MTSRBElementSize": v, err := strconv.Atoi(value) if err != nil || v < 0 { From c14bdb6a8d90d38194e32b9e9993c8a6f2c509a3 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 9 Aug 2019 17:16:03 +0930 Subject: [PATCH 21/58] Use ausocean/iot v1.2.6. --- go.mod | 2 +- go.sum | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 37a7a31f..d35601e0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module bitbucket.org/ausocean/av go 1.12 require ( - bitbucket.org/ausocean/iot v1.2.5 + bitbucket.org/ausocean/iot v1.2.6 bitbucket.org/ausocean/utils v1.2.6 github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 diff --git a/go.sum b/go.sum index 5ee9f294..692bf5a1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ +bitbucket.org/ausocean/av v0.0.0-20190416003121-6ee286e98874/go.mod h1:DxZEprrNNQ2slHKAQVUHryDaWc5CbjxyHAvomhzg+AE= bitbucket.org/ausocean/iot v1.2.4 h1:M/473iQ0d4q+76heerjAQuqXzQyc5dZ3F7Bfuq6X7q4= bitbucket.org/ausocean/iot v1.2.4/go.mod h1:5HVLgPHccW2PxS7WDUQO6sKWMgk3Vfze/7d5bHs8EWU= bitbucket.org/ausocean/iot v1.2.5 h1:udD5X4oXUuKwdjO7bcq4StcDdjP8fJa2L0FnJJwF+6Q= bitbucket.org/ausocean/iot v1.2.5/go.mod h1:dOclxXkdxAQGWO7Y5KcP1wpNfxg9oKUA2VqjJ3Le4RA= +bitbucket.org/ausocean/iot v1.2.6 h1:KAAY1KZDbyOpoKajT1dM8BawupHiW9hUOelseSV1Ptc= +bitbucket.org/ausocean/iot v1.2.6/go.mod h1:71AYHh8yGZ8XyzDBskwIWMF+8E8ORagXpXE24wlhoE0= +bitbucket.org/ausocean/utils v0.0.0-20190408050157-66d3b4d4041e/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= bitbucket.org/ausocean/utils v1.2.4/go.mod h1:5JIXFTAMMNl5Ob79tpZfDCJ+gOO8rj7v4ORj56tHZpw= bitbucket.org/ausocean/utils v1.2.6 h1:JN66APCV+hu6GebIHSu2KSywhLym4vigjSz5+fB0zXc= bitbucket.org/ausocean/utils v1.2.6/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= @@ -49,6 +53,8 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/sys v0.0.0-20190305064518-30e92a19ae4a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 2470ce95ad8a1905d089aedfe58147e8b3a6befa Mon Sep 17 00:00:00 2001 From: Saxon Milton Date: Fri, 9 Aug 2019 09:41:47 +0000 Subject: [PATCH 22/58] revid-cli: setting default InputCodec to codecutil.H264 --- cmd/revid-cli/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index ff53a176..c98513cc 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -108,7 +108,7 @@ func handleFlags() revid.Config { var ( cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") - inputCodecPtr = flag.String("InputCodec", "", "The codec of the input: H264, Mjpeg, PCM, ADPCM") + inputCodecPtr = flag.String("InputCodec", "H264", "The codec of the input: H264, Mjpeg, PCM, ADPCM") inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP") rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.") quantizePtr = flag.Bool("Quantize", false, "Quantize input (non-variable bitrate)") @@ -203,7 +203,6 @@ func handleFlags() revid.Config { cfg.InputCodec = codecutil.PCM case "ADPCM": cfg.InputCodec = codecutil.ADPCM - case "": default: log.Log(logger.Error, pkg+"bad input codec argument") } From b41ae4bcd3e8b217ee86f1f37cd9d1f419e518de Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 9 Aug 2019 19:18:52 +0930 Subject: [PATCH 23/58] revid: fixed config check bug --- revid/config.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/revid/config.go b/revid/config.go index a995d238..82e06115 100644 --- a/revid/config.go +++ b/revid/config.go @@ -283,10 +283,6 @@ func (c *Config) Validate(r *Revid) error { c.Quantization = defaultQuantization } - if (c.Bitrate > 0 && c.Quantize) || (c.Bitrate == 0 && !c.Quantize) { - return errors.New("bad bitrate and quantization combination for H264 input") - } - case codecutil.MJPEG: if c.Quantization > 0 || c.Bitrate == 0 { return errors.New("bad bitrate or quantization for mjpeg input") From cd83cf9e8e631818af6ccf0530074e8df07607a0 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 14 Aug 2019 14:08:24 +0930 Subject: [PATCH 24/58] codec/h264/h2646dec: fixed up binarization tables and added mbTypeBinarization function along with test TestMbTypeBinarization. --- codec/h264/h264dec/cabac.go | 277 +++++++++++++++++-------------- codec/h264/h264dec/cabac_test.go | 42 ++++- codec/h264/h264dec/mbtype.go | 32 ++++ codec/h264/h264dec/slice.go | 9 + 4 files changed, 230 insertions(+), 130 deletions(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 8b3a6e4c..12a07ddc 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -100,13 +100,7 @@ func CondTermFlag(mbAddr, mbSkipFlag int) int { } // s9.3.3 p 278: Returns the value of the syntax element -func (bin *Binarization) Decode(sliceContext *SliceContext, b *bits.BitReader, rbsp []byte) { - if bin.SyntaxElement == "MbType" { - bin.binString = binIdxMbMap[sliceContext.Slice.Data.SliceTypeName][sliceContext.Slice.Data.MbType] - } else { - logger.Printf("TODO: no means to find binString for %s\n", bin.SyntaxElement) - } -} +func (bin *Binarization) Decode(sliceContext *SliceContext, b *bits.BitReader, rbsp []byte) {} // 9.3.3.1.1 : returns ctxIdxInc func Decoder9_3_3_1_1_1(condTermFlagA, condTermFlagB int) int { @@ -178,135 +172,160 @@ func initCabac(binarization *Binarization, context *SliceContext) *CABAC { } } -// Table 9-36, 9-37 -// func BinIdx(mbType int, sliceTypeName string) []int { -// Map of SliceTypeName[MbType][]int{binString} -// {"SliceTypeName": {MbTypeCode: []BinString}} +// Binarizations for macroblock types in slice types. var ( - binIdxMbMap = map[string]map[int][]int{ - "I": { - 0: {0}, - 1: {1, 0, 0, 0, 0, 0}, - 2: {1, 0, 0, 0, 0, 1}, - 3: {1, 0, 0, 0, 1, 0}, - 4: {1, 0, 0, 0, 1, 1}, - 5: {1, 0, 0, 1, 0, 0, 0}, - 6: {1, 0, 0, 1, 0, 0, 1}, - 7: {1, 0, 0, 1, 0, 1, 0}, - 8: {1, 0, 0, 1, 0, 1, 1}, - 9: {1, 0, 0, 1, 1, 0, 0}, - 10: {1, 0, 0, 1, 1, 0, 1}, - 11: {1, 0, 0, 1, 1, 1, 0}, - 12: {1, 0, 0, 1, 1, 1, 1}, - 13: {1, 0, 1, 0, 0, 0}, - 14: {1, 0, 1, 0, 0, 1}, - 15: {1, 0, 1, 0, 1, 0}, - 16: {1, 0, 1, 0, 1, 1}, - 17: {1, 0, 1, 1, 0, 0, 0}, - 18: {1, 0, 1, 1, 0, 0, 1}, - 19: {1, 0, 1, 1, 0, 1, 0}, - 20: {1, 0, 1, 1, 0, 1, 1}, - 21: {1, 0, 1, 1, 1, 0, 0}, - 22: {1, 0, 1, 1, 1, 0, 1}, - 23: {1, 0, 1, 1, 1, 1, 0}, - 24: {1, 0, 1, 1, 1, 1, 1}, - 25: {1, 1}, - }, - // Table 9-37 - "P": { - 0: {0, 0, 0}, - 1: {0, 1, 1}, - 2: {0, 1, 0}, - 3: {0, 0, 1}, - 4: {}, - 5: {1}, - 6: {1}, - 7: {1}, - 8: {1}, - 9: {1}, - 10: {1}, - 11: {1}, - 12: {1}, - 13: {1}, - 14: {1}, - 15: {1}, - 16: {1}, - 17: {1}, - 18: {1}, - 19: {1}, - 20: {1}, - 21: {1}, - 22: {1}, - 23: {1}, - 24: {1}, - 25: {1}, - 26: {1}, - 27: {1}, - 28: {1}, - 29: {1}, - 30: {1}, - }, - // Table 9-37 - "SP": { - 0: {0, 0, 0}, - 1: {0, 1, 1}, - 2: {0, 1, 0}, - 3: {0, 0, 1}, - 4: {}, - 5: {1}, - 6: {1}, - 7: {1}, - 8: {1}, - 9: {1}, - 10: {1}, - 11: {1}, - 12: {1}, - 13: {1}, - 14: {1}, - 15: {1}, - 16: {1}, - 17: {1}, - 18: {1}, - 19: {1}, - 20: {1}, - 21: {1}, - 22: {1}, - 23: {1}, - 24: {1}, - 25: {1}, - 26: {1}, - 27: {1}, - 28: {1}, - 29: {1}, - 30: {1}, - }, - // TODO: B Slice table 9-37 + // binOfIMBTypes provides binarization strings for values of macroblock + // type in I slices as defined in table 9-36 of the specifications. + binOfIMBTypes = [numOfIMBTypes][]int{ + 0: {0}, + 1: {1, 0, 0, 0, 0, 0}, + 2: {1, 0, 0, 0, 0, 1}, + 3: {1, 0, 0, 0, 1, 0}, + 4: {1, 0, 0, 0, 1, 1}, + 5: {1, 0, 0, 1, 0, 0, 0}, + 6: {1, 0, 0, 1, 0, 0, 1}, + 7: {1, 0, 0, 1, 0, 1, 0}, + 8: {1, 0, 0, 1, 0, 1, 1}, + 9: {1, 0, 0, 1, 1, 0, 0}, + 10: {1, 0, 0, 1, 1, 0, 1}, + 11: {1, 0, 0, 1, 1, 1, 0}, + 12: {1, 0, 0, 1, 1, 1, 1}, + 13: {1, 0, 1, 0, 0, 0}, + 14: {1, 0, 1, 0, 0, 1}, + 15: {1, 0, 1, 0, 1, 0}, + 16: {1, 0, 1, 0, 1, 1}, + 17: {1, 0, 1, 1, 0, 0, 0}, + 18: {1, 0, 1, 1, 0, 0, 1}, + 19: {1, 0, 1, 1, 0, 1, 0}, + 20: {1, 0, 1, 1, 0, 1, 1}, + 21: {1, 0, 1, 1, 1, 0, 0}, + 22: {1, 0, 1, 1, 1, 0, 1}, + 23: {1, 0, 1, 1, 1, 1, 0}, + 24: {1, 0, 1, 1, 1, 1, 1}, + 25: {1, 1}, } - // Map of SliceTypeName[SubMbType][]int{binString} - binIdxSubMbMap = map[string]map[int][]int{ - "P": { - 0: {1}, - 1: {0, 0}, - 2: {0, 1, 1}, - 3: {0, 1, 0}, - }, - "SP": { - 0: {1}, - 1: {0, 0}, - 2: {0, 1, 1}, - 3: {0, 1, 0}, - }, - // TODO: B slice table 9-38 + // binOfPOrSPMBTypes provides binarization strings for values of macroblock + // type in P or SP slices as defined in table 9-37 of the specifications. + // NB: binarization of macroblock types 5 to 30 is 1 and not included here. + binOfPOrSPMBTypes = [5][]int{ + 0: {0, 0, 0}, + 1: {0, 1, 1}, + 2: {0, 1, 0}, + 3: {0, 0, 1}, + 4: {}, } - // Table 9-36, 9-37 - MbBinIdx = []int{1, 2, 3, 4, 5, 6} - - // Table 9-38 - SubMbBinIdx = []int{0, 1, 2, 3, 4, 5} + // binOfBMBTypes provides binarization strings for values of macroblock + // type in B slice as defined in table 9-37 of the specifications. + // NB: binarization of macroblock types 23 to 48 is 111101 and is not + // included here. + binOfBMBTypes = [23][]int{ + 0: {0}, + 1: {1, 0, 0}, + 2: {1, 0, 1}, + 3: {1, 1, 0, 0, 0, 0}, + 4: {1, 1, 0, 0, 0, 1}, + 5: {1, 1, 0, 0, 1, 0}, + 6: {1, 1, 0, 0, 1, 1}, + 7: {1, 1, 0, 1, 0, 0}, + 8: {1, 1, 0, 1, 0, 1}, + 9: {1, 1, 0, 1, 1, 0}, + 10: {1, 1, 0, 1, 1, 1}, + 11: {1, 1, 1, 1, 1, 0}, + 12: {1, 1, 1, 0, 0, 0, 0}, + 13: {1, 1, 1, 0, 0, 0, 1}, + 14: {1, 1, 1, 0, 0, 1, 0}, + 15: {1, 1, 1, 0, 0, 1, 1}, + 16: {1, 1, 1, 0, 1, 0, 0}, + 17: {1, 1, 1, 0, 1, 0, 1}, + 18: {1, 1, 1, 0, 1, 1, 0}, + 19: {1, 1, 1, 0, 1, 1, 1}, + 20: {1, 1, 1, 1, 0, 0, 0}, + 21: {1, 1, 1, 1, 0, 0, 1}, + 22: {1, 1, 1, 1, 1, 1}, + } ) +// Binarizations for sub-macroblock types in slice types. +var ( + // binOfPorSPSubMBTypes provides binarization strings for values of sub-macroblock + // type in P or SP slices as defined in table 9-38 of the specifications. + binOfPOrSPSubMBTypes = [4][]int{ + 0: {1}, + 1: {0, 0}, + 2: {0, 1, 1}, + 3: {0, 1, 0}, + } + + // binOfBSubMBTypes provides binarization strings for values of sub-macroblock + // type in B slices as defined in table 9-38 of the specifications. + binOfBSubMBTypes = [numOfBSubMBTypes][]int{ + 0: {1}, + 1: {1, 0, 0}, + 2: {1, 0, 1}, + 3: {1, 1, 0, 0, 0}, + 4: {1, 1, 0, 0, 1}, + 5: {1, 1, 0, 1, 0}, + 6: {1, 1, 0, 1, 1}, + 7: {1, 1, 1, 0, 0, 0}, + 8: {1, 1, 1, 0, 0, 1}, + 9: {1, 1, 1, 0, 1, 0}, + 10: {1, 1, 1, 0, 1, 1}, + 11: {1, 1, 1, 1, 0}, + 12: {1, 1, 1, 1, 1}, + } +) + +// Errors used by mbTypeBinarization. +var ( + errBadMbType = errors.New("macroblock type outside of valid range") + errInvalidSliceType = errors.New("slice type does not exist") +) + +// mbTypeBinarization returns the macroblock type binarization for the given +// macroblock type value and slice type using the process defined in section +// 9.3.2.5 of the specifications. +func mbTypeBinarization(v, slice int) ([]int, error) { + switch slice { + case sliceTypeI: + if v < minIMbType || v > maxIMbType { + return nil, errBadMbType + } + return binOfIMBTypes[v], nil + + case sliceTypeSI: + if v < minSIMbType || v > maxSIMbType { + return nil, errBadMbType + } + if v == sliceTypeSI { + return []int{0}, nil + } + return append([]int{1}, binOfIMBTypes[v-1]...), nil + + case sliceTypeP, sliceTypeSP: + if v < minPOrSPMbType || v > maxPOrSPMbType || v == P8x8ref0 { + return nil, errBadMbType + } + if v < 5 { + return binOfPOrSPMBTypes[v], nil + } + return append([]int{1}, binOfIMBTypes[v-5]...), nil + + case sliceTypeB: + if v < minBMbType || v > maxBMbType { + return nil, errBadMbType + } + if v < 23 { + return binOfBMBTypes[v], nil + } + return append([]int{1, 1, 1, 1, 0, 1}, binOfIMBTypes[v-23]...), nil + + default: + return nil, errInvalidSliceType + } +} + // Table 9-34 type MaxBinIdxCtx struct { // When false, Prefix is the MaxBinIdxCtx diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 66d82a93..26350f18 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -1,6 +1,46 @@ package h264dec -import "testing" +import ( + "reflect" + "testing" +) + +func TestMbTypeBinarization(t *testing.T) { + tests := []struct { + v, slice int + want []int + err error + }{ + {v: 6, slice: sliceTypeI, want: []int{1, 0, 0, 1, 0, 0, 1}}, + {v: 26, slice: sliceTypeI, err: errBadMbType}, + {v: -1, slice: sliceTypeI, err: errBadMbType}, + {v: 4, slice: sliceTypeSI, want: []int{0}}, + {v: 6, slice: sliceTypeSI, want: []int{1, 1, 0, 0, 1, 0, 0, 0}}, + {v: 0, slice: sliceTypeSI, err: errBadMbType}, + {v: 27, slice: sliceTypeSI, err: errBadMbType}, + {v: 2, slice: sliceTypeP, want: []int{0, 1, 0}}, + {v: 3, slice: sliceTypeSP, want: []int{0, 0, 1}}, + {v: 7, slice: sliceTypeP, want: []int{1, 1, 0, 0, 0, 0, 1}}, + {v: 7, slice: sliceTypeSP, want: []int{1, 1, 0, 0, 0, 0, 1}}, + {v: -1, slice: sliceTypeP, err: errBadMbType}, + {v: 31, slice: sliceTypeP, err: errBadMbType}, + {v: 8, slice: sliceTypeB, want: []int{1, 1, 0, 1, 0, 1}}, + {v: 30, slice: sliceTypeB, want: []int{1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0}}, + {v: -1, slice: sliceTypeB, err: errBadMbType}, + {v: 49, slice: sliceTypeB, err: errBadMbType}, + } + + for i, test := range tests { + got, err := mbTypeBinarization(test.v, test.slice) + 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(got, test.want) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want) + } + } +} var ctxIdxTests = []struct { binIdx int diff --git a/codec/h264/h264dec/mbtype.go b/codec/h264/h264dec/mbtype.go index e335e756..02466d45 100644 --- a/codec/h264/h264dec/mbtype.go +++ b/codec/h264/h264dec/mbtype.go @@ -4,6 +4,38 @@ import ( "errors" ) +// P slice macroblock types. +const ( + P8x8ref0 = 4 +) + +// Number of macroblock types for each slice type. +const ( + numOfIMBTypes = 26 + numOfPMBTypes = 31 + numOfSPMBTypes = 31 + numOfBMBTypes = 49 +) + +// Number of sub macroblock types for each slice type. +const ( + numOfPSubMBTypes = 4 + numOfSPSubMBTypes = 4 + numOfBSubMBTypes = 13 +) + +// Min and max macroblock types for slice types. +const ( + minIMbType = 0 + maxIMbType = 25 + minPOrSPMbType = 0 + maxPOrSPMbType = 30 + minBMbType = 0 + maxBMbType = 48 + minSIMbType = 1 + maxSIMbType = 26 +) + const MB_TYPE_INFERRED = 1000 var ( diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 77c6091b..ee8ed35e 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -9,6 +9,15 @@ import ( "github.com/pkg/errors" ) +// Slice types. +const ( + sliceTypeP = 0 + sliceTypeB = 1 + sliceTypeI = 2 + sliceTypeSP = 3 + sliceTypeSI = 4 +) + // Chroma formats as defined in section 6.2, tab 6-1. const ( chromaMonochrome = iota From 6d06fab39f6d27366bc3eb7f60322574e01e63d4 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 15 Aug 2019 00:30:17 +0930 Subject: [PATCH 25/58] codec/h264/h264dec: added subMbTypeBinarization and TestSubMbTypeBinarization --- codec/h264/h264dec/cabac.go | 31 ++++++++++++++++++++++++++++--- codec/h264/h264dec/cabac_test.go | 27 +++++++++++++++++++++++++++ codec/h264/h264dec/mbtype.go | 20 ++++++++++++-------- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 12a07ddc..21ae8316 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -279,8 +279,8 @@ var ( // Errors used by mbTypeBinarization. var ( - errBadMbType = errors.New("macroblock type outside of valid range") - errInvalidSliceType = errors.New("slice type does not exist") + errBadMbType = errors.New("macroblock type outside of valid range") + errBadMbSliceType = errors.New("bad slice type for macroblock") ) // mbTypeBinarization returns the macroblock type binarization for the given @@ -322,7 +322,32 @@ func mbTypeBinarization(v, slice int) ([]int, error) { return append([]int{1, 1, 1, 1, 0, 1}, binOfIMBTypes[v-23]...), nil default: - return nil, errInvalidSliceType + return nil, errBadMbSliceType + } +} + +// Error used by subMbTypeBinarization. +var errBadSubMbSliceType = errors.New("bad slice type for sub-macroblock") + +// subMbTypeBinarization returns the binarization of a sub-macroblock type +// given the slice in which it is in using the process defined in section +// 9.3.2.5 of the specifications. +func subMbTypeBinarization(v, slice int) ([]int, error) { + switch slice { + case sliceTypeP, sliceTypeSP: + if v < minPOrSPSubMbType || v > maxPOrSPSubMbType { + return nil, errBadMbType + } + return binOfPOrSPSubMBTypes[v], nil + + case sliceTypeB: + if v < minBSubMbType || v > maxBSubMbType { + return nil, errBadMbType + } + return binOfBSubMBTypes[v], nil + + default: + return nil, errBadSubMbSliceType } } diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 26350f18..0d8adaa4 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -28,6 +28,7 @@ func TestMbTypeBinarization(t *testing.T) { {v: 30, slice: sliceTypeB, want: []int{1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0}}, {v: -1, slice: sliceTypeB, err: errBadMbType}, {v: 49, slice: sliceTypeB, err: errBadMbType}, + {v: 6, slice: 20, err: errBadMbSliceType}, } for i, test := range tests { @@ -42,6 +43,32 @@ func TestMbTypeBinarization(t *testing.T) { } } +func TestSubMbTypeBinarization(t *testing.T) { + tests := []struct { + v, slice int + want []int + err error + }{ + {v: 2, slice: sliceTypeP, want: []int{0, 1, 1}}, + {v: 2, slice: sliceTypeSP, want: []int{0, 1, 1}}, + {v: -1, slice: sliceTypeSP, err: errBadMbType}, + {v: 4, slice: sliceTypeSP, err: errBadMbType}, + {v: 9, slice: sliceTypeB, want: []int{1, 1, 1, 0, 1, 0}}, + {v: 9, slice: 40, err: errBadSubMbSliceType}, + } + + for i, test := range tests { + got, err := subMbTypeBinarization(test.v, test.slice) + 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(got, test.want) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want) + } + } +} + var ctxIdxTests = []struct { binIdx int maxBinIdxCtx int diff --git a/codec/h264/h264dec/mbtype.go b/codec/h264/h264dec/mbtype.go index 02466d45..eb716c17 100644 --- a/codec/h264/h264dec/mbtype.go +++ b/codec/h264/h264dec/mbtype.go @@ -26,14 +26,18 @@ const ( // Min and max macroblock types for slice types. const ( - minIMbType = 0 - maxIMbType = 25 - minPOrSPMbType = 0 - maxPOrSPMbType = 30 - minBMbType = 0 - maxBMbType = 48 - minSIMbType = 1 - maxSIMbType = 26 + minIMbType = 0 + maxIMbType = 25 + minPOrSPMbType = 0 + maxPOrSPMbType = 30 + minBMbType = 0 + maxBMbType = 48 + minSIMbType = 1 + maxSIMbType = 26 + minPOrSPSubMbType = 0 + maxPOrSPSubMbType = 3 + minBSubMbType = 0 + maxBSubMbType = 12 ) const MB_TYPE_INFERRED = 1000 From 806264cd4272334567abd466ab092f53210b5057 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 19 Aug 2019 16:24:53 +0930 Subject: [PATCH 26/58] codec/h264/h264dec/slice.go: added comment to slice type consts to reference specifications --- codec/h264/h264dec/slice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index ee8ed35e..5295d27c 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -9,7 +9,7 @@ import ( "github.com/pkg/errors" ) -// Slice types. +// Slice types as defined by table 7-6 in specifications. const ( sliceTypeP = 0 sliceTypeB = 1 From 336aa7310720acd471d8292468847e6b6a721999 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 11 Aug 2019 14:32:10 +0930 Subject: [PATCH 27/58] codec/h264/h264dec: wrote UnaryBinarization function and TestUnaryBinarization --- codec/h264/h264dec/cabac.go | 18 ++++++++++++++++++ codec/h264/h264dec/cabac_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 21ae8316..9e353b5f 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -733,3 +733,21 @@ func CtxIdx(binIdx, maxBinIdxCtx, ctxIdxOffset int) int { return ctxIdx } + +// Error used by unaryBinarization. +var errNegativeSyntaxVal = errors.New("cannot get unary binarization of negative value") + +// unaryBinarization returns the unary binarization of a syntax element having +// value v, as specified by setion 9.3.2.1 in the specifications. +func unaryBinarization(v int) ([]int, error) { + if v < 0 { + return nil, errNegativeSyntaxVal + } + r := make([]int, v+1) + for i := 0; i <= v; i++ { + if i < v { + r[i] = 1 + } + } + return r, nil +} diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 0d8adaa4..9bc3a35a 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -186,3 +186,31 @@ func TestCtxIdx(t *testing.T) { } } } + +func TestUnaryBinarization(t *testing.T) { + // Test data has been extracted from table 9-35 of the specifications. + tests := []struct { + in int + want []int + err error + }{ + {in: 0, want: []int{0}, err: nil}, + {in: 1, want: []int{1, 0}, err: nil}, + {in: 2, want: []int{1, 1, 0}, err: nil}, + {in: 3, want: []int{1, 1, 1, 0}, err: nil}, + {in: 4, want: []int{1, 1, 1, 1, 0}, err: nil}, + {in: 5, want: []int{1, 1, 1, 1, 1, 0}, err: nil}, + {in: -3, want: nil, err: errNegativeSyntaxVal}, + } + + for i, test := range tests { + got, err := unaryBinarization(test.in) + 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) + } + } +} From a4130404fd773837dfa51be16359f75f8878e7ee Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 11 Aug 2019 22:03:25 +0930 Subject: [PATCH 28/58] 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))) +} From 4e29a70b7811a475f02fb7c37432be6dd6171bb5 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 15 Aug 2019 01:16:28 +0930 Subject: [PATCH 29/58] codec/h264/h264dec: made maxi faster and more readable --- codec/h264/h264dec/helpers.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codec/h264/h264dec/helpers.go b/codec/h264/h264dec/helpers.go index d9780c29..c15b2416 100644 --- a/codec/h264/h264dec/helpers.go +++ b/codec/h264/h264dec/helpers.go @@ -44,7 +44,10 @@ func binToSlice(s string) ([]byte, error) { } func maxi(a, b int) int { - return int(math.Max(float64(a), float64(b))) + if a > b { + return a + } + return b } func mini(a, b int) int { From a7abd76113c5853bde5385850f3cc7c6aa7e1375 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 15 Aug 2019 01:19:13 +0930 Subject: [PATCH 30/58] codec/h264/h264dec: made mini faster and more readable --- codec/h264/h264dec/helpers.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/codec/h264/h264dec/helpers.go b/codec/h264/h264dec/helpers.go index c15b2416..ae4c935d 100644 --- a/codec/h264/h264dec/helpers.go +++ b/codec/h264/h264dec/helpers.go @@ -51,7 +51,10 @@ func maxi(a, b int) int { } func mini(a, b int) int { - return int(math.Min(float64(a), float64(b))) + if a < b { + return a + } + return b } func absi(i int) int { From 08d3f6c3d1e0e0b27380d94d75593db4a11bbb4b Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 15 Aug 2019 01:20:25 +0930 Subject: [PATCH 31/58] codec/h264/h264dec: made absi faster and more readable --- codec/h264/h264dec/helpers.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/codec/h264/h264dec/helpers.go b/codec/h264/h264dec/helpers.go index ae4c935d..4289f1f8 100644 --- a/codec/h264/h264dec/helpers.go +++ b/codec/h264/h264dec/helpers.go @@ -9,7 +9,6 @@ package h264dec import ( "errors" - "math" ) // binToSlice is a helper function to convert a string of binary into a @@ -57,6 +56,9 @@ func mini(a, b int) int { return b } -func absi(i int) int { - return int(math.Abs(float64(i))) +func absi(a int) int { + if a < 0 { + return -a + } + return a } From 4a2c3487c7e136cd27352c6ae5610fb5f165697c Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 15 Aug 2019 08:04:40 +0930 Subject: [PATCH 32/58] codec/h264/h264dec: got rid of unneeded closure and renamed suffix to s --- codec/h264/h264dec/cabac.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 2fd48367..23f7db16 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -796,8 +796,7 @@ func unaryExpGolombBinarization(v, uCoff, k int, signedValFlag bool) ([]int, err // 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) } + var s []int if absi(v) >= uCoff { sufS := absi(v) - uCoff @@ -805,13 +804,13 @@ func suffix(v, uCoff, k int, signedValFlag bool) []int { for { if sufS >= (1 << uint(k)) { - put(1) + s = append(s, 1) sufS = sufS - (1 << uint(k)) k++ } else { - put(0) + s = append(s, 0) for k = k - 1; k >= 0; k-- { - put((sufS >> uint(k)) & 1) + s = append(s, (sufS>>uint(k))&1) } stop = true } @@ -823,11 +822,11 @@ func suffix(v, uCoff, k int, signedValFlag bool) []int { if signedValFlag && v != 0 { if v > 0 { - put(0) + s = append(s, 0) } else { - put(1) + s = append(s, 1) } } - return suffix + return s } From 1a5f07305df1d831c76b402ac8ba2e4fbd1d6a63 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 20 Aug 2019 08:04:17 +0930 Subject: [PATCH 33/58] codec/h264/h2646dec/cabac.go: runcated=>truncated in truncUnaryBinarization comment --- codec/h264/h264dec/cabac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 23f7db16..74cf527c 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -755,7 +755,7 @@ func unaryBinarization(v int) ([]int, error) { // Error used by truncUnaryBinarization. var errInvalidSyntaxVal = errors.New("syntax value cannot be greater than cMax") -// truncUnaryBinarization returns the runcated unary binarization of a syntax +// truncUnaryBinarization returns the truncated 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 { From 04fef9ce1d0b88806901869b2426af3373b148f0 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 12 Aug 2019 02:02:14 +0930 Subject: [PATCH 34/58] codec/h264/h264dec: added fixedLenBinarization and testing --- codec/h264/h264dec/cabac.go | 21 +++++++++++++++++++++ codec/h264/h264dec/cabac_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 74cf527c..ea58c124 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -1,6 +1,8 @@ package h264dec import ( + "math" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) @@ -830,3 +832,22 @@ func suffix(v, uCoff, k int, signedValFlag bool) []int { return s } + +// Error used by fixedLenBinariztion. +var errNegativeValue = errors.New("cannot get fixed length binarization of negative number") + +// fixedLenBinarization returns the fixed-length (FL) binarization of the syntax +// element v, given cMax to determine bin length, as specified by section 9.3.2.4 +// of the specifications. +func fixedLenBinarization(v, cMax int) ([]int, error) { + if v < 0 { + return nil, errNegativeValue + } + l := int(math.Ceil(math.Log2(float64(cMax + 1)))) + r := make([]int, l) + for i := l - 1; i >= 0; i-- { + r[i] = v % 2 + v = v / 2 + } + return r, nil +} diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 990ba780..a72be4f8 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -215,6 +215,36 @@ func TestUnaryBinarization(t *testing.T) { } } +func TestFixedLengthBinarization(t *testing.T) { + tests := []struct { + v int + cMax int + want []int + err error + }{ + {v: 0, cMax: 7, want: []int{0, 0, 0}}, + {v: 1, cMax: 7, want: []int{0, 0, 1}}, + {v: 2, cMax: 7, want: []int{0, 1, 0}}, + {v: 3, cMax: 7, want: []int{0, 1, 1}}, + {v: 4, cMax: 7, want: []int{1, 0, 0}}, + {v: 5, cMax: 7, want: []int{1, 0, 1}}, + {v: 6, cMax: 7, want: []int{1, 1, 0}}, + {v: 7, cMax: 7, want: []int{1, 1, 1}}, + {v: -1, cMax: 7, want: nil, err: errNegativeValue}, + } + + for i, test := range tests { + got, err := fixedLenBinarization(test.v, test.cMax) + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", 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 TestTruncUnaryBinarization(t *testing.T) { tests := []struct { v int From 9691b1346ccb67235e6dc88dc18295e38593d66b Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 15 Aug 2019 01:03:00 +0930 Subject: [PATCH 35/58] codec/h264/h264dec: removed comment for unexported error message and bettered text --- codec/h264/h264dec/cabac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index ea58c124..b215cfa2 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -834,7 +834,7 @@ func suffix(v, uCoff, k int, signedValFlag bool) []int { } // Error used by fixedLenBinariztion. -var errNegativeValue = errors.New("cannot get fixed length binarization of negative number") +var errNegativeValue = errors.New("cannot get fixed length binarization of negative value") // fixedLenBinarization returns the fixed-length (FL) binarization of the syntax // element v, given cMax to determine bin length, as specified by section 9.3.2.4 From 657d62a10b60af4b5a8205b7ae720e11aeb2f0fb Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 20 Aug 2019 09:07:55 +0930 Subject: [PATCH 36/58] codec/h2646/h264dec: added file headers for cabac.go and cabac_test.go --- codec/h264/h264dec/cabac.go | 27 +++++++++++++++++++++++++++ codec/h264/h264dec/cabac_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index b215cfa2..89a05b79 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -1,3 +1,30 @@ +/* +DESCRIPTION + cabac.go provides utilities for context-adaptive binary artihmetic decoding + for the parsing of H.264 syntax structure fields. + +AUTHORS + Saxon A. Nelson-Milton + Bruce McMoran + Shawn Smith + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + package h264dec import ( diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index a72be4f8..778e4a96 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -1,3 +1,28 @@ +/* +DESCRIPTION + cabac_test.go provides testing for functionality found in cabac.go. + +AUTHORS + Saxon A. Nelson-Milton + Shawn Smith + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + package h264dec import ( From 2735f99f9cf3cb8503f860d337bb744deac4f1a6 Mon Sep 17 00:00:00 2001 From: Saxon Milton Date: Mon, 19 Aug 2019 23:41:56 +0000 Subject: [PATCH 37/58] codec/h264/h264dec/cabac.go: fixed indentation of Shawn's name in file header --- codec/h264/h264dec/cabac.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 89a05b79..b5940a00 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -6,7 +6,7 @@ DESCRIPTION AUTHORS Saxon A. Nelson-Milton Bruce McMoran - Shawn Smith + Shawn Smith LICENSE Copyright (C) 2019 the Australian Ocean Lab (AusOcean). From e7f36162b19a05fe8c5d2f6749533e739d1c6804 Mon Sep 17 00:00:00 2001 From: Saxon Milton Date: Mon, 19 Aug 2019 23:43:28 +0000 Subject: [PATCH 38/58] codec/h264/h264dec/cabac_test.go: fixed indentation of Shawn's name in file header --- codec/h264/h264dec/cabac_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 778e4a96..5ca17ce8 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -4,7 +4,7 @@ DESCRIPTION AUTHORS Saxon A. Nelson-Milton - Shawn Smith + Shawn Smith LICENSE Copyright (C) 2019 the Australian Ocean Lab (AusOcean). From dab94f6ae2cfb06128540b54f26325a6f96bdaf7 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 20 Aug 2019 11:39:12 +0930 Subject: [PATCH 39/58] codec/h264/h264dec: addressing PR feedback. Updated comment. Put a space between file header and package declaration. Not dereferencing things --- codec/h264/h264dec/nalunit.go | 6 +-- codec/h264/h264dec/nalunit_test.go | 63 +++++++++++++++++++----------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index e8ae3e3f..9e3360ed 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -263,9 +263,9 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { next3Bytes, err := br.PeekBits(24) // If PeekBits cannot get 3 bytes, but there still might be 2 bytes left in - // the source, we will get an io.EOF; we wish to ignore this and continue. - // The call to moreRBSPData will determine when we have reached the end of - // the NAL unit. + // the source, we will get an io.ErrUnexpectedEOF; we wish to ignore this + // and continue. The call to moreRBSPData will determine when we have + // reached the end of the NAL unit. if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF { return nil, errors.Wrap(err, "could not Peek next 3 bytes") } diff --git a/codec/h264/h264dec/nalunit_test.go b/codec/h264/h264dec/nalunit_test.go index d9925fdc..afa2cdf1 100644 --- a/codec/h264/h264dec/nalunit_test.go +++ b/codec/h264/h264dec/nalunit_test.go @@ -1,10 +1,27 @@ /* DESCRIPTION - nalunit_test.go provides testing for NAL unit parsing utilities in nalunit.go. + nalunit_test.go provides testing for functionality in nalunit.go. AUTHORS - Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. */ + package h264dec import ( @@ -18,7 +35,7 @@ import ( func TestNewMVCExtension(t *testing.T) { tests := []struct { in string - want MVCExtension + want *MVCExtension err error }{ { @@ -30,7 +47,7 @@ func TestNewMVCExtension(t *testing.T) { "0" + // u(1) inter_view_flag = false "1" + // u(1) reserved_one_bit = 1 "0", // Some padding - want: MVCExtension{ + want: &MVCExtension{ NonIdrFlag: false, PriorityID: 2, ViewID: 24, @@ -53,7 +70,7 @@ func TestNewMVCExtension(t *testing.T) { t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) } - if !reflect.DeepEqual(*got, test.want) { + 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) } } @@ -62,7 +79,7 @@ func TestNewMVCExtension(t *testing.T) { func TestNewThreeDAVCExtension(t *testing.T) { tests := []struct { in string - want ThreeDAVCExtension + want *ThreeDAVCExtension err error }{ { @@ -73,7 +90,7 @@ func TestNewThreeDAVCExtension(t *testing.T) { "1" + // u(1) anchor_pic_flag = true "1" + // u(1) inter_view_flag = true "000", // Some padding - want: ThreeDAVCExtension{ + want: &ThreeDAVCExtension{ ViewIdx: 16, DepthFlag: true, NonIdrFlag: false, @@ -95,7 +112,7 @@ func TestNewThreeDAVCExtension(t *testing.T) { t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) } - if !reflect.DeepEqual(*got, test.want) { + 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) } } @@ -104,7 +121,7 @@ func TestNewThreeDAVCExtension(t *testing.T) { func TestSVCExtension(t *testing.T) { tests := []struct { in string - want SVCExtension + want *SVCExtension err error }{ { @@ -119,7 +136,7 @@ func TestSVCExtension(t *testing.T) { "0" + // u(1) output_flag = false "11" + // ReservedThree2Bits "0", // padding - want: SVCExtension{ + want: &SVCExtension{ IdrFlag: false, PriorityID: 32, NoInterLayerPredFlag: false, @@ -145,7 +162,7 @@ func TestSVCExtension(t *testing.T) { t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) } - if !reflect.DeepEqual(*got, test.want) { + 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) } } @@ -154,7 +171,7 @@ func TestSVCExtension(t *testing.T) { func TestNewNALUnit(t *testing.T) { tests := []struct { in string - want NALUnit + want *NALUnit err error }{ { @@ -182,7 +199,7 @@ func TestNewNALUnit(t *testing.T) { "0000 1000" + "1000 0000", // trailing bits - want: NALUnit{ + want: &NALUnit{ ForbiddenZeroBit: 0, RefIdc: 1, Type: 14, @@ -221,21 +238,16 @@ func TestNewNALUnit(t *testing.T) { t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) } - if !nalEqual(*got, test.want) { + if !nalEqual(got, test.want) { t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v\n", i, *got, test.want) } } } // nalEqual returns true if two NALUnits are equal. -func nalEqual(a, b NALUnit) bool { - aCopy := a - bCopy := b - for _, n := range [](*NALUnit){&aCopy, &bCopy} { - n.SVCExtension = nil - n.MVCExtension = nil - n.ThreeDAVCExtension = nil - } +func nalEqual(a, b *NALUnit) bool { + aCopy := nalWithoutExtensions(*a) + bCopy := nalWithoutExtensions(*b) if !reflect.DeepEqual(aCopy, bCopy) { return false @@ -263,3 +275,10 @@ func nalEqual(a, b NALUnit) bool { } return true } + +func nalWithoutExtensions(n NALUnit) NALUnit { + n.SVCExtension = nil + n.MVCExtension = nil + n.ThreeDAVCExtension = nil + return n +} From 27d8d0992cde5fd7f117f33f9041d5120d2b7053 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 20 Aug 2019 11:56:42 +0930 Subject: [PATCH 40/58] codec/h264/h264dec/slice.go: made corrections to file header --- codec/h264/h264dec/slice.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index a46360c9..3b7c4a57 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -1,10 +1,10 @@ /* DESCRIPTION - slice.go provides parsing functionality for slice RBSP. + slice.go provides parsing functionality for slice raw byte sequence data. AUTHORS Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) - mrmod + Bruce McMoran */ package h264dec From bdcd3408021979c50e4e7011a3aea98bf3b01457 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 20 Aug 2019 12:02:39 +0930 Subject: [PATCH 41/58] codec/h264/h264dec/slice_test.go: removed redundant comment --- codec/h264/h264dec/slice_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index 55e249ea..3f0282b0 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -125,7 +125,7 @@ func TestNewPredWeightTable(t *testing.T) { tests := []struct { in string sliceHeader SliceHeader - chromaArrayType int // ChromaArrayType + chromaArrayType int want PredWeightTable }{ { From 0f35786d0693dc114e4796034aec0c13283b13a8 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 20 Aug 2019 12:06:11 +0930 Subject: [PATCH 42/58] codec/h264/h264dec/slice_test.go: added license information to file header --- codec/h264/h264dec/slice_test.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index 3f0282b0..e9bdee9f 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -1,10 +1,26 @@ /* DESCRIPTION - slice_test.go provides testing for parsing utilities found in slice.go. + slice_test.go provides testing for parsing functionality found in slice.go. AUTHORS - Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) - Shawn Smith , The Australian Ocean Laboratory (AusOcean) + Saxon Nelson-Milton + Shawn Smith + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. */ package h264dec From 4b8864ff207580492c48e4aa6d12ae01485ce1f2 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 21 Aug 2019 15:39:58 +0930 Subject: [PATCH 43/58] adpcm: better decoding of chunks added chunk length to chunk header added to decoder the ability to decode consecutive chunks of variable length. --- codec/adpcm/adpcm.go | 109 ++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 43 deletions(-) diff --git a/codec/adpcm/adpcm.go b/codec/adpcm/adpcm.go index ce8ae9f7..479f6261 100644 --- a/codec/adpcm/adpcm.go +++ b/codec/adpcm/adpcm.go @@ -40,10 +40,11 @@ import ( const ( byteDepth = 2 // We are working with 16-bit samples. TODO(Trek): make configurable. initSamps = 2 // Number of samples used to initialise the encoder. - initBytes = initSamps * byteDepth - headBytes = 4 // Number of bytes in the header of ADPCM. + initSize = initSamps * byteDepth + headSize = 8 // Number of bytes in the header of ADPCM. samplesPerEnc = 2 // Number of sample encoded at a time eg. 2 16-bit samples get encoded into 1 byte. bytesPerEnc = samplesPerEnc * byteDepth + chunkLenSize = 4 // Size of the chunk length in bytes, chunk length is a 32 bit number. compFact = 4 // In general ADPCM compresses by a factor of 4. ) @@ -176,7 +177,7 @@ func (e *Encoder) calcHead(sample []byte, pad bool) (int, error) { // The suitable step size is the closest step size in the stepTable to half the absolute difference of the first two samples. func (e *Encoder) init(samples []byte) { int1 := int16(binary.LittleEndian.Uint16(samples[:byteDepth])) - int2 := int16(binary.LittleEndian.Uint16(samples[byteDepth:initBytes])) + int2 := int16(binary.LittleEndian.Uint16(samples[byteDepth:initSize])) e.est = int1 halfDiff := math.Abs(math.Abs(float64(int1)) - math.Abs(float64(int2))/2) @@ -197,8 +198,8 @@ func (e *Encoder) init(samples []byte) { func (e *Encoder) Write(b []byte) (int, error) { // Check that pcm has enough data to initialize Decoder. pcmLen := len(b) - if pcmLen < initBytes { - return 0, fmt.Errorf("length of given byte array must be >= %v", initBytes) + if pcmLen < initSize { + return 0, fmt.Errorf("length of given byte array must be >= %v", initSize) } // Determine if there will be a byte that won't contain two full nibbles and will need padding. @@ -207,8 +208,18 @@ func (e *Encoder) Write(b []byte) (int, error) { pad = true } - e.init(b[:initBytes]) - n, err := e.calcHead(b[:byteDepth], pad) + // Write the first 4 bytes of the adpcm chunk, which represent its length, ie. the number of bytes following the chunk length. + chunkLen := EncBytes(pcmLen) + chunkLenBytes := make([]byte, chunkLenSize) + binary.LittleEndian.PutUint32(chunkLenBytes, uint32(chunkLen)) + n, err := e.dst.Write(chunkLenBytes) + if err != nil { + return n, err + } + + e.init(b[:initSize]) + _n, err := e.calcHead(b[:byteDepth], pad) + n += _n if err != nil { return n, err } @@ -284,47 +295,59 @@ func (d *Decoder) decodeSample(nibble byte) int16 { // It writes its output to the Decoder's dst. // The number of bytes written out is returned along with any error that occured. func (d *Decoder) Write(b []byte) (int, error) { - // Initialize Decoder with first 4 bytes of b. - d.est = int16(binary.LittleEndian.Uint16(b[:byteDepth])) - d.idx = int16(b[byteDepth]) - d.step = stepTable[d.idx] - n, err := d.dst.Write(b[:byteDepth]) - if err != nil { - return n, err - } + // Iterate over each chunk and decode it. + var n int + var chunkLen int + for off := 0; off+headSize <= len(b); off += chunkLen { + // Read length of chunk and check if whole chunk exists. + chunkLen = int(binary.LittleEndian.Uint32(b[off : off+chunkLenSize])) + if off+chunkLen > len(b) { + break + } - // For each byte, seperate it into two nibbles (each nibble is a compressed sample), - // then decode each nibble and output the resulting 16-bit samples. - // If padding flag is true (Adpcm[3]), only decode up until the last byte, then decode that separately. - for i := headBytes; i < len(b)-int(b[3]); i++ { - twoNibs := b[i] - nib2 := byte(twoNibs >> 4) - nib1 := byte((nib2 << 4) ^ twoNibs) - - firstBytes := make([]byte, byteDepth) - binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) - _n, err := d.dst.Write(firstBytes) + // Initialize Decoder with header of b. + d.est = int16(binary.LittleEndian.Uint16(b[off+chunkLenSize : off+chunkLenSize+byteDepth])) + d.idx = int16(b[off+chunkLenSize+byteDepth]) + d.step = stepTable[d.idx] + _n, err := d.dst.Write(b[off+chunkLenSize : off+chunkLenSize+byteDepth]) n += _n if err != nil { return n, err } - secondBytes := make([]byte, byteDepth) - binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) - _n, err = d.dst.Write(secondBytes) - n += _n - if err != nil { - return n, err + // For each byte, seperate it into two nibbles (each nibble is a compressed sample), + // then decode each nibble and output the resulting 16-bit samples. + // If padding flag is true only decode up until the last byte, then decode that separately. + for i := off + headSize; i < off+chunkLen-int(b[off+chunkLenSize+3]); i++ { + twoNibs := b[i] + nib2 := byte(twoNibs >> 4) + nib1 := byte((nib2 << 4) ^ twoNibs) + + firstBytes := make([]byte, byteDepth) + binary.LittleEndian.PutUint16(firstBytes, uint16(d.decodeSample(nib1))) + _n, err := d.dst.Write(firstBytes) + n += _n + if err != nil { + return n, err + } + + secondBytes := make([]byte, byteDepth) + binary.LittleEndian.PutUint16(secondBytes, uint16(d.decodeSample(nib2))) + _n, err = d.dst.Write(secondBytes) + n += _n + if err != nil { + return n, err + } } - } - if b[3] == 0x01 { - padNib := b[len(b)-1] - samp := make([]byte, byteDepth) - binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib))) - _n, err := d.dst.Write(samp) - n += _n - if err != nil { - return n, err + if b[off+chunkLenSize+3] == 0x01 { + padNib := b[off+chunkLen-1] + samp := make([]byte, byteDepth) + binary.LittleEndian.PutUint16(samp, uint16(d.decodeSample(padNib))) + _n, err := d.dst.Write(samp) + n += _n + if err != nil { + return n, err + } } } return n, nil @@ -349,7 +372,7 @@ func EncBytes(n int) int { // and a start index and padding-flag byte are added. // Also if there are an even number of samples, there will be half a byte of padding added to the last byte. if n%bytesPerEnc == 0 { - return (n-byteDepth)/compFact + headBytes + 1 + return (n-byteDepth)/compFact + headSize + 1 } - return (n-byteDepth)/compFact + headBytes + return (n-byteDepth)/compFact + headSize } From 4e48a7aa095888bbcbc3003a656767753f785a9b Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 21 Aug 2019 16:20:11 +0930 Subject: [PATCH 44/58] adpcm: updated test file names --- codec/adpcm/adpcm_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codec/adpcm/adpcm_test.go b/codec/adpcm/adpcm_test.go index 8b825696..0afde670 100644 --- a/codec/adpcm/adpcm_test.go +++ b/codec/adpcm/adpcm_test.go @@ -51,7 +51,7 @@ func TestEncodeBlock(t *testing.T) { } // Read expected adpcm file. - exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded_8kHz_adpcm_test.adpcm") + exp, err := ioutil.ReadFile("../../../test/test-data/av/output/encoded_8kHz_adpcm_test2.adpcm") if err != nil { t.Errorf("Unable to read expected ADPCM file: %v", err) } @@ -65,7 +65,7 @@ func TestEncodeBlock(t *testing.T) { // resulting PCM with the expected decoded PCM. func TestDecodeBlock(t *testing.T) { // Read adpcm. - comp, err := ioutil.ReadFile("../../../test/test-data/av/input/encoded_8kHz_adpcm_test.adpcm") + comp, err := ioutil.ReadFile("../../../test/test-data/av/input/encoded_8kHz_adpcm_test2.adpcm") if err != nil { t.Errorf("Unable to read input ADPCM file: %v", err) } @@ -79,7 +79,7 @@ func TestDecodeBlock(t *testing.T) { } // Read expected pcm file. - exp, err := ioutil.ReadFile("../../../test/test-data/av/output/decoded_8kHz_adpcm_test.pcm") + exp, err := ioutil.ReadFile("../../../test/test-data/av/output/decoded_8kHz_adpcm_test2.pcm") if err != nil { t.Errorf("Unable to read expected PCM file: %v", err) } From 37b8e7a8bcdc92fa7b35794b1c173413b0d98a40 Mon Sep 17 00:00:00 2001 From: Saxon Milton Date: Fri, 23 Aug 2019 06:11:54 +0000 Subject: [PATCH 45/58] revid: increase sender ring buffer read timeouts to slow down output loops --- revid/senders.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/revid/senders.go b/revid/senders.go index 7de06342..303663ec 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -50,6 +50,12 @@ import ( // Log is used by the multiSender. type Log func(level int8, message string, params ...interface{}) +// Sender ring buffer read timeouts. +const ( + rtmpRBReadTimeout = 1 * time.Second + mtsRBReadTimeout = 1 * time.Second +) + // httpSender provides an implemntation of io.Writer to perform sends to a http // destination. type httpSender struct { @@ -192,7 +198,7 @@ func (s *mtsSender) output() { // If chunk is nil then we're ready to get another from the ringBuffer. if chunk == nil { var err error - chunk, err = s.ring.Next(0) + chunk, err = s.ring.Next(mtsRBReadTimeout) switch err { case nil, io.EOF: continue @@ -301,7 +307,7 @@ func (s *rtmpSender) output() { // If chunk is nil then we're ready to get another from the ring buffer. if chunk == nil { var err error - chunk, err = s.ring.Next(0) + chunk, err = s.ring.Next(rtmpRBReadTimeout) switch err { case nil, io.EOF: continue From ce8295bb3613ab22fb5a2b93a7443c43047a20a6 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 24 Aug 2019 14:02:24 +0930 Subject: [PATCH 46/58] revid: variable bitrate default for HTTP mode, also wrote some testing for config validation --- revid/config.go | 71 ++++++++++++++++---------------- revid/config_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++++ revid/revid.go | 10 ++--- 3 files changed, 138 insertions(+), 40 deletions(-) create mode 100644 revid/config_test.go diff --git a/revid/config.go b/revid/config.go index 82e06115..701c0753 100644 --- a/revid/config.go +++ b/revid/config.go @@ -109,7 +109,7 @@ const ( defaultWidth = 1280 defaultHeight = 720 defaultIntraRefreshPeriod = 100 - defaultQuantization = 40 + defaultQuantization = 30 defaultBitrate = 400000 // Audio defaults. @@ -170,10 +170,6 @@ type Config struct { // localhost:6970. MPEGT-TS packetization is used. Outputs []uint8 - // Quantize specifies whether the input to revid will have constant or variable - // bitrate, if configurable with the chosen input. Raspivid supports quantization. - Quantize bool - // RTMPURL specifies the Rtmp output destination URL. This must be defined if // RTMP is to be used as an output. RTMPURL string @@ -252,9 +248,14 @@ type Config struct { MTSRBElementSize int // The element size in bytes of the MTS sender RingBuffer. } +// Validation errors. +var ( + errInvalidQuantization = errors.New("invalid quantization") +) + // Validate checks for any errors in the config fields and defaults settings // if particular parameters have not been defined. -func (c *Config) Validate(r *Revid) error { +func (c *Config) Validate() error { switch c.LogLevel { case logger.Debug: case logger.Info: @@ -277,12 +278,6 @@ func (c *Config) Validate(r *Revid) error { switch c.InputCodec { case codecutil.H264: - // FIXME(kortschak): This is not really what we want. - // Configuration really needs to be rethought here. - if c.Quantize && c.Quantization == 0 { - c.Quantization = defaultQuantization - } - case codecutil.MJPEG: if c.Quantization > 0 || c.Bitrate == 0 { return errors.New("bad bitrate or quantization for mjpeg input") @@ -304,22 +299,32 @@ func (c *Config) Validate(r *Revid) error { if c.Outputs == nil { c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput) c.Outputs = append(c.Outputs, defaultOutput) - } else { - for i, o := range c.Outputs { - switch o { - case File: - case RTMP: - if c.RTMPURL == "" { - c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") - c.Outputs[i] = HTTP - // FIXME(kortschak): Does this want the same line as below? - // c.FramesPerClip = httpFramesPerClip - break - } - case HTTP, RTP: - default: - return errors.New("bad output type defined in config") + } + + var haveRTMPOut bool + for i, o := range c.Outputs { + switch o { + case File: + case RTMP: + haveRTMPOut = true + if c.Bitrate == 0 { + c.Bitrate = defaultBitrate } + c.Quantization = 0 + if c.RTMPURL == "" { + c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") + c.Outputs[i] = HTTP + break + } + case HTTP, RTP: + if !haveRTMPOut { + c.Bitrate = 0 + if c.Quantization == 0 { + c.Quantization = defaultQuantization + } + } + default: + return errors.New("bad output type defined in config") } } @@ -373,9 +378,8 @@ func (c *Config) Validate(r *Revid) error { c.WriteRate = defaultWriteRate } - if c.Bitrate == 0 { - c.Logger.Log(logger.Info, pkg+"no bitrate defined, defaulting", "bitrate", defaultBitrate) - c.Bitrate = defaultBitrate + if c.Bitrate < 0 { + return errors.New("invalid bitrate") } if c.IntraRefreshPeriod == 0 { @@ -383,11 +387,8 @@ func (c *Config) Validate(r *Revid) error { c.IntraRefreshPeriod = defaultIntraRefreshPeriod } - if c.Quantization == 0 { - c.Logger.Log(logger.Info, pkg+"no quantization defined, defaulting", "quantization", defaultQuantization) - c.Quantization = defaultQuantization - } else if c.Quantization > 51 { - return errors.New("quantisation is over threshold") + if c.Quantization != 0 && (c.Quantization < 10 || c.Quantization > 40) { + return errInvalidQuantization } if c.RTPAddress == "" { diff --git a/revid/config_test.go b/revid/config_test.go new file mode 100644 index 00000000..53f81ea9 --- /dev/null +++ b/revid/config_test.go @@ -0,0 +1,97 @@ +/* +DESCRIPTION + config_test.go provides testing for configuration functionality found in config.go. + +AUTHORS + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package revid + +import ( + "fmt" + "testing" +) + +type dumbLogger struct{} + +func (dl *dumbLogger) SetLevel(l int8) {} +func (dl *dumbLogger) Log(l int8, m string, p ...interface{}) {} + +func TestValidate(t *testing.T) { + tests := []struct { + in Config + check func(c Config) error + err error + }{ + { + in: Config{Outputs: []uint8{HTTP}, Logger: &dumbLogger{}}, + check: func(c Config) error { + if c.Bitrate != 0 { + return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0) + } + if c.Quantization != defaultQuantization { + return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, defaultQuantization) + } + return nil + }, + }, + { + in: Config{Outputs: []uint8{RTMP}, Logger: &dumbLogger{}}, + check: func(c Config) error { + if c.Bitrate != defaultBitrate { + return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, defaultBitrate) + } + if c.Quantization != 0 { + return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, 0) + } + return nil + }, + }, + { + in: Config{Outputs: []uint8{HTTP}, Quantization: 50, Logger: &dumbLogger{}}, + check: func(c Config) error { return nil }, + err: errInvalidQuantization, + }, + { + in: Config{Outputs: []uint8{HTTP}, Quantization: 20, Logger: &dumbLogger{}}, + check: func(c Config) error { + if c.Bitrate != 0 { + return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0) + } + if c.Quantization != 20 { + return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, 20) + } + return nil + }, + }, + } + + for i, test := range tests { + err := (&test.in).Validate() + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err) + } + + err = test.check(test.in) + if err != nil { + t.Errorf("test %d failed with err: %v", i, err) + } + } +} diff --git a/revid/revid.go b/revid/revid.go index f18a6a43..9e11844a 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -197,7 +197,7 @@ func (r *Revid) reset(config Config) error { // revid config. func (r *Revid) setConfig(config Config) error { r.config.Logger = config.Logger - err := config.Validate(r) + err := config.Validate() if err != nil { return errors.New("Config struct is bad: " + err.Error()) } @@ -450,12 +450,12 @@ func (r *Revid) Update(vars map[string]string) error { case "HttpAddress": r.config.HTTPAddress = value case "Quantization": - q, err := strconv.ParseUint(value, 10, 0) + v, err := strconv.Atoi(value) if err != nil { - r.config.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", value) + r.config.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", v) break } - r.config.Quantization = uint(q) + r.config.Quantization = uint(v) case "IntraRefreshPeriod": p, err := strconv.ParseUint(value, 10, 0) if err != nil { @@ -579,7 +579,7 @@ func (r *Revid) startRaspivid() (func() error, error) { "--inline", "--intra", fmt.Sprint(r.config.IntraRefreshPeriod), ) - if r.config.Quantize { + if r.config.Quantization != 0 { args = append(args, "-qp", fmt.Sprint(r.config.Quantization)) } case codecutil.MJPEG: From 9eb155dfed8c8469355b11e5bd7300feb044d163 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 24 Aug 2019 14:05:34 +0930 Subject: [PATCH 47/58] revid-cli: removed use of config quantize param in revid-cli --- cmd/revid-cli/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index c98513cc..53f38833 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -111,7 +111,6 @@ func handleFlags() revid.Config { inputCodecPtr = flag.String("InputCodec", "H264", "The codec of the input: H264, Mjpeg, PCM, ADPCM") inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP") rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.") - quantizePtr = flag.Bool("Quantize", false, "Quantize input (non-variable bitrate)") verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal") rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: : (port is generally 6970-6999)") logPathPtr = flag.String("LogPath", defaultLogPath, "The log path") @@ -235,7 +234,6 @@ func handleFlags() revid.Config { } cfg.RTSPURL = *rtspURLPtr - cfg.Quantize = *quantizePtr cfg.Rotation = *rotationPtr cfg.FlipHorizontal = *horizontalFlipPtr cfg.FlipVertical = *verticalFlipPtr From d3909182098a3ff8057ba546df53ff4a68d13f1a Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 24 Aug 2019 14:53:49 +0930 Subject: [PATCH 48/58] revid: corrected logic for RTMPURL fallback and fixed bug in test --- revid/config.go | 3 ++- revid/config_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/revid/config.go b/revid/config.go index 701c0753..42ab2b5d 100644 --- a/revid/config.go +++ b/revid/config.go @@ -314,8 +314,9 @@ func (c *Config) Validate() error { if c.RTMPURL == "" { c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") c.Outputs[i] = HTTP - break + haveRTMPOut = false } + fallthrough case HTTP, RTP: if !haveRTMPOut { c.Bitrate = 0 diff --git a/revid/config_test.go b/revid/config_test.go index 53f81ea9..eb248c93 100644 --- a/revid/config_test.go +++ b/revid/config_test.go @@ -53,7 +53,7 @@ func TestValidate(t *testing.T) { }, }, { - in: Config{Outputs: []uint8{RTMP}, Logger: &dumbLogger{}}, + in: Config{Outputs: []uint8{RTMP}, RTMPURL: "dummURL", Logger: &dumbLogger{}}, check: func(c Config) error { if c.Bitrate != defaultBitrate { return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, defaultBitrate) From 817cc86a3420fef884d9dfd06a90310d204aef4c Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 25 Aug 2019 17:14:06 +0930 Subject: [PATCH 49/58] revid: now have MinPeriod and ClipDuration params This change included a rename of IntraRefreshPeriod to MinPeriod, and the addition of the ClipDuration param. PSI are now written before IDR. Clips are no longer outputed based on PSI but rather a time ClipDuration, where ClipDuration >= MinPeriod, however, PSI must still be at the beginning of each clip. Also created functionality to update meta time even if we don't have a response to update. --- cmd/revid-cli/main.go | 51 +++++++++++++------------- codec/h264/parse.go | 71 ++++++++++++++++++++++++++++++++++++ container/mts/encoder.go | 77 +++++++++++++++++++++++++++++++++------- container/mts/mpegts.go | 16 ++++++--- revid/config.go | 48 +++++++++++++++++-------- revid/revid.go | 24 ++++++++----- revid/senders.go | 26 +++++++++----- revid/senders_test.go | 13 +++---- 8 files changed, 244 insertions(+), 82 deletions(-) create mode 100644 codec/h264/parse.go diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 53f38833..5eca3ff7 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -106,32 +106,30 @@ func handleFlags() revid.Config { var cfg revid.Config var ( - cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") - - inputCodecPtr = flag.String("InputCodec", "H264", "The codec of the input: H264, Mjpeg, PCM, ADPCM") - inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP") - rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.") - verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal") - rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: : (port is generally 6970-6999)") - logPathPtr = flag.String("LogPath", defaultLogPath, "The log path") - configFilePtr = flag.String("ConfigFile", "", "NetSender config file") - rtmpUrlPtr = flag.String("RtmpUrl", "", "Url of rtmp endpoint") - outputPathPtr = flag.String("OutputPath", "", "The directory of the output file") - inputFilePtr = flag.String("InputPath", "", "The directory of the input file") - httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts") - verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No") - horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No") - bitratePtr = flag.Uint("Bitrate", 0, "Bitrate of recorded video") - heightPtr = flag.Uint("Height", 0, "Height in pixels") - widthPtr = flag.Uint("Width", 0, "Width in pixels") - frameRatePtr = flag.Uint("FrameRate", 0, "Frame rate of captured video") - quantizationPtr = flag.Uint("Quantization", 0, "Desired quantization value: 0-40") - intraRefreshPeriodPtr = flag.Uint("IntraRefreshPeriod", 0, "The IntraRefreshPeriod i.e. how many keyframes we send") - rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)") - brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ") - saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)") - exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")") - autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(revid.AutoWhiteBalanceModes[:], ",")+")") + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") + inputCodecPtr = flag.String("InputCodec", "H264", "The codec of the input: H264, Mjpeg, PCM, ADPCM") + inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP") + rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.") + verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal") + rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: : (port is generally 6970-6999)") + logPathPtr = flag.String("LogPath", defaultLogPath, "The log path") + configFilePtr = flag.String("ConfigFile", "", "NetSender config file") + rtmpUrlPtr = flag.String("RtmpUrl", "", "Url of rtmp endpoint") + outputPathPtr = flag.String("OutputPath", "", "The directory of the output file") + inputFilePtr = flag.String("InputPath", "", "The directory of the input file") + httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts") + verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No") + horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No") + bitratePtr = flag.Uint("Bitrate", 0, "Bitrate of recorded video") + heightPtr = flag.Uint("Height", 0, "Height in pixels") + widthPtr = flag.Uint("Width", 0, "Width in pixels") + frameRatePtr = flag.Uint("FrameRate", 0, "Frame rate of captured video") + quantizationPtr = flag.Uint("Quantization", 0, "Desired quantization value: 0-40") + rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)") + brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ") + saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)") + exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")") + autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(revid.AutoWhiteBalanceModes[:], ",")+")") // Audio specific flags. sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio") @@ -246,7 +244,6 @@ func handleFlags() revid.Config { cfg.FrameRate = *frameRatePtr cfg.HTTPAddress = *httpAddressPtr cfg.Quantization = *quantizationPtr - cfg.IntraRefreshPeriod = *intraRefreshPeriodPtr cfg.RTPAddress = *rtpAddrPtr cfg.Brightness = *brightnessPtr cfg.Saturation = *saturationPtr diff --git a/codec/h264/parse.go b/codec/h264/parse.go new file mode 100644 index 00000000..9465fb1c --- /dev/null +++ b/codec/h264/parse.go @@ -0,0 +1,71 @@ +/* +DESCRIPTION + parse.go provides H.264 NAL unit parsing utilities for the extraction of + syntax elements. + +AUTHORS + Saxon A. Nelson-Milton + Dan Kortschak + +LICENSE + Copyright (C) 2017-2018 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package h264 + +import "errors" + +var errNotEnoughBytes = errors.New("not enough bytes to read") + +// NALType returns the NAL type of the given NAL unit bytes. The given NAL unit +// may be in byte stream or packet format. +func NALType(n []byte) (int, error) { + sc := frameScanner{buf: n} + b, ok := sc.readByte() + if !ok { + return 0, errNotEnoughBytes + } + for i := 1; b == 0x00 && i != 4; i++ { + b, ok = sc.readByte() + if !ok { + return 0, errNotEnoughBytes + } + if b != 0x01 || (i != 2 && i != 3) { + continue + } + + b, ok = sc.readByte() + if !ok { + return 0, errNotEnoughBytes + } + return int(b & 0x1f), nil + } + return int(b & 0x1f), nil +} + +type frameScanner struct { + off int + buf []byte +} + +func (s *frameScanner) readByte() (b byte, ok bool) { + if s.off >= len(s.buf) { + return 0, false + } + b = s.buf[s.off] + s.off++ + return b, true +} diff --git a/container/mts/encoder.go b/container/mts/encoder.go index 5d5533cb..63413b77 100644 --- a/container/mts/encoder.go +++ b/container/mts/encoder.go @@ -26,9 +26,13 @@ LICENSE package mts import ( + "fmt" "io" + "strconv" + "sync" "time" + "bitbucket.org/ausocean/av/codec/h264" "bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/pes" "bitbucket.org/ausocean/av/container/mts/psi" @@ -117,6 +121,42 @@ const ( MaxPTS = (1 << 33) - 1 ) +// Globals for use in keeping real time. +var ( + realRefTime time.Time // Holds a reference real time given to SetTime. + sysRefTime time.Time // Holds a system reference time set when realRefTime is obtained. + timeIsSet bool // Indicates if the time has been set. + mu = sync.Mutex{} // Used when accessing/mutating above time vars. +) + +// SetTime allows setting of current time. This is useful if the system running +// this encoder does not have time keeping. The user may wish to obtain an +// accurate time from an NTP server or local machine and pass to this function. +func SetTime(t time.Time) { + mu.Lock() + realRefTime = t + sysRefTime = time.Now() + timeIsSet = true + mu.Unlock() +} + +// Time provides either a real time that has been calculated from a reference +// set by SetTime, or using the current system time. +func Time() time.Time { + mu.Lock() + t := realRefTime.Add(time.Now().Sub(sysRefTime)) + mu.Unlock() + return t +} + +// TimeIsSet returns true if SetTime has been used to set a real reference time. +func TimeIsSet() bool { + mu.Lock() + b := timeIsSet + mu.Unlock() + return b +} + // Encoder encapsulates properties of an MPEG-TS generator. type Encoder struct { dst io.WriteCloser @@ -130,13 +170,11 @@ type Encoder struct { continuity map[int]byte - timeBasedPsi bool + nalBasedPSI bool pktCount int psiSendCount int mediaPid int streamID byte - - psiLastTime time.Time } // NewEncoder returns an Encoder with the specified media type and rate eg. if a video stream @@ -174,7 +212,7 @@ func NewEncoder(dst io.WriteCloser, rate float64, mediaType int) *Encoder { writePeriod: time.Duration(float64(time.Second) / rate), ptsOffset: ptsOffset, - timeBasedPsi: true, + nalBasedPSI: true, pktCount: 8, @@ -199,12 +237,8 @@ const ( hasPTS = 0x2 ) -// TimeBasedPsi allows for the setting of the PSI writing method, therefore, if -// PSI is written based on some time duration, or based on a packet count. -// If b is true, then time based PSI is used, otherwise the PSI is written -// every sendCount. -func (e *Encoder) TimeBasedPsi(b bool, sendCount int) { - e.timeBasedPsi = b +func (e *Encoder) NALBasedPSI(b bool, sendCount int) { + e.nalBasedPSI = b e.psiSendCount = sendCount e.pktCount = e.psiSendCount } @@ -212,14 +246,28 @@ func (e *Encoder) TimeBasedPsi(b bool, sendCount int) { // Write implements io.Writer. Write takes raw video or audio data and encodes into MPEG-TS, // then sending it to the encoder's io.Writer destination. func (e *Encoder) Write(data []byte) (int, error) { - now := time.Now() - if (e.timeBasedPsi && (now.Sub(e.psiLastTime) > psiInterval)) || (!e.timeBasedPsi && (e.pktCount >= e.psiSendCount)) { + if e.nalBasedPSI { + nalType, err := h264.NALType(data) + if err != nil { + return 0, fmt.Errorf("could not get type from NAL unit, failed with error: %v", err) + } + + // NAL type that will signify refresh. These are defined in H.264 specifications + // table 7-1. + const nalTypeSPS = 7 + + if nalType == nalTypeSPS { + err := e.writePSI() + if err != nil { + return 0, err + } + } + } else if e.pktCount >= e.psiSendCount { e.pktCount = 0 err := e.writePSI() if err != nil { return 0, err } - e.psiLastTime = now } // Prepare PES data. @@ -328,6 +376,9 @@ func (e *Encoder) ccFor(pid int) byte { // contained in the global Meta struct. func updateMeta(b []byte) ([]byte, error) { p := psi.PSIBytes(b) + if TimeIsSet() { + Meta.Add("ts", strconv.Itoa(int(Time().Unix()))) + } err := p.AddDescriptor(psi.MetadataTag, Meta.Encode()) return []byte(p), err } diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 8ca509ba..fe31b5ce 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -650,8 +650,11 @@ func SegmentForMeta(d []byte, key, val string) ([][]byte, error) { } // pid returns the packet identifier for the given packet. -func pid(p []byte) uint16 { - return uint16(p[1]&0x1f)<<8 | uint16(p[2]) +func PID(p []byte) (uint16, error) { + if len(p) < PacketSize { + return 0, errors.New("packet length less than 188") + } + return uint16(p[1]&0x1f)<<8 | uint16(p[2]), nil } // Programs returns a map of program numbers and corresponding PMT PIDs for a @@ -683,10 +686,14 @@ func Streams(p []byte) ([]gotspsi.PmtElementaryStream, error) { // but this program may contain different streams, i.e. a video stream + audio // stream. func MediaStreams(p []byte) ([]gotspsi.PmtElementaryStream, error) { + if len(p) < 2*PacketSize { + return nil, errors.New("PSI is not two packets or more long") + } pat := p[:PacketSize] pmt := p[PacketSize : 2*PacketSize] - if pid(pat) != PatPid { + pid, _ := PID(pat) + if pid != PatPid { return nil, errors.New("first packet is not a PAT") } @@ -703,7 +710,8 @@ func MediaStreams(p []byte) ([]gotspsi.PmtElementaryStream, error) { return nil, ErrMultiplePrograms } - if pid(pmt) != pmtPIDs(m)[0] { + pid, _ = PID(pmt) + if pid != pmtPIDs(m)[0] { return nil, errors.New("second packet is not desired PMT") } diff --git a/revid/config.go b/revid/config.go index 42ab2b5d..e0870c40 100644 --- a/revid/config.go +++ b/revid/config.go @@ -27,6 +27,7 @@ package revid import ( "errors" + "time" "bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/utils/logger" @@ -102,15 +103,16 @@ const ( defaultBurstPeriod = 10 // Seconds // Raspivid video defaults. - defaultBrightness = 50 - defaultExposure = "auto" - defaultAutoWhiteBalance = "auto" - defaultRotation = 0 // Degrees - defaultWidth = 1280 - defaultHeight = 720 - defaultIntraRefreshPeriod = 100 - defaultQuantization = 30 - defaultBitrate = 400000 + defaultBrightness = 50 + defaultExposure = "auto" + defaultAutoWhiteBalance = "auto" + defaultRotation = 0 // Degrees + defaultWidth = 1280 + defaultHeight = 720 + defaultRefreshPeriod = 100 + defaultClipDuration = 0 + defaultQuantization = 30 + defaultBitrate = 400000 // Audio defaults. defaultAudioInputCodec = codecutil.ADPCM @@ -202,9 +204,16 @@ type Config struct { // are using Raspivid input. Quantization uint - // IntraRefreshPeriod defines the frequency of video parameter NAL units for - // Raspivid input. - IntraRefreshPeriod uint + // MinPeriod defines the frequency of key NAL units SPS, PPS and IDR in + // number of NAL units. This will also determine the frequency of PSI if the + // output container is MPEG-TS. If ClipDuration is less than MinPeriod, + // ClipDuration will default to MinPeriod. + MinPeriod uint + + // ClipDuration is the duration of MTS data that is sent using HTTP or RTP + // output. This defaults to 0, therefore MinPeriod will determine the length of + // clips by default. + ClipDuration time.Duration // Logger holds an implementation of the Logger interface as defined in revid.go. // This must be set for revid to work correctly. @@ -383,9 +392,18 @@ func (c *Config) Validate() error { return errors.New("invalid bitrate") } - if c.IntraRefreshPeriod == 0 { - c.Logger.Log(logger.Info, pkg+"no intra refresh defined, defaulting", "intraRefresh", defaultIntraRefreshPeriod) - c.IntraRefreshPeriod = defaultIntraRefreshPeriod + if c.MinPeriod == 0 { + c.Logger.Log(logger.Info, pkg+"no intra refresh defined, defaulting", "intraRefresh", defaultRefreshPeriod) + c.MinPeriod = defaultRefreshPeriod + } else if c.MinPeriod < 0 { + return errors.New("refresh period is less than 0") + } + + if c.ClipDuration == 0 { + c.Logger.Log(logger.Info, pkg+"no clip duration defined, defaulting", "ClipDuration", defaultClipDuration) + c.ClipDuration = defaultClipDuration + } else if c.ClipDuration < 0 { + return errors.New("clip duration is less than 0") } if c.Quantization != 0 && (c.Quantization < 10 || c.Quantization > 40) { diff --git a/revid/revid.go b/revid/revid.go index 9e11844a..13efdd7b 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -51,6 +51,7 @@ import ( "bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/utils/ioext" "bitbucket.org/ausocean/utils/logger" + "bitbucket.org/ausocean/utils/ring" ) // RTMP connection properties. @@ -230,9 +231,8 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. w = newMtsSender( newHttpSender(r.ns, r.config.Logger.Log), r.config.Logger.Log, - r.config.MTSRBSize, - r.config.MTSRBElementSize, - 0, + ring.NewBuffer(r.config.MTSRBSize, r.config.MTSRBElementSize, 0), + r.config.ClipDuration, ) mtsSenders = append(mtsSenders, w) case RTP: @@ -456,13 +456,21 @@ func (r *Revid) Update(vars map[string]string) error { break } r.config.Quantization = uint(v) - case "IntraRefreshPeriod": - p, err := strconv.ParseUint(value, 10, 0) + case "MinPeriod": + v, err := strconv.Atoi(value) if err != nil { - r.config.Logger.Log(logger.Warning, pkg+"invalid intrarefreshperiod param", "value", value) + r.config.Logger.Log(logger.Warning, pkg+"invalid MinPeriod param", "value", value) break } - r.config.IntraRefreshPeriod = uint(p) + r.config.MinPeriod = uint(v) + + case "ClipDuration": + v, err := strconv.Atoi(value) + if err != nil { + r.config.Logger.Log(logger.Warning, pkg+"invalid ClipDuration param", "value", value) + break + } + r.config.ClipDuration = time.Duration(v) * time.Second case "HorizontalFlip": switch strings.ToLower(value) { @@ -577,7 +585,7 @@ func (r *Revid) startRaspivid() (func() error, error) { args = append(args, "--codec", "H264", "--inline", - "--intra", fmt.Sprint(r.config.IntraRefreshPeriod), + "--intra", fmt.Sprint(r.config.MinPeriod), ) if r.config.Quantization != 0 { args = append(args, "-qp", fmt.Sprint(r.config.Quantization)) diff --git a/revid/senders.go b/revid/senders.go index 303663ec..18389354 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -29,11 +29,11 @@ LICENSE package revid import ( + "errors" "fmt" "io" "net" "os" - "strconv" "sync" "time" @@ -118,7 +118,7 @@ func extractMeta(r string, log func(lvl int8, msg string, args ...interface{})) log(logger.Warning, pkg+"No timestamp in reply") } else { log(logger.Debug, fmt.Sprintf("%v got timestamp: %v", pkg, t)) - mts.Meta.Add("ts", strconv.Itoa(t)) + mts.SetTime(time.Unix(int64(t), 0)) } // Extract location from reply @@ -156,8 +156,8 @@ func (s *fileSender) Close() error { return s.file.Close() } // mtsSender implements io.WriteCloser and provides sending capability specifically // for use with MPEGTS packetization. It handles the construction of appropriately -// lengthed clips based on PSI. It also accounts for discontinuities by -// setting the discontinuity indicator for the first packet of a clip. +// lengthed clips based on clip duration and PSI. It also accounts for +// discontinuities by setting the discontinuity indicator for the first packet of a clip. type mtsSender struct { dst io.WriteCloser buf []byte @@ -166,19 +166,22 @@ type mtsSender struct { pkt packet.Packet repairer *mts.DiscontinuityRepairer curPid int + clipDur time.Duration + prev time.Time done chan struct{} log func(lvl int8, msg string, args ...interface{}) wg sync.WaitGroup } // newMtsSender returns a new mtsSender. -func newMtsSender(dst io.WriteCloser, log func(lvl int8, msg string, args ...interface{}), ringSize int, ringElementSize int, wTimeout time.Duration) *mtsSender { +func newMtsSender(dst io.WriteCloser, log func(lvl int8, msg string, args ...interface{}), rb *ring.Buffer, clipDur time.Duration) *mtsSender { s := &mtsSender{ dst: dst, repairer: mts.NewDiscontinuityRepairer(), log: log, - ring: ring.NewBuffer(ringSize, ringElementSize, wTimeout), + ring: rb, done: make(chan struct{}), + clipDur: clipDur, } s.wg.Add(1) go s.output() @@ -229,15 +232,20 @@ func (s *mtsSender) output() { // Write implements io.Writer. func (s *mtsSender) Write(d []byte) (int, error) { + if len(d) < mts.PacketSize { + return 0, errors.New("do not have full MTS packet") + } + if s.next != nil { s.buf = append(s.buf, s.next...) } bytes := make([]byte, len(d)) copy(bytes, d) s.next = bytes - copy(s.pkt[:], bytes) - s.curPid = s.pkt.PID() - if s.curPid == mts.PatPid && len(s.buf) > 0 { + p, _ := mts.PID(bytes) + s.curPid = int(p) + if time.Now().Sub(s.prev) >= s.clipDur && s.curPid == mts.PatPid && len(s.buf) > 0 { + s.prev = time.Now() _, err := s.ring.Write(s.buf) if err != nil { s.log(logger.Warning, pkg+"mtsSender: ringBuffer write error", "error", err.Error()) diff --git a/revid/senders_test.go b/revid/senders_test.go index d92f19f4..3fe2f291 100644 --- a/revid/senders_test.go +++ b/revid/senders_test.go @@ -39,6 +39,7 @@ import ( "bitbucket.org/ausocean/av/container/mts" "bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/utils/logger" + "bitbucket.org/ausocean/utils/ring" ) var ( @@ -133,12 +134,12 @@ func TestMtsSenderSegment(t *testing.T) { // Create ringBuffer, sender, sender and the MPEGTS encoder. const numberOfClips = 11 dst := &destination{t: t, done: make(chan struct{}), doneAt: numberOfClips} - sender := newMtsSender(dst, (*dummyLogger)(t).log, defaultMTSRBSize, defaultMTSRBElementSize, 0) + sender := newMtsSender(dst, (*dummyLogger)(t).log, ring.NewBuffer(defaultMTSRBSize, defaultMTSRBElementSize, 0), 0) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) // Turn time based PSI writing off for encoder. const psiSendCount = 10 - encoder.TimeBasedPsi(false, psiSendCount) + encoder.NALBasedPSI(false, psiSendCount) // Write the packets to the encoder, which will in turn write to the mtsSender. // Payload will just be packet number. @@ -211,12 +212,12 @@ func TestMtsSenderFailedSend(t *testing.T) { // Create destination, the mtsSender and the mtsEncoder const clipToFailAt = 3 dst := &destination{t: t, testFails: true, failAt: clipToFailAt, done: make(chan struct{})} - sender := newMtsSender(dst, (*dummyLogger)(t).log, defaultMTSRBSize, defaultMTSRBElementSize, 0) + sender := newMtsSender(dst, (*dummyLogger)(t).log, ring.NewBuffer(defaultMTSRBSize, defaultMTSRBElementSize, 0), 0) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) // Turn time based PSI writing off for encoder and send PSI every 10 packets. const psiSendCount = 10 - encoder.TimeBasedPsi(false, psiSendCount) + encoder.NALBasedPSI(false, psiSendCount) // Write the packets to the encoder, which will in turn write to the mtsSender. // Payload will just be packet number. @@ -291,12 +292,12 @@ func TestMtsSenderDiscontinuity(t *testing.T) { // Create destination, the mtsSender and the mtsEncoder. const clipToDelay = 3 dst := &destination{t: t, sendDelay: 10 * time.Millisecond, delayAt: clipToDelay, done: make(chan struct{})} - sender := newMtsSender(dst, (*dummyLogger)(t).log, 1, defaultMTSRBElementSize, 0) + sender := newMtsSender(dst, (*dummyLogger)(t).log, ring.NewBuffer(1, defaultMTSRBElementSize, 0), 0) encoder := mts.NewEncoder(sender, 25, mts.EncodeH264) // Turn time based PSI writing off for encoder. const psiSendCount = 10 - encoder.TimeBasedPsi(false, psiSendCount) + encoder.NALBasedPSI(false, psiSendCount) // Write the packets to the encoder, which will in turn write to the mtsSender. // Payload will just be packet number. From b9cd6b3f135e23c0291d98de79cf2611c0a87686 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 26 Aug 2019 09:24:18 +0930 Subject: [PATCH 50/58] container/mts/encoder.go: using NALTypeSPS from h264dec package --- codec/h264/h264dec/frame.go | 2 +- codec/h264/h264dec/read.go | 2 +- container/mts/encoder.go | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/codec/h264/h264dec/frame.go b/codec/h264/h264dec/frame.go index 82b32ae5..ca85982b 100644 --- a/codec/h264/h264dec/frame.go +++ b/codec/h264/h264dec/frame.go @@ -9,7 +9,7 @@ const ( naluTypeSlicePartC naluTypeSliceIDRPicture naluTypeSEI - naluTypeSPS + NALTypeSPS naluTypePPS naluTypeAccessUnitDelimiter naluTypeEndOfSequence diff --git a/codec/h264/h264dec/read.go b/codec/h264/h264dec/read.go index c98d46ca..9b283c92 100644 --- a/codec/h264/h264dec/read.go +++ b/codec/h264/h264dec/read.go @@ -61,7 +61,7 @@ func (h *H264Reader) Start() { // TODO: need to handle error from this. nalUnit, _, _ := h.readNalUnit() switch nalUnit.Type { - case naluTypeSPS: + case NALTypeSPS: // TODO: handle this error sps, _ := NewSPS(nalUnit.RBSP, false) h.VideoStreams = append( diff --git a/container/mts/encoder.go b/container/mts/encoder.go index 63413b77..cb3d9d06 100644 --- a/container/mts/encoder.go +++ b/container/mts/encoder.go @@ -33,6 +33,7 @@ import ( "time" "bitbucket.org/ausocean/av/codec/h264" + "bitbucket.org/ausocean/av/codec/h264/h264dec" "bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/pes" "bitbucket.org/ausocean/av/container/mts/psi" @@ -252,11 +253,7 @@ func (e *Encoder) Write(data []byte) (int, error) { return 0, fmt.Errorf("could not get type from NAL unit, failed with error: %v", err) } - // NAL type that will signify refresh. These are defined in H.264 specifications - // table 7-1. - const nalTypeSPS = 7 - - if nalType == nalTypeSPS { + if nalType == h264dec.NALTypeSPS { err := e.writePSI() if err != nil { return 0, err From 24e9ed69ca3e47f9edc5c71d7f1d12cfe76237ba Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 26 Aug 2019 09:26:35 +0930 Subject: [PATCH 51/58] revid/config.go: got rid of remaining references of 'RefreshPeriod' which is now MinPeriod --- revid/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/revid/config.go b/revid/config.go index e0870c40..7807b52c 100644 --- a/revid/config.go +++ b/revid/config.go @@ -109,7 +109,7 @@ const ( defaultRotation = 0 // Degrees defaultWidth = 1280 defaultHeight = 720 - defaultRefreshPeriod = 100 + defaultMinPeriod = 100 defaultClipDuration = 0 defaultQuantization = 30 defaultBitrate = 400000 @@ -393,8 +393,8 @@ func (c *Config) Validate() error { } if c.MinPeriod == 0 { - c.Logger.Log(logger.Info, pkg+"no intra refresh defined, defaulting", "intraRefresh", defaultRefreshPeriod) - c.MinPeriod = defaultRefreshPeriod + c.Logger.Log(logger.Info, pkg+"no min period defined, defaulting", "MinPeriod", defaultMinPeriod) + c.MinPeriod = defaultMinPeriod } else if c.MinPeriod < 0 { return errors.New("refresh period is less than 0") } From e57e14678a496e02075067d52fa2bfcd474a9387 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 26 Aug 2019 13:29:07 +0930 Subject: [PATCH 52/58] container/mts: using RealTime type from utils package instead of global vars with mutator functions --- container/mts/encoder.go | 113 ++++++++++++++------------------------- revid/senders.go | 2 +- 2 files changed, 41 insertions(+), 74 deletions(-) diff --git a/container/mts/encoder.go b/container/mts/encoder.go index cb3d9d06..7d968d5f 100644 --- a/container/mts/encoder.go +++ b/container/mts/encoder.go @@ -29,7 +29,6 @@ import ( "fmt" "io" "strconv" - "sync" "time" "bitbucket.org/ausocean/av/codec/h264" @@ -37,8 +36,43 @@ import ( "bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/pes" "bitbucket.org/ausocean/av/container/mts/psi" + "bitbucket.org/ausocean/utils/realtime" ) +// Media type values. +// TODO: reference relevant specifications. +const ( + H264ID = 27 + H265ID = 36 + audioStreamID = 0xc0 // First audio stream ID. +) + +// Constants used to communicate which media codec will be packetized. +const ( + EncodeH264 = iota + EncodeH265 + EncodeAudio +) + +// Time-related constants. +const ( + // ptsOffset is the offset added to the clock to determine + // the current presentation timestamp. + ptsOffset = 700 * time.Millisecond + + // PCRFrequency is the base Program Clock Reference frequency in Hz. + PCRFrequency = 90000 + + // PTSFrequency is the presentation timestamp frequency in Hz. + PTSFrequency = 90000 + + // MaxPTS is the largest PTS value (i.e., for a 33-bit unsigned integer). + MaxPTS = (1 << 33) - 1 +) + +// If we are not using NAL based PSI intervals then we will send PSI every 7 packets. +const psiSendCount = 7 + // Some common manifestations of PSI. var ( // StandardPAT is a minimal PAT. @@ -77,87 +111,20 @@ var ( } ) -const ( - psiInterval = 1 * time.Second - psiSendCount = 7 -) - // Meta allows addition of metadata to encoded mts from outside of this pkg. // See meta pkg for usage. // // TODO: make this not global. var Meta *meta.Data +// This will help us obtain a realtime for timestamp meta encoding. +var RealTime = realtime.NewRealTime() + var ( patTable = StandardPAT.Bytes() pmtTable []byte ) -const ( - H264ID = 27 - H265ID = 36 - audioStreamID = 0xc0 // First audio stream ID. -) - -// Constants used to communicate which media codec will be packetized. -const ( - EncodeH264 = iota - EncodeH265 - EncodeAudio -) - -// Time-related constants. -const ( - // ptsOffset is the offset added to the clock to determine - // the current presentation timestamp. - ptsOffset = 700 * time.Millisecond - - // PCRFrequency is the base Program Clock Reference frequency in Hz. - PCRFrequency = 90000 - - // PTSFrequency is the presentation timestamp frequency in Hz. - PTSFrequency = 90000 - - // MaxPTS is the largest PTS value (i.e., for a 33-bit unsigned integer). - MaxPTS = (1 << 33) - 1 -) - -// Globals for use in keeping real time. -var ( - realRefTime time.Time // Holds a reference real time given to SetTime. - sysRefTime time.Time // Holds a system reference time set when realRefTime is obtained. - timeIsSet bool // Indicates if the time has been set. - mu = sync.Mutex{} // Used when accessing/mutating above time vars. -) - -// SetTime allows setting of current time. This is useful if the system running -// this encoder does not have time keeping. The user may wish to obtain an -// accurate time from an NTP server or local machine and pass to this function. -func SetTime(t time.Time) { - mu.Lock() - realRefTime = t - sysRefTime = time.Now() - timeIsSet = true - mu.Unlock() -} - -// Time provides either a real time that has been calculated from a reference -// set by SetTime, or using the current system time. -func Time() time.Time { - mu.Lock() - t := realRefTime.Add(time.Now().Sub(sysRefTime)) - mu.Unlock() - return t -} - -// TimeIsSet returns true if SetTime has been used to set a real reference time. -func TimeIsSet() bool { - mu.Lock() - b := timeIsSet - mu.Unlock() - return b -} - // Encoder encapsulates properties of an MPEG-TS generator. type Encoder struct { dst io.WriteCloser @@ -373,8 +340,8 @@ func (e *Encoder) ccFor(pid int) byte { // contained in the global Meta struct. func updateMeta(b []byte) ([]byte, error) { p := psi.PSIBytes(b) - if TimeIsSet() { - Meta.Add("ts", strconv.Itoa(int(Time().Unix()))) + if RealTime.IsSet() { + Meta.Add("ts", strconv.Itoa(int(RealTime.Get().Unix()))) } err := p.AddDescriptor(psi.MetadataTag, Meta.Encode()) return []byte(p), err diff --git a/revid/senders.go b/revid/senders.go index 18389354..a6a593bd 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -118,7 +118,7 @@ func extractMeta(r string, log func(lvl int8, msg string, args ...interface{})) log(logger.Warning, pkg+"No timestamp in reply") } else { log(logger.Debug, fmt.Sprintf("%v got timestamp: %v", pkg, t)) - mts.SetTime(time.Unix(int64(t), 0)) + mts.RealTime.Set(time.Unix(int64(t), 0)) } // Extract location from reply From a8081b52b298359ae8d50ae5ee2572f8af6a1faf Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 26 Aug 2019 13:43:45 +0930 Subject: [PATCH 53/58] revid/config.go: MinPeriod => MinFrames --- revid/config.go | 20 ++++++++++---------- revid/revid.go | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/revid/config.go b/revid/config.go index 7807b52c..87fb9139 100644 --- a/revid/config.go +++ b/revid/config.go @@ -109,7 +109,7 @@ const ( defaultRotation = 0 // Degrees defaultWidth = 1280 defaultHeight = 720 - defaultMinPeriod = 100 + defaultMinFrames = 100 defaultClipDuration = 0 defaultQuantization = 30 defaultBitrate = 400000 @@ -204,14 +204,14 @@ type Config struct { // are using Raspivid input. Quantization uint - // MinPeriod defines the frequency of key NAL units SPS, PPS and IDR in + // MinFrames defines the frequency of key NAL units SPS, PPS and IDR in // number of NAL units. This will also determine the frequency of PSI if the - // output container is MPEG-TS. If ClipDuration is less than MinPeriod, - // ClipDuration will default to MinPeriod. - MinPeriod uint + // output container is MPEG-TS. If ClipDuration is less than MinFrames, + // ClipDuration will default to MinFrames. + MinFrames uint // ClipDuration is the duration of MTS data that is sent using HTTP or RTP - // output. This defaults to 0, therefore MinPeriod will determine the length of + // output. This defaults to 0, therefore MinFrames will determine the length of // clips by default. ClipDuration time.Duration @@ -392,10 +392,10 @@ func (c *Config) Validate() error { return errors.New("invalid bitrate") } - if c.MinPeriod == 0 { - c.Logger.Log(logger.Info, pkg+"no min period defined, defaulting", "MinPeriod", defaultMinPeriod) - c.MinPeriod = defaultMinPeriod - } else if c.MinPeriod < 0 { + if c.MinFrames == 0 { + c.Logger.Log(logger.Info, pkg+"no min period defined, defaulting", "MinFrames", defaultMinFrames) + c.MinFrames = defaultMinFrames + } else if c.MinFrames < 0 { return errors.New("refresh period is less than 0") } diff --git a/revid/revid.go b/revid/revid.go index 13efdd7b..fa1156b1 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -456,13 +456,13 @@ func (r *Revid) Update(vars map[string]string) error { break } r.config.Quantization = uint(v) - case "MinPeriod": + case "MinFrames": v, err := strconv.Atoi(value) if err != nil { - r.config.Logger.Log(logger.Warning, pkg+"invalid MinPeriod param", "value", value) + r.config.Logger.Log(logger.Warning, pkg+"invalid MinFrames param", "value", value) break } - r.config.MinPeriod = uint(v) + r.config.MinFrames = uint(v) case "ClipDuration": v, err := strconv.Atoi(value) @@ -585,7 +585,7 @@ func (r *Revid) startRaspivid() (func() error, error) { args = append(args, "--codec", "H264", "--inline", - "--intra", fmt.Sprint(r.config.MinPeriod), + "--intra", fmt.Sprint(r.config.MinFrames), ) if r.config.Quantization != 0 { args = append(args, "-qp", fmt.Sprint(r.config.Quantization)) From d4fe1498f5ac020f807fd057fef1bf1375cde72a Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 26 Aug 2019 16:01:48 +0930 Subject: [PATCH 54/58] codec/h264/h264dec: moved CABAC encoding related stuff to cabacenc.go and cabacenc_test.go and corrected function names --- codec/h264/h264dec/cabac.go | 192 ---------------------- codec/h264/h264dec/cabac_test.go | 206 ------------------------ codec/h264/h264dec/cabacenc.go | 222 ++++++++++++++++++++++++++ codec/h264/h264dec/cabacenc_test.go | 237 ++++++++++++++++++++++++++++ 4 files changed, 459 insertions(+), 398 deletions(-) create mode 100644 codec/h264/h264dec/cabacenc.go create mode 100644 codec/h264/h264dec/cabacenc_test.go diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index b5940a00..7b28414e 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -28,8 +28,6 @@ LICENSE package h264dec import ( - "math" - "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) @@ -306,80 +304,6 @@ var ( } ) -// Errors used by mbTypeBinarization. -var ( - errBadMbType = errors.New("macroblock type outside of valid range") - errBadMbSliceType = errors.New("bad slice type for macroblock") -) - -// mbTypeBinarization returns the macroblock type binarization for the given -// macroblock type value and slice type using the process defined in section -// 9.3.2.5 of the specifications. -func mbTypeBinarization(v, slice int) ([]int, error) { - switch slice { - case sliceTypeI: - if v < minIMbType || v > maxIMbType { - return nil, errBadMbType - } - return binOfIMBTypes[v], nil - - case sliceTypeSI: - if v < minSIMbType || v > maxSIMbType { - return nil, errBadMbType - } - if v == sliceTypeSI { - return []int{0}, nil - } - return append([]int{1}, binOfIMBTypes[v-1]...), nil - - case sliceTypeP, sliceTypeSP: - if v < minPOrSPMbType || v > maxPOrSPMbType || v == P8x8ref0 { - return nil, errBadMbType - } - if v < 5 { - return binOfPOrSPMBTypes[v], nil - } - return append([]int{1}, binOfIMBTypes[v-5]...), nil - - case sliceTypeB: - if v < minBMbType || v > maxBMbType { - return nil, errBadMbType - } - if v < 23 { - return binOfBMBTypes[v], nil - } - return append([]int{1, 1, 1, 1, 0, 1}, binOfIMBTypes[v-23]...), nil - - default: - return nil, errBadMbSliceType - } -} - -// Error used by subMbTypeBinarization. -var errBadSubMbSliceType = errors.New("bad slice type for sub-macroblock") - -// subMbTypeBinarization returns the binarization of a sub-macroblock type -// given the slice in which it is in using the process defined in section -// 9.3.2.5 of the specifications. -func subMbTypeBinarization(v, slice int) ([]int, error) { - switch slice { - case sliceTypeP, sliceTypeSP: - if v < minPOrSPSubMbType || v > maxPOrSPSubMbType { - return nil, errBadMbType - } - return binOfPOrSPSubMBTypes[v], nil - - case sliceTypeB: - if v < minBSubMbType || v > maxBSubMbType { - return nil, errBadMbType - } - return binOfBSubMBTypes[v], nil - - default: - return nil, errBadSubMbSliceType - } -} - // Table 9-34 type MaxBinIdxCtx struct { // When false, Prefix is the MaxBinIdxCtx @@ -762,119 +686,3 @@ func CtxIdx(binIdx, maxBinIdxCtx, ctxIdxOffset int) int { return ctxIdx } - -// Error used by unaryBinarization. -var errNegativeSyntaxVal = errors.New("cannot get unary binarization of negative value") - -// unaryBinarization returns the unary binarization of a syntax element having -// value v, as specified by setion 9.3.2.1 in the specifications. -func unaryBinarization(v int) ([]int, error) { - if v < 0 { - return nil, errNegativeSyntaxVal - } - r := make([]int, v+1) - for i := 0; i <= v; i++ { - if i < v { - r[i] = 1 - } - } - return r, nil -} - -// Error used by truncUnaryBinarization. -var errInvalidSyntaxVal = errors.New("syntax value cannot be greater than cMax") - -// truncUnaryBinarization returns the truncated 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 s []int - - if absi(v) >= uCoff { - sufS := absi(v) - uCoff - var stop bool - - for { - if sufS >= (1 << uint(k)) { - s = append(s, 1) - sufS = sufS - (1 << uint(k)) - k++ - } else { - s = append(s, 0) - for k = k - 1; k >= 0; k-- { - s = append(s, (sufS>>uint(k))&1) - } - stop = true - } - if stop { - break - } - } - } - - if signedValFlag && v != 0 { - if v > 0 { - s = append(s, 0) - } else { - s = append(s, 1) - } - } - - return s -} - -// Error used by fixedLenBinariztion. -var errNegativeValue = errors.New("cannot get fixed length binarization of negative value") - -// fixedLenBinarization returns the fixed-length (FL) binarization of the syntax -// element v, given cMax to determine bin length, as specified by section 9.3.2.4 -// of the specifications. -func fixedLenBinarization(v, cMax int) ([]int, error) { - if v < 0 { - return nil, errNegativeValue - } - l := int(math.Ceil(math.Log2(float64(cMax + 1)))) - r := make([]int, l) - for i := l - 1; i >= 0; i-- { - r[i] = v % 2 - v = v / 2 - } - return r, nil -} diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 5ca17ce8..20f1e066 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -26,74 +26,9 @@ LICENSE package h264dec import ( - "reflect" "testing" ) -func TestMbTypeBinarization(t *testing.T) { - tests := []struct { - v, slice int - want []int - err error - }{ - {v: 6, slice: sliceTypeI, want: []int{1, 0, 0, 1, 0, 0, 1}}, - {v: 26, slice: sliceTypeI, err: errBadMbType}, - {v: -1, slice: sliceTypeI, err: errBadMbType}, - {v: 4, slice: sliceTypeSI, want: []int{0}}, - {v: 6, slice: sliceTypeSI, want: []int{1, 1, 0, 0, 1, 0, 0, 0}}, - {v: 0, slice: sliceTypeSI, err: errBadMbType}, - {v: 27, slice: sliceTypeSI, err: errBadMbType}, - {v: 2, slice: sliceTypeP, want: []int{0, 1, 0}}, - {v: 3, slice: sliceTypeSP, want: []int{0, 0, 1}}, - {v: 7, slice: sliceTypeP, want: []int{1, 1, 0, 0, 0, 0, 1}}, - {v: 7, slice: sliceTypeSP, want: []int{1, 1, 0, 0, 0, 0, 1}}, - {v: -1, slice: sliceTypeP, err: errBadMbType}, - {v: 31, slice: sliceTypeP, err: errBadMbType}, - {v: 8, slice: sliceTypeB, want: []int{1, 1, 0, 1, 0, 1}}, - {v: 30, slice: sliceTypeB, want: []int{1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0}}, - {v: -1, slice: sliceTypeB, err: errBadMbType}, - {v: 49, slice: sliceTypeB, err: errBadMbType}, - {v: 6, slice: 20, err: errBadMbSliceType}, - } - - for i, test := range tests { - got, err := mbTypeBinarization(test.v, test.slice) - 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(got, test.want) { - t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want) - } - } -} - -func TestSubMbTypeBinarization(t *testing.T) { - tests := []struct { - v, slice int - want []int - err error - }{ - {v: 2, slice: sliceTypeP, want: []int{0, 1, 1}}, - {v: 2, slice: sliceTypeSP, want: []int{0, 1, 1}}, - {v: -1, slice: sliceTypeSP, err: errBadMbType}, - {v: 4, slice: sliceTypeSP, err: errBadMbType}, - {v: 9, slice: sliceTypeB, want: []int{1, 1, 1, 0, 1, 0}}, - {v: 9, slice: 40, err: errBadSubMbSliceType}, - } - - for i, test := range tests { - got, err := subMbTypeBinarization(test.v, test.slice) - 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(got, test.want) { - t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want) - } - } -} - var ctxIdxTests = []struct { binIdx int maxBinIdxCtx int @@ -211,144 +146,3 @@ func TestCtxIdx(t *testing.T) { } } } - -func TestUnaryBinarization(t *testing.T) { - // Test data has been extracted from table 9-35 of the specifications. - tests := []struct { - in int - want []int - err error - }{ - {in: 0, want: []int{0}, err: nil}, - {in: 1, want: []int{1, 0}, err: nil}, - {in: 2, want: []int{1, 1, 0}, err: nil}, - {in: 3, want: []int{1, 1, 1, 0}, err: nil}, - {in: 4, want: []int{1, 1, 1, 1, 0}, err: nil}, - {in: 5, want: []int{1, 1, 1, 1, 1, 0}, err: nil}, - {in: -3, want: nil, err: errNegativeSyntaxVal}, - } - - for i, test := range tests { - got, err := unaryBinarization(test.in) - 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 TestFixedLengthBinarization(t *testing.T) { - tests := []struct { - v int - cMax int - want []int - err error - }{ - {v: 0, cMax: 7, want: []int{0, 0, 0}}, - {v: 1, cMax: 7, want: []int{0, 0, 1}}, - {v: 2, cMax: 7, want: []int{0, 1, 0}}, - {v: 3, cMax: 7, want: []int{0, 1, 1}}, - {v: 4, cMax: 7, want: []int{1, 0, 0}}, - {v: 5, cMax: 7, want: []int{1, 0, 1}}, - {v: 6, cMax: 7, want: []int{1, 1, 0}}, - {v: 7, cMax: 7, want: []int{1, 1, 1}}, - {v: -1, cMax: 7, want: nil, err: errNegativeValue}, - } - - for i, test := range tests { - got, err := fixedLenBinarization(test.v, test.cMax) - if err != test.err { - t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", 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 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/cabacenc.go b/codec/h264/h264dec/cabacenc.go new file mode 100644 index 00000000..224fc529 --- /dev/null +++ b/codec/h264/h264dec/cabacenc.go @@ -0,0 +1,222 @@ +/* +TODO: this file should really be in a 'h264enc' package. + +DESCRIPTION + cabacenc.go provides functionality for CABAC encoding. + +AUTHORS + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package h264dec + +import ( + "errors" + "math" +) + +// Error used by unaryBinString. +var errNegativeSyntaxVal = errors.New("cannot get unary binary string of negative value") + +// unaryBinString returns the unary binary string of a syntax element having +// value v, as specified by setion 9.3.2.1 in the specifications. +func unaryBinString(v int) ([]int, error) { + if v < 0 { + return nil, errNegativeSyntaxVal + } + r := make([]int, v+1) + for i := 0; i <= v; i++ { + if i < v { + r[i] = 1 + } + } + return r, nil +} + +// Error used by truncUnaryBinString. +var errInvalidSyntaxVal = errors.New("syntax value cannot be greater than cMax") + +// truncUnaryBinString returns the truncated unary binary string of a syntax +// element v given a cMax as specified in section 9.3.2.2 of the specifications. +func truncUnaryBinString(v, cMax int) ([]int, error) { + if v < 0 { + return nil, errNegativeSyntaxVal + } + + if v > cMax { + return nil, errInvalidSyntaxVal + } + + if v == cMax { + b, _ := unaryBinString(v) + return b[:len(b)-1], nil + } + return unaryBinString(v) +} + +// Error used by unaryExpGolombBinString. +var errInvalidUCoff = errors.New("uCoff cannot be less than or equal to zero") + +// unaryExpGolombBinString returns the concatendated unary/k-th order +// Exp-Golomb (UEGk) binary string of a syntax element using the process defined +// in section 9.3.2.3 of the specifications. +func unaryExpGolombBinString(v, uCoff, k int, signedValFlag bool) ([]int, error) { + if uCoff <= 0 { + return nil, errInvalidUCoff + } + + prefix, err := truncUnaryBinString(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 binar string +// 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 s []int + + if absi(v) >= uCoff { + sufS := absi(v) - uCoff + var stop bool + + for { + if sufS >= (1 << uint(k)) { + s = append(s, 1) + sufS = sufS - (1 << uint(k)) + k++ + } else { + s = append(s, 0) + for k = k - 1; k >= 0; k-- { + s = append(s, (sufS>>uint(k))&1) + } + stop = true + } + if stop { + break + } + } + } + + if signedValFlag && v != 0 { + if v > 0 { + s = append(s, 0) + } else { + s = append(s, 1) + } + } + + return s +} + +// Error used by fixedLenBinString. +var errNegativeValue = errors.New("cannot get fixed length binary string of negative value") + +// fixedLenBinString returns the fixed-length (FL) binary string of the syntax +// element v, given cMax to determine bin length, as specified by section 9.3.2.4 +// of the specifications. +func fixedLenBinString(v, cMax int) ([]int, error) { + if v < 0 { + return nil, errNegativeValue + } + l := int(math.Ceil(math.Log2(float64(cMax + 1)))) + r := make([]int, l) + for i := l - 1; i >= 0; i-- { + r[i] = v % 2 + v = v / 2 + } + return r, nil +} + +// Errors used by mbTypeBinString. +var ( + errBadMbType = errors.New("macroblock type outside of valid range") + errBadMbSliceType = errors.New("bad slice type for macroblock") +) + +// mbTypeBinString returns the macroblock type binary string for the given +// macroblock type value and slice type using the process defined in section +// 9.3.2.5 of the specifications. +func mbTypeBinString(v, slice int) ([]int, error) { + switch slice { + case sliceTypeI: + if v < minIMbType || v > maxIMbType { + return nil, errBadMbType + } + return binOfIMBTypes[v], nil + + case sliceTypeSI: + if v < minSIMbType || v > maxSIMbType { + return nil, errBadMbType + } + if v == sliceTypeSI { + return []int{0}, nil + } + return append([]int{1}, binOfIMBTypes[v-1]...), nil + + case sliceTypeP, sliceTypeSP: + if v < minPOrSPMbType || v > maxPOrSPMbType || v == P8x8ref0 { + return nil, errBadMbType + } + if v < 5 { + return binOfPOrSPMBTypes[v], nil + } + return append([]int{1}, binOfIMBTypes[v-5]...), nil + + case sliceTypeB: + if v < minBMbType || v > maxBMbType { + return nil, errBadMbType + } + if v < 23 { + return binOfBMBTypes[v], nil + } + return append([]int{1, 1, 1, 1, 0, 1}, binOfIMBTypes[v-23]...), nil + + default: + return nil, errBadMbSliceType + } +} + +// Error used by subMbTypeBinString. +var errBadSubMbSliceType = errors.New("bad slice type for sub-macroblock") + +// subMbTypeBinString returns the binary string of a sub-macroblock type +// given the slice in which it is in using the process defined in section +// 9.3.2.5 of the specifications. +func subMbTypeBinString(v, slice int) ([]int, error) { + switch slice { + case sliceTypeP, sliceTypeSP: + if v < minPOrSPSubMbType || v > maxPOrSPSubMbType { + return nil, errBadMbType + } + return binOfPOrSPSubMBTypes[v], nil + + case sliceTypeB: + if v < minBSubMbType || v > maxBSubMbType { + return nil, errBadMbType + } + return binOfBSubMBTypes[v], nil + + default: + return nil, errBadSubMbSliceType + } +} diff --git a/codec/h264/h264dec/cabacenc_test.go b/codec/h264/h264dec/cabacenc_test.go new file mode 100644 index 00000000..d27b1624 --- /dev/null +++ b/codec/h264/h264dec/cabacenc_test.go @@ -0,0 +1,237 @@ +/* +TODO: this file should really be in a 'h264enc' package. + +DESCRIPTION + cabacenc_test.go provides testing for functionality found in cabacenc.go. + +AUTHORS + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package h264dec + +import ( + "reflect" + "testing" +) + +func TestMbTypeBinString(t *testing.T) { + tests := []struct { + v, slice int + want []int + err error + }{ + {v: 6, slice: sliceTypeI, want: []int{1, 0, 0, 1, 0, 0, 1}}, + {v: 26, slice: sliceTypeI, err: errBadMbType}, + {v: -1, slice: sliceTypeI, err: errBadMbType}, + {v: 4, slice: sliceTypeSI, want: []int{0}}, + {v: 6, slice: sliceTypeSI, want: []int{1, 1, 0, 0, 1, 0, 0, 0}}, + {v: 0, slice: sliceTypeSI, err: errBadMbType}, + {v: 27, slice: sliceTypeSI, err: errBadMbType}, + {v: 2, slice: sliceTypeP, want: []int{0, 1, 0}}, + {v: 3, slice: sliceTypeSP, want: []int{0, 0, 1}}, + {v: 7, slice: sliceTypeP, want: []int{1, 1, 0, 0, 0, 0, 1}}, + {v: 7, slice: sliceTypeSP, want: []int{1, 1, 0, 0, 0, 0, 1}}, + {v: -1, slice: sliceTypeP, err: errBadMbType}, + {v: 31, slice: sliceTypeP, err: errBadMbType}, + {v: 8, slice: sliceTypeB, want: []int{1, 1, 0, 1, 0, 1}}, + {v: 30, slice: sliceTypeB, want: []int{1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0}}, + {v: -1, slice: sliceTypeB, err: errBadMbType}, + {v: 49, slice: sliceTypeB, err: errBadMbType}, + {v: 6, slice: 20, err: errBadMbSliceType}, + } + + for i, test := range tests { + got, err := mbTypeBinString(test.v, test.slice) + 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(got, test.want) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want) + } + } +} + +func TestSubMbTypeBinString(t *testing.T) { + tests := []struct { + v, slice int + want []int + err error + }{ + {v: 2, slice: sliceTypeP, want: []int{0, 1, 1}}, + {v: 2, slice: sliceTypeSP, want: []int{0, 1, 1}}, + {v: -1, slice: sliceTypeSP, err: errBadMbType}, + {v: 4, slice: sliceTypeSP, err: errBadMbType}, + {v: 9, slice: sliceTypeB, want: []int{1, 1, 1, 0, 1, 0}}, + {v: 9, slice: 40, err: errBadSubMbSliceType}, + } + + for i, test := range tests { + got, err := subMbTypeBinString(test.v, test.slice) + 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(got, test.want) { + t.Errorf("did not get expected result for test %d\nGot: %v\nWant: %v", i, got, test.want) + } + } +} + +func TestUnaryBinString(t *testing.T) { + // Test data has been extracted from table 9-35 of the specifications. + tests := []struct { + in int + want []int + err error + }{ + {in: 0, want: []int{0}, err: nil}, + {in: 1, want: []int{1, 0}, err: nil}, + {in: 2, want: []int{1, 1, 0}, err: nil}, + {in: 3, want: []int{1, 1, 1, 0}, err: nil}, + {in: 4, want: []int{1, 1, 1, 1, 0}, err: nil}, + {in: 5, want: []int{1, 1, 1, 1, 1, 0}, err: nil}, + {in: -3, want: nil, err: errNegativeSyntaxVal}, + } + + for i, test := range tests { + got, err := unaryBinString(test.in) + 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 TestFixedLengthBinString(t *testing.T) { + tests := []struct { + v int + cMax int + want []int + err error + }{ + {v: 0, cMax: 7, want: []int{0, 0, 0}}, + {v: 1, cMax: 7, want: []int{0, 0, 1}}, + {v: 2, cMax: 7, want: []int{0, 1, 0}}, + {v: 3, cMax: 7, want: []int{0, 1, 1}}, + {v: 4, cMax: 7, want: []int{1, 0, 0}}, + {v: 5, cMax: 7, want: []int{1, 0, 1}}, + {v: 6, cMax: 7, want: []int{1, 1, 0}}, + {v: 7, cMax: 7, want: []int{1, 1, 1}}, + {v: -1, cMax: 7, want: nil, err: errNegativeValue}, + } + + for i, test := range tests { + got, err := fixedLenBinString(test.v, test.cMax) + if err != test.err { + t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", 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 TestTruncUnaryBinString(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 := truncUnaryBinString(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 TestUnaryExpGolombBinString(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 := unaryExpGolombBinString(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) + } + } +} From 132059d26ece95c3bfbf5cc1270687b678eb3ce4 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 20 Aug 2019 16:18:39 +0930 Subject: [PATCH 55/58] codec/h264/h264dec/cabac.go: added binarization process for coded block pattern --- codec/h264/h264dec/cabacenc.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/codec/h264/h264dec/cabacenc.go b/codec/h264/h264dec/cabacenc.go index 224fc529..f9b0cca5 100644 --- a/codec/h264/h264dec/cabacenc.go +++ b/codec/h264/h264dec/cabacenc.go @@ -28,6 +28,7 @@ package h264dec import ( "errors" + "fmt" "math" ) @@ -220,3 +221,23 @@ func subMbTypeBinString(v, slice int) ([]int, error) { return nil, errBadSubMbSliceType } } + +// codedBlockPatternBinString returns the binarization for the syntax element +// coded_block_pattern as defined by section 9.3.2.6 in specifications. +func codedBlockPatternBinString(luma, chroma, arrayType int) ([]int, error) { + p, err := fixedLenBinString(luma, 15) + if err != nil { + return nil, fmt.Errorf("fixed length binarization failed with error: %v", err) + } + + if arrayType == 0 || arrayType == 3 { + return p, nil + } + + s, err := truncUnaryBinString(chroma, 2) + if err != nil { + return nil, fmt.Errorf("truncated unary binarization failed with error: %v", err) + } + + return append(p, s...), nil +} From 838d5dd5d68afafbf3f1b27dae96188bec3131bf Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 28 Aug 2019 10:09:27 +0930 Subject: [PATCH 56/58] codec/h264/h264dec: added level_prefix parsing process and test --- codec/h264/h264dec/cavlc.go | 46 ++++++++++++++++++++++++++ codec/h264/h264dec/cavlc_test.go | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 codec/h264/h264dec/cavlc.go create mode 100644 codec/h264/h264dec/cavlc_test.go diff --git a/codec/h264/h264dec/cavlc.go b/codec/h264/h264dec/cavlc.go new file mode 100644 index 00000000..c477a490 --- /dev/null +++ b/codec/h264/h264dec/cavlc.go @@ -0,0 +1,46 @@ +/* +DESCRIPTION + cavlc.go provides utilities for context-adaptive variable-length coding + for the parsing of H.264 syntax structure fields. + +AUTHORS + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package h264dec + +import ( + "fmt" + + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" +) + +// 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) { + zeros := -1 + for b := 0; b != 1; zeros++ { + _b, err := br.ReadBits(1) + if err != nil { + return -1, fmt.Errorf("could not read bit, failed with error: %v", err) + } + b = int(_b) + } + return zeros, nil +} diff --git a/codec/h264/h264dec/cavlc_test.go b/codec/h264/h264dec/cavlc_test.go new file mode 100644 index 00000000..a6740858 --- /dev/null +++ b/codec/h264/h264dec/cavlc_test.go @@ -0,0 +1,55 @@ +/* +DESCRIPTION + cavlc_test.go provides testing for functionality in cavlc.go. + +AUTHORS + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2019 the Australian Ocean Lab (AusOcean). + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package h264dec + +import ( + "bytes" + "testing" + + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" +) + +func TestParseLevelPrefix(t *testing.T) { + tests := []struct { + in string + want int + }{ + {in: "00001", want: 4}, + {in: "0000001", want: 6}, + {in: "1", want: 0}, + } + + for i, test := range tests { + s, _ := binToSlice(test.in) + l, err := parseLevelPrefix(bits.NewBitReader(bytes.NewReader(s))) + if err != nil { + t.Errorf("did not expect error: %v, for test %d", err, i) + } + + if l != test.want { + t.Errorf("did not get expected result for test %d\nGot: %d\nWant: %d\n", i, l, test.want) + } + } +} From 1fd439be11037a3b5ed12bbdb399d52d407d80d7 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 5 Sep 2019 17:13:27 +0930 Subject: [PATCH 57/58] Use ausocean/utils v1.2.8. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d35601e0..adbdf113 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( bitbucket.org/ausocean/iot v1.2.6 - bitbucket.org/ausocean/utils v1.2.6 + bitbucket.org/ausocean/utils v1.2.8 github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 From fc9ced1ae8ef12b91557d0401c45a930029a74ef Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 5 Sep 2019 17:14:40 +0930 Subject: [PATCH 58/58] Use ausocean/utils v1.2.8. --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 692bf5a1..c2d78459 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ bitbucket.org/ausocean/utils v0.0.0-20190408050157-66d3b4d4041e/go.mod h1:uXzX9z bitbucket.org/ausocean/utils v1.2.4/go.mod h1:5JIXFTAMMNl5Ob79tpZfDCJ+gOO8rj7v4ORj56tHZpw= bitbucket.org/ausocean/utils v1.2.6 h1:JN66APCV+hu6GebIHSu2KSywhLym4vigjSz5+fB0zXc= bitbucket.org/ausocean/utils v1.2.6/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= +bitbucket.org/ausocean/utils v1.2.8 h1:wRlajOtaMz/loUrGmFf4SUcTnZALtTqgPmk49iHMWxs= +bitbucket.org/ausocean/utils v1.2.8/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXShkLA3/kpxXb6LJpH+ekU2krbzw=