From 9af964cfc99ea308271bfcb8cbaccb89c87a5d78 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 11 Jun 2019 22:07:51 +0930 Subject: [PATCH 01/93] container/mts/meta: added GetAllAsMap function This function is very similar to GetAll, except that is returns a map[string]string rather than a [][2]string. It's become apparent that a map[string]string might be more useful in some circumstances. --- container/mts/meta/meta.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/container/mts/meta/meta.go b/container/mts/meta/meta.go index 188c2d4e..4d3078a0 100644 --- a/container/mts/meta/meta.go +++ b/container/mts/meta/meta.go @@ -216,6 +216,34 @@ func GetAll(d []byte) ([][2]string, error) { return all, nil } +// GetAllAsMap returns a map containging keys and values from a slice d containing +// AusOcean metadata. +func GetAllAsMap(d []byte) (map[string]string, error) { + err := checkMeta(d) + if err != nil { + return nil, err + } + + // Skip the header, which is our data length and version. + d = d[headSize:] + + // Each metadata entry (key and value) is seperated by a tab, so split at tabs + // to get individual entries. + entries := strings.Split(string(d), "\t") + + // Go through entries and add to all map. + all := make(map[string]string) + for _, entry := range entries { + // Keys and values are seperated by '=', so split and check that len(kv)=2. + kv := strings.Split(entry, "=") + if len(kv) != 2 { + return nil, errUnexpectedMetaFormat + } + all[kv[0]] = kv[1] + } + return all, nil +} + // checkHeader checks that a valid metadata header exists in the given data. func checkMeta(d []byte) error { if len(d) == 0 || d[0] != 0 || binary.BigEndian.Uint16(d[2:headSize]) != uint16(len(d[headSize:])) { From 482f5609e5a9a017013c0dd7ee3e6aaa4a73ae29 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 11 Jun 2019 22:15:27 +0930 Subject: [PATCH 02/93] container/mts/meta: wrote test for GetAllAsMap --- container/mts/meta/meta_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/container/mts/meta/meta_test.go b/container/mts/meta/meta_test.go index 38e4dbb6..9316ce55 100644 --- a/container/mts/meta/meta_test.go +++ b/container/mts/meta/meta_test.go @@ -189,6 +189,23 @@ func TestGetAll(t *testing.T) { } } +// TestGetAllAsMap checks that GetAllAsMap will correctly return a map of meta +// keys and values from a slice of meta. +func TestGetAllAsMap(t *testing.T) { + tstMeta := append([]byte{0x00, 0x10, 0x00, 0x12}, "loc=a,b,c\tts=12345"...) + want := map[string]string{ + "loc": "a,b,c", + "ts": "12345", + } + got, err := GetAllAsMap(tstMeta) + if err != nil { + t.Errorf("Unexpected error: %v\n", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Did not get expected out. \nGot : %v, \nWant: %v\n", got, want) + } +} + // TestKeys checks that we can successfully get keys from some metadata using // the meta.Keys method. func TestKeys(t *testing.T) { From e4bce3bcb33d2bed529f40937badc769da396a92 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 12 Jun 2019 01:01:51 +0930 Subject: [PATCH 03/93] container/mts: added ExtractMeta function and testing Added function to extract meta from first PMT found in a mpegts clip. This simply wraps the logic we've used in vidrecord, i.e. Finding the PMT, converting to a comcast gots psi.PSIBytesm, using HasDescriptor to get the meta descriptor, and then mapping metadata. Also added testing. --- container/mts/metaEncode_test.go | 26 ++++++++++++++++++++++++++ container/mts/mpegts.go | 25 ++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/container/mts/metaEncode_test.go b/container/mts/metaEncode_test.go index 83660777..3a0dbd62 100644 --- a/container/mts/metaEncode_test.go +++ b/container/mts/metaEncode_test.go @@ -29,6 +29,7 @@ package mts import ( "bytes" + "reflect" "testing" "bitbucket.org/ausocean/av/container/mts/meta" @@ -98,3 +99,28 @@ func TestMetaEncode2(t *testing.T) { t.Errorf(errNotExpectedOut, got, want) } } + +// TestExtractMeta checks that ExtractMeta can correclty get a map of metadata +// from the first PMT in a clip of MPEG-TS. +func TestExtractMeta(t *testing.T) { + Meta = meta.New() + var buf bytes.Buffer + e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) + Meta.Add("ts", "12345678") + Meta.Add("loc", "1234,4321,1234") + if err := e.writePSI(); err != nil { + t.Errorf(errUnexpectedErr, err.Error()) + } + out := buf.Bytes() + got, err := ExtractMeta(out) + if err != nil { + t.Errorf(errUnexpectedErr, err.Error()) + } + want := map[string]string{ + "ts": "12345678", + "loc": "1234,4321,1234", + } + if !reflect.DeepEqual(got, want) { + t.Errorf(errNotExpectedOut, got, want) + } +} diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index eb4bee5d..0aabd5a1 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -33,6 +33,8 @@ import ( "errors" "fmt" + "bitbucket.org/ausocean/av/container/mts/meta" + "bitbucket.org/ausocean/av/container/mts/psi" "github.com/Comcast/gots/packet" "github.com/Comcast/gots/pes" ) @@ -331,7 +333,6 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { copy(_pkt[:], pkt) payload, err := packet.Payload(&_pkt) if err != nil { - fmt.Printf("_pkt: %v\n", _pkt) return [2]uint64{}, err } @@ -361,3 +362,25 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { return [2]uint64{}, errors.New("could only find one access unit in mpegts clip") } + +// ExtractMeta returns a map of metadata from the first PMT's metaData +// descriptor, that is found in the MPEG-TS clip d. d must contain a series of +// complete MPEG-TS packets. +func ExtractMeta(d []byte) (map[string]string, error) { + pkt, _, err := FindPid(d, PmtPid) + if err != nil { + return nil, err + } + + // Get as PSI type. Need to skip MTS header. + pmt := psi.PSIBytes(pkt[4:]) + _, metaDescriptor := pmt.HasDescriptor(psi.MetadataTag) + + if metaDescriptor == nil { + return nil, errors.New("PMT does not have descriptor") + } + // Skip the descriptor head. + m := metaDescriptor[2:] + + return meta.GetAllAsMap(m) +} From b473451288dc11a7fe981289826d393988425b43 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 12 Jun 2019 01:17:14 +0930 Subject: [PATCH 04/93] container/mts: added payload.go file with Extract function and types payload.go has been added which will contain functionality for dealing with MTS payloads. A function has been added called Extract, which will return a Clip. Clip is a type representing a sequence of media frames ([]Frame). Type Frame has been added which represents a media frame. It provides fields to hold the media as byte slice, PTS to hold the PES timestamp, an ID for identification of codec type and finally the relevant Meta from the most recent PMT. --- container/mts/payload.go | 98 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 container/mts/payload.go diff --git a/container/mts/payload.go b/container/mts/payload.go new file mode 100644 index 00000000..ce8db949 --- /dev/null +++ b/container/mts/payload.go @@ -0,0 +1,98 @@ +package mts + +import ( + "errors" + + "github.com/Comcast/gots/packet" + "github.com/Comcast/gots/pes" +) + +// Extract extracts the media from an MPEG-TS clip given by p. +func Extract(p []byte) (Clip, error) { + l := len(p) + // Check that clip is divisible by 188, i.e. contains a series of full MPEG-TS clips. + if l%PacketSize != 0 { + return nil, errors.New("MTS clip is not of valid size") + } + + // This will hold a copy of all the media in the MTS clip. + buf := make([]byte, 0, l/PacketSize) + + var clip Clip + var meta map[string]string + var err error + var prev *Frame + // Go through and get the number of frames, and also their size and allocate + // mem to clip. + var pkt packet.Packet + for i := 0; i < l; i += PacketSize { + copy(pkt[:], p[i:i+PacketSize]) + idx := len(buf) + + switch pkt.PID() { + case PatPid: // Do nothing. + case PmtPid: + meta, err = ExtractMeta(pkt[:]) + if err != nil { + return nil, err + } + default: + // Get the MTS payload. + payload, err := pkt.Payload() + if err != nil { + return nil, err + } + + // If PUSI is true then we know it's the start of a new frame, and we have + // a PES header in the MTS payload. + if pkt.PayloadUnitStartIndicator() { + _pes, err := pes.NewPESHeader(payload) + if err != nil { + return nil, err + } + pesData := _pes.Data() + lenOfFrame := len(pesData) + + // If we have a previous Frame, then we need to adjust upper bound of + // it's media data. + if prev != nil { + prev.Media = prev.Media[:lenOfFrame] + } + + // Create a new frame. + prev = &Frame{ + Media: buf[idx:], + PTS: _pes.PTS(), + Meta: meta, + } + // Append media data to underlying media buffer. + buf = append(buf, pesData...) + } else { + buf = append(buf, payload...) + } + + // And then append to the clip. + clip = append(clip, *prev) + } + } + + // Copy over data + + return nil, nil +} + +// Clip represents a clip of media, i.e. a sequence of media frames. +type Clip []Frame + +// Frame describes a media frame that may be extracted from a PES packet. +type Frame struct { + Media []byte // Contains the media from the frame. + PTS uint64 // PTS from PES packet (this gives time relative from start of stream). + ID int8 // StreamID from the PES packet, identifying media codec. + Meta map[string]string // Contains metadata from PMT relevant to this frame. +} + +// Bytes returns the concatentated media bytes from each frame in the Clip c. +func (c *Clip) Bytes() []byte { + return nil +} From 8f434e970327559403fce6b7f9306ca069898e6f Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 13 Jun 2019 15:52:25 +0930 Subject: [PATCH 05/93] container/mts: added payload_test.go and fixed bugs in Extract --- container/mts/meta/meta.go | 2 +- container/mts/payload.go | 76 +++++++++++-------- container/mts/payload_test.go | 137 ++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 container/mts/payload_test.go diff --git a/container/mts/meta/meta.go b/container/mts/meta/meta.go index 4d3078a0..2baa33b7 100644 --- a/container/mts/meta/meta.go +++ b/container/mts/meta/meta.go @@ -216,7 +216,7 @@ func GetAll(d []byte) ([][2]string, error) { return all, nil } -// GetAllAsMap returns a map containging keys and values from a slice d containing +// GetAllAsMap returns a map containing keys and values from a slice d containing // AusOcean metadata. func GetAllAsMap(d []byte) (map[string]string, error) { err := checkMeta(d) diff --git a/container/mts/payload.go b/container/mts/payload.go index ce8db949..2c9c79ac 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -15,19 +15,25 @@ func Extract(p []byte) (Clip, error) { return nil, errors.New("MTS clip is not of valid size") } - // This will hold a copy of all the media in the MTS clip. + // This will hold a copy of all the media in the MPEG-TS clip. buf := make([]byte, 0, l/PacketSize) - var clip Clip - var meta map[string]string - var err error - var prev *Frame - // Go through and get the number of frames, and also their size and allocate - // mem to clip. + var ( + clip Clip // The data that will be returned. + meta map[string]string // Holds the most recently extracted meta. + lenOfFrame int // Len of current frame. + dataLen int // Len of data from MPEG-TS packet. + curPTS uint64 // Holds the current PTS. + curStreamID uint8 // Holds current StreamID (shouldn't change) + firstPUSI = true // Indicates that we have not yet received a PUSI. + err error + ) + + // Go through the MPEGT-TS packets. var pkt packet.Packet for i := 0; i < l; i += PacketSize { + // We will use comcast/gots Packet type, so copy in. copy(pkt[:], p[i:i+PacketSize]) - idx := len(buf) switch pkt.PID() { case PatPid: // Do nothing. @@ -36,8 +42,8 @@ func Extract(p []byte) (Clip, error) { if err != nil { return nil, err } - default: - // Get the MTS payload. + default: // Must be media. + // Get the MPEG-TS payload. payload, err := pkt.Payload() if err != nil { return nil, err @@ -45,40 +51,46 @@ func Extract(p []byte) (Clip, error) { // If PUSI is true then we know it's the start of a new frame, and we have // a PES header in the MTS payload. + if pkt.PayloadUnitStartIndicator() { _pes, err := pes.NewPESHeader(payload) if err != nil { return nil, err } - pesData := _pes.Data() - lenOfFrame := len(pesData) - // If we have a previous Frame, then we need to adjust upper bound of - // it's media data. - if prev != nil { - prev.Media = prev.Media[:lenOfFrame] - } + // Extract the PTS and ID, then add a new frame to the clip. + curPTS = _pes.PTS() + curStreamID = _pes.StreamId() + clip = append(clip, Frame{ + PTS: curPTS, + ID: curStreamID, + Meta: meta, + }) - // Create a new frame. - prev = &Frame{ - Media: buf[idx:], - PTS: _pes.PTS(), - Meta: meta, + // Append the data to the underlying buffer and get appended lenghth. + buf = append(buf, _pes.Data()...) + dataLen = len(_pes.Data()) + + // If we haven't hit the first PUSI, then we know we have a full frame + // and can add this data to the frame pertaining to the finish frame. + if !firstPUSI { + clip[len(clip)-2].Media = buf[:lenOfFrame] + buf = buf[lenOfFrame:] + lenOfFrame = 0 } - // Append media data to underlying media buffer. - buf = append(buf, pesData...) + firstPUSI = false } else { + // We're not at the start of the frame, so we don't have a PES header. + // We can append the MPEG-TS data directly to the underlying buf. + dataLen = len(payload) buf = append(buf, payload...) } - - // And then append to the clip. - clip = append(clip, *prev) + lenOfFrame += dataLen } } - - // Copy over data - - return nil, nil + // We're finished up with media frames, so give the final Frame it's data. + clip[len(clip)-1].Media = buf[:lenOfFrame] + return clip, nil } // Clip represents a clip of media, i.e. a sequence of media frames. @@ -88,7 +100,7 @@ type Clip []Frame type Frame struct { Media []byte // Contains the media from the frame. PTS uint64 // PTS from PES packet (this gives time relative from start of stream). - ID int8 // StreamID from the PES packet, identifying media codec. + ID uint8 // StreamID from the PES packet, identifying media codec. Meta map[string]string // Contains metadata from PMT relevant to this frame. } diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go new file mode 100644 index 00000000..bdbf0d21 --- /dev/null +++ b/container/mts/payload_test.go @@ -0,0 +1,137 @@ +package mts + +import ( + "bytes" + "math/rand" + "reflect" + "strconv" + "testing" + "time" + + "bitbucket.org/ausocean/av/container/mts/meta" + "bitbucket.org/ausocean/av/container/mts/psi" +) + +// TestExtract checks that we can coorectly extract media, pts, id and meta from +// an mpegts stream using Extract. +func TestExtract(t *testing.T) { + Meta = meta.New() + + const ( + psiInterval = 5 // Write PSI at start and after every 5 frames. + numOfFrames = 30 // Total number of frames to write. + maxFrameSize = 1000 // Max frame size to randomly generate. + minFrameSize = 100 // Min frame size to randomly generate. + rate = 25 // Framerate (fps) + interval = float64(1) / rate // Time interval between frames. + ptsFreq = 90000 // Standard PTS frequency base. + ) + + // Generate randomly sized data for each frame and fill. + rand.Seed(time.Now().UnixNano()) + frames := make([][]byte, numOfFrames) + for i := range frames { + size := rand.Intn(maxFrameSize-minFrameSize) + minFrameSize + frames[i] = make([]byte, size) + for j := 0; j < len(frames[i]); j++ { + frames[i][j] = byte(j) + } + } + + var ( + clip bytes.Buffer // This will hold the MPEG-TS data. + want Clip // This is the Clip that we should get. + err error + ) + + // Now write frames. + var curTime float64 + for i, frame := range frames { + // Check to see if it's time to write another lot of PSI. + if i%psiInterval == 0 && i != len(frames)-1 { + // We'll add the frame number as meta. + Meta.Add("frameNum", strconv.Itoa(i)) + + err = writePSIWithMeta(&clip) + if err != nil { + t.Fatalf("did not expect error writing psi: %v", err) + } + } + nextPTS := uint64(curTime * ptsFreq) + + err = writeFrame(&clip, frame, uint64(nextPTS)) + if err != nil { + t.Fatalf("did not expect error writing frame: %v", err) + } + + curTime += interval + + // Need the meta map for the new expected Frame. + metaMap, err := meta.GetAllAsMap(Meta.Encode()) + if err != nil { + t.Fatalf("did not expect error getting meta map: %v", err) + } + + // Create an equivalent Frame and append to our Clip want. + want = append(want, Frame{ + Media: frame, + PTS: nextPTS, + ID: H264ID, + Meta: metaMap, + }) + } + + // Now use Extract to get frames from clip. + got, err := Extract(clip.Bytes()) + if err != nil { + t.Fatalf("did not expect error using Extract. Err: %v", err) + } + + // Check length of got and want. + if len(want) != len(got) { + t.Fatalf("did not get expected length for got.\nGot: %v\n, Want: %v\n", len(got), len(want)) + } + + // Check frames individually. + for i, frame := range want { + if !reflect.DeepEqual(frame, got[i]) { + t.Fatalf("did not get expected result.\nGot: %v\n, Want: %v\n", got[i], frame) + } + } +} + +// writePSIWithMeta writes PSI to b with updated metadata. +func writePSIWithMeta(b *bytes.Buffer) error { + // Write PAT. + pat := Packet{ + PUSI: true, + PID: PatPid, + CC: 0, + AFC: HasPayload, + Payload: psi.AddPadding(patTable), + } + _, err := b.Write(pat.Bytes(nil)) + if err != nil { + return err + } + + // Update the meta in the pmt table. + pmtTable, err = updateMeta(pmtTable) + if err != nil { + return err + } + + // Write PMT. + pmt := Packet{ + PUSI: true, + PID: PmtPid, + CC: 0, + AFC: HasPayload, + Payload: psi.AddPadding(pmtTable), + } + _, err = b.Write(pmt.Bytes(nil)) + if err != nil { + return err + } + return nil +} From 1323cbcae3c3df3f4e727814e370b5f97361a687 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 13 Jun 2019 16:30:39 +0930 Subject: [PATCH 06/93] container/mts: filled Clip.Bytes writing Clip.Bytes required a change to the Clip type. The Clip type now possess a slice that references the memory in which the Frames should reference for the media. Appropriate changes have been made to Extract and TestExtract to accomidate this change. --- container/mts/payload.go | 34 ++++++++++++++++++++-------------- container/mts/payload_test.go | 12 ++++++------ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index 2c9c79ac..f1f6bb6a 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -8,18 +8,16 @@ import ( ) // Extract extracts the media from an MPEG-TS clip given by p. -func Extract(p []byte) (Clip, error) { +func Extract(p []byte) (*Clip, error) { l := len(p) // Check that clip is divisible by 188, i.e. contains a series of full MPEG-TS clips. if l%PacketSize != 0 { return nil, errors.New("MTS clip is not of valid size") } - // This will hold a copy of all the media in the MPEG-TS clip. - buf := make([]byte, 0, l/PacketSize) - var ( - clip Clip // The data that will be returned. + frameStart int // Index used to indicate the start of current frame in backing slice. + clip = &Clip{} // The data that will be returned. meta map[string]string // Holds the most recently extracted meta. lenOfFrame int // Len of current frame. dataLen int // Len of data from MPEG-TS packet. @@ -29,6 +27,9 @@ func Extract(p []byte) (Clip, error) { err error ) + // This will hold a copy of all the media in the MPEG-TS clip. + clip.backing = make([]byte, 0, l/PacketSize) + // Go through the MPEGT-TS packets. var pkt packet.Packet for i := 0; i < l; i += PacketSize { @@ -61,40 +62,42 @@ func Extract(p []byte) (Clip, error) { // Extract the PTS and ID, then add a new frame to the clip. curPTS = _pes.PTS() curStreamID = _pes.StreamId() - clip = append(clip, Frame{ + clip.frames = append(clip.frames, Frame{ PTS: curPTS, ID: curStreamID, Meta: meta, }) // Append the data to the underlying buffer and get appended lenghth. - buf = append(buf, _pes.Data()...) + clip.backing = append(clip.backing, _pes.Data()...) dataLen = len(_pes.Data()) // If we haven't hit the first PUSI, then we know we have a full frame // and can add this data to the frame pertaining to the finish frame. if !firstPUSI { - clip[len(clip)-2].Media = buf[:lenOfFrame] - buf = buf[lenOfFrame:] - lenOfFrame = 0 + clip.frames[len(clip.frames)-2].Media = clip.backing[frameStart:lenOfFrame] + frameStart = lenOfFrame } firstPUSI = false } else { // We're not at the start of the frame, so we don't have a PES header. // We can append the MPEG-TS data directly to the underlying buf. dataLen = len(payload) - buf = append(buf, payload...) + clip.backing = append(clip.backing, payload...) } lenOfFrame += dataLen } } // We're finished up with media frames, so give the final Frame it's data. - clip[len(clip)-1].Media = buf[:lenOfFrame] + clip.frames[len(clip.frames)-1].Media = clip.backing[frameStart:lenOfFrame] return clip, nil } // Clip represents a clip of media, i.e. a sequence of media frames. -type Clip []Frame +type Clip struct { + frames []Frame + backing []byte +} // Frame describes a media frame that may be extracted from a PES packet. type Frame struct { @@ -106,5 +109,8 @@ type Frame struct { // Bytes returns the concatentated media bytes from each frame in the Clip c. func (c *Clip) Bytes() []byte { - return nil + if c.backing == nil { + panic("the clip backing array cannot be nil") + } + return c.backing } diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index bdbf0d21..05696a24 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -73,7 +73,7 @@ func TestExtract(t *testing.T) { } // Create an equivalent Frame and append to our Clip want. - want = append(want, Frame{ + want.frames = append(want.frames, Frame{ Media: frame, PTS: nextPTS, ID: H264ID, @@ -88,14 +88,14 @@ func TestExtract(t *testing.T) { } // Check length of got and want. - if len(want) != len(got) { - t.Fatalf("did not get expected length for got.\nGot: %v\n, Want: %v\n", len(got), len(want)) + if len(want.frames) != len(got.frames) { + t.Fatalf("did not get expected length for got.\nGot: %v\n, Want: %v\n", len(got.frames), len(want.frames)) } // Check frames individually. - for i, frame := range want { - if !reflect.DeepEqual(frame, got[i]) { - t.Fatalf("did not get expected result.\nGot: %v\n, Want: %v\n", got[i], frame) + for i, frame := range want.frames { + if !reflect.DeepEqual(frame, got.frames[i]) { + t.Fatalf("did not get expected result.\nGot: %v\n, Want: %v\n", got.frames[i], frame) } } } From 12c205d75f74ef85a5ce56793f6b87a235f502c8 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 13 Jun 2019 18:12:17 +0930 Subject: [PATCH 07/93] container/mts: wrote test for Clip.Bytes and generalised logic for generating frames into genFrames func --- container/mts/payload_test.go | 92 +++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 05696a24..568a842e 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -27,16 +27,7 @@ func TestExtract(t *testing.T) { ptsFreq = 90000 // Standard PTS frequency base. ) - // Generate randomly sized data for each frame and fill. - rand.Seed(time.Now().UnixNano()) - frames := make([][]byte, numOfFrames) - for i := range frames { - size := rand.Intn(maxFrameSize-minFrameSize) + minFrameSize - frames[i] = make([]byte, size) - for j := 0; j < len(frames[i]); j++ { - frames[i][j] = byte(j) - } - } + frames := genFrames(numOfFrames, minFrameSize, maxFrameSize) var ( clip bytes.Buffer // This will hold the MPEG-TS data. @@ -135,3 +126,84 @@ func writePSIWithMeta(b *bytes.Buffer) error { } return nil } + +// TestClipBytes checks that Clip.Bytes correctly returns the concatendated media +// data from the Clip's frames slice. +func TestClipBytes(t *testing.T) { + Meta = meta.New() + + const ( + psiInterval = 5 // Write PSI at start and after every 5 frames. + numOfFrames = 30 // Total number of frames to write. + maxFrameSize = 1000 // Max frame size to randomly generate. + minFrameSize = 100 // Min frame size to randomly generate. + rate = 25 // Framerate (fps) + interval = float64(1) / rate // Time interval between frames. + ptsFreq = 90000 // Standard PTS frequency base. + ) + + frames := genFrames(numOfFrames, minFrameSize, maxFrameSize) + + var ( + clip bytes.Buffer // This will hold the MPEG-TS data. + want []byte // This is the Clip that we should get. + err error + ) + + // Now write frames. + var curTime float64 + for i, frame := range frames { + // Check to see if it's time to write another lot of PSI. + if i%psiInterval == 0 && i != len(frames)-1 { + // We'll add the frame number as meta. + Meta.Add("frameNum", strconv.Itoa(i)) + + err = writePSIWithMeta(&clip) + if err != nil { + t.Fatalf("did not expect error writing psi: %v", err) + } + } + nextPTS := uint64(curTime * ptsFreq) + + err = writeFrame(&clip, frame, uint64(nextPTS)) + if err != nil { + t.Fatalf("did not expect error writing frame: %v", err) + } + + curTime += interval + + // Append the frame straight to the expected pure media slice. + want = append(want, frame...) + } + + // Now use Extract to get Clip and then use Bytes to get the slice of straight media. + gotClip, err := Extract(clip.Bytes()) + if err != nil { + t.Fatalf("did not expect error using Extract. Err: %v", err) + } + got := gotClip.Bytes() + + // Check length and equality of got and want. + if len(want) != len(got) { + t.Fatalf("did not get expected length for got.\nGot: %v\n, Want: %v\n", len(got), len(want)) + } + if !bytes.Equal(want, got) { + t.Error("did not get expected result") + } +} + +// genFrames is a helper function to generate a series of dummy media frames +// with randomized size. n is the number of frames to generate, min is the min +// size is min size of random frame and max is max size of random frames. +func genFrames(n, min, max int) [][]byte { + // Generate randomly sized data for each frame and fill. + rand.Seed(time.Now().UnixNano()) + frames := make([][]byte, n) + for i := range frames { + frames[i] = make([]byte, rand.Intn(max-min)+min) + for j := 0; j < len(frames[i]); j++ { + frames[i][j] = byte(j) + } + } + return frames +} From 70eb8193cb8eda885bed1c51056d93940a6fe602 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 14 Jun 2019 01:09:55 +0930 Subject: [PATCH 08/93] container/mts: wrote BytesForPTSInterval This function will return the media between two provided PTS. Binary search has been used to find the corresponding Frames to 'from' and 'to'. --- container/mts/payload.go | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/container/mts/payload.go b/container/mts/payload.go index f1f6bb6a..a824cb64 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -2,6 +2,7 @@ package mts import ( "errors" + "sort" "github.com/Comcast/gots/packet" "github.com/Comcast/gots/pes" @@ -76,6 +77,7 @@ func Extract(p []byte) (*Clip, error) { // and can add this data to the frame pertaining to the finish frame. if !firstPUSI { clip.frames[len(clip.frames)-2].Media = clip.backing[frameStart:lenOfFrame] + clip.frames[len(clip.frames)-2].idx = frameStart frameStart = lenOfFrame } firstPUSI = false @@ -90,6 +92,7 @@ func Extract(p []byte) (*Clip, error) { } // We're finished up with media frames, so give the final Frame it's data. clip.frames[len(clip.frames)-1].Media = clip.backing[frameStart:lenOfFrame] + clip.frames[len(clip.frames)-1].idx = frameStart return clip, nil } @@ -105,6 +108,7 @@ type Frame struct { PTS uint64 // PTS from PES packet (this gives time relative from start of stream). ID uint8 // StreamID from the PES packet, identifying media codec. Meta map[string]string // Contains metadata from PMT relevant to this frame. + idx int // Index in the backing slice. } // Bytes returns the concatentated media bytes from each frame in the Clip c. @@ -114,3 +118,53 @@ func (c *Clip) Bytes() []byte { } return c.backing } + +// BytesForPTSInterval returns the media data between PTS' from and to. If from +// sits between two PTS, the Frame posessing lower PTS will be considered the start. +// The Frame before the Frame corresponding to to will be considered the final +// Frame. +func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { + // First check that the interval makes sense. + if from >= to { + return nil, errors.New("PTS interval is invalid") + } + + // Use binary search to find 'from'. + n := len(c.frames) + idx := sort.Search( + n, + func(i int) bool { + if from >= c.frames[i].PTS && from < c.frames[i].PTS { + return true + } + return false + }, + ) + if idx == n { + return nil, errors.New("'from' cannot be found") + } + + // Now get the start index for the backing slice from this Frame. + start := c.frames[idx].idx + + // Now use binary search again to find 'to'. + off := idx + 1 + n = n - (off) + idx = sort.Search( + n, + func(i int) bool { + if to >= c.frames[i+off].PTS && from < c.frames[i+off].PTS { + return true + } + return false + }, + ) + if idx == n { + return nil, errors.New("'to' cannot be found") + } + + // Now get the end index for the backing slice from this Frame, and return + // segment from backing slice corresponding to start and end. + end := c.frames[idx+off].idx + return c.backing[start:end], nil +} From 22079fcb48790aa954dec3eee0c9c029beb22d8d Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 14 Jun 2019 14:21:45 +0930 Subject: [PATCH 09/93] container/mts: wrote test for BytesForPTSInterval and corrected bugs --- container/mts/payload.go | 23 ++++--- container/mts/payload_test.go | 112 +++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 10 deletions(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index a824cb64..f8491ade 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -119,6 +119,13 @@ func (c *Clip) Bytes() []byte { return c.backing } +// Errors used in BytesForPTSInterval. +var ( + errBadLowerBound = errors.New("'from' cannot be found") + errBadUpperBound = errors.New("'to' cannot be found") + errInvalidPTSRange = errors.New("PTS interval invalid") +) + // BytesForPTSInterval returns the media data between PTS' from and to. If from // sits between two PTS, the Frame posessing lower PTS will be considered the start. // The Frame before the Frame corresponding to to will be considered the final @@ -126,22 +133,22 @@ func (c *Clip) Bytes() []byte { func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { // First check that the interval makes sense. if from >= to { - return nil, errors.New("PTS interval is invalid") + return nil, errInvalidPTSRange } // Use binary search to find 'from'. - n := len(c.frames) + n := len(c.frames) - 1 idx := sort.Search( n, func(i int) bool { - if from >= c.frames[i].PTS && from < c.frames[i].PTS { + if from < c.frames[i+1].PTS { return true } return false }, ) if idx == n { - return nil, errors.New("'from' cannot be found") + return nil, errBadLowerBound } // Now get the start index for the backing slice from this Frame. @@ -153,18 +160,18 @@ func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { idx = sort.Search( n, func(i int) bool { - if to >= c.frames[i+off].PTS && from < c.frames[i+off].PTS { + if to <= c.frames[i+off].PTS { return true } return false }, ) if idx == n { - return nil, errors.New("'to' cannot be found") + return nil, errBadUpperBound } // Now get the end index for the backing slice from this Frame, and return // segment from backing slice corresponding to start and end. - end := c.frames[idx+off].idx - return c.backing[start:end], nil + end := c.frames[idx+off-1].idx + return c.backing[start : end+len(c.frames[idx+off].Media)], nil } diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 568a842e..7f7dd968 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -85,8 +85,25 @@ func TestExtract(t *testing.T) { // Check frames individually. for i, frame := range want.frames { - if !reflect.DeepEqual(frame, got.frames[i]) { - t.Fatalf("did not get expected result.\nGot: %v\n, Want: %v\n", got.frames[i], frame) + // Check media data. + wantMedia := frame.Media + gotMedia := got.frames[i].Media + if !bytes.Equal(wantMedia, gotMedia) { + t.Fatalf("did not get expected data for frame: %v\nGot: %v\nWant: %v\n", i, gotMedia, wantMedia) + } + + // Check stream ID. + wantID := frame.ID + gotID := got.frames[i].ID + if wantID != gotID { + t.Fatalf("did not get expected ID for frame: %v\nGot: %v\nWant: %v\n", i, gotID, wantID) + } + + // Check meta. + wantMeta := frame.Meta + gotMeta := got.frames[i].Meta + if !reflect.DeepEqual(wantMeta, gotMeta) { + t.Fatalf("did not get expected meta for frame: %v\nGot: %v\nwant: %v\n", i, gotMeta, wantMeta) } } } @@ -207,3 +224,94 @@ func genFrames(n, min, max int) [][]byte { } return frames } + +// TestBytesForPTSInterval checks that BytesForPTSInterval can correctly return +// a slice of media data corresponding to a given PTS interval. +func TestBytesForPTSInterval(t *testing.T) { + const ( + numOfTestFrames = 10 + ptsInterval = 4 + frameSize = 3 + ) + + clip := &Clip{} + + // Generate test frames. + for i := 0; i < numOfTestFrames; i++ { + clip.backing = append(clip.backing, []byte{byte(i), byte(i), byte(i)}...) + clip.frames = append( + clip.frames, + Frame{ + Media: clip.backing[i*frameSize : (i+1)*frameSize], + PTS: uint64(i * ptsInterval), + idx: i * frameSize, + }, + ) + } + + // We test each of these scenarios. + tests := []struct { + from uint64 + to uint64 + expect []byte + err error + }{ + { + from: 6, + to: 15, + expect: []byte{ + 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, + }, + err: nil, + }, + { + from: 4, + to: 16, + expect: []byte{ + 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, + }, + err: nil, + }, + { + from: 10, + to: 5, + expect: nil, + err: errInvalidPTSRange, + }, + { + from: 50, + to: 70, + expect: nil, + err: errBadLowerBound, + }, + { + from: 5, + to: 70, + expect: nil, + err: errBadUpperBound, + }, + } + + // Run tests. + for i, test := range tests { + got, err := clip.BytesForPTSInterval(test.from, test.to) + + // First check the error. + if err != nil && err != test.err { + t.Errorf("unexpected error: %v for test: %v from BytesForPTSInterval", err, i) + continue + } else if err != test.err { + t.Errorf("expected to get error: %v for test: %v from BytesForPTSInterval", test.err, i) + continue + } + + // Now check data. + if test.err == nil && !bytes.Equal(test.expect, got) { + t.Errorf("did not get expected data for test: %v from BytesForPTSInterval.\n Got: %v\n, Want: %v\n", i, got, test.expect) + } + } +} From 5f78ef8666be415cabeedbca373894bfa1f1ee63 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 14 Jun 2019 17:41:34 +0930 Subject: [PATCH 10/93] container/mts: wrote function BytesForMetaInterval to return media between two points of meta. --- container/mts/payload.go | 68 +++++++++++++++++++++++++++++++---- container/mts/payload_test.go | 6 ++-- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index f8491ade..9d33fa59 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -8,7 +8,8 @@ import ( "github.com/Comcast/gots/pes" ) -// Extract extracts the media from an MPEG-TS clip given by p. +// Extract extracts the media, PTS, stream ID and meta for an MPEG-TS clip given +// by p, and returns as a Clip. The MPEG-TS must contain only complete packets. func Extract(p []byte) (*Clip, error) { l := len(p) // Check that clip is divisible by 188, i.e. contains a series of full MPEG-TS clips. @@ -121,9 +122,9 @@ func (c *Clip) Bytes() []byte { // Errors used in BytesForPTSInterval. var ( - errBadLowerBound = errors.New("'from' cannot be found") - errBadUpperBound = errors.New("'to' cannot be found") - errInvalidPTSRange = errors.New("PTS interval invalid") + errPTSLowerBound = errors.New("PTS 'from' cannot be found") + errPTSUpperBound = errors.New("PTS 'to' cannot be found") + errPTSRange = errors.New("PTS interval invalid") ) // BytesForPTSInterval returns the media data between PTS' from and to. If from @@ -133,7 +134,7 @@ var ( func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { // First check that the interval makes sense. if from >= to { - return nil, errInvalidPTSRange + return nil, errPTSRange } // Use binary search to find 'from'. @@ -148,7 +149,7 @@ func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { }, ) if idx == n { - return nil, errBadLowerBound + return nil, errPTSLowerBound } // Now get the start index for the backing slice from this Frame. @@ -167,7 +168,7 @@ func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { }, ) if idx == n { - return nil, errBadUpperBound + return nil, errPTSUpperBound } // Now get the end index for the backing slice from this Frame, and return @@ -175,3 +176,56 @@ func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { end := c.frames[idx+off-1].idx return c.backing[start : end+len(c.frames[idx+off].Media)], nil } + +// Errors that maybe returned from BytesForMetaInterval. +var ( + errMetaRange = errors.New("invalid meta range") + errMetaLowerBound = errors.New("meta 'from' cannot be found") + errMetaUpperBound = errors.New("meta 'to' cannot be found") +) + +func (c *Clip) BytesForMetaInterval(key, from, to string) ([]byte, error) { + // First check that the interval makes sense. + if from >= to { + return nil, errMetaRange + } + + // Use binary search to find 'from'. + n := len(c.frames) + idx := sort.Search( + n, + func(i int) bool { + if c.frames[i].Meta[key] == from { + return true + } + return false + }, + ) + if idx == n { + return nil, errMetaLowerBound + } + + // Now get the start index for the backing slice from this Frame. + start := c.frames[idx].idx + + // Now use binary search again to find 'to'. + off := idx + 1 + n = n - (off) + idx = sort.Search( + n, + func(i int) bool { + if c.frames[i+off].Meta[key] == to { + return true + } + return false + }, + ) + if idx == n { + return nil, errMetaUpperBound + } + + // Now get the end index for the backing slice from this Frame, and return + // segment from backing slice corresponding to start and end. + end := c.frames[idx+off].idx + return c.backing[start : end+len(c.frames[idx+off].Media)], nil +} diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 7f7dd968..9cc36b88 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -280,19 +280,19 @@ func TestBytesForPTSInterval(t *testing.T) { from: 10, to: 5, expect: nil, - err: errInvalidPTSRange, + err: errPTSRange, }, { from: 50, to: 70, expect: nil, - err: errBadLowerBound, + err: errPTSLowerBound, }, { from: 5, to: 70, expect: nil, - err: errBadUpperBound, + err: errPTSUpperBound, }, } From f0d1b994bf9b8586e0ef3fa046706c3945a5b23a Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 14 Jun 2019 19:27:19 +0930 Subject: [PATCH 11/93] container/mts: wrote test for BytesForMetaInterval and corrected bugs --- container/mts/payload.go | 56 ++++++++-------------- container/mts/payload_test.go | 88 +++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 40 deletions(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index 9d33fa59..9785f2f0 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -186,46 +186,28 @@ var ( func (c *Clip) BytesForMetaInterval(key, from, to string) ([]byte, error) { // First check that the interval makes sense. - if from >= to { + if from == to { return nil, errMetaRange } - // Use binary search to find 'from'. - n := len(c.frames) - idx := sort.Search( - n, - func(i int) bool { - if c.frames[i].Meta[key] == from { - return true + var start, end int + + // Try and find from. + for i := 0; i < len(c.frames); i++ { + f := c.frames[i] + if f.Meta[key] == from { + start = f.idx + + // Now try and find to. + for ; i < len(c.frames); i++ { + f = c.frames[i] + if f.Meta[key] == to { + end = f.idx + return c.backing[start : end+len(f.Media)], nil + } } - return false - }, - ) - if idx == n { - return nil, errMetaLowerBound + return nil, errMetaUpperBound + } } - - // Now get the start index for the backing slice from this Frame. - start := c.frames[idx].idx - - // Now use binary search again to find 'to'. - off := idx + 1 - n = n - (off) - idx = sort.Search( - n, - func(i int) bool { - if c.frames[i+off].Meta[key] == to { - return true - } - return false - }, - ) - if idx == n { - return nil, errMetaUpperBound - } - - // Now get the end index for the backing slice from this Frame, and return - // segment from backing slice corresponding to start and end. - end := c.frames[idx+off].idx - return c.backing[start : end+len(c.frames[idx+off].Media)], nil + return nil, errMetaLowerBound } diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 9cc36b88..3ae114e7 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -302,16 +302,98 @@ func TestBytesForPTSInterval(t *testing.T) { // First check the error. if err != nil && err != test.err { - t.Errorf("unexpected error: %v for test: %v from BytesForPTSInterval", err, i) + t.Errorf("unexpected error: %v for test: %v", err, i) continue } else if err != test.err { - t.Errorf("expected to get error: %v for test: %v from BytesForPTSInterval", test.err, i) + t.Errorf("expected to get error: %v for test: %v", test.err, i) continue } // Now check data. if test.err == nil && !bytes.Equal(test.expect, got) { - t.Errorf("did not get expected data for test: %v from BytesForPTSInterval.\n Got: %v\n, Want: %v\n", i, got, test.expect) + t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect) + } + } +} + +func TestBytesForMetaInterval(t *testing.T) { + const ( + numOfTestFrames = 10 + ptsInterval = 4 + frameSize = 3 + key = "n" + ) + + clip := &Clip{} + + // Generate test frames. + for i := 0; i < numOfTestFrames; i++ { + clip.backing = append(clip.backing, []byte{byte(i), byte(i), byte(i)}...) + clip.frames = append( + clip.frames, + Frame{ + Media: clip.backing[i*frameSize : (i+1)*frameSize], + idx: i * frameSize, + Meta: map[string]string{ + key: strconv.Itoa(i), + }, + }, + ) + } + + // We test each of these scenarios. + tests := []struct { + from string + to string + expect []byte + err error + }{ + { + from: "1", + to: "3", + expect: []byte{ + 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, + }, + err: nil, + }, + { + from: "1", + to: "1", + expect: nil, + err: errMetaRange, + }, + { + from: "20", + to: "1", + expect: nil, + err: errMetaLowerBound, + }, + { + from: "1", + to: "20", + expect: nil, + err: errMetaUpperBound, + }, + } + + // Run tests. + for i, test := range tests { + got, err := clip.BytesForMetaInterval(key, test.from, test.to) + + // First check the error. + if err != nil && err != test.err { + t.Errorf("unexpected error: %v for test: %v", err, i) + continue + } else if err != test.err { + t.Errorf("expected to get error: %v for test: %v", test.err, i) + continue + } + + // Now check data. + if test.err == nil && !bytes.Equal(test.expect, got) { + t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect) } } } From eff69e87cebeace890764486f657ca4cc67e89dc Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 14 Jun 2019 19:33:40 +0930 Subject: [PATCH 12/93] container/mts: added some commenting --- container/mts/payload.go | 2 ++ container/mts/payload_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/container/mts/payload.go b/container/mts/payload.go index 9785f2f0..d05e994e 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -184,6 +184,8 @@ var ( errMetaUpperBound = errors.New("meta 'to' cannot be found") ) +// BytesForMetaInterval will return the media data as a slice between two meta +// values 'from' and 'to', of key 'key'. The meta values must not be the same. func (c *Clip) BytesForMetaInterval(key, from, to string) ([]byte, error) { // First check that the interval makes sense. if from == to { diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 3ae114e7..d56ca516 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -316,6 +316,8 @@ func TestBytesForPTSInterval(t *testing.T) { } } +// TestBytesForMetaInterval checks that we can correctly get media data between +// two meta values using BytesForMetaInterval. func TestBytesForMetaInterval(t *testing.T) { const ( numOfTestFrames = 10 From ceee163b74a6368ca6fd950afaa67f60605cfa11 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 15 Jun 2019 02:04:26 +0930 Subject: [PATCH 13/93] container/mts: BytesForPTSInterval renamed to TrimToPTSRange, and now returns a Clip It was concluded that it would be more useful if a function that provided a data segment from the original clip just provided a new Clip, i.e. useful things like PTS and meta is still available in the segment. So, BytesForPTSInterval was renamed to TrimToPTSRange and now provides a Clip. The test for this function was updated accordingly. --- container/mts/payload.go | 38 +++++++++++++++++++++-------------- container/mts/payload_test.go | 10 ++++----- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index d05e994e..71c7c022 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -8,8 +8,11 @@ import ( "github.com/Comcast/gots/pes" ) +// TODO: write function for getting subslice of mpegts for meta interval + // Extract extracts the media, PTS, stream ID and meta for an MPEG-TS clip given // by p, and returns as a Clip. The MPEG-TS must contain only complete packets. +// The resultant data is a copy of the original. func Extract(p []byte) (*Clip, error) { l := len(p) // Check that clip is divisible by 188, i.e. contains a series of full MPEG-TS clips. @@ -127,11 +130,12 @@ var ( errPTSRange = errors.New("PTS interval invalid") ) -// BytesForPTSInterval returns the media data between PTS' from and to. If from -// sits between two PTS, the Frame posessing lower PTS will be considered the start. -// The Frame before the Frame corresponding to to will be considered the final -// Frame. -func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { +// TrimToPTSRange returns the sub Clip in PTS range defined by from and to. +// The first Frame in the new Clip will be the Frame for which from corresponds +// exactly with Frame.PTS, or the Frame in which from lies within. The final +// Frame in the Clip will be the previous of that for which to coincides with, +// or the Frame that to lies within. +func (c *Clip) TrimToPTSRange(from, to uint64) (*Clip, error) { // First check that the interval makes sense. if from >= to { return nil, errPTSRange @@ -139,7 +143,7 @@ func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { // Use binary search to find 'from'. n := len(c.frames) - 1 - idx := sort.Search( + startFrameIdx := sort.Search( n, func(i int) bool { if from < c.frames[i+1].PTS { @@ -148,17 +152,17 @@ func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { return false }, ) - if idx == n { + if startFrameIdx == n { return nil, errPTSLowerBound } // Now get the start index for the backing slice from this Frame. - start := c.frames[idx].idx + startBackingIdx := c.frames[startFrameIdx].idx // Now use binary search again to find 'to'. - off := idx + 1 + off := startFrameIdx + 1 n = n - (off) - idx = sort.Search( + endFrameIdx := sort.Search( n, func(i int) bool { if to <= c.frames[i+off].PTS { @@ -167,14 +171,18 @@ func (c *Clip) BytesForPTSInterval(from, to uint64) ([]byte, error) { return false }, ) - if idx == n { + if endFrameIdx == n { return nil, errPTSUpperBound } - // Now get the end index for the backing slice from this Frame, and return - // segment from backing slice corresponding to start and end. - end := c.frames[idx+off-1].idx - return c.backing[start : end+len(c.frames[idx+off].Media)], nil + // Now get the end index for the backing slice from this Frame. + endBackingIdx := c.frames[endFrameIdx+off-1].idx + + // Now return a new clip. NB: data is not copied. + return &Clip{ + frames: c.frames[startFrameIdx : endFrameIdx+1], + backing: c.backing[startBackingIdx : endBackingIdx+len(c.frames[endFrameIdx+off].Media)], + }, nil } // Errors that maybe returned from BytesForMetaInterval. diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index d56ca516..c98876fe 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -225,9 +225,9 @@ func genFrames(n, min, max int) [][]byte { return frames } -// TestBytesForPTSInterval checks that BytesForPTSInterval can correctly return -// a slice of media data corresponding to a given PTS interval. -func TestBytesForPTSInterval(t *testing.T) { +// TestTrimToPTSRange checks that Clip.TrimToPTSRange will correctly return a +// sub Clip of the given PTS range. +func TestTrimToPTSRange(t *testing.T) { const ( numOfTestFrames = 10 ptsInterval = 4 @@ -298,7 +298,7 @@ func TestBytesForPTSInterval(t *testing.T) { // Run tests. for i, test := range tests { - got, err := clip.BytesForPTSInterval(test.from, test.to) + got, err := clip.TrimToPTSRange(test.from, test.to) // First check the error. if err != nil && err != test.err { @@ -310,7 +310,7 @@ func TestBytesForPTSInterval(t *testing.T) { } // Now check data. - if test.err == nil && !bytes.Equal(test.expect, got) { + if test.err == nil && !bytes.Equal(test.expect, got.Bytes()) { t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect) } } From 2bd7a009cecca92343164a01a0c5a15d61ec9449 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 15 Jun 2019 02:12:17 +0930 Subject: [PATCH 14/93] container/mts: BytesForMetaInterval renamed to TrimToMetaRange and now returns Clip for similar reasons to previous commit --- container/mts/payload.go | 13 +++++++++---- container/mts/payload_test.go | 10 +++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index 71c7c022..37394ce6 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -192,9 +192,9 @@ var ( errMetaUpperBound = errors.New("meta 'to' cannot be found") ) -// BytesForMetaInterval will return the media data as a slice between two meta -// values 'from' and 'to', of key 'key'. The meta values must not be the same. -func (c *Clip) BytesForMetaInterval(key, from, to string) ([]byte, error) { +// TrimToMetaRange returns a sub Clip with meta range described by from and to +// with key 'key'. The meta values must not be equivalent. +func (c *Clip) TrimToMetaRange(key, from, to string) (*Clip, error) { // First check that the interval makes sense. if from == to { return nil, errMetaRange @@ -205,6 +205,7 @@ func (c *Clip) BytesForMetaInterval(key, from, to string) ([]byte, error) { // Try and find from. for i := 0; i < len(c.frames); i++ { f := c.frames[i] + startFrameIdx := i if f.Meta[key] == from { start = f.idx @@ -213,7 +214,11 @@ func (c *Clip) BytesForMetaInterval(key, from, to string) ([]byte, error) { f = c.frames[i] if f.Meta[key] == to { end = f.idx - return c.backing[start : end+len(f.Media)], nil + endFrameIdx := i + return &Clip{ + frames: c.frames[startFrameIdx : endFrameIdx+1], + backing: c.backing[start : end+len(f.Media)], + }, nil } } return nil, errMetaUpperBound diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index c98876fe..b39e3ebe 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -316,9 +316,9 @@ func TestTrimToPTSRange(t *testing.T) { } } -// TestBytesForMetaInterval checks that we can correctly get media data between -// two meta values using BytesForMetaInterval. -func TestBytesForMetaInterval(t *testing.T) { +// TestTrimToMetaRange checks that Clip.TrimToMetaRange correctly provides a +// sub Clip for a given meta range. +func TestTrimToMetaRange(t *testing.T) { const ( numOfTestFrames = 10 ptsInterval = 4 @@ -382,7 +382,7 @@ func TestBytesForMetaInterval(t *testing.T) { // Run tests. for i, test := range tests { - got, err := clip.BytesForMetaInterval(key, test.from, test.to) + got, err := clip.TrimToMetaRange(key, test.from, test.to) // First check the error. if err != nil && err != test.err { @@ -394,7 +394,7 @@ func TestBytesForMetaInterval(t *testing.T) { } // Now check data. - if test.err == nil && !bytes.Equal(test.expect, got) { + if test.err == nil && !bytes.Equal(test.expect, got.Bytes()) { t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect) } } From 513e9d06fff3e3de6f83b7d161b64158b88fb6a9 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 15 Jun 2019 02:13:49 +0930 Subject: [PATCH 15/93] container/mts: moved contents of metaEncode_test.go to encoder_test.go and deleted metaEncode_test.go The contents within metaEncode_test.go were strongly related to the contents in encoder.go, so the code was moved and the file was deleted. --- container/mts/encoder_test.go | 91 ++++++++++++++++++++++ container/mts/metaEncode_test.go | 126 ------------------------------- 2 files changed, 91 insertions(+), 126 deletions(-) delete mode 100644 container/mts/metaEncode_test.go diff --git a/container/mts/encoder_test.go b/container/mts/encoder_test.go index 24fb823d..fe85a396 100644 --- a/container/mts/encoder_test.go +++ b/container/mts/encoder_test.go @@ -29,12 +29,14 @@ import ( "bytes" "io" "io/ioutil" + "reflect" "testing" "github.com/Comcast/gots/packet" "github.com/Comcast/gots/pes" "bitbucket.org/ausocean/av/container/mts/meta" + "bitbucket.org/ausocean/av/container/mts/psi" ) type nopCloser struct{ io.Writer } @@ -250,3 +252,92 @@ func TestEncodePcm(t *testing.T) { t.Error("data decoded from mts did not match input data") } } + +const ( + errNotExpectedOut = "Unexpected output. \n Got : %v\n, Want: %v\n" + errUnexpectedErr = "Unexpected error: %v\n" +) + +const fps = 25 + +// TestMetaEncode1 checks that we can externally add a single metadata entry to +// the mts global Meta meta.Data struct and then successfully have the mts encoder +// write this to psi. +func TestMetaEncode1(t *testing.T) { + Meta = meta.New() + var buf bytes.Buffer + e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) + Meta.Add("ts", "12345678") + if err := e.writePSI(); err != nil { + t.Errorf(errUnexpectedErr, err.Error()) + } + out := buf.Bytes() + got := out[PacketSize+4:] + + want := []byte{ + 0x00, 0x02, 0xb0, 0x23, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x11, + psi.MetadataTag, // Descriptor tag + 0x0f, // Length of bytes to follow + 0x00, 0x10, 0x00, 0x0b, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', // timestamp + 0x1b, 0xe1, 0x00, 0xf0, 0x00, + } + want = psi.AddCrc(want) + want = psi.AddPadding(want) + if !bytes.Equal(got, want) { + t.Errorf(errNotExpectedOut, got, want) + } +} + +// TestMetaEncode2 checks that we can externally add two metadata entries to the +// Meta meta.Data global and then have the mts encoder successfully encode this +// into psi. +func TestMetaEncode2(t *testing.T) { + Meta = meta.New() + var buf bytes.Buffer + e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) + Meta.Add("ts", "12345678") + Meta.Add("loc", "1234,4321,1234") + if err := e.writePSI(); err != nil { + t.Errorf(errUnexpectedErr, err.Error()) + } + out := buf.Bytes() + got := out[PacketSize+4:] + want := []byte{ + 0x00, 0x02, 0xb0, 0x36, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x24, + psi.MetadataTag, // Descriptor tag + 0x22, // Length of bytes to follow + 0x00, 0x10, 0x00, 0x1e, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', '\t', // timestamp + 'l', 'o', 'c', '=', '1', '2', '3', '4', ',', '4', '3', '2', '1', ',', '1', '2', '3', '4', // location + 0x1b, 0xe1, 0x00, 0xf0, 0x00, + } + want = psi.AddCrc(want) + want = psi.AddPadding(want) + if !bytes.Equal(got, want) { + t.Errorf(errNotExpectedOut, got, want) + } +} + +// TestExtractMeta checks that ExtractMeta can correclty get a map of metadata +// from the first PMT in a clip of MPEG-TS. +func TestExtractMeta(t *testing.T) { + Meta = meta.New() + var buf bytes.Buffer + e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) + Meta.Add("ts", "12345678") + Meta.Add("loc", "1234,4321,1234") + if err := e.writePSI(); err != nil { + t.Errorf(errUnexpectedErr, err.Error()) + } + out := buf.Bytes() + got, err := ExtractMeta(out) + if err != nil { + t.Errorf(errUnexpectedErr, err.Error()) + } + want := map[string]string{ + "ts": "12345678", + "loc": "1234,4321,1234", + } + if !reflect.DeepEqual(got, want) { + t.Errorf(errNotExpectedOut, got, want) + } +} diff --git a/container/mts/metaEncode_test.go b/container/mts/metaEncode_test.go deleted file mode 100644 index 3a0dbd62..00000000 --- a/container/mts/metaEncode_test.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -NAME - metaEncode_test.go - -DESCRIPTION - See Readme.md - -AUTHOR - Saxon Nelson-Milton - -LICENSE - metaEncode_test.go is 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 - along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. -*/ - -package mts - -import ( - "bytes" - "reflect" - "testing" - - "bitbucket.org/ausocean/av/container/mts/meta" - "bitbucket.org/ausocean/av/container/mts/psi" -) - -const ( - errNotExpectedOut = "Unexpected output. \n Got : %v\n, Want: %v\n" - errUnexpectedErr = "Unexpected error: %v\n" -) - -const fps = 25 - -// TestMetaEncode1 checks that we can externally add a single metadata entry to -// the mts global Meta meta.Data struct and then successfully have the mts encoder -// write this to psi. -func TestMetaEncode1(t *testing.T) { - Meta = meta.New() - var buf bytes.Buffer - e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) - Meta.Add("ts", "12345678") - if err := e.writePSI(); err != nil { - t.Errorf(errUnexpectedErr, err.Error()) - } - out := buf.Bytes() - got := out[PacketSize+4:] - - want := []byte{ - 0x00, 0x02, 0xb0, 0x23, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x11, - psi.MetadataTag, // Descriptor tag - 0x0f, // Length of bytes to follow - 0x00, 0x10, 0x00, 0x0b, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', // timestamp - 0x1b, 0xe1, 0x00, 0xf0, 0x00, - } - want = psi.AddCrc(want) - want = psi.AddPadding(want) - if !bytes.Equal(got, want) { - t.Errorf(errNotExpectedOut, got, want) - } -} - -// TestMetaEncode2 checks that we can externally add two metadata entries to the -// Meta meta.Data global and then have the mts encoder successfully encode this -// into psi. -func TestMetaEncode2(t *testing.T) { - Meta = meta.New() - var buf bytes.Buffer - e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) - Meta.Add("ts", "12345678") - Meta.Add("loc", "1234,4321,1234") - if err := e.writePSI(); err != nil { - t.Errorf(errUnexpectedErr, err.Error()) - } - out := buf.Bytes() - got := out[PacketSize+4:] - want := []byte{ - 0x00, 0x02, 0xb0, 0x36, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x24, - psi.MetadataTag, // Descriptor tag - 0x22, // Length of bytes to follow - 0x00, 0x10, 0x00, 0x1e, 't', 's', '=', '1', '2', '3', '4', '5', '6', '7', '8', '\t', // timestamp - 'l', 'o', 'c', '=', '1', '2', '3', '4', ',', '4', '3', '2', '1', ',', '1', '2', '3', '4', // location - 0x1b, 0xe1, 0x00, 0xf0, 0x00, - } - want = psi.AddCrc(want) - want = psi.AddPadding(want) - if !bytes.Equal(got, want) { - t.Errorf(errNotExpectedOut, got, want) - } -} - -// TestExtractMeta checks that ExtractMeta can correclty get a map of metadata -// from the first PMT in a clip of MPEG-TS. -func TestExtractMeta(t *testing.T) { - Meta = meta.New() - var buf bytes.Buffer - e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) - Meta.Add("ts", "12345678") - Meta.Add("loc", "1234,4321,1234") - if err := e.writePSI(); err != nil { - t.Errorf(errUnexpectedErr, err.Error()) - } - out := buf.Bytes() - got, err := ExtractMeta(out) - if err != nil { - t.Errorf(errUnexpectedErr, err.Error()) - } - want := map[string]string{ - "ts": "12345678", - "loc": "1234,4321,1234", - } - if !reflect.DeepEqual(got, want) { - t.Errorf(errNotExpectedOut, got, want) - } -} From 1a233d8576cf9d887b41104613609879b62c7042 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 16 Jun 2019 04:08:41 +0930 Subject: [PATCH 16/93] container/mts: wrote function TrimToMtsRange and added related testing This function will return a sub slice of MPEG-TS corresponding to an interval of metadata. Also wrote testing for this function. --- container/mts/mpegts.go | 66 +++++++++++++++++++++++++++++++-- container/mts/mpegts_test.go | 70 +++++++++++++++++++++++++++++++++++ container/mts/payload_test.go | 2 +- 3 files changed, 133 insertions(+), 5 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 0aabd5a1..4e6cbd51 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -31,7 +31,6 @@ package mts import ( "errors" - "fmt" "bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/psi" @@ -175,11 +174,16 @@ func FindPat(d []byte) ([]byte, int, error) { return FindPid(d, PatPid) } +var ( + errInvalidLen = errors.New("MPEG-TS data not of valid length") + errCouldNotFind = errors.New("could not find packet with given PID") +) + // FindPid will take a clip of MPEG-TS and try to find a packet with given PID - if one // is found, then it is returned along with its index, otherwise nil, -1 and an error is returned. func FindPid(d []byte, pid uint16) (pkt []byte, i int, err error) { if len(d) < PacketSize { - return nil, -1, errors.New("MPEG-TS data not of valid length") + return nil, -1, errInvalidLen } for i = 0; i < len(d); i += PacketSize { p := (uint16(d[i+1]&0x1f) << 8) | uint16(d[i+2]) @@ -188,7 +192,7 @@ func FindPid(d []byte, pid uint16) (pkt []byte, i int, err error) { return } } - return nil, -1, fmt.Errorf("could not find packet with pid: %d", pid) + return nil, -1, errCouldNotFind } // FillPayload takes a channel and fills the packets Payload field until the @@ -363,6 +367,8 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { return [2]uint64{}, errors.New("could only find one access unit in mpegts clip") } +var errNoMeta = errors.New("PMT does not contain meta") + // ExtractMeta returns a map of metadata from the first PMT's metaData // descriptor, that is found in the MPEG-TS clip d. d must contain a series of // complete MPEG-TS packets. @@ -377,10 +383,62 @@ func ExtractMeta(d []byte) (map[string]string, error) { _, metaDescriptor := pmt.HasDescriptor(psi.MetadataTag) if metaDescriptor == nil { - return nil, errors.New("PMT does not have descriptor") + return nil, errNoMeta } // Skip the descriptor head. m := metaDescriptor[2:] return meta.GetAllAsMap(m) } + +// TrimToMetaRange trims a slice of MPEG-TS to a segment between two points of +// meta data described by key, from and to. +func TrimToMetaRange(d []byte, key, from, to string) ([]byte, error) { + if len(d)%PacketSize != 0 { + return nil, errors.New("MTS clip is not of valid size") + } + + if from == to { + return nil, errors.New("'from' and 'to' cannot be equivalent") + } + + var ( + start = -1 // Index of the start of the segment in d. + end = -1 // Index of the end of segment in d. + off int // Index of remaining slice of d to check after each PMT found. + ) + + for { + // Find the next PMT. + pmt, idx, err := FindPid(d[off:], PmtPid) + if err != nil { + switch -1 { + case start: + return nil, errMetaLowerBound + case end: + return nil, errMetaUpperBound + default: + panic("should not have got error from FindPid") + } + } + off += idx + PacketSize + + meta, err := ExtractMeta(pmt) + switch err { + case nil: // do nothing + case errNoMeta: + continue + default: + return nil, err + } + + if start == -1 { + if meta[key] == from { + start = off - PacketSize + } + } else if meta[key] == to { + end = off + return d[start:end], nil + } + } +} diff --git a/container/mts/mpegts_test.go b/container/mts/mpegts_test.go index 4c90cc0e..5ac633f7 100644 --- a/container/mts/mpegts_test.go +++ b/container/mts/mpegts_test.go @@ -30,9 +30,11 @@ package mts import ( "bytes" "math/rand" + "strconv" "testing" "time" + "bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/pes" "bitbucket.org/ausocean/av/container/mts/psi" "github.com/Comcast/gots/packet" @@ -264,3 +266,71 @@ func TestFindPid(t *testing.T) { t.Errorf("index of found packet is not correct.\nGot: %v, want: %v\n", _got, targetPacketNum) } } + +// TestTrimToMetaRange checks that TrimToMetaRange can correctly return a segment +// of MPEG-TS corresponding to a meta interval in a slice of MPEG-TS. +func TestTrimToMetaRange(t *testing.T) { + Meta = meta.New() + + const ( + nPSI = 10 + key = "n" + ) + + var ( + clip bytes.Buffer + ) + + for i := 0; i < nPSI; i++ { + Meta.Add(key, strconv.Itoa((i*2)+1)) + err := writePSIWithMeta(&clip) + if err != nil { + t.Fatalf("did not expect to get error writing PSI, error: %v", err) + } + } + + tests := []struct { + from string + to string + expect []byte + err error + }{ + { + from: "3", + to: "9", + expect: clip.Bytes()[3*PacketSize : 10*PacketSize], + err: nil, + }, + { + from: "30", + to: "8", + expect: nil, + err: errMetaLowerBound, + }, + { + from: "3", + to: "30", + expect: nil, + err: errMetaUpperBound, + }, + } + + // Run tests. + for i, test := range tests { + got, err := TrimToMetaRange(clip.Bytes(), key, test.from, test.to) + + // First check the error. + if err != nil && err != test.err { + t.Errorf("unexpected error: %v for test: %v", err, i) + continue + } else if err != test.err { + t.Errorf("expected to get error: %v for test: %v", test.err, i) + continue + } + + // Now check data. + if test.err == nil && !bytes.Equal(test.expect, got) { + t.Errorf("did not get expected data for test: %v\n Got: %v\n, Want: %v\n", i, got, test.expect) + } + } +} diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index b39e3ebe..4ca12f42 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -318,7 +318,7 @@ func TestTrimToPTSRange(t *testing.T) { // TestTrimToMetaRange checks that Clip.TrimToMetaRange correctly provides a // sub Clip for a given meta range. -func TestTrimToMetaRange(t *testing.T) { +func TestClipTrimToMetaRange(t *testing.T) { const ( numOfTestFrames = 10 ptsInterval = 4 From 3eab25e18be49352cb77d5852aade9aefeab5d78 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 17 Jun 2019 04:30:52 +0930 Subject: [PATCH 17/93] container/mts: added SegmentForMeta func and testing This func will further segment an MTS segment to each series of packets that correspond to a given key and val of meta. Testing for this func has also been written. --- container/mts/meta/meta.go | 6 +-- container/mts/mpegts.go | 41 ++++++++++++++++++ container/mts/mpegts_test.go | 83 ++++++++++++++++++++++++++++++++++++ container/mts/payload.go | 2 +- 4 files changed, 128 insertions(+), 4 deletions(-) diff --git a/container/mts/meta/meta.go b/container/mts/meta/meta.go index 2baa33b7..b45c5796 100644 --- a/container/mts/meta/meta.go +++ b/container/mts/meta/meta.go @@ -51,7 +51,7 @@ const ( var ( errKeyAbsent = errors.New("Key does not exist in map") errInvalidMeta = errors.New("Invalid metadata given") - errUnexpectedMetaFormat = errors.New("Unexpected meta format") + ErrUnexpectedMetaFormat = errors.New("Unexpected meta format") ) // Metadata provides functionality for the storage and encoding of metadata @@ -209,7 +209,7 @@ func GetAll(d []byte) ([][2]string, error) { for i, entry := range entries { kv := strings.Split(entry, "=") if len(kv) != 2 { - return nil, errUnexpectedMetaFormat + return nil, ErrUnexpectedMetaFormat } copy(all[i][:], kv) } @@ -237,7 +237,7 @@ func GetAllAsMap(d []byte) (map[string]string, error) { // Keys and values are seperated by '=', so split and check that len(kv)=2. kv := strings.Split(entry, "=") if len(kv) != 2 { - return nil, errUnexpectedMetaFormat + return nil, ErrUnexpectedMetaFormat } all[kv[0]] = kv[1] } diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 4e6cbd51..622a19aa 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -442,3 +442,44 @@ func TrimToMetaRange(d []byte, key, from, to string) ([]byte, error) { } } } + +func SegmentForMeta(d []byte, key, val string) ([][]byte, error) { + var ( + pkt packet.Packet + segmenting bool + res [][]byte + start int + ) + + for i := 0; i < len(d); i += PacketSize { + copy(pkt[:], d[i:i+PacketSize]) + if pkt.PID() == PmtPid { + _meta, err := ExtractMeta(pkt[:]) + switch err { + case errNoMeta, meta.ErrUnexpectedMetaFormat: + if segmenting { + res = append(res, d[start:i]) + segmenting = false + } + continue + case nil: // do nothing. + default: + return nil, err + } + + if _meta[key] == val && !segmenting { + start = i + segmenting = true + } else if _meta[key] != val && segmenting { + res = append(res, d[start:i]) + segmenting = false + } + } + } + + if segmenting { + res = append(res, d[start:len(d)]) + } + + return res, nil +} diff --git a/container/mts/mpegts_test.go b/container/mts/mpegts_test.go index 5ac633f7..5d74c7ce 100644 --- a/container/mts/mpegts_test.go +++ b/container/mts/mpegts_test.go @@ -30,6 +30,7 @@ package mts import ( "bytes" "math/rand" + "reflect" "strconv" "testing" "time" @@ -334,3 +335,85 @@ func TestTrimToMetaRange(t *testing.T) { } } } + +func TestSegmentForMeta(t *testing.T) { + Meta = meta.New() + + const ( + nPSI = 10 + key = "n" + val = "*" + ) + + tests := []struct { + metaVals [nPSI]string + expectIdxs []rng + }{ + { + metaVals: [nPSI]string{"1", "2", val, val, val, "3", val, val, "4", "4"}, + expectIdxs: []rng{ + scale(2, 5), + scale(6, 8), + }, + }, + { + metaVals: [nPSI]string{"1", "2", val, val, val, "", "3", val, val, "4"}, + expectIdxs: []rng{ + scale(2, 5), + scale(2, 5), + }, + }, + { + metaVals: [nPSI]string{"1", "2", val, val, val, "", "3", val, val, val}, + expectIdxs: []rng{ + scale(2, 5), + {((7 * 2) + 1) * PacketSize, (nPSI * 2) * PacketSize}, + }, + }, + { + metaVals: [nPSI]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, + expectIdxs: nil, + }, + } + + var clip bytes.Buffer + + for testn, test := range tests { + clip.Reset() + for i := 0; i < nPSI; i++ { + if test.metaVals[i] != "" { + Meta.Add(key, test.metaVals[i]) + } else { + Meta.Delete(key) + } + err := writePSIWithMeta(&clip) + if err != nil { + t.Fatalf("did not expect to get error writing PSI, error: %v", err) + } + } + var want [][]byte + for _, idxs := range test.expectIdxs { + want = append(want, clip.Bytes()[idxs.start:idxs.end]) + } + got, err := SegmentForMeta(clip.Bytes(), key, val) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(want, got) { + t.Errorf("did not get expected result for test %v\nGot: %v\nWant: %v\n", testn, got, want) + } + } +} + +type rng struct { + start int + end int +} + +func scale(x, y int) rng { + return rng{ + ((x * 2) + 1) * PacketSize, + ((y * 2) + 1) * PacketSize, + } +} diff --git a/container/mts/payload.go b/container/mts/payload.go index 37394ce6..b7ccba4e 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -8,7 +8,7 @@ import ( "github.com/Comcast/gots/pes" ) -// TODO: write function for getting subslice of mpegts for meta interval +// TODO(saxon): write function to get sub clips that posess a particular meta value. // Extract extracts the media, PTS, stream ID and meta for an MPEG-TS clip given // by p, and returns as a Clip. The MPEG-TS must contain only complete packets. From 74992aee190fab12a9d5bd7a185316ae80dd87c8 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 17 Jun 2019 16:34:22 +0930 Subject: [PATCH 18/93] container/mts: added commenting to SegmentForMeta and accompanying testing --- container/mts/mpegts.go | 19 +++++++++++++++---- container/mts/mpegts_test.go | 25 ++++++++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 622a19aa..ce832b30 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -443,19 +443,25 @@ func TrimToMetaRange(d []byte, key, from, to string) ([]byte, error) { } } +// SegmentForMeta returns segments of MTS slice d that correspond to a value of +// meta for key and val. Therefore, any sequence of packets corresponding to +// key and val will be appended to the returned [][]byte. func SegmentForMeta(d []byte, key, val string) ([][]byte, error) { var ( - pkt packet.Packet - segmenting bool - res [][]byte - start int + pkt packet.Packet // We copy data to this so that we can use comcast gots stuff. + segmenting bool // If true we are currently in a segment corresponsing to given meta. + res [][]byte // The resultant [][]byte holding the segments. + start int // The start index of the current segment. ) + // Go through packets. for i := 0; i < len(d); i += PacketSize { copy(pkt[:], d[i:i+PacketSize]) if pkt.PID() == PmtPid { _meta, err := ExtractMeta(pkt[:]) switch err { + // If there's no meta or a problem with meta, we consider this the end + // of the segment. case errNoMeta, meta.ErrUnexpectedMetaFormat: if segmenting { res = append(res, d[start:i]) @@ -467,6 +473,9 @@ func SegmentForMeta(d []byte, key, val string) ([][]byte, error) { return nil, err } + // If we've got the meta of interest in the PMT and we're not segmenting + // then start segmenting. If we don't have the meta of interest in the PMT + // and we are segmenting then we want to stop and append the segment to result. if _meta[key] == val && !segmenting { start = i segmenting = true @@ -477,6 +486,8 @@ func SegmentForMeta(d []byte, key, val string) ([][]byte, error) { } } + // We've reached the end of the entire MTS clip so if we're segmenting we need + // to append current segment to res. if segmenting { res = append(res, d[start:len(d)]) } diff --git a/container/mts/mpegts_test.go b/container/mts/mpegts_test.go index 5d74c7ce..1e735095 100644 --- a/container/mts/mpegts_test.go +++ b/container/mts/mpegts_test.go @@ -336,18 +336,20 @@ func TestTrimToMetaRange(t *testing.T) { } } +// TestSegmentForMeta checks that SegmentForMeta can correctly segment some MTS +// data based on a given meta key and value. func TestSegmentForMeta(t *testing.T) { Meta = meta.New() const ( - nPSI = 10 - key = "n" - val = "*" + nPSI = 10 // The number of PSI pairs to write. + key = "n" // The meta key we will work with. + val = "*" // This is the meta value we will look for. ) tests := []struct { - metaVals [nPSI]string - expectIdxs []rng + metaVals [nPSI]string // This represents the meta value for meta pairs (PAT and PMT) + expectIdxs []rng // This gives the expect index ranges for the segments. }{ { metaVals: [nPSI]string{"1", "2", val, val, val, "3", val, val, "4", "4"}, @@ -379,7 +381,10 @@ func TestSegmentForMeta(t *testing.T) { var clip bytes.Buffer for testn, test := range tests { + // We want a clean buffer for each new test, so reset. clip.Reset() + + // Add meta and write PSI to clip. for i := 0; i < nPSI; i++ { if test.metaVals[i] != "" { Meta.Add(key, test.metaVals[i]) @@ -391,26 +396,36 @@ func TestSegmentForMeta(t *testing.T) { t.Fatalf("did not expect to get error writing PSI, error: %v", err) } } + + // Now we get the expected segments using the index ranges from the test. var want [][]byte for _, idxs := range test.expectIdxs { want = append(want, clip.Bytes()[idxs.start:idxs.end]) } + + // Now use the function we're testing to get the segments. got, err := SegmentForMeta(clip.Bytes(), key, val) if err != nil { t.Fatalf("unexpected error: %v", err) } + // Check that segments are OK. if !reflect.DeepEqual(want, got) { t.Errorf("did not get expected result for test %v\nGot: %v\nWant: %v\n", testn, got, want) } } } +// rng describes an index range and is used by TestSegmentForMeta. type rng struct { start int end int } +// scale takes a PSI index (i.e. first PSI is 0, next is 1) and modifies to be +// the index of the first byte of the PSI pair (PAT and PMT) in the byte stream. +// This assumes there are only PSI written consequitively, and is used by +// TestSegmentForMeta. func scale(x, y int) rng { return rng{ ((x * 2) + 1) * PacketSize, From b42510ae22291ab09106cd8c876b44cdfcef4416 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 17 Jun 2019 20:05:08 +0930 Subject: [PATCH 19/93] container/mts: added Clip.SegmentForMeta and accompanying testing. Added a function to segment a Clip into sub Clips that have a consistent meta key and value. Also added testing for this method. --- container/mts/mpegts_test.go | 2 +- container/mts/payload.go | 53 +++++++++++++++++++++++++ container/mts/payload_test.go | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/container/mts/mpegts_test.go b/container/mts/mpegts_test.go index 1e735095..ed4d8534 100644 --- a/container/mts/mpegts_test.go +++ b/container/mts/mpegts_test.go @@ -362,7 +362,7 @@ func TestSegmentForMeta(t *testing.T) { metaVals: [nPSI]string{"1", "2", val, val, val, "", "3", val, val, "4"}, expectIdxs: []rng{ scale(2, 5), - scale(2, 5), + scale(7, 9), }, }, { diff --git a/container/mts/payload.go b/container/mts/payload.go index b7ccba4e..64411228 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -226,3 +226,56 @@ func (c *Clip) TrimToMetaRange(key, from, to string) (*Clip, error) { } return nil, errMetaLowerBound } + +// SegmentForMeta segments sequences of frames within c possesing meta described +// by key and val and are appended to a []Clip which is subsequently returned. +func (c *Clip) SegmentForMeta(key, val string) []Clip { + var ( + segmenting bool // If true we are currently in a segment corresponsing to given meta. + res []Clip // The resultant [][]Clip holding the segments. + start int // The start index of the current segment. + ) + + // Go through frames of clip. + for i, frame := range c.frames { + // If there is no meta (meta = nil) and we are segmenting, then append the + // current segment to res. + if frame.Meta == nil { + if segmenting { + res = appendSegment(res, c, start, i) + segmenting = false + } + continue + } + + // If we've got the meta of interest in current frame and we're not + // segmenting, set this i as start and set segmenting true. If we don't + // have the meta of interest and we are segmenting then we + // want to stop and append the segment to res. + if frame.Meta[key] == val && !segmenting { + start = i + segmenting = true + } else if frame.Meta[key] != val && segmenting { + res = appendSegment(res, c, start, i) + segmenting = false + } + } + + // We've reached the end of the entire clip so if we're segmenting we need + // to append current segment to res. + if segmenting { + res = appendSegment(res, c, start, len(c.frames)) + } + + return res +} + +// appendSegment is a helper function used by Clip.SegmentForMeta to append a +// clip to a []Clip. +func appendSegment(segs []Clip, c *Clip, start, end int) []Clip { + return append(segs, Clip{ + frames: c.frames[start:end], + backing: c.backing[c.frames[start].idx : c.frames[end-1].idx+len(c.frames[end-1].Media)], + }, + ) +} diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 4ca12f42..881f5890 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -399,3 +399,77 @@ func TestClipTrimToMetaRange(t *testing.T) { } } } + +// TestClipSegmentForMeta checks that Clip.SegmentForMeta correctly returns +// segments from a clip with consistent meta defined by a key and value. +func TestClipSegmentForMeta(t *testing.T) { + const ( + nFrames = 10 // The number of test frames we want to create. + fSize = 3 // The size of the frame media. + key = "n" // Meta key we will use. + val = "*" // The meta val of interest. + ) + + tests := []struct { + metaVals []string // These will be the meta vals each frame has. + fIndices []rng // These are the indices of the segments of interest. + }{ + { + metaVals: []string{"1", "2", "*", "*", "*", "3", "*", "*", "4", "5"}, + fIndices: []rng{{2, 5}, {6, 8}}, + }, + { + metaVals: []string{"1", "2", "*", "*", "*", "", "*", "*", "4", "5"}, + fIndices: []rng{{2, 5}, {6, 8}}, + }, + { + metaVals: []string{"1", "2", "*", "*", "*", "3", "4", "5", "*", "*"}, + fIndices: []rng{{2, 5}, {8, nFrames}}, + }, + { + metaVals: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, + fIndices: nil, + }, + } + + // Run the tests. + for testn, test := range tests { + clip := &Clip{} + + // Generate test frames. + for i := 0; i < nFrames; i++ { + clip.backing = append(clip.backing, []byte{byte(i), byte(i), byte(i)}...) + clip.frames = append( + clip.frames, + Frame{ + Media: clip.backing[i*fSize : (i+1)*fSize], + idx: i * fSize, + Meta: map[string]string{ + key: test.metaVals[i], + }, + }, + ) + } + + // Use function we're testing to get segments. + got := clip.SegmentForMeta(key, val) + + // Now get expected segments using indices defined in the test. + var want []Clip + for _, indices := range test.fIndices { + // Calculate the indices for the backing array from the original clip. + backStart := clip.frames[indices.start].idx + backEnd := clip.frames[indices.end-1].idx + len(clip.frames[indices.end-1].Media) + + // Use calculated indices to create Clip for current expected segment. + want = append(want, Clip{ + frames: clip.frames[indices.start:indices.end], + backing: clip.backing[backStart:backEnd], + }) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("did not get expected result for test %v\nGot: %v\nWant: %v\n", testn, got, want) + } + } +} From 2fa6ecfe26c34bb77b3e483e145f5f61adae3806 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 17 Jun 2019 20:15:26 +0930 Subject: [PATCH 20/93] container/mts: added file headers for payload.go and payload_test.go --- container/mts/payload.go | 28 ++++++++++++++++++++++++++++ container/mts/payload_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/container/mts/payload.go b/container/mts/payload.go index 64411228..af0a773c 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -1,3 +1,31 @@ +/* +NAME + payload.go + +DESCRIPTION + payload.go provides functionality for extracting and manipulating the payload + data from MPEG-TS. + +AUTHOR + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2017 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 + along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + package mts import ( diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 881f5890..4ad8c9bb 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -1,3 +1,30 @@ +/* +NAME + payload_test.go + +DESCRIPTION + payload_test.go provides testing to validate utilities found in payload.go. + +AUTHOR + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2017 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 + along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + package mts import ( From 611dbd14be4fedbdc24a357a4b922ba20f6291ce Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 23 Jun 2019 23:58:05 +0930 Subject: [PATCH 21/93] codec/h264: added decode folder, decode/parse.go and first function in parse.go. Added decode folder which will contain the h264 decoder and utilities. Added first file parse.go, which contains parsing processes for syntax elements. --- codec/h264/decode/parse.go | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 codec/h264/decode/parse.go diff --git a/codec/h264/decode/parse.go b/codec/h264/decode/parse.go new file mode 100644 index 00000000..07087c7d --- /dev/null +++ b/codec/h264/decode/parse.go @@ -0,0 +1,53 @@ +/* +NAME + parse.go + +DESCRIPTION + parse.go provides parsing processes for syntax elements of different + descriptors specified in 7.2 of ITU-T H.264. + +AUTHOR + Saxon Nelson-Milton + +LICENSE + Copyright (C) 2017 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 + along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package decode + +import ( + "math" + + "github.com/icza/bitio" +) + +// readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer +// Exp-Golomb-coded element. This process is specified in 9.1. +func readUe(r bitio.Reader) (int, error) { + nZeros := -1 + var err error + for b := uint64(0); b == 0; nZeros++ { + b, err = r.ReadBits(1) + if err != nil { + return 0, err + } + } + rem, err := r.ReadBits(byte(nZeros)) + if err != nil { + return 0, err + } + return int(math.Pow(float64(2), float64(nZeros)) - 1 + float64(rem)), nil +} From dae6151bae60a8d03d74fb75ac79716d1f001499 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 27 Jun 2019 13:57:30 +0930 Subject: [PATCH 22/93] codec/h264: removed decode folder (shouldn't have been on master branch) --- codec/h264/decode/parse.go | 53 -------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 codec/h264/decode/parse.go diff --git a/codec/h264/decode/parse.go b/codec/h264/decode/parse.go deleted file mode 100644 index 07087c7d..00000000 --- a/codec/h264/decode/parse.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -NAME - parse.go - -DESCRIPTION - parse.go provides parsing processes for syntax elements of different - descriptors specified in 7.2 of ITU-T H.264. - -AUTHOR - Saxon Nelson-Milton - -LICENSE - Copyright (C) 2017 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 - along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. -*/ - -package decode - -import ( - "math" - - "github.com/icza/bitio" -) - -// readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer -// Exp-Golomb-coded element. This process is specified in 9.1. -func readUe(r bitio.Reader) (int, error) { - nZeros := -1 - var err error - for b := uint64(0); b == 0; nZeros++ { - b, err = r.ReadBits(1) - if err != nil { - return 0, err - } - } - rem, err := r.ReadBits(byte(nZeros)) - if err != nil { - return 0, err - } - return int(math.Pow(float64(2), float64(nZeros)) - 1 + float64(rem)), nil -} From ebc72cfbfc4070263b6ce6ba5049949e2b0e250e Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 29 Jun 2019 14:58:16 +0930 Subject: [PATCH 23/93] container/mts: for GetPTSRange if a second PTS is not found error is no longer returned and pts2=pts1 --- container/mts/mpegts.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index eb4bee5d..3f3851a3 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -331,7 +331,6 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { copy(_pkt[:], pkt) payload, err := packet.Payload(&_pkt) if err != nil { - fmt.Printf("_pkt: %v\n", _pkt) return [2]uint64{}, err } @@ -358,6 +357,5 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { return } } - - return [2]uint64{}, errors.New("could only find one access unit in mpegts clip") + return } From 0497ee5302392b4f5556cb77a7cd7a487a342ed5 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 1 Jul 2019 12:36:11 +0930 Subject: [PATCH 24/93] container/mts: GetPTSRange checks for PUSI when looking for first PTS --- container/mts/mpegts.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 3f3851a3..25e36ff1 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -320,15 +320,22 @@ func DiscontinuityIndicator(f bool) Option { // GetPTSRange retreives the first and last PTS of an MPEGTS clip. func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { - // Find the first packet with PID pidType. - pkt, _, err := FindPid(clip, pid) - if err != nil { - return [2]uint64{}, err + var _pkt packet.Packet + // Find the first packet with PID pidType and PUSI. + var i int + for { + pkt, _i, err := FindPid(clip[i:], pid) + if err != nil { + return [2]uint64{}, err + } + copy(_pkt[:], pkt) + if _pkt.PayloadUnitStartIndicator() { + break + } + i = _i + PacketSize } // Get the payload of the packet, which will be the start of the PES packet. - var _pkt packet.Packet - copy(_pkt[:], pkt) payload, err := packet.Payload(&_pkt) if err != nil { return [2]uint64{}, err From 0d240fa7ff9fa1ca6d23d9f6785325017282b1b5 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 1 Jul 2019 13:54:18 +0930 Subject: [PATCH 25/93] container/mts: checking index so that we don't go out of bounds --- container/mts/mpegts.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 25e36ff1..27ad6528 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -324,6 +324,9 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { // Find the first packet with PID pidType and PUSI. var i int for { + if i >= len(clip) { + return [2]uint64{}, errors.New("could not find payload start") + } pkt, _i, err := FindPid(clip[i:], pid) if err != nil { return [2]uint64{}, err From 7bd885bcfb23cdfe66253d5dfd6072444707bd72 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 1 Jul 2019 14:01:49 +0930 Subject: [PATCH 26/93] container/mts: fixed infinite loop --- container/mts/mpegts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 27ad6528..6ade2f87 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -335,7 +335,7 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { if _pkt.PayloadUnitStartIndicator() { break } - i = _i + PacketSize + i += _i + PacketSize } // Get the payload of the packet, which will be the start of the PES packet. From b017e92185d8cb64ee301556ca2520b6aef38f02 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 1 Jul 2019 19:08:20 +0930 Subject: [PATCH 27/93] container/mts: wrote more tests for GetPTSRange --- container/mts/mpegts.go | 8 ++-- container/mts/mpegts_test.go | 84 +++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 6ade2f87..60b1c484 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -30,11 +30,11 @@ LICENSE package mts import ( - "errors" "fmt" "github.com/Comcast/gots/packet" "github.com/Comcast/gots/pes" + "github.com/pkg/errors" ) const PacketSize = 188 @@ -325,11 +325,11 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { var i int for { if i >= len(clip) { - return [2]uint64{}, errors.New("could not find payload start") + return [2]uint64{}, errNoPTS } pkt, _i, err := FindPid(clip[i:], pid) if err != nil { - return [2]uint64{}, err + return [2]uint64{}, errors.Wrap(err, fmt.Sprintf("could not find packet of PID: %d", pid)) } copy(_pkt[:], pkt) if _pkt.PayloadUnitStartIndicator() { @@ -369,3 +369,5 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { } return } + +var errNoPTS = errors.New("could not find PTS") diff --git a/container/mts/mpegts_test.go b/container/mts/mpegts_test.go index 4c90cc0e..1a6409c7 100644 --- a/container/mts/mpegts_test.go +++ b/container/mts/mpegts_test.go @@ -39,8 +39,8 @@ import ( ) // TestGetPTSRange checks that GetPTSRange can correctly get the first and last -// PTS in an MPEGTS clip. -func TestGetPTSRange(t *testing.T) { +// PTS in an MPEGTS clip for a general case. +func TestGetPTSRange1(t *testing.T) { const ( numOfFrames = 20 maxFrameSize = 1000 @@ -157,6 +157,86 @@ func writeFrame(b *bytes.Buffer, frame []byte, pts uint64) error { return nil } +// TestGetPTSRange2 checks that GetPTSRange behaves correctly with cases where +// the first instance of a PID is not a payload start, and also where there +// are no payload starts. +func TestGetPTSRange2(t *testing.T) { + const ( + nPackets = 8 // The number of MTS packets we will generate. + wantPID = 1 // The PID we want. + ) + tests := []struct { + pusi []bool // The value of PUSI for each packet. + pid []uint16 // The PIDs for each packet. + pts []uint64 // The PTS for each packet. + want [2]uint64 // The wanted PTS from GetPTSRange. + err error // The error we expect from GetPTSRange. + }{ + { + []bool{false, false, false, true, false, false, true, false}, + []uint16{0, 0, 1, 1, 1, 1, 1, 1}, + []uint64{0, 0, 0, 1, 0, 0, 2, 0}, + [2]uint64{1, 2}, + nil, + }, + { + []bool{false, false, false, true, false, false, false, false}, + []uint16{0, 0, 1, 1, 1, 1, 1, 1}, + []uint64{0, 0, 0, 1, 0, 0, 0, 0}, + [2]uint64{1, 1}, + nil, + }, + { + []bool{false, false, false, false, false, false, false, false}, + []uint16{0, 0, 1, 1, 1, 1, 1, 1}, + []uint64{0, 0, 0, 0, 0, 0, 0, 0}, + [2]uint64{0, 0}, + errNoPTS, + }, + } + + var clip bytes.Buffer + + for i, test := range tests { + // Generate MTS packets for this test. + clip.Reset() + for j := 0; j < nPackets; j++ { + pesPkt := pes.Packet{ + StreamID: H264ID, + PDI: hasPTS, + PTS: test.pts[j], + Data: []byte{}, + HeaderLength: 5, + } + buf := pesPkt.Bytes(nil) + + pkt := Packet{ + PUSI: test.pusi[j], + PID: test.pid[j], + RAI: true, + CC: 0, + AFC: hasAdaptationField | hasPayload, + PCRF: true, + } + pkt.FillPayload(buf) + + _, err := clip.Write(pkt.Bytes(nil)) + if err != nil { + t.Fatalf("did not expect clip write error: %v", err) + } + } + + pts, err := GetPTSRange(clip.Bytes(), wantPID) + if err != test.err { + t.Errorf("did not get expected error for test: %v\nGot: %v\nWant: %v\n", i, err, test.err) + } + + if pts != test.want { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, pts, test.want) + } + } +} + // TestBytes checks that Packet.Bytes() correctly produces a []byte // representation of a Packet. func TestBytes(t *testing.T) { From edb056d19b28c1c48c9d4e62310b6d8895963e6d Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:31:49 +0930 Subject: [PATCH 28/93] container/mts: not using string consts for error messages --- container/mts/encoder_test.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/container/mts/encoder_test.go b/container/mts/encoder_test.go index fe85a396..2d4501d9 100644 --- a/container/mts/encoder_test.go +++ b/container/mts/encoder_test.go @@ -253,11 +253,6 @@ func TestEncodePcm(t *testing.T) { } } -const ( - errNotExpectedOut = "Unexpected output. \n Got : %v\n, Want: %v\n" - errUnexpectedErr = "Unexpected error: %v\n" -) - const fps = 25 // TestMetaEncode1 checks that we can externally add a single metadata entry to @@ -269,7 +264,7 @@ func TestMetaEncode1(t *testing.T) { e := NewEncoder(nopCloser{&buf}, fps, EncodeH264) Meta.Add("ts", "12345678") if err := e.writePSI(); err != nil { - t.Errorf(errUnexpectedErr, err.Error()) + t.Errorf("unexpected error: %v\n", err.Error()) } out := buf.Bytes() got := out[PacketSize+4:] @@ -284,7 +279,7 @@ func TestMetaEncode1(t *testing.T) { want = psi.AddCrc(want) want = psi.AddPadding(want) if !bytes.Equal(got, want) { - t.Errorf(errNotExpectedOut, got, want) + t.Errorf("unexpected output. \n Got : %v\n, Want: %v\n", got, want) } } @@ -298,7 +293,7 @@ func TestMetaEncode2(t *testing.T) { Meta.Add("ts", "12345678") Meta.Add("loc", "1234,4321,1234") if err := e.writePSI(); err != nil { - t.Errorf(errUnexpectedErr, err.Error()) + t.Errorf("did not expect error: %v from writePSI", err.Error()) } out := buf.Bytes() got := out[PacketSize+4:] @@ -313,7 +308,7 @@ func TestMetaEncode2(t *testing.T) { want = psi.AddCrc(want) want = psi.AddPadding(want) if !bytes.Equal(got, want) { - t.Errorf(errNotExpectedOut, got, want) + t.Errorf("did not get expected results.\ngot: %v\nwant: %v\n", got, want) } } @@ -326,18 +321,18 @@ func TestExtractMeta(t *testing.T) { Meta.Add("ts", "12345678") Meta.Add("loc", "1234,4321,1234") if err := e.writePSI(); err != nil { - t.Errorf(errUnexpectedErr, err.Error()) + t.Errorf("did not expect error: %v", err.Error()) } out := buf.Bytes() got, err := ExtractMeta(out) if err != nil { - t.Errorf(errUnexpectedErr, err.Error()) + t.Errorf("did not expect error: %v", err.Error()) } want := map[string]string{ "ts": "12345678", "loc": "1234,4321,1234", } if !reflect.DeepEqual(got, want) { - t.Errorf(errNotExpectedOut, got, want) + t.Errorf("did not get expected result.\ngot: %v\nwant: %v\n", got, want) } } From 866aa6bef31acca5a0258abd06e31c18f74602d2 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:33:12 +0930 Subject: [PATCH 29/93] container/mts/meta/meta.go: altered comment for GetAll --- container/mts/meta/meta.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/meta/meta.go b/container/mts/meta/meta.go index b45c5796..0e67aa96 100644 --- a/container/mts/meta/meta.go +++ b/container/mts/meta/meta.go @@ -217,7 +217,7 @@ func GetAll(d []byte) ([][2]string, error) { } // GetAllAsMap returns a map containing keys and values from a slice d containing -// AusOcean metadata. +// metadata. func GetAllAsMap(d []byte) (map[string]string, error) { err := checkMeta(d) if err != nil { From 5bbb8d01f29147a9a6f8202423d8fc37a4d3803b Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:34:34 +0930 Subject: [PATCH 30/93] container/mts/mpegts.go: added comment for global error vars used by FindPid --- container/mts/mpegts.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index a2162e86..8554f702 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -176,6 +176,7 @@ func FindPat(d []byte) ([]byte, int, error) { return FindPid(d, PatPid) } +// Errors used by FindPid. var ( errInvalidLen = errors.New("MPEG-TS data not of valid length") errCouldNotFind = errors.New("could not find packet with given PID") From 30dd27596b5c0fa9141659245a91eaad1649918a Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:36:26 +0930 Subject: [PATCH 31/93] container/mts/mpegts_test.go: removed var block for single var clip --- container/mts/mpegts_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/container/mts/mpegts_test.go b/container/mts/mpegts_test.go index 802b2765..3a603a20 100644 --- a/container/mts/mpegts_test.go +++ b/container/mts/mpegts_test.go @@ -358,9 +358,7 @@ func TestTrimToMetaRange(t *testing.T) { key = "n" ) - var ( - clip bytes.Buffer - ) + var clip bytes.Buffer for i := 0; i < nPSI; i++ { Meta.Add(key, strconv.Itoa((i*2)+1)) From ac0384127c88c45b33df6cb135cdd74d9cda7f8a Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:37:10 +0930 Subject: [PATCH 32/93] container/mts/payload.go: removed TODO that's been resolved --- container/mts/payload.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index af0a773c..87502f30 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -36,8 +36,6 @@ import ( "github.com/Comcast/gots/pes" ) -// TODO(saxon): write function to get sub clips that posess a particular meta value. - // Extract extracts the media, PTS, stream ID and meta for an MPEG-TS clip given // by p, and returns as a Clip. The MPEG-TS must contain only complete packets. // The resultant data is a copy of the original. From b66f11db19d68aed908b1180bd08200d8abb9818 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:37:43 +0930 Subject: [PATCH 33/93] container/mts/payload.go: removed space after comment --- container/mts/payload.go | 1 - 1 file changed, 1 deletion(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index 87502f30..e26d33f6 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -83,7 +83,6 @@ func Extract(p []byte) (*Clip, error) { // If PUSI is true then we know it's the start of a new frame, and we have // a PES header in the MTS payload. - if pkt.PayloadUnitStartIndicator() { _pes, err := pes.NewPESHeader(payload) if err != nil { From 0f92f7d727f082bd485517a6f04bbc5cf54991f9 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:38:15 +0930 Subject: [PATCH 34/93] container/mts/payload.go: fix spelling error in comment --- container/mts/payload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index e26d33f6..e5b5dd6b 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -98,7 +98,7 @@ func Extract(p []byte) (*Clip, error) { Meta: meta, }) - // Append the data to the underlying buffer and get appended lenghth. + // Append the data to the underlying buffer and get appended length. clip.backing = append(clip.backing, _pes.Data()...) dataLen = len(_pes.Data()) From 081fb49d54d8c66560bf8f6fbc93f2e02a8070ee Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:39:07 +0930 Subject: [PATCH 35/93] container/mts/payload.go: corrected comment for errors used in TrimToPTSRange --- container/mts/payload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index e5b5dd6b..ee6f0190 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -148,7 +148,7 @@ func (c *Clip) Bytes() []byte { return c.backing } -// Errors used in BytesForPTSInterval. +// Errors used in TrimToPTSRange. var ( errPTSLowerBound = errors.New("PTS 'from' cannot be found") errPTSUpperBound = errors.New("PTS 'to' cannot be found") From cf600d4aa8151c4848b64ab0885d6385c62eb2e8 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:39:38 +0930 Subject: [PATCH 36/93] container/mts/payload.go: corrected comment for TimeToPTSRange --- container/mts/payload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index ee6f0190..2d6b2bbd 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -155,7 +155,7 @@ var ( errPTSRange = errors.New("PTS interval invalid") ) -// TrimToPTSRange returns the sub Clip in PTS range defined by from and to. +// TrimToPTSRange returns the sub Clip in a PTS range defined by from and to. // The first Frame in the new Clip will be the Frame for which from corresponds // exactly with Frame.PTS, or the Frame in which from lies within. The final // Frame in the Clip will be the previous of that for which to coincides with, From 346dda0f4fd73252f9a8f9dd9146fd6bf230cb69 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:40:21 +0930 Subject: [PATCH 37/93] container/mts/payload.go: corrected comment for errors used in TrimToMetaRange --- container/mts/payload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index 2d6b2bbd..ddb9554e 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -210,7 +210,7 @@ func (c *Clip) TrimToPTSRange(from, to uint64) (*Clip, error) { }, nil } -// Errors that maybe returned from BytesForMetaInterval. +// Errors that maybe returned from TrimToMetaRange. var ( errMetaRange = errors.New("invalid meta range") errMetaLowerBound = errors.New("meta 'from' cannot be found") From d2278cb9144710dbd44b891b1f4599e86ecd88af Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 14:40:59 +0930 Subject: [PATCH 38/93] container/mts/payload_test.go: corrected comment for TestExtract --- container/mts/payload_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/payload_test.go b/container/mts/payload_test.go index 4ad8c9bb..cc8f13fa 100644 --- a/container/mts/payload_test.go +++ b/container/mts/payload_test.go @@ -40,7 +40,7 @@ import ( ) // TestExtract checks that we can coorectly extract media, pts, id and meta from -// an mpegts stream using Extract. +// an MPEGTS stream using Extract. func TestExtract(t *testing.T) { Meta = meta.New() From 09d772e9c372a17ef86c1d0aa91c57f6cbb7e178 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 19:47:34 +0930 Subject: [PATCH 39/93] container/mts/mpegts.go: equivalent -> identical in TrimToMetaRange error message --- container/mts/mpegts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 8554f702..00028478 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -414,7 +414,7 @@ func TrimToMetaRange(d []byte, key, from, to string) ([]byte, error) { } if from == to { - return nil, errors.New("'from' and 'to' cannot be equivalent") + return nil, errors.New("'from' and 'to' cannot be identical") } var ( From 36f1e8c78b17d07a8143cd091d7ec94246a64f45 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 4 Jul 2019 19:48:30 +0930 Subject: [PATCH 40/93] container/mts/payload.go: fixed date in license --- container/mts/payload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/payload.go b/container/mts/payload.go index ddb9554e..089eba76 100644 --- a/container/mts/payload.go +++ b/container/mts/payload.go @@ -10,7 +10,7 @@ AUTHOR Saxon A. Nelson-Milton LICENSE - Copyright (C) 2017 the Australian Ocean Lab (AusOcean) + 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 From 1728c3a531919d84f636e873b83bce8334b76f01 Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 10 Jul 2019 09:26:54 +0930 Subject: [PATCH 41/93] Export StandardPAT, StandardPMT and MaxPTS. --- container/mts/encoder.go | 70 +++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/container/mts/encoder.go b/container/mts/encoder.go index a1598dd8..c47537bf 100644 --- a/container/mts/encoder.go +++ b/container/mts/encoder.go @@ -34,10 +34,10 @@ import ( "bitbucket.org/ausocean/av/container/mts/psi" ) -// Some common manifestations of PSI +// Some common manifestations of PSI. var ( - // standardPat is a minimal PAT. - standardPat = psi.PSI{ + // StandardPAT is a minimal PAT. + StandardPAT = psi.PSI{ Pf: 0x00, Tid: 0x00, Ssi: true, @@ -55,6 +55,30 @@ var ( }, }, } + + // Standard PMT is a minimal PMT. + StandardPMT = psi.PSI{ + Pf: 0x00, + Tid: 0x02, + Ssi: true, + Sl: 0x12, + Tss: &psi.TSS{ + Tide: 0x01, + V: 0, + Cni: true, + Sn: 0, + Lsn: 0, + Sd: &psi.PMT{ + Pcrpid: 0x0100, + Pil: 0, + Essd: &psi.ESSD{ + St: H264ID, + Epid: 0x0100, + Esil: 0x00, + }, + }, + }, + } ) const ( @@ -69,7 +93,7 @@ const ( var Meta *meta.Data var ( - patTable = standardPat.Bytes() + patTable = StandardPAT.Bytes() pmtTable []byte ) @@ -102,6 +126,9 @@ const ( // 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 ) // Encoder encapsulates properties of an MPEG-TS generator. @@ -143,29 +170,20 @@ func NewEncoder(dst io.WriteCloser, rate float64, mediaType int) *Encoder { sid = H264ID } - // standardPmt is a minimal PMT, without descriptors for metadata. - pmtTable = (&psi.PSI{ - Pf: 0x00, - Tid: 0x02, - Ssi: true, - Sl: 0x12, - Tss: &psi.TSS{ - Tide: 0x01, - V: 0, - Cni: true, - Sn: 0, - Lsn: 0, - Sd: &psi.PMT{ - Pcrpid: 0x0100, - Pil: 0, - Essd: &psi.ESSD{ - St: byte(sid), - Epid: 0x0100, - Esil: 0x00, - }, + // StandardPmt is a minimal PMT, without descriptors for metadata. + pmt := StandardPMT + if sid != H264ID { + pmt.Tss.Sd = &psi.PMT{ + Pcrpid: 0x0100, + Pil: 0, + Essd: &psi.ESSD{ + St: byte(sid), + Epid: 0x0100, + Esil: 0x00, }, - }, - }).Bytes() + } + } + pmtTable = pmt.Bytes() return &Encoder{ dst: dst, From 74143174292e0a26d332745b3d1f27a5ef46153a Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 10 Jul 2019 09:45:55 +0930 Subject: [PATCH 42/93] Skip TestFromFrame if RTMP_TEST_FRAME env var not defined. --- protocol/rtmp/rtmp_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/protocol/rtmp/rtmp_test.go b/protocol/rtmp/rtmp_test.go index e1e79796..f1b44554 100644 --- a/protocol/rtmp/rtmp_test.go +++ b/protocol/rtmp/rtmp_test.go @@ -173,6 +173,10 @@ func TestErorHandling(t *testing.T) { // TestFromFrame tests streaming from a single H.264 frame which is repeated. func TestFromFrame(t *testing.T) { testLog(0, "TestFromFrame") + testFrame := os.Getenv("RTMP_TEST_FRAME") + if testFrame == "" { + t.Skip("Skipping TestFromFrame since no RTMP_TEST_FRAME") + } if testKey == "" { t.Skip("Skipping TestFromFrame since no RTMP_TEST_KEY") } @@ -181,7 +185,6 @@ func TestFromFrame(t *testing.T) { t.Errorf("Dial failed with error: %v", err) } - testFrame := os.Getenv("RTMP_TEST_FRAME") b, err := ioutil.ReadFile(testFrame) if err != nil { t.Errorf("ReadFile failed with error: %v", err) From 90efebdc91d17a2d9d37b21e519c3f2262f4d818 Mon Sep 17 00:00:00 2001 From: scruzin Date: Wed, 10 Jul 2019 10:17:34 +0930 Subject: [PATCH 43/93] Added BasePMT which is a PMT without specific data, and removed StandardPMT. --- container/mts/encoder.go | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/container/mts/encoder.go b/container/mts/encoder.go index c47537bf..883e3a5e 100644 --- a/container/mts/encoder.go +++ b/container/mts/encoder.go @@ -56,8 +56,8 @@ var ( }, } - // Standard PMT is a minimal PMT. - StandardPMT = psi.PSI{ + // Base PMT is a minimal PMT without specific data. + BasePMT = psi.PSI{ Pf: 0x00, Tid: 0x02, Ssi: true, @@ -68,15 +68,6 @@ var ( Cni: true, Sn: 0, Lsn: 0, - Sd: &psi.PMT{ - Pcrpid: 0x0100, - Pil: 0, - Essd: &psi.ESSD{ - St: H264ID, - Epid: 0x0100, - Esil: 0x00, - }, - }, }, } ) @@ -170,18 +161,15 @@ func NewEncoder(dst io.WriteCloser, rate float64, mediaType int) *Encoder { sid = H264ID } - // StandardPmt is a minimal PMT, without descriptors for metadata. - pmt := StandardPMT - if sid != H264ID { - pmt.Tss.Sd = &psi.PMT{ - Pcrpid: 0x0100, - Pil: 0, - Essd: &psi.ESSD{ - St: byte(sid), - Epid: 0x0100, - Esil: 0x00, - }, - } + pmt := BasePMT + pmt.Tss.Sd = &psi.PMT{ + Pcrpid: 0x0100, + Pil: 0, + Essd: &psi.ESSD{ + St: byte(sid), + Epid: 0x0100, + Esil: 0x00, + }, } pmtTable = pmt.Bytes() From feea0697582a63d0dfd23abc34bf8b7194403ac4 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 11 Jul 2019 14:51:23 +0930 Subject: [PATCH 44/93] Factored metaFromPMT out of ExtractMeta and added LastPid and IndexPid. --- container/mts/mpegts.go | 121 ++++++++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 28 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 00028478..338fa235 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -198,6 +198,52 @@ func FindPid(d []byte, pid uint16) (pkt []byte, i int, err error) { return nil, -1, errCouldNotFind } +// LastPid will take a clip of MPEG-TS and try to find a packet +// with given PID searching in reverse from the end of the clip. If +// one is found, then it is returned along with its index, otherwise +// nil, -1 and an error is returned. +func LastPid(d []byte, pid uint16) (pkt []byte, i int, err error) { + if len(d) < PacketSize { + return nil, -1, errInvalidLen + } + + for i = len(d) - PacketSize; i >= 0; i -= PacketSize { + p := (uint16(d[i+1]&0x1f) << 8) | uint16(d[i+2]) + if p == pid { + pkt = d[i : i+PacketSize] + return + } + } + return nil, -1, errCouldNotFind +} + +// IndexPid returns the position of one or more consecutive pids, +// along with optional metadata if present. Commonly used to find a +// PAT immediately followed by a PMT. +func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error) { + prev := 0 + for _, pid := range pids { + pkt, i, _err := FindPid(d, pid) + if err != nil { + err = errors.Wrap(_err, "could not find PID") + return + } + if pid == PmtPid { + m, _ = metaFromPMT(pkt) + } + if prev == 0 { + idx = i + prev = i + continue + } + if i != prev+PacketSize { + err = errors.Wrap(err, "PIDs not consecutive") + return + } + } + return +} + // FillPayload takes a channel and fills the packets Payload field until the // channel is empty or we've the packet reaches capacity func (p *Packet) FillPayload(data []byte) int { @@ -331,17 +377,18 @@ func DiscontinuityIndicator(f bool) Option { var errNoPTS = errors.New("could not find PTS") // GetPTSRange retreives the first and last PTS of an MPEGTS clip. +// If there is only one PTS, it is included twice in the pts return value. func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { var _pkt packet.Packet // Find the first packet with PID pidType and PUSI. var i int for { if i >= len(clip) { - return [2]uint64{}, errNoPTS + return pts, errNoPTS } pkt, _i, err := FindPid(clip[i:], pid) if err != nil { - return [2]uint64{}, errors.Wrap(err, fmt.Sprintf("could not find packet of PID: %d", pid)) + return pts, errors.Wrap(err, fmt.Sprintf("could not find packet of PID: %d", pid)) } copy(_pkt[:], pkt) if _pkt.PayloadUnitStartIndicator() { @@ -353,32 +400,47 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { // Get the payload of the packet, which will be the start of the PES packet. payload, err := packet.Payload(&_pkt) if err != nil { - return [2]uint64{}, err + return pts, err } // Get the the first PTS from the PES header. _pes, err := pes.NewPESHeader(payload) if err != nil { - return [2]uint64{}, err + return pts, err } pts[0] = _pes.PTS() + pts[1] = pts[0] // Until we have find a second PTS. - // Get the final PTS searching from end of clip for access unit start. - for i := len(clip) - PacketSize; i >= 0; i -= PacketSize { - copy(_pkt[:], clip[i:i+PacketSize]) - if packet.PayloadUnitStartIndicator(&_pkt) && uint16(_pkt.PID()) == pid { - payload, err = packet.Payload(&_pkt) - if err != nil { - return [2]uint64{}, err - } - _pes, err = pes.NewPESHeader(payload) - if err != nil { - return [2]uint64{}, err - } - pts[1] = _pes.PTS() - return + // Get the last PTS searching in reverse from end of the clip. + first := i + i = len(clip) + for { + pkt, _i, err := LastPid(clip[:i], pid) + if err != nil || i <= first { + return pts, nil } + + copy(_pkt[:], pkt) + if packet.PayloadUnitStartIndicator(&_pkt) { + break + } + + i = _i } + + // Get the payload of the packet. + payload, err = packet.Payload(&_pkt) + if err != nil { + return + } + + // Get the the last PTS from the PES header. + _pes, err = pes.NewPESHeader(payload) + if err != nil { + return + } + pts[1] = _pes.PTS() + return } @@ -388,22 +450,25 @@ var errNoMeta = errors.New("PMT does not contain meta") // descriptor, that is found in the MPEG-TS clip d. d must contain a series of // complete MPEG-TS packets. func ExtractMeta(d []byte) (map[string]string, error) { - pkt, _, err := FindPid(d, PmtPid) + pmt, _, err := FindPid(d, PmtPid) if err != nil { return nil, err } + return metaFromPMT(pmt) +} - // Get as PSI type. Need to skip MTS header. - pmt := psi.PSIBytes(pkt[4:]) - _, metaDescriptor := pmt.HasDescriptor(psi.MetadataTag) +// metaFromPMT returns metadata, if any, from a PMT. +func metaFromPMT(d []byte) (m map[string]string, err error) { + // Get as PSI type, skipping the MTS header. + pmt := psi.PSIBytes(d[4:]) - if metaDescriptor == nil { - return nil, errNoMeta + // Get the metadata descriptor. + _, desc := pmt.HasDescriptor(psi.MetadataTag) + if desc == nil { + return m, errNoMeta } - // Skip the descriptor head. - m := metaDescriptor[2:] - - return meta.GetAllAsMap(m) + // Get the metadata as a map, skipping the descriptor head. + return meta.GetAllAsMap(desc[2:]) } // TrimToMetaRange trims a slice of MPEG-TS to a segment between two points of From b1e58905547034ef183da647a22ff1ecafe32369 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 11 Jul 2019 14:54:06 +0930 Subject: [PATCH 45/93] Update prev in IndexPid. --- container/mts/mpegts.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 338fa235..6e5db0b0 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -240,6 +240,7 @@ func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error err = errors.Wrap(err, "PIDs not consecutive") return } + prev = i } return } From 4b57407e36b1ac6c000fe37393dd71fe46f42c04 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 11 Jul 2019 14:57:47 +0930 Subject: [PATCH 46/93] Don't wrap error unnecessarily. --- container/mts/mpegts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 6e5db0b0..c43df44c 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -237,7 +237,7 @@ func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error continue } if i != prev+PacketSize { - err = errors.Wrap(err, "PIDs not consecutive") + err = errors.New("PIDs not consecutive") return } prev = i From c8a0b7df071218304d0753910a3a81c9d3fd2e09 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 11 Jul 2019 17:33:16 +0930 Subject: [PATCH 47/93] Fix IndexPid. --- container/mts/mpegts.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index c43df44c..2a5f9483 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -221,7 +221,7 @@ func LastPid(d []byte, pid uint16) (pkt []byte, i int, err error) { // along with optional metadata if present. Commonly used to find a // PAT immediately followed by a PMT. func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error) { - prev := 0 + idx = -1 for _, pid := range pids { pkt, i, _err := FindPid(d, pid) if err != nil { @@ -231,16 +231,13 @@ func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error if pid == PmtPid { m, _ = metaFromPMT(pkt) } - if prev == 0 { + if idx == -1 { idx = i - prev = i - continue - } - if i != prev+PacketSize { + } else if i != 0 { err = errors.New("PIDs not consecutive") return } - prev = i + d = d[i+PacketSize:] } return } From c717595adcd95325d471028a50e90c746bb01bf0 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 11 Jul 2019 18:16:33 +0930 Subject: [PATCH 48/93] Added length check to IndexPid. --- container/mts/mpegts.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 2a5f9483..28ad802b 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -178,8 +178,9 @@ func FindPat(d []byte) ([]byte, int, error) { // Errors used by FindPid. var ( - errInvalidLen = errors.New("MPEG-TS data not of valid length") - errCouldNotFind = errors.New("could not find packet with given PID") + errInvalidLen = errors.New("MPEG-TS data not of valid length") + errCouldNotFind = errors.New("could not find packet with given PID") + errNotConsecutive = errors.New("could not find consecutive PIDs") ) // FindPid will take a clip of MPEG-TS and try to find a packet with given PID - if one @@ -223,10 +224,12 @@ func LastPid(d []byte, pid uint16) (pkt []byte, i int, err error) { func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error) { idx = -1 for _, pid := range pids { - pkt, i, _err := FindPid(d, pid) + if len(d) < PacketSize { + return idx, m, errInvalidLen + } + pkt, i, err := FindPid(d, pid) if err != nil { - err = errors.Wrap(_err, "could not find PID") - return + return idx, m, errCouldNotFind } if pid == PmtPid { m, _ = metaFromPMT(pkt) @@ -234,8 +237,7 @@ func IndexPid(d []byte, pids ...uint16) (idx int, m map[string]string, err error if idx == -1 { idx = i } else if i != 0 { - err = errors.New("PIDs not consecutive") - return + return idx, m, errNotConsecutive } d = d[i+PacketSize:] } From 01351a308bc0f83561e92c3956bad94b25e278d9 Mon Sep 17 00:00:00 2001 From: scruzin Date: Thu, 11 Jul 2019 19:29:46 +0930 Subject: [PATCH 49/93] Added tests for IndexPid. --- container/mts/mpegts_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/container/mts/mpegts_test.go b/container/mts/mpegts_test.go index 3a603a20..9ce93b5b 100644 --- a/container/mts/mpegts_test.go +++ b/container/mts/mpegts_test.go @@ -491,6 +491,26 @@ func TestSegmentForMeta(t *testing.T) { if !reflect.DeepEqual(want, got) { t.Errorf("did not get expected result for test %v\nGot: %v\nWant: %v\n", testn, got, want) } + + // Now test IndexPid. + i, m, err := IndexPid(clip.Bytes(), PatPid, PmtPid) + if err != nil { + t.Fatalf("IndexPid failed with error: %v", err) + } + if i != 0 { + t.Fatalf("IndexPid unexpected index; got %d, expected 0", i) + } + if m["n"] != "1" { + t.Fatalf("IndexPid unexpected metadata; got %s, expected 1", m["n"]) + } + } + + // Finally, test IndexPid error handling. + for _, d := range [][]byte{[]byte{}, make([]byte, PacketSize/2), make([]byte, PacketSize)} { + _, _, err := IndexPid(d, PatPid, PmtPid) + if err == nil { + t.Fatalf("IndexPid expected error") + } } } From aa671349692bfdbdfdcb8aafed9b69cbcca4dfbf Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 12 Jul 2019 14:01:49 +0930 Subject: [PATCH 50/93] Added GetPTS() function. --- container/mts/mpegts.go | 89 +++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 28ad802b..ee898177 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -33,7 +33,6 @@ import ( "fmt" "github.com/Comcast/gots/packet" - "github.com/Comcast/gots/pes" "github.com/pkg/errors" "bitbucket.org/ausocean/av/container/mts/meta" @@ -379,8 +378,8 @@ var errNoPTS = errors.New("could not find PTS") // GetPTSRange retreives the first and last PTS of an MPEGTS clip. // If there is only one PTS, it is included twice in the pts return value. func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { - var _pkt packet.Packet - // Find the first packet with PID pidType and PUSI. + var _pts int64 + // Get the first PTS for the given PID. var i int for { if i >= len(clip) { @@ -390,25 +389,14 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { if err != nil { return pts, errors.Wrap(err, fmt.Sprintf("could not find packet of PID: %d", pid)) } - copy(_pkt[:], pkt) - if _pkt.PayloadUnitStartIndicator() { + _pts, err = GetPTS(pkt) + if err == nil { break } i += _i + PacketSize } - // Get the payload of the packet, which will be the start of the PES packet. - payload, err := packet.Payload(&_pkt) - if err != nil { - return pts, err - } - - // Get the the first PTS from the PES header. - _pes, err := pes.NewPESHeader(payload) - if err != nil { - return pts, err - } - pts[0] = _pes.PTS() + pts[0] = uint64(_pts) pts[1] = pts[0] // Until we have find a second PTS. // Get the last PTS searching in reverse from end of the clip. @@ -419,31 +407,64 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { if err != nil || i <= first { return pts, nil } - - copy(_pkt[:], pkt) - if packet.PayloadUnitStartIndicator(&_pkt) { + _pts, err = GetPTS(pkt) + if err == nil { break } - i = _i } - // Get the payload of the packet. - payload, err = packet.Payload(&_pkt) - if err != nil { - return - } - - // Get the the last PTS from the PES header. - _pes, err = pes.NewPESHeader(payload) - if err != nil { - return - } - pts[1] = _pes.PTS() + pts[1] = uint64(_pts) return } +var errNoPesPayload = errors.New("no PES payload") +var errNoPesPTS = errors.New("no PES PTS") +var errInvalidPesHeader = errors.New("invalid PES header") +var errInvalidPesPayload = errors.New("invalid PES payload") + +// GetPTS returns a PTS from an MTS packet that has PES payload, or an error otherwise. +func GetPTS(pkt []byte) (pts int64, err error) { + // Bail if packet does not contain a PES. + if pkt[1]&0x040 == 0 { + err = errNoPesPayload + return + } + + // Compute start of PES payload and check its length. + start := HeadSize + if pkt[3]&0x20 != 0 { + // Take into account length of the adaptation field. + start += 1 + int(pkt[4]) + } + pes := pkt[start:] + + if len(pes) < 9 { + err = errInvalidPesHeader + return + } + + // Check that PES has a PTS. + if pes[7]&0xc0 == 0 { + err = errNoPesPTS + return + } + + // Extract the PTS. + if len(pes) < 14 { + err = errInvalidPesHeader + return + } + pts = extractPTS(pes[9:14]) + return +} + +// extractTime extracts a PTS from the given data. +func extractPTS(d []byte) int64 { + return (int64((d[0]>>1)&0x07) << 30) | (int64(d[1]) << 22) | (int64((d[2]>>1)&0x7f) << 15) | (int64(d[3]) << 7) | int64((d[4]>>1)&0x7f) +} + var errNoMeta = errors.New("PMT does not contain meta") // ExtractMeta returns a map of metadata from the first PMT's metaData @@ -460,7 +481,7 @@ func ExtractMeta(d []byte) (map[string]string, error) { // metaFromPMT returns metadata, if any, from a PMT. func metaFromPMT(d []byte) (m map[string]string, err error) { // Get as PSI type, skipping the MTS header. - pmt := psi.PSIBytes(d[4:]) + pmt := psi.PSIBytes(d[HeadSize:]) // Get the metadata descriptor. _, desc := pmt.HasDescriptor(psi.MetadataTag) From 9e5bc3806f8913bbb715fcc84d0c3040f8565c83 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 12 Jul 2019 14:24:30 +0930 Subject: [PATCH 51/93] Added GetPTS. --- container/mts/mpegts.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index ee898177..484254fe 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -424,9 +424,9 @@ var errNoPesPTS = errors.New("no PES PTS") var errInvalidPesHeader = errors.New("invalid PES header") var errInvalidPesPayload = errors.New("invalid PES payload") -// GetPTS returns a PTS from an MTS packet that has PES payload, or an error otherwise. +// GetPTS returns a PTS from a packet that has PES payload, or an error otherwise. func GetPTS(pkt []byte) (pts int64, err error) { - // Bail if packet does not contain a PES. + // Check the Payload Unit Start Indicator. if pkt[1]&0x040 == 0 { err = errNoPesPayload return @@ -435,27 +435,22 @@ func GetPTS(pkt []byte) (pts int64, err error) { // Compute start of PES payload and check its length. start := HeadSize if pkt[3]&0x20 != 0 { - // Take into account length of the adaptation field. + // Adaptation field is present, so adjust start of payload accordingly. start += 1 + int(pkt[4]) } pes := pkt[start:] - if len(pes) < 9 { + if len(pes) < 14 { err = errInvalidPesHeader return } - // Check that PES has a PTS. + // Check the PTS DTS indicator. if pes[7]&0xc0 == 0 { err = errNoPesPTS return } - // Extract the PTS. - if len(pes) < 14 { - err = errInvalidPesHeader - return - } pts = extractPTS(pes[9:14]) return } From e76b8b3800eacf07db4380e24e851c158db0e7be Mon Sep 17 00:00:00 2001 From: scruzin Date: Sat, 13 Jul 2019 09:17:44 +0930 Subject: [PATCH 52/93] Tidy up errors used by GetPTS(). --- container/mts/mpegts.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 484254fe..0b356770 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -419,10 +419,12 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { return } -var errNoPesPayload = errors.New("no PES payload") -var errNoPesPTS = errors.New("no PES PTS") -var errInvalidPesHeader = errors.New("invalid PES header") -var errInvalidPesPayload = errors.New("invalid PES payload") +var ( + errNoPesPayload = errors.New("no PES payload") + errNoPesPTS = errors.New("no PES PTS") + errInvalidPesHeader = errors.New("invalid PES header") + errInvalidPesPayload = errors.New("invalid PES payload") +) // GetPTS returns a PTS from a packet that has PES payload, or an error otherwise. func GetPTS(pkt []byte) (pts int64, err error) { From 05f79ddbe3c3c15c957c22dda623f8f170b9a121 Mon Sep 17 00:00:00 2001 From: Saxon Milton Date: Sun, 14 Jul 2019 05:59:55 +0000 Subject: [PATCH 53/93] revid: increase RtmpSender ring buffer size --- revid/senders.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/revid/senders.go b/revid/senders.go index 17fa7dc1..7ae3c769 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -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(10, rbElementSize, 0), + ring: ring.NewBuffer(100, rbElementSize, 0), done: make(chan struct{}), } s.wg.Add(1) From 6efed19b7fd772904639a36ea2234d8604b8c234 Mon Sep 17 00:00:00 2001 From: scruzin Date: Mon, 15 Jul 2019 08:50:45 +0930 Subject: [PATCH 54/93] Use ausocean/iot v1.2.5. --- go.mod | 9 ++------- go.sum | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index c3d766c5..37a7a31f 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,12 @@ module bitbucket.org/ausocean/av go 1.12 require ( - bitbucket.org/ausocean/iot v1.2.4 + bitbucket.org/ausocean/iot v1.2.5 bitbucket.org/ausocean/utils v1.2.6 - github.com/BurntSushi/toml v0.3.1 // indirect github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 - github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect - github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 // indirect github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 github.com/mewkiz/flac v1.0.5 - github.com/sergi/go-diff v1.0.0 // indirect + github.com/pkg/errors v0.8.1 github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index cd09945f..5ee9f294 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ 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/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= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -8,16 +11,21 @@ github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXSh github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7/go.mod h1:O5HA0jgDXkBp+jw0770QNBT8fsRJCbH7JXmM7wxLUBU= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/adrianmo/go-nmea v1.1.1-0.20190109062325-c448653979f7/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-audio/aiff v0.0.0-20180403003018-6c3a8a6aff12/go.mod h1:AMSAp6W1zd0koOdX6QDgGIuBDTUvLa2SLQtm7d9eM3c= github.com/go-audio/audio v0.0.0-20180206231410-b697a35b5608/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 h1:4sGU+UABMMsRJyD+Y2yzMYxq0GJFUsRRESI0P1gZ2ig= github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs= +github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ= github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21 h1:Hc1iKlyxNHp3CV59G2E/qabUkHvEwOIJxDK0CJ7CRjA= github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21/go.mod h1:LlQmBGkOuV/SKzEDXBPKauvN2UqCgzXO2XjecTGj40s= github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc= @@ -29,16 +37,19 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e h1:3NIzz7weXhh3NToPgbtlQtKiVgerEaG4/nY2skGoGG0= github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e/go.mod h1:CaowXBWOiSGWEpBBV8LoVnQTVPV4ycyviC9IBLj8dRw= +github.com/yryz/ds18b20 v0.0.0-20180211073435-3cf383a40624/go.mod h1:MqFju5qeLDFh+S9PqxYT7TEla8xeW7bgGr/69q3oki0= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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= +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= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= From f856671ee74044096f15280086cb99960a99ec73 Mon Sep 17 00:00:00 2001 From: Saxon Date: Thu, 18 Jul 2019 14:02:42 +0930 Subject: [PATCH 55/93] codec/h264: added mrmod h264decode pkg to our av/codec/h264 dir --- codec/h264/decode/.gitignore | 3 + codec/h264/decode/CODEOWNERS | 2 + codec/h264/decode/LICENSE | 339 ++++++ codec/h264/decode/README.md | 44 + codec/h264/decode/bits/bitreader.go | 170 +++ codec/h264/decode/bits/bitreader_test.go | 311 +++++ codec/h264/decode/cabac.go | 691 +++++++++++ codec/h264/decode/cabac_test.go | 121 ++ codec/h264/decode/frame.go | 89 ++ codec/h264/decode/go.mod | 8 + codec/h264/decode/go.sum | 4 + codec/h264/decode/mbType.go | 170 +++ codec/h264/decode/mn_vars.go | 440 +++++++ codec/h264/decode/nalUnit.go | 161 +++ codec/h264/decode/parse.go | 161 +++ codec/h264/decode/parse_test.go | 154 +++ codec/h264/decode/pps.go | 239 ++++ codec/h264/decode/rangeTabLPS.go | 100 ++ codec/h264/decode/rbsp.go | 86 ++ codec/h264/decode/read.go | 230 ++++ codec/h264/decode/server.go | 70 ++ codec/h264/decode/slice.go | 1369 ++++++++++++++++++++++ codec/h264/decode/slice_test.go | 49 + codec/h264/decode/sps.go | 690 +++++++++++ codec/h264/decode/stateTransxTab.go | 74 ++ 25 files changed, 5775 insertions(+) create mode 100644 codec/h264/decode/.gitignore create mode 100644 codec/h264/decode/CODEOWNERS create mode 100644 codec/h264/decode/LICENSE create mode 100644 codec/h264/decode/README.md create mode 100644 codec/h264/decode/bits/bitreader.go create mode 100644 codec/h264/decode/bits/bitreader_test.go create mode 100644 codec/h264/decode/cabac.go create mode 100644 codec/h264/decode/cabac_test.go create mode 100644 codec/h264/decode/frame.go create mode 100644 codec/h264/decode/go.mod create mode 100644 codec/h264/decode/go.sum create mode 100644 codec/h264/decode/mbType.go create mode 100644 codec/h264/decode/mn_vars.go create mode 100644 codec/h264/decode/nalUnit.go create mode 100644 codec/h264/decode/parse.go create mode 100644 codec/h264/decode/parse_test.go create mode 100644 codec/h264/decode/pps.go create mode 100644 codec/h264/decode/rangeTabLPS.go create mode 100644 codec/h264/decode/rbsp.go create mode 100644 codec/h264/decode/read.go create mode 100644 codec/h264/decode/server.go create mode 100644 codec/h264/decode/slice.go create mode 100644 codec/h264/decode/slice_test.go create mode 100644 codec/h264/decode/sps.go create mode 100644 codec/h264/decode/stateTransxTab.go diff --git a/codec/h264/decode/.gitignore b/codec/h264/decode/.gitignore new file mode 100644 index 00000000..86ede22e --- /dev/null +++ b/codec/h264/decode/.gitignore @@ -0,0 +1,3 @@ +.*.swp +*.mp4 +*.h264 diff --git a/codec/h264/decode/CODEOWNERS b/codec/h264/decode/CODEOWNERS new file mode 100644 index 00000000..2ba2c957 --- /dev/null +++ b/codec/h264/decode/CODEOWNERS @@ -0,0 +1,2 @@ +# Default owners for everything in this repo. +* @scruzin @saxon-milton diff --git a/codec/h264/decode/LICENSE b/codec/h264/decode/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/codec/h264/decode/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/codec/h264/decode/README.md b/codec/h264/decode/README.md new file mode 100644 index 00000000..6a5e673b --- /dev/null +++ b/codec/h264/decode/README.md @@ -0,0 +1,44 @@ +[![Build Status](https://travis-ci.org/ausocean/h264decode.svg?branch=master)](https://travis-ci.org/ausocean/h264decode) [![Go Report Card](https://goreportcard.com/badge/github.com/ausocean/h264decode)](https://goreportcard.com/report/github.com/ausocean/h264decode) + +Listens for a stream of H264 bytes on port 8000. + +A stream is read sequentially dropping each NAL into a struct with access to the RBSP and seekable features. No interface contracts are implemented right now. This is heavily a work in progress. + +# TODO + +* CABAC initialization +* Context-adaptive arithmetic entropy-coded syntax element support +* Macroblock to YCbCr image decoding + +## Done + +* DecodeBypass, 9.3.3.2.3 +* DecodeTerminate, 9.3.3.2.4 +* RenormD - 9.3.3.2.2 +* rangeTableLPS ( Table 9-44 ) +* Derive ctxIDX per 9.3.3.1 +* Select M, N values + +### ArithmeticDecoding S 9.3.3.2 + +* cabac.go : ArithmeticDecoding 9.3.3.3.2 +* cabac.go : DecodeBypass, DecodeTerminate, DecodeDecision + +## In Progress + +* Make use of DecodeBypass and DecodeTerminate information +* 9.3.3.2.1 - BinaryDecision + * 9.3.3.2.1.1, 9.3.3.2.2 + +## Next + +* Make use of initCabac (initialized CABACs) + +# Background + +The last point was and is the entire driving force behind this project: To decode a single frame to an image and begin doing computer vision tasks on it. A while back, this project was started to keep an eye on rodents moving their way around various parts of our house and property. What was supposed to happen was motion detected from one-frame to another of an MJPEG stream would trigger capturing the stream. Analyzing the stream, even down at 24 fps, caused captures to be triggered too late. When it was triggered, there was so much blur in the resulting captured stream, it wasn't very watchable. + +Doing a little prototyping it was apparent reading an h.264 stream into VLC provided a watchable, unblurry, video. With a little searching on the internet, (https://github.com/gqf2008/codec) provided an example of using [ffMPEG](https://www.ffmpeg.org/) to decode an h.264 frame/NAL unit's macroblocks into a YCbCr image. Were this some shippable product with deadlines and things, [GGF2008](https://github.com/gqf2008/codec) would've been wired up and progress would've happened. But this is a tinkering project. Improving development skills, learning a bit about streaming deata, and filling dead-air were the criteria for this project. + +Because of that, a pure Go h.264 stream decoder was the only option. Duh. + diff --git a/codec/h264/decode/bits/bitreader.go b/codec/h264/decode/bits/bitreader.go new file mode 100644 index 00000000..7e17f407 --- /dev/null +++ b/codec/h264/decode/bits/bitreader.go @@ -0,0 +1,170 @@ +/* +DESCRIPTION + bitreader.go provides a bit reader implementation that can read or peek from + an io.Reader data source. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + +LICENSE + This code is a modification of code from: + https://golang.org/src/compress/bzip2/bit_reader.go. + + Copyright (c) 2009 The Go Authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + 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 bits provides a bit reader implementation that can read or peek from +// an io.Reader data source. +package bits + +import ( + "bufio" + "io" +) + +type bytePeeker interface { + io.ByteReader + Peek(int) ([]byte, error) +} + +// BitReader is a bit reader that provides methods for reading bits from an +// io.Reader source. +type BitReader struct { + r bytePeeker + n uint64 + bits int + nRead int +} + +// NewBitReader returns a new BitReader. +func NewBitReader(r io.Reader) *BitReader { + byter, ok := r.(bytePeeker) + if !ok { + byter = bufio.NewReader(r) + } + return &BitReader{r: byter} +} + +// ReadBits reads n bits from the source and returns them the least-significant +// part of a uint64. +// For example, with a source as []byte{0x8f,0xe3} (1000 1111, 1110 0011), we +// would get the following results for consequtive reads with n values: +// n = 4, res = 0x8 (1000) +// n = 2, res = 0x3 (0011) +// n = 4, res = 0xf (1111) +// n = 6, res = 0x23 (0010 0011) +func (br *BitReader) ReadBits(n int) (uint64, error) { + for n > br.bits { + b, err := br.r.ReadByte() + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } + if err != nil { + return 0, err + } + br.nRead++ + br.n <<= 8 + br.n |= uint64(b) + br.bits += 8 + } + + // br.n looks like this (assuming that br.bits = 14 and bits = 6): + // Bit: 111111 + // 5432109876543210 + // + // (6 bits, the desired output) + // |-----| + // V V + // 0101101101001110 + // ^ ^ + // |------------| + // br.bits (num valid bits) + // + // This the next line right shifts the desired bits into the + // least-significant places and masks off anything above. + r := (br.n >> uint(br.bits-n)) & ((1 << uint(n)) - 1) + br.bits -= n + return r, nil +} + +// PeekBits provides the next n bits returning them in the least-significant +// part of a uint64, without advancing through the source. +// For example, with a source as []byte{0x8f,0xe3} (1000 1111, 1110 0011), we +// would get the following results for consequtive peeks with n values: +// n = 4, res = 0x8 (1000) +// n = 8, res = 0x8f (1000 1111) +// n = 16, res = 0x8fe3 (1000 1111, 1110 0011) +func (br *BitReader) PeekBits(n int) (uint64, error) { + byt, err := br.r.Peek(int((n-br.bits)+7) / 8) + bits := br.bits + if err != nil { + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } + return 0, err + } + for i := 0; n > bits; i++ { + b := byt[i] + if err != nil { + return 0, err + } + br.n <<= 8 + br.n |= uint64(b) + bits += 8 + } + + r := (br.n >> uint(bits-n)) & ((1 << uint(n)) - 1) + return r, nil +} + +// ByteAligned returns true if the reader position is at the start of a byte, +// and false otherwise. +func (br *BitReader) ByteAligned() bool { + return br.bits == 0 +} + +// BytesRead returns the number of bytes that have been read by the BitReader. +func (br *BitReader) BytesRead() int { + return br.nRead +} diff --git a/codec/h264/decode/bits/bitreader_test.go b/codec/h264/decode/bits/bitreader_test.go new file mode 100644 index 00000000..79a83635 --- /dev/null +++ b/codec/h264/decode/bits/bitreader_test.go @@ -0,0 +1,311 @@ +/* +DESCRIPTION + bitreader_test.go provides testing for functionality defined in bitreader.go. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + +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 bits + +import ( + "bytes" + "fmt" + "io" + "reflect" + "testing" +) + +func TestReadBits(t *testing.T) { + tests := []struct { + in []byte // The bytes the source io.Reader will be initialised with. + n []int // The values of n for the reads we wish to do. + want []uint64 // The results we expect for each ReadBits call. + err []error // The error expected from each ReadBits call. + }{ + { + in: []byte{0xff}, + n: []int{8}, + want: []uint64{0xff}, + err: []error{nil}, + }, + { + in: []byte{0xff}, + n: []int{4, 4}, + want: []uint64{0x0f, 0x0f}, + err: []error{nil, nil}, + }, + { + in: []byte{0xff}, + n: []int{1, 7}, + want: []uint64{0x01, 0x7f}, + err: []error{nil, nil}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{8, 8}, + want: []uint64{0xff, 0xff}, + err: []error{nil, nil}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{8, 10}, + want: []uint64{0xff, 0}, + err: []error{nil, io.ErrUnexpectedEOF}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{4, 8, 4}, + want: []uint64{0x0f, 0xff, 0x0f}, + err: []error{nil, nil, nil}, + }, + { + in: []byte{0xff, 0xff}, + n: []int{16}, + want: []uint64{0xffff}, + err: []error{nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{4, 2, 4, 6}, + want: []uint64{0x8, 0x3, 0xf, 0x23}, + err: []error{nil, nil, nil, nil}, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + // For each value of n defined in test.reads, we call br.ReadBits, collect + // the result and check the error. + var got []uint64 + for j, n := range test.n { + bits, err := br.ReadBits(n) + if err != test.err[j] { + t.Fatalf("did not expect error: %v for read: %d test: %d", err, j, i) + } + got = append(got, bits) + } + + // Now we can check the read results. + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected results from ReadBits for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestPeekBits(t *testing.T) { + tests := []struct { + in []byte + n []int + want []uint64 + err []error + }{ + { + in: []byte{0xff}, + n: []int{8}, + want: []uint64{0xff}, + err: []error{nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{4, 8, 16}, + want: []uint64{0x8, 0x8f, 0x8fe3}, + err: []error{nil, nil, nil}, + }, + { + in: []byte{0x8f, 0xe3, 0x8f, 0xe3}, + n: []int{32}, + want: []uint64{0x8fe38fe3}, + err: []error{nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{3, 5, 10}, + want: []uint64{0x4, 0x11, 0x23f}, + err: []error{nil, nil, nil}, + }, + { + in: []byte{0x8f, 0xe3}, + n: []int{3, 20, 10}, + want: []uint64{0x4, 0, 0x23f}, + err: []error{nil, io.ErrUnexpectedEOF, nil}, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + // Call PeekBits for each value of n defined in test. + var got []uint64 + for j, n := range test.n { + bits, err := br.PeekBits(n) + if err != test.err[j] { + t.Fatalf("did not expect error: %v for peek: %d test: %d", err, j, i) + } + got = append(got, bits) + } + + // Now we can check the peek results. + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected results from PeekBits for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestReadOrPeek(t *testing.T) { + // The possible operations we might make. + const ( + read = iota + peek + ) + + tests := []struct { + in []byte // The bytes the source io.Reader will be initialised with. + op []int // The series of operations we want to perform (read or peek). + n []int // The values of n for the reads/peeks we wish to do. + want []uint64 // The results we expect for each ReadBits call. + }{ + { + in: []byte{0x8f, 0xe3, 0x8f, 0xe3}, + op: []int{read, peek, peek, read, peek}, + n: []int{13, 3, 3, 7, 12}, + want: []uint64{0x11fc, 0x3, 0x3, 0x38, 0xfe3}, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + var ( + bits uint64 + got []uint64 + err error + ) + + // Go through the operations we wish to perform for this test and collect + // results/errors. + for j, op := range test.op { + switch op { + case read: + bits, err = br.ReadBits(test.n[j]) + case peek: + bits, err = br.PeekBits(test.n[j]) + default: + panic(fmt.Sprintf("bad test: invalid operation: %d", op)) + } + got = append(got, bits) + if err != nil { + t.Fatalf("did not expect error: %v for operation: %d test: %d", err, j, i) + } + } + + // Now we can check the results from the reads/peeks. + if !reflect.DeepEqual(got, test.want) { + t.Errorf("did not get expected results for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +func TestByteAligned(t *testing.T) { + tests := []struct { + in []byte + n []int + want bool + }{ + { + in: []byte{0xff}, + n: []int{1}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{2}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{3}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{4}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{5}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{6}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{7}, + want: false, + }, + { + in: []byte{0xff}, + n: []int{8}, + want: true, + }, + { + in: []byte{0xff, 0xff}, + n: []int{9}, + want: false, + }, + { + in: []byte{0xff, 0xff}, + n: []int{16}, + want: true, + }, + { + in: []byte{0xff, 0xff}, + n: []int{5, 2}, + want: false, + }, + { + in: []byte{0xff, 0xff}, + n: []int{5, 3}, + want: true, + }, + } + + for i, test := range tests { + br := NewBitReader(bytes.NewReader(test.in)) + + // Call ReadBits for each value of n defined in test. + for j, n := range test.n { + _, err := br.ReadBits(n) + if err != nil { + t.Fatalf("did not expect error: %v for ReadBits: %d test: %d", err, j, i) + } + } + + // Now check br.ByteAligned. + got := br.ByteAligned() + if got != test.want { + t.Errorf("did not get expected results from ByteAligned for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} diff --git a/codec/h264/decode/cabac.go b/codec/h264/decode/cabac.go new file mode 100644 index 00000000..3697f630 --- /dev/null +++ b/codec/h264/decode/cabac.go @@ -0,0 +1,691 @@ +package h264 + +import ( + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +const ( + NaCtxId = 10000 + NA_SUFFIX = -1 + MbAddrNotAvailable = 10000 +) + +// G.7.4.3.4 via G.7.3.3.4 via 7.3.2.13 for NalUnitType 20 or 21 +// refLayerMbWidthC is equal to MbWidthC for the reference layer representation +func RefMbW(chromaFlag, refLayerMbWidthC int) int { + if chromaFlag == 0 { + return 16 + } + return refLayerMbWidthC +} + +// refLayerMbHeightC is equal to MbHeightC for the reference layer representation +func RefMbH(chromaFlag, refLayerMbHeightC int) int { + if chromaFlag == 0 { + return 16 + } + return refLayerMbHeightC +} +func XOffset(xRefMin16, refMbW int) int { + return (((xRefMin16 - 64) >> 8) << 4) - (refMbW >> 1) +} +func YOffset(yRefMin16, refMbH int) int { + return (((yRefMin16 - 64) >> 8) << 4) - (refMbH >> 1) +} +func MbWidthC(sps *SPS) int { + mbWidthC := 16 / SubWidthC(sps) + if sps.ChromaFormat == chromaMonochrome || sps.UseSeparateColorPlane { + mbWidthC = 0 + } + return mbWidthC +} +func MbHeightC(sps *SPS) int { + mbHeightC := 16 / SubHeightC(sps) + if sps.ChromaFormat == chromaMonochrome || sps.UseSeparateColorPlane { + mbHeightC = 0 + } + return mbHeightC +} + +// G.8.6.2.2.2 +func Xr(x, xOffset, refMbW int) int { + return (x + xOffset) % refMbW +} +func Yr(y, yOffset, refMbH int) int { + return (y + yOffset) % refMbH +} + +// G.8.6.2.2.2 +func Xd(xr, refMbW int) int { + if xr >= refMbW/2 { + return xr - refMbW + } + return xr + 1 +} +func Yd(yr, refMbH int) int { + if yr >= refMbH/2 { + return yr - refMbH + } + return yr + 1 +} +func Ya(yd, refMbH, signYd int) int { + return yd - (refMbH/2+1)*signYd +} + +// 6.4.11.1 +func MbAddr(xd, yd, predPartWidth int) { + // TODO: Unfinished + var n string + if xd == -1 && yd == 0 { + n = "A" + } + if xd == 0 && yd == -1 { + n = "B" + } + if xd == predPartWidth && yd == -1 { + n = "C" + } + if xd == -1 && yd == -1 { + n = "D" + } + _ = n +} + +func CondTermFlag(mbAddr, mbSkipFlag int) int { + if mbAddr == MbAddrNotAvailable || mbSkipFlag == 1 { + return 0 + } + return 1 +} + +// 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) + } +} + +// 9.3.3.1.1 : returns ctxIdxInc +func Decoder9_3_3_1_1_1(condTermFlagA, condTermFlagB int) int { + return condTermFlagA + condTermFlagB +} + +// 9-5 +// 7-30 p 112 +func SliceQPy(pps *PPS, header *SliceHeader) int { + return 26 + pps.PicInitQpMinus26 + header.SliceQpDelta +} + +// 9-5 +func PreCtxState(m, n, sliceQPy int) int { + // slicQPy-subY + return Clip3(1, 126, ((m*Clip3(0, 51, sliceQPy))>>4)+n) +} + +func Clip1y(x, bitDepthY int) int { + return Clip3(0, (1< y { + return y + } + return z +} + +type CABAC struct { + PStateIdx int + ValMPS int + Context *SliceContext +} + +// table 9-1 +func initCabac(binarization *Binarization, context *SliceContext) *CABAC { + var valMPS, pStateIdx int + // TODO: When to use prefix, when to use suffix? + ctxIdx := CtxIdx( + binarization.binIdx, + binarization.MaxBinIdxCtx.Prefix, + binarization.CtxIdxOffset.Prefix) + mn := MNVars[ctxIdx] + + preCtxState := PreCtxState(mn[0].M, mn[0].N, SliceQPy(context.PPS, context.Header)) + if preCtxState <= 63 { + pStateIdx = 63 - preCtxState + valMPS = 0 + } else { + pStateIdx = preCtxState - 64 + valMPS = 1 + } + _ = pStateIdx + _ = valMPS + // Initialization of context variables + // Initialization of decoding engine + return &CABAC{ + PStateIdx: pStateIdx, + ValMPS: valMPS, + Context: context, + } +} + +// Table 9-36, 9-37 +// func BinIdx(mbType int, sliceTypeName string) []int { +// Map of SliceTypeName[MbType][]int{binString} +// {"SliceTypeName": {MbTypeCode: []BinString}} +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 + } + + // 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 + } + + // Table 9-36, 9-37 + MbBinIdx = []int{1, 2, 3, 4, 5, 6} + + // Table 9-38 + SubMbBinIdx = []int{0, 1, 2, 3, 4, 5} +) + +// Table 9-34 +type MaxBinIdxCtx struct { + // When false, Prefix is the MaxBinIdxCtx + IsPrefixSuffix bool + Prefix, Suffix int +} +type CtxIdxOffset struct { + // When false, Prefix is the MaxBinIdxCtx + IsPrefixSuffix bool + Prefix, Suffix int +} + +// Table 9-34 +type Binarization struct { + SyntaxElement string + BinarizationType + MaxBinIdxCtx + CtxIdxOffset + UseDecodeBypass int + // TODO: Why are these private but others aren't? + binIdx int + binString []int +} +type BinarizationType struct { + PrefixSuffix bool + FixedLength bool + Unary bool + TruncatedUnary bool + CMax bool + // 9.3.2.3 + UEGk bool + CMaxValue int +} + +// 9.3.2.5 +func NewBinarization(syntaxElement string, data *SliceData) *Binarization { + sliceTypeName := data.SliceTypeName + logger.Printf("debug: binarization of %s in sliceType %s\n", syntaxElement, sliceTypeName) + binarization := &Binarization{SyntaxElement: syntaxElement} + switch syntaxElement { + case "CodedBlockPattern": + binarization.BinarizationType = BinarizationType{PrefixSuffix: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 3, Suffix: 1} + binarization.CtxIdxOffset = CtxIdxOffset{IsPrefixSuffix: true, Prefix: 73, Suffix: 77} + case "IntraChromaPredMode": + binarization.BinarizationType = BinarizationType{ + TruncatedUnary: true, CMax: true, CMaxValue: 3} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 1} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 64} + case "MbQpDelta": + binarization.BinarizationType = BinarizationType{} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 2} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 60} + case "MvdLnEnd0": + binarization.UseDecodeBypass = 1 + binarization.BinarizationType = BinarizationType{UEGk: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 4, Suffix: NA_SUFFIX} + binarization.CtxIdxOffset = CtxIdxOffset{ + IsPrefixSuffix: true, + Prefix: 40, + Suffix: NA_SUFFIX, + } + case "MvdLnEnd1": + binarization.UseDecodeBypass = 1 + binarization.BinarizationType = BinarizationType{UEGk: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{ + IsPrefixSuffix: true, + Prefix: 4, + Suffix: NA_SUFFIX, + } + binarization.CtxIdxOffset = CtxIdxOffset{ + IsPrefixSuffix: true, + Prefix: 47, + Suffix: NA_SUFFIX, + } + // 9.3.2.5 + case "MbType": + logger.Printf("debug: \tMbType is %s\n", data.MbTypeName) + switch sliceTypeName { + case "SI": + binarization.BinarizationType = BinarizationType{PrefixSuffix: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 0, Suffix: 6} + binarization.CtxIdxOffset = CtxIdxOffset{IsPrefixSuffix: true, Prefix: 0, Suffix: 3} + case "I": + binarization.BinarizationType = BinarizationType{} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 6} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 3} + case "SP": + fallthrough + case "P": + binarization.BinarizationType = BinarizationType{PrefixSuffix: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{IsPrefixSuffix: true, Prefix: 2, Suffix: 5} + binarization.CtxIdxOffset = CtxIdxOffset{IsPrefixSuffix: true, Prefix: 14, Suffix: 17} + } + case "MbFieldDecodingFlag": + binarization.BinarizationType = BinarizationType{ + FixedLength: true, CMax: true, CMaxValue: 1} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 70} + case "PrevIntra4x4PredModeFlag": + fallthrough + case "PrevIntra8x8PredModeFlag": + binarization.BinarizationType = BinarizationType{FixedLength: true, CMax: true, CMaxValue: 1} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 68} + case "RefIdxL0": + fallthrough + case "RefIdxL1": + binarization.BinarizationType = BinarizationType{Unary: true} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 2} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 54} + case "RemIntra4x4PredMode": + fallthrough + case "RemIntra8x8PredMode": + binarization.BinarizationType = BinarizationType{FixedLength: true, CMax: true, CMaxValue: 7} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 69} + case "TransformSize8x8Flag": + binarization.BinarizationType = BinarizationType{FixedLength: true, CMax: true, CMaxValue: 1} + binarization.MaxBinIdxCtx = MaxBinIdxCtx{Prefix: 0} + binarization.CtxIdxOffset = CtxIdxOffset{Prefix: 399} + } + return binarization +} +func (b *Binarization) IsBinStringMatch(bits []int) bool { + for i, _b := range bits { + if b.binString[i] != _b { + return false + } + } + return len(b.binString) == len(bits) +} + +// 9.3.1.2: output is codIRange and codIOffset +func initDecodingEngine(bitReader *bits.BitReader) (int, int, error) { + logger.Printf("debug: initializing arithmetic decoding engine\n") + codIRange := 510 + codIOffset, err := bitReader.ReadBits(9) + if err != nil { + return 0, 0, errors.Wrap(err, "could not read codIOffset") + } + logger.Printf("debug: codIRange: %d :: codIOffsset: %d\n", codIRange, codIOffset) + return codIRange, int(codIOffset), nil +} + +// 9.3.3.2: output is value of the bin +func NewArithmeticDecoding(context *SliceContext, binarization *Binarization, ctxIdx, codIRange, codIOffset int) (ArithmeticDecoding, error) { + a := ArithmeticDecoding{Context: context, Binarization: binarization} + logger.Printf("debug: decoding bypass %d, for ctx %d\n", binarization.UseDecodeBypass, ctxIdx) + // TODO: Implement + if binarization.UseDecodeBypass == 1 { + // TODO: 9.3.3.2.3 : DecodeBypass() + var err error + codIOffset, a.BinVal, err = a.DecodeBypass(context.Slice.Data, codIRange, codIOffset) + if err != nil { + return ArithmeticDecoding{}, errors.Wrap(err, "error from DecodeBypass getting codIOffset and BinVal") + } + + } else if binarization.UseDecodeBypass == 0 && ctxIdx == 276 { + // TODO: 9.3.3.2.4 : DecodeTerminate() + } else { + // TODO: 9.3.3.2.1 : DecodeDecision() + } + a.BinVal = -1 + return a, nil +} + +// 9.3.3.2.3 +// Invoked when bypassFlag is equal to 1 +func (a ArithmeticDecoding) DecodeBypass(sliceData *SliceData, codIRange, codIOffset int) (int, int, error) { + // Decoded value binVal + codIOffset = codIOffset << uint(1) + // TODO: Concurrency check + // TODO: Possibly should be codIOffset | ReadOneBit + shift, err := sliceData.BitReader.ReadBits(1) + if err != nil { + return 0, 0, errors.Wrap(err, "coult not read shift bit from sliceData.") + } + codIOffset = codIOffset << uint(shift) + if codIOffset >= codIRange { + a.BinVal = 1 + codIOffset -= codIRange + } else { + a.BinVal = 0 + } + return codIOffset, a.BinVal, nil +} + +// 9.3.3.2.4 +// Decodes endOfSliceFlag and I_PCM +// Returns codIRange, codIOffSet, decoded value of binVal +func (a ArithmeticDecoding) DecodeTerminate(sliceData *SliceData, codIRange, codIOffset int) (int, int, int, error) { + codIRange -= 2 + if codIOffset >= codIRange { + a.BinVal = 1 + // Terminate CABAC decoding, last bit inserted into codIOffset is = 1 + // this is now also the rbspStopOneBit + // TODO: How is this denoting termination? + return codIRange, codIOffset, a.BinVal, nil + } + a.BinVal = 0 + var err error + codIRange, codIOffset, err = a.RenormD(sliceData, codIRange, codIOffset) + if err != nil { + return 0, 0, 0, errors.Wrap(err, "error from RenormD") + } + return codIRange, codIOffset, a.BinVal, nil +} + +// 9.3.3.2.2 Renormalization process of ADecEngine +// Returns codIRange, codIOffset +func (a ArithmeticDecoding) RenormD(sliceData *SliceData, codIRange, codIOffset int) (int, int, error) { + if codIRange >= 256 { + return codIRange, codIOffset, nil + } + codIRange = codIRange << uint(1) + codIOffset = codIOffset << uint(1) + bit, err := sliceData.BitReader.ReadBits(1) + if err != nil { + return 0, 0, errors.Wrap(err, "could not read bit from sliceData") + } + codIOffset = codIOffset | int(bit) + return a.RenormD(sliceData, codIRange, codIOffset) +} + +type ArithmeticDecoding struct { + Context *SliceContext + Binarization *Binarization + BinVal int +} + +// 9.3.3.2.1 +// returns: binVal, updated codIRange, updated codIOffset +func (a ArithmeticDecoding) BinaryDecision(ctxIdx, codIRange, codIOffset int) (int, int, int, error) { + var binVal int + cabac := initCabac(a.Binarization, a.Context) + // Derivce codIRangeLPS + qCodIRangeIdx := (codIRange >> 6) & 3 + pStateIdx := cabac.PStateIdx + codIRangeLPS, err := retCodIRangeLPS(pStateIdx, qCodIRangeIdx) + if err != nil { + return 0, 0, 0, errors.Wrap(err, "could not get codIRangeLPS from retCodIRangeLPS") + } + + codIRange = codIRange - codIRangeLPS + if codIOffset >= codIRange { + binVal = 1 - cabac.ValMPS + codIOffset -= codIRange + codIRange = codIRangeLPS + } else { + binVal = cabac.ValMPS + } + + // TODO: Do StateTransition and then RenormD happen here? See: 9.3.3.2.1 + return binVal, codIRange, codIOffset, nil +} + +// 9.3.3.2.1.1 +// Returns: pStateIdx, valMPS +func (c *CABAC) StateTransitionProcess(binVal int) { + if binVal == c.ValMPS { + c.PStateIdx = stateTransxTab[c.PStateIdx].TransIdxMPS + } else { + if c.PStateIdx == 0 { + c.ValMPS = 1 - c.ValMPS + } + c.PStateIdx = stateTransxTab[c.PStateIdx].TransIdxLPS + } +} + +var ctxIdxLookup = map[int]map[int]int{ + 3: {0: NaCtxId, 1: 276, 2: 3, 3: 4, 4: NaCtxId, 5: NaCtxId}, + 14: {0: 0, 1: 1, 2: NaCtxId}, + 17: {0: 0, 1: 276, 2: 1, 3: 2, 4: NaCtxId}, + 27: {0: NaCtxId, 1: 3, 2: NaCtxId}, + 32: {0: 0, 1: 276, 2: 1, 3: 2, 4: NaCtxId}, + 36: {2: NaCtxId, 3: 3, 4: 3, 5: 3}, + 40: {0: NaCtxId}, + 47: {0: NaCtxId, 1: 3, 2: 4, 3: 5}, + 54: {0: NaCtxId, 1: 4}, + 64: {0: NaCtxId, 1: 3, 2: 3}, + 69: {0: 0, 1: 0, 2: 0}, + 77: {0: NaCtxId, 1: NaCtxId}, +} + +// 9.3.3.1 +// Returns ctxIdx +func CtxIdx(binIdx, maxBinIdxCtx, ctxIdxOffset int) int { + ctxIdx := NaCtxId + // table 9-39 + c, ok := ctxIdxLookup[ctxIdxOffset] + if ok { + v, ok := c[binIdx] + if ok { + return v + } + } + + switch ctxIdxOffset { + case 0: + if binIdx != 0 { + return NaCtxId + } + // 9.3.3.1.1.3 + case 3: + return 7 + case 11: + if binIdx != 0 { + return NaCtxId + } + + // 9.3.3.1.1.3 + case 14: + if binIdx > 2 { + return NaCtxId + } + case 17: + return 3 + case 21: + if binIdx < 3 { + ctxIdx = binIdx + } else { + return NaCtxId + } + case 24: + // 9.3.3.1.1.1 + case 27: + return 5 + case 32: + return 3 + case 36: + if binIdx == 0 || binIdx == 1 { + ctxIdx = binIdx + } + case 40: + fallthrough + case 47: + return 6 + case 54: + if binIdx > 1 { + ctxIdx = 5 + } + case 60: + if binIdx == 0 { + // 9.3.3.1.1.5 + } + if binIdx == 1 { + ctxIdx = 2 + } + if binIdx > 1 { + ctxIdx = 3 + } + case 64: + return NaCtxId + case 68: + if binIdx != 0 { + return NaCtxId + } + ctxIdx = 0 + case 69: + return NaCtxId + case 70: + if binIdx != 0 { + return NaCtxId + } + // 9.3.3.1.1.2 + case 77: + return NaCtxId + case 276: + if binIdx != 0 { + return NaCtxId + } + ctxIdx = 0 + case 399: + if binIdx != 0 { + return NaCtxId + } + // 9.3.3.1.1.10 + } + + return ctxIdx +} diff --git a/codec/h264/decode/cabac_test.go b/codec/h264/decode/cabac_test.go new file mode 100644 index 00000000..1c78b4a6 --- /dev/null +++ b/codec/h264/decode/cabac_test.go @@ -0,0 +1,121 @@ +package h264 + +import "testing" + +var ctxIdxTests = []struct { + binIdx int + maxBinIdxCtx int + ctxIdxOffset int + want int +}{ + {0, 0, 0, 10000}, + {999, 0, 0, 10000}, + + {0, 0, 3, 10000}, + {1, 0, 3, 276}, + {2, 0, 3, 3}, + {3, 0, 3, 4}, + {4, 0, 3, 10000}, + {5, 0, 3, 10000}, + {999, 0, 3, 7}, + + {0, 0, 11, 10000}, + {999, 0, 11, 10000}, + + {0, 0, 14, 0}, + {1, 0, 14, 1}, + {2, 0, 14, 10000}, + {999, 0, 14, 10000}, + + {0, 0, 17, 0}, + {1, 0, 17, 276}, + {2, 0, 17, 1}, + {3, 0, 17, 2}, + {4, 0, 17, 10000}, + {999, 0, 17, 3}, + + {0, 0, 21, 0}, + {1, 0, 21, 1}, + {2, 0, 21, 2}, + {999, 0, 21, 10000}, + + {0, 0, 24, 10000}, + {999, 0, 24, 10000}, + + {0, 0, 27, 10000}, + {1, 0, 27, 3}, + {2, 0, 27, 10000}, + {999, 0, 27, 5}, + + {0, 0, 32, 0}, + {1, 0, 32, 276}, + {2, 0, 32, 1}, + {3, 0, 32, 2}, + {4, 0, 32, 10000}, + {999, 0, 32, 3}, + + {0, 0, 36, 0}, + {1, 0, 36, 1}, + {2, 0, 36, 10000}, + {3, 0, 36, 3}, + {4, 0, 36, 3}, + {5, 0, 36, 3}, + + {0, 0, 40, 10000}, + + {0, 0, 47, 10000}, + {1, 0, 47, 3}, + {2, 0, 47, 4}, + {3, 0, 47, 5}, + {999, 0, 47, 6}, + + {0, 0, 54, 10000}, + {1, 0, 54, 4}, + {999, 0, 54, 5}, + + {0, 0, 60, 10000}, + {1, 0, 60, 2}, + {999, 0, 60, 3}, + + {0, 0, 64, 10000}, + {1, 0, 64, 3}, + {2, 0, 64, 3}, + {999, 0, 64, 10000}, + + {0, 0, 68, 0}, + {999, 0, 68, 10000}, + + {0, 0, 69, 0}, + {1, 0, 69, 0}, + {2, 0, 69, 0}, + + {0, 0, 70, 10000}, + {999, 0, 70, 10000}, + + {0, 0, 73, 10000}, + {1, 0, 73, 10000}, + {2, 0, 73, 10000}, + {3, 0, 73, 10000}, + {4, 0, 73, 10000}, + {999, 0, 73, 10000}, + + {0, 0, 77, 10000}, + {1, 0, 77, 10000}, + {999, 0, 77, 10000}, + + {0, 0, 276, 0}, + {999, 0, 276, 10000}, + + {0, 0, 399, 10000}, + {999, 0, 399, 10000}, +} + +// TestCtxIdx tests that the CtxIdx function returns the correct +// value given binIdx and ctxIdxOffset. +func TestCtxIdx(t *testing.T) { + for _, tt := range ctxIdxTests { + if got := CtxIdx(tt.binIdx, tt.maxBinIdxCtx, tt.ctxIdxOffset); got != tt.want { + t.Errorf("CtxIdx(%d, %d, %d) = %d, want %d", tt.binIdx, tt.maxBinIdxCtx, tt.ctxIdxOffset, got, tt.want) + } + } +} diff --git a/codec/h264/decode/frame.go b/codec/h264/decode/frame.go new file mode 100644 index 00000000..94143f53 --- /dev/null +++ b/codec/h264/decode/frame.go @@ -0,0 +1,89 @@ +package h264 + +// NALU types, as defined in table 7-1 in specifications. +const ( + naluTypeUnspecified = iota + naluTypeSliceNonIDRPicture + naluTypeSlicePartA + naluTypeSlicePartB + naluTypeSlicePartC + naluTypeSliceIDRPicture + naluTypeSEI + naluTypeSPS + naluTypePPS + naluTypeAccessUnitDelimiter + naluTypeEndOfSequence + naluTypeEndOfStream + naluTypeFillerData + naluTypeSPSExtension + naluTypePrefixNALU + naluTypeSubsetSPS + naluTypeDepthParamSet +) + +var ( + // Refer to ITU-T H.264 4/10/2017 + // Specifieds the RBSP structure in the NAL unit + NALUnitType = map[int]string{ + 0: "unspecified", + // slice_layer_without_partitioning_rbsp + 1: "coded slice of non-IDR picture", + // slice_data_partition_a_layer_rbsp + 2: "coded slice data partition a", + // slice_data_partition_b_layer_rbsp + 3: "coded slice data partition b", + // slice_data_partition_c_layer_rbsp + 4: "coded slice data partition c", + // slice_layer_without_partitioning_rbsp + 5: "coded IDR slice of picture", + // sei_rbsp + 6: "sei suppl. enhancem. info", + // seq_parameter_set_rbsp + 7: "sequence parameter set", + // pic_parameter_set_rbsp + 8: "picture parameter set", + // access_unit_delimiter_rbsp + 9: "access unit delimiter", + // end_of_seq_rbsp + 10: "end of sequence", + // end_of_stream_rbsp + 11: "end of stream", + // filler_data_rbsp + 12: "filler data", + // seq_parameter_set_extension_rbsp + 13: "sequence parameter set extensions", + // prefix_nal_unit_rbsp + 14: "prefix NAL unit", + // subset sequence parameter set + 15: "subset SPS", + // depth_parameter_set_rbsp + 16: "depth parameter set", + // 17, 18 are reserved + 17: "reserved", + 18: "reserved", + // slice_layer_without_partitioning_rbsp + 19: "coded slice of aux coded pic w/o partit.", + // slice_layer_extension_rbsp + 20: "coded slice extension", + // slice_layer_extension_rbsp + 21: "slice ext. for depth of view or 3Davc view comp.", + 22: "reserved", + 23: "reserved", + // 24 - 31 undefined + } + // ITU-T H.265 Section 7.4.1 nal_ref_idc + NALRefIDC = map[int]string{ + 0: "only nal_unit_type 6, 9, 10, 11, or 12", + 1: "anything", + 2: "anything", + 3: "anything", + 4: "anything", + } +) + +func rbspBytes(frame []byte) []byte { + if len(frame) > 8 { + return frame[8:] + } + return frame +} diff --git a/codec/h264/decode/go.mod b/codec/h264/decode/go.mod new file mode 100644 index 00000000..ffa61433 --- /dev/null +++ b/codec/h264/decode/go.mod @@ -0,0 +1,8 @@ +module github.com/ausocean/h264decode + +go 1.12 + +require ( + github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508 + github.com/pkg/errors v0.8.1 +) diff --git a/codec/h264/decode/go.sum b/codec/h264/decode/go.sum new file mode 100644 index 00000000..15c64706 --- /dev/null +++ b/codec/h264/decode/go.sum @@ -0,0 +1,4 @@ +github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508 h1:2LdkN1icT8cEFyB95fUbPE0TmQL9ZOjUv9MNJ1kg3XE= +github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508/go.mod h1:1+iKpsBoI5fsqBTrjxjM81vidVQcxXCmDrM9vc6EU2w= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/codec/h264/decode/mbType.go b/codec/h264/decode/mbType.go new file mode 100644 index 00000000..1e266446 --- /dev/null +++ b/codec/h264/decode/mbType.go @@ -0,0 +1,170 @@ +package h264 + +import ( + "errors" +) + +const MB_TYPE_INFERRED = 1000 + +var ( + ISliceMbType = map[int]string{ + 0: "I_NxN", + 1: "I_16x16_0_0_0", + 2: "I_16x16_1_0_0", + 3: "I_16x16_2_0_0", + 4: "I_16x16_3_0_0", + 5: "I_16x16_0_1_0", + 6: "I_16x16_1_1_0", + 7: "I_16x16_2_1_0", + 8: "I_16x16_3_1_0", + 9: "I_16x16_0_2_0", + 10: "I_16x16_1_2_0", + 11: "I_16x16_2_2_0", + 12: "I_16x16_3_2_0", + 13: "I_16x16_0_0_1", + 14: "I_16x16_1_0_1", + 15: "I_16x16_2_0_1", + 16: "I_16x16_3_0_1", + 17: "I_16x16_0_1_1", + 18: "I_16x16_1_1_1", + 19: "I_16x16_2_1_1", + 20: "I_16x16_3_1_1", + 21: "I_16x16_0_2_1", + 22: "I_16x16_1_2_1", + 23: "I_16x16_2_2_1", + 24: "I_16x16_3_2_1", + 25: "I_PCM", + } + SISliceMbType = map[int]string{ + 0: "SI", + } + PSliceMbType = map[int]string{ + 0: "P_L0_16x16", + 1: "P_L0_16x8", + 2: "P_L0_L0_8x16", + 3: "P_8x8", + 4: "P_8x8ref0", + MB_TYPE_INFERRED: "P_Skip", + } + BSliceMbType = map[int]string{ + 0: "B_Direct_16x16", + 1: "B_L0_16x16", + 2: "B_L1_16x16", + 3: "B_Bi_16x16", + 4: "B_L0_L0_16x8", + 5: "B_L0_L0_8x16", + 6: "B_L1_L1_16x8", + 7: "B_L1_L1_8x16", + 8: "B_L0_L1_16x8", + 9: "B_L0_L1_8x16", + 10: "B_L1_L0_16x8", + 11: "B_L1_L0_8x16", + 12: "B_L0_Bi_16x8", + 13: "B_L0_Bi_8x16", + 14: "B_L1_Bi_16x8", + 15: "B_L1_Bi_8x16", + 16: "B_Bi_L0_16x8", + 17: "B_Bi_L0_8x16", + 18: "B_Bi_l1_16x8", + 19: "B_Bi_L1_8x16", + 20: "B_Bi_Bi_16x8", + 21: "B_Bi_Bi_8x16", + 22: "B_8x8", + MB_TYPE_INFERRED: "B_Skip", + } +) + +func MbTypeName(sliceType string, mbType int) string { + sliceTypeName := "NaSliceType" + switch sliceType { + case "I": + sliceTypeName = ISliceMbType[mbType] + case "SI": + sliceTypeName = SISliceMbType[mbType] + case "P": + sliceTypeName = PSliceMbType[mbType] + case "B": + sliceTypeName = BSliceMbType[mbType] + } + return sliceTypeName +} + +// Errors used by MbPartPredMode. +var ( + errNaMode = errors.New("no mode for given slice and mb type") + errPartition = errors.New("partition must be 0") +) + +// MbPartPredMode returns a macroblock partition prediction mode for the given +// slice data, slice type, macroblock type and partition, consistent with tables +// 7-11, 7-12, 7-13 and 7-14 from the specifications. +func MbPartPredMode(data *SliceData, sliceType string, mbType, partition int) (mbPartPredMode, error) { + if partition == 0 { + switch sliceType { + case "I": + if mbType == 0 { + if data.TransformSize8x8Flag { + return intra8x8, nil + } + return intra4x4, nil + } + if mbType > 0 && mbType < 25 { + return intra16x16, nil + } + return -1, errNaMode + case "SI": + return intra4x4, nil + case "P": + fallthrough + case "SP": + if mbType >= 0 && mbType < 3 { + return predL0, nil + } else if mbType == 3 || mbType == 4 { + return -1, errNaMode + } else { + return predL0, nil + } + case "B": + switch mbType { + case 0: + return direct, nil + case 1: + fallthrough + case 4: + fallthrough + case 5: + fallthrough + case 8: + fallthrough + case 9: + fallthrough + case 12: + fallthrough + case 13: + return predL0, nil + case 2: + fallthrough + case 6: + fallthrough + case 7: + fallthrough + case 10: + fallthrough + case 11: + fallthrough + case 14: + fallthrough + case 15: + return predL1, nil + case 22: + return -1, errNaMode + default: + if mbType > 15 && mbType < 22 { + return biPred, nil + } + return direct, nil + } + } + } + return -1, errPartition +} diff --git a/codec/h264/decode/mn_vars.go b/codec/h264/decode/mn_vars.go new file mode 100644 index 00000000..3aba8d5e --- /dev/null +++ b/codec/h264/decode/mn_vars.go @@ -0,0 +1,440 @@ +package h264 + +type MN struct { + M, N int +} + +const NoCabacInitIdc = -1 + +// tables 9-12 to 9-13 +var ( + // 0-39 : MB_Type + // Maybe mapping all values in the range -128 to 128 to + // a list of tuples for input vars would be less verbose + // map[ctxIdx]MN + MNVars = map[int]map[int]MN{ + 0: {NoCabacInitIdc: {20, -15}}, + 1: {NoCabacInitIdc: {2, 54}}, + 2: {NoCabacInitIdc: {3, 74}}, + 3: {NoCabacInitIdc: {20, -15}}, + 4: {NoCabacInitIdc: {2, 54}}, + 5: {NoCabacInitIdc: {3, 74}}, + 6: {NoCabacInitIdc: {-28, 127}}, + 7: {NoCabacInitIdc: {-23, 104}}, + 8: {NoCabacInitIdc: {-6, 53}}, + 9: {NoCabacInitIdc: {-1, 54}}, + 10: {NoCabacInitIdc: {7, 51}}, + 11: { + 0: {23, 33}, + 1: {22, 25}, + 2: {29, 16}, + }, + 12: { + 0: {23, 2}, + 1: {34, 0}, + 2: {25, 0}, + }, + 13: { + 0: {21, 0}, + 1: {16, 0}, + 2: {14, 0}, + }, + 14: { + 0: {1, 9}, + 1: {-2, 9}, + 2: {-10, 51}, + }, + 15: { + 0: {0, 49}, + 1: {4, 41}, + 2: {-3, 62}, + }, + 16: { + 0: {-37, 118}, + 1: {-29, 118}, + 2: {-27, 99}, + }, + 17: { + 0: {5, 57}, + 1: {2, 65}, + 2: {26, 16}, + }, + 18: { + 0: {-13, 78}, + 1: {-6, 71}, + 2: {-4, 85}, + }, + 19: { + 0: {-11, 65}, + 1: {-13, 79}, + 2: {-24, 102}, + }, + 20: { + 0: {1, 62}, + 1: {5, 52}, + 2: {5, 57}, + }, + 21: { + 0: {12, 49}, + 1: {9, 50}, + 2: {6, 57}, + }, + 22: { + 0: {-4, 73}, + 1: {-3, 70}, + 2: {-17, 73}, + }, + 23: { + 0: {17, 50}, + 1: {10, 54}, + 2: {14, 57}, + }, + // Table 9-14 + // Should use MNSecond to get the second M value if it exists + // TODO: MNSecond determine when to provide second + 24: { + 0: {18, 64}, + 1: {26, 34}, + 2: {20, 40}, + }, + 25: { + 0: {9, 43}, + 1: {19, 22}, + 2: {20, 10}, + }, + 26: { + 0: {29, 0}, + 1: {40, 0}, + 2: {29, 0}, + }, + 27: { + 0: {26, 67}, + 1: {57, 2}, + 2: {54, 0}, + }, + 28: { + 0: {16, 90}, + 1: {41, 36}, + 2: {37, 42}, + }, + 29: { + 0: {9, 104}, + 1: {26, 59}, + 2: {12, 97}, + }, + 30: { + 0: {-4, 127}, // Second M: 6 + 1: {-4, 127}, // Second M: 5 + 2: {-3, 127}, // Second M: 2 + }, + 31: { + 0: {-2, 104}, // Second M: 0 + 1: {-1, 101}, // Second M: 5 + 2: {-2, 117}, // Second M: 2 + }, + 32: { + 0: {1, 67}, + 1: {-4, 76}, + 2: {-2, 74}, + }, + 33: { + 0: {-1, 78}, // Second M: 3 + 1: {-6, 71}, + 2: {-4, 85}, + }, + 34: { + 0: {-1, 65}, // Second M: 1 + 1: {-1, 79}, // Second M: 3 + 2: {-2, 102}, // Second M: 4 + }, + 35: { + 0: {1, 62}, + 1: {5, 52}, + 2: {5, 57}, + }, + 36: { + 0: {-6, 86}, + 1: {6, 69}, + 2: {-6, 93}, + }, + 37: { + 0: {-1, 95}, // Second M: 7 + 1: {-1, 90}, // Second M: 3 + 2: {-1, 88}, // Second M: 4 + }, + 38: { + 0: {-6, 61}, + 1: {0, 52}, + 2: {-6, 44}, + }, + 39: { + 0: {9, 45}, + 1: {8, 43}, + 2: {4, 55}, + }, + } +) + +// TODO: MNSecond determine when to provide second +func MNSecond(ctxIdx, cabacInitIdc int) {} + +// Table 9-18 +// Coded block pattern (luma y chroma) +// map[ctxIdx][cabacInitIdc]MN +func CodedblockPatternMN(ctxIdx, cabacInitIdc int, sliceType string) MN { + var mn MN + if sliceType != "I" && sliceType != "SI" { + logger.Printf("warning: trying to initialize %s slice type\n", sliceType) + } + switch ctxIdx { + case 70: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {0, 45}, {13, 15}, {7, 34}, + }[cabacInitIdc] + } + return MN{0, 11} + case 71: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-4, 78}, {7, 51}, {-9, 88}, + }[cabacInitIdc] + } + return MN{1, 55} + case 72: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-3, 96}, {2, 80}, {-20, 127}, + }[cabacInitIdc] + } + return MN{0, 69} + case 73: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-27, 126}, {-39, 127}, {-36, 127}, + }[cabacInitIdc] + } + return MN{-17, 127} + case 74: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-28, 98}, {-18, 91}, {-17, 91}, + }[cabacInitIdc] + } + return MN{-13, 102} + case 75: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-25, 101}, {-17, 96}, {-14, 95}, + }[cabacInitIdc] + } + return MN{0, 82} + case 76: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-23, 67}, {-26, 81}, {-25, 84}, + }[cabacInitIdc] + } + return MN{-7, 24} + case 77: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-28, 82}, {-35, 98}, {-25, 86}, + }[cabacInitIdc] + } + return MN{-21, 107} + case 78: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-20, 94}, {-24, 102}, {-12, 89}, + }[cabacInitIdc] + } + return MN{-27, 127} + case 79: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-16, 83}, {-23, 97}, {-17, 91}, + }[cabacInitIdc] + } + return MN{-31, 127} + case 80: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-22, 110}, {-27, 119}, {-31, 127}, + }[cabacInitIdc] + } + return MN{-24, 127} + case 81: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-21, 91}, {-24, 99}, {-14, 76}, + }[cabacInitIdc] + } + return MN{-18, 95} + case 82: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-18, 102}, {-21, 110}, {-18, 103}, + }[cabacInitIdc] + } + return MN{-27, 127} + case 83: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-13, 93}, {-18, 102}, {-13, 90}, + }[cabacInitIdc] + } + return MN{-21, 114} + case 84: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-29, 127}, {-36, 127}, {-37, 127}, + }[cabacInitIdc] + } + return MN{-30, 127} + case 85: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-7, 92}, {0, 80}, {11, 80}, + }[cabacInitIdc] + } + return MN{-17, 123} + case 86: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-5, 89}, {-5, 89}, {5, 76}, + }[cabacInitIdc] + } + return MN{-12, 115} + case 87: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-7, 96}, {-7, 94}, {2, 84}, + }[cabacInitIdc] + } + return MN{-16, 122} + // TODO: 88 to 104 + case 88: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-13, 108}, {-4, 92}, {5, 78}, + }[cabacInitIdc] + } + return MN{-11, 115} + case 89: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-3, 46}, {0, 39}, {-6, 55}, + }[cabacInitIdc] + } + return MN{-12, 63} + case 90: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-1, 65}, {0, 65}, {4, 61}, + }[cabacInitIdc] + } + return MN{-2, 68} + case 91: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-1, 57}, {-15, 84}, {-14, 83}, + }[cabacInitIdc] + } + return MN{-15, 85} + case 92: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-9, 93}, {-36, 127}, {-37, 127}, + }[cabacInitIdc] + } + return MN{-13, 104} + case 93: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-3, 74}, {-2, 73}, {-5, 79}, + }[cabacInitIdc] + } + return MN{-3, 70} + case 94: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-9, 92}, {-12, 104}, {-11, 104}, + }[cabacInitIdc] + } + return MN{-8, 93} + case 95: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-8, 87}, {-9, 91}, {-11, 91}, + }[cabacInitIdc] + } + return MN{-10, 90} + case 96: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-23, 126}, {-31, 127}, {-30, 127}, + }[cabacInitIdc] + } + return MN{-30, 127} + case 97: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {5, 54}, {3, 55}, {0, 65}, + }[cabacInitIdc] + } + return MN{-1, 74} + case 98: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {6, 60}, {7, 56}, {-2, 79}, + }[cabacInitIdc] + } + return MN{-6, 97} + case 99: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {6, 59}, {7, 55}, {0, 72}, + }[cabacInitIdc] + } + return MN{-7, 91} + case 100: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {6, 69}, {8, 61}, {-4, 92}, + }[cabacInitIdc] + } + return MN{-20, 127} + case 101: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-1, 48}, {-3, 53}, {-6, 56}, + }[cabacInitIdc] + } + return MN{-4, 56} + case 102: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {0, 68}, {0, 68}, {3, 68}, + }[cabacInitIdc] + } + return MN{-5, 82} + case 103: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-4, 69}, {-7, 74}, {-8, 71}, + }[cabacInitIdc] + } + return MN{-7, 76} + case 104: + if cabacInitIdc >= 0 && cabacInitIdc <= 2 { + return []MN{ + {-8, 88}, {-9, 88}, {-13, 98}, + }[cabacInitIdc] + } + return MN{-22, 125} + + } + + return mn +} diff --git a/codec/h264/decode/nalUnit.go b/codec/h264/decode/nalUnit.go new file mode 100644 index 00000000..35eeab76 --- /dev/null +++ b/codec/h264/decode/nalUnit.go @@ -0,0 +1,161 @@ +package h264 + +import ( + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +type NalUnit struct { + NumBytes int + ForbiddenZeroBit int + RefIdc int + Type int + SvcExtensionFlag int + Avc3dExtensionFlag int + IdrFlag int + PriorityId int + NoInterLayerPredFlag int + DependencyId int + QualityId int + TemporalId int + UseRefBasePicFlag int + DiscardableFlag int + OutputFlag int + ReservedThree2Bits int + HeaderBytes int + NonIdrFlag int + ViewId int + AnchorPicFlag int + InterViewFlag int + ReservedOneBit int + ViewIdx int + DepthFlag int + EmulationPreventionThreeByte byte + rbsp []byte +} + +func NalUnitHeaderSvcExtension(nalUnit *NalUnit, br *bits.BitReader) error { + return readFields(br, []field{ + {&nalUnit.IdrFlag, "IdrFlag", 1}, + {&nalUnit.PriorityId, "PriorityId", 6}, + {&nalUnit.NoInterLayerPredFlag, "NoInterLayerPredFlag", 1}, + {&nalUnit.DependencyId, "DependencyId", 3}, + {&nalUnit.QualityId, "QualityId", 4}, + {&nalUnit.TemporalId, "TemporalId", 3}, + {&nalUnit.UseRefBasePicFlag, "UseRefBasePicFlag", 1}, + {&nalUnit.DiscardableFlag, "DiscardableFlag", 1}, + {&nalUnit.OutputFlag, "OutputFlag", 1}, + {&nalUnit.ReservedThree2Bits, "ReservedThree2Bits", 2}, + }) +} + +func NalUnitHeader3davcExtension(nalUnit *NalUnit, br *bits.BitReader) error { + return readFields(br, []field{ + {&nalUnit.ViewIdx, "ViewIdx", 8}, + {&nalUnit.DepthFlag, "DepthFlag", 1}, + {&nalUnit.NonIdrFlag, "NonIdrFlag", 1}, + {&nalUnit.TemporalId, "TemporalId", 3}, + {&nalUnit.AnchorPicFlag, "AnchorPicFlag", 1}, + {&nalUnit.InterViewFlag, "InterViewFlag", 1}, + }) +} + +func NalUnitHeaderMvcExtension(nalUnit *NalUnit, br *bits.BitReader) error { + return readFields(br, []field{ + {&nalUnit.NonIdrFlag, "NonIdrFlag", 1}, + {&nalUnit.PriorityId, "PriorityId", 6}, + {&nalUnit.ViewId, "ViewId", 10}, + {&nalUnit.TemporalId, "TemporalId", 3}, + {&nalUnit.AnchorPicFlag, "AnchorPicFlag", 1}, + {&nalUnit.InterViewFlag, "InterViewFlag", 1}, + {&nalUnit.ReservedOneBit, "ReservedOneBit", 1}, + }) +} + +func (n *NalUnit) RBSP() []byte { + return n.rbsp +} + +func NewNalUnit(frame []byte, numBytesInNal int) (*NalUnit, error) { + logger.Printf("debug: reading %d byte NAL\n", numBytesInNal) + nalUnit := NalUnit{ + NumBytes: numBytesInNal, + HeaderBytes: 1, + } + // TODO: pass in actual io.Reader to NewBitReader + br := bits.NewBitReader(nil) + + err := readFields(br, []field{ + {&nalUnit.ForbiddenZeroBit, "ForbiddenZeroBit", 1}, + {&nalUnit.RefIdc, "NalRefIdc", 2}, + {&nalUnit.Type, "NalUnitType", 5}, + }) + if err != nil { + return nil, err + } + + if nalUnit.Type == 14 || nalUnit.Type == 20 || nalUnit.Type == 21 { + if nalUnit.Type != 21 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SvcExtensionFlag") + } + nalUnit.SvcExtensionFlag = int(b) + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read Avc3dExtensionFlag") + } + nalUnit.Avc3dExtensionFlag = int(b) + } + if nalUnit.SvcExtensionFlag == 1 { + NalUnitHeaderSvcExtension(&nalUnit, br) + nalUnit.HeaderBytes += 3 + } else if nalUnit.Avc3dExtensionFlag == 1 { + NalUnitHeader3davcExtension(&nalUnit, br) + nalUnit.HeaderBytes += 2 + } else { + NalUnitHeaderMvcExtension(&nalUnit, br) + nalUnit.HeaderBytes += 3 + + } + } + + logger.Printf("debug: found %d byte header. Reading body\n", nalUnit.HeaderBytes) + for i := nalUnit.HeaderBytes; i < nalUnit.NumBytes; i++ { + next3Bytes, err := br.PeekBits(24) + if err != nil { + logger.Printf("error: while reading next 3 NAL bytes: %v\n", err) + break + } + // Little odd, the err above and the i+2 check might be synonyms + if i+2 < nalUnit.NumBytes && next3Bytes == 0x000003 { + for j := 0; j < 2; j++ { + rbspByte, err := br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read rbspByte") + } + nalUnit.rbsp = append(nalUnit.rbsp, byte(rbspByte)) + } + i += 2 + + // Read Emulation prevention three byte. + eptByte, err := br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read eptByte") + } + nalUnit.EmulationPreventionThreeByte = byte(eptByte) + } else { + if b, err := br.ReadBits(8); err == nil { + nalUnit.rbsp = append(nalUnit.rbsp, byte(b)) + } else { + logger.Printf("error: while reading byte %d of %d nal bytes: %v\n", i, nalUnit.NumBytes, err) + break + } + } + } + + // nalUnit.rbsp = frame[nalUnit.HeaderBytes:] + logger.Printf("info: decoded %s NAL with %d RBSP bytes\n", NALUnitType[nalUnit.Type], len(nalUnit.rbsp)) + return &nalUnit, nil +} diff --git a/codec/h264/decode/parse.go b/codec/h264/decode/parse.go new file mode 100644 index 00000000..18245f3e --- /dev/null +++ b/codec/h264/decode/parse.go @@ -0,0 +1,161 @@ +/* +NAME + parse.go + +DESCRIPTION + parse.go provides parsing processes for syntax elements of different + descriptors specified in 7.2 of ITU-T H.264. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + mrmod +*/ + +package h264 + +import ( + "math" + + "github.com/icza/bitio" + "github.com/pkg/errors" +) + +type mbPartPredMode int8 + +const ( + intra4x4 mbPartPredMode = iota + intra8x8 + intra16x16 + predL0 + predL1 + direct + biPred + inter +) + +// readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer +// Exp-Golomb-coded element using method as specified in section 9.1 of ITU-T H.264. +// +// TODO: this should return uint, but rest of code needs to be changed for this +// to happen. +func readUe(r bitio.Reader) (int, error) { + nZeros := -1 + var err error + for b := uint64(0); b == 0; nZeros++ { + b, err = r.ReadBits(1) + if err != nil { + return 0, err + } + } + rem, err := r.ReadBits(byte(nZeros)) + if err != nil { + return 0, err + } + return int(math.Pow(float64(2), float64(nZeros)) - 1 + float64(rem)), nil +} + +// readTe parses a syntax element of te(v) descriptor i.e, truncated +// Exp-Golomb-coded syntax element using method as specified in section 9.1 +// Rec. ITU-T H.264 (04/2017). +// +// TODO: this should also return uint. +func readTe(r bitio.Reader, x uint) (int, error) { + if x > 1 { + return readUe(r) + } + + if x == 1 { + b, err := r.ReadBits(1) + if err != nil { + return 0, errors.Wrap(err, "could not read bit") + } + if b == 0 { + return 1, nil + } + return 0, nil + } + + return 0, errReadTeBadX +} + +var errReadTeBadX = errors.New("x must be more than or equal to 1") + +// readSe parses a syntax element with descriptor se(v), i.e. a signed integer +// Exp-Golomb-coded syntax element, using the method described in sections +// 9.1 and 9.1.1 in Rec. ITU-T H.264 (04/2017). +func readSe(r bitio.Reader) (int, error) { + codeNum, err := readUe(r) + if err != nil { + return 0, errors.Wrap(err, "error reading ue(v)") + } + + return int(math.Pow(-1, float64(codeNum+1)) * math.Ceil(float64(codeNum)/2.0)), nil +} + +// readMe parses a syntax element of me(v) descriptor, i.e. mapped +// Exp-Golomb-coded element, using methods described in sections 9.1 and 9.1.2 +// in Rec. ITU-T H.264 (04/2017). +func readMe(r bitio.Reader, chromaArrayType uint, mpm mbPartPredMode) (uint, error) { + // Indexes to codedBlockPattern map. + var i1, i2, i3 int + + // ChromaArrayType selects first index. + switch chromaArrayType { + case 1, 2: + i1 = 0 + case 0, 3: + i1 = 1 + default: + return 0, errInvalidCAT + } + + // CodeNum from readUe selects second index. + i2, err := readUe(r) + if err != nil { + return 0, errors.Wrap(err, "error from readUe") + } + + // Need to check that we won't go out of bounds with this index. + if i2 >= len(codedBlockPattern[i1]) { + return 0, errInvalidCodeNum + } + + // Macroblock prediction mode selects third index. + switch mpm { + case intra4x4, intra8x8: + i3 = 0 + case inter: + i3 = 1 + default: + return 0, errInvalidMPM + } + + return codedBlockPattern[i1][i2][i3], nil +} + +// Errors used by readMe. +var ( + errInvalidCodeNum = errors.New("invalid codeNum") + errInvalidMPM = errors.New("invalid macroblock prediction mode") + errInvalidCAT = errors.New("invalid chroma array type") +) + +// codedBlockPattern contains data from table 9-4 in ITU-T H.264 (04/2017) +// for mapping a chromaArrayType, codeNum and macroblock prediction mode to a +// coded block pattern. +var codedBlockPattern = [][][2]uint{ + // Table 9-4 (a) for ChromaArrayType = 1 or 2 + { + {47, 0}, {31, 16}, {15, 1}, {0, 2}, {23, 4}, {27, 8}, {29, 32}, {30, 3}, + {7, 5}, {11, 10}, {13, 12}, {14, 15}, {39, 47}, {43, 7}, {45, 11}, {46, 13}, + {16, 14}, {3, 6}, {31, 9}, {10, 31}, {12, 35}, {19, 37}, {21, 42}, {26, 44}, + {28, 33}, {35, 34}, {37, 36}, {42, 40}, {44, 39}, {1, 43}, {2, 45}, {4, 46}, + {8, 17}, {17, 18}, {18, 20}, {20, 24}, {24, 19}, {6, 21}, {9, 26}, {22, 28}, + {25, 23}, {32, 27}, {33, 29}, {34, 30}, {36, 22}, {40, 25}, {38, 38}, {41, 41}, + }, + // Table 9-4 (b) for ChromaArrayType = 0 or 3 + { + {15, 0}, {0, 1}, {7, 2}, {11, 4}, {13, 8}, {14, 3}, {3, 5}, {5, 10}, {10, 12}, + {12, 15}, {1, 7}, {2, 11}, {4, 13}, {8, 14}, {6, 6}, {9, 9}, + }, +} diff --git a/codec/h264/decode/parse_test.go b/codec/h264/decode/parse_test.go new file mode 100644 index 00000000..4356fa50 --- /dev/null +++ b/codec/h264/decode/parse_test.go @@ -0,0 +1,154 @@ +/* +NAME + parse_test.go + +DESCRIPTION + parse_test.go provides testing for parsing utilities provided in parse.go + +AUTHOR + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) +*/ + +package h264 + +import ( + "bytes" + "testing" + + "github.com/icza/bitio" +) + +// TestReadUe checks that readUe correctly parses an Exp-Golomb-coded element +// to a code number. +func TestReadUe(t *testing.T) { + // tests has been derived from Table 9-2 in ITU-T H.H264, showing bit strings + // and corresponding codeNums. + tests := []struct { + in []byte // The bitstring we wish to read. + want uint // The expected codeNum. + }{ + {[]byte{0x80}, 0}, // Bit string: 1, codeNum: 0 + {[]byte{0x40}, 1}, // Bit string: 010, codeNum: 1 + {[]byte{0x60}, 2}, // Bit string: 011, codeNum: 2 + {[]byte{0x20}, 3}, // Bit string: 00100, codeNum: 3 + {[]byte{0x28}, 4}, // Bit string: 00101, codeNum: 4 + {[]byte{0x30}, 5}, // Bit string: 00110, codeNum: 5 + {[]byte{0x38}, 6}, // Bit string: 00111, codeNum: 6 + {[]byte{0x10}, 7}, // Bit string: 0001000, codeNum: 7 + {[]byte{0x12}, 8}, // Bit string: 0001001, codeNum: 8 + {[]byte{0x14}, 9}, // Bit string: 0001010, codeNum: 9 + {[]byte{0x16}, 10}, // Bit string: 0001011, codeNum: 10 + } + + for i, test := range tests { + got, err := readUe(bitio.NewReader(bytes.NewReader(test.in))) + if err != nil { + t.Fatalf("did not expect error: %v from readUe", err) + } + + if test.want != uint(got) { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +// TestReadTe checks that readTe correctly parses a truncated Exp-Golomb-coded +// syntax element. Expected results are outlined in section 9.1 pg209 Rec ITU-T +// H.264 (04/2017) +func TestReadTe(t *testing.T) { + tests := []struct { + in []byte // The bitstring we will read. + x uint // The upper bound of the range. + want uint // Expected result from readTe. + err error // Expected error from readTe. + }{ + {[]byte{0x30}, 1, 1, nil}, + {[]byte{0x80}, 1, 0, nil}, + {[]byte{0x30}, 5, 5, nil}, + {[]byte{0x30}, 0, 0, errReadTeBadX}, + } + + for i, test := range tests { + got, err := readTe(bitio.NewReader(bytes.NewReader(test.in)), test.x) + if err != test.err { + t.Fatalf("did not get expected error for test: %v\nGot: %v\nWant: %v\n", i, err, test.err) + } + + if test.want != uint(got) { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +// TestReadSe checks that readSe correctly parses an se(v) signed integer +// Exp-Golomb-coded syntax element. Expected behaviour is found in section 9.1 +// and 9.1.1 of the Rec. ITU-T H.264(04/2017). +func TestReadSe(t *testing.T) { + // tests has been derived from table 9-3 of the specifications. + tests := []struct { + in []byte // Bitstring to read. + want int // Expected value from se(v) parsing process. + }{ + {[]byte{0x80}, 0}, + {[]byte{0x40}, 1}, + {[]byte{0x60}, -1}, + {[]byte{0x20}, 2}, + {[]byte{0x28}, -2}, + {[]byte{0x30}, 3}, + {[]byte{0x38}, -3}, + } + + for i, test := range tests { + got, err := readSe(bitio.NewReader(bytes.NewReader(test.in))) + if err != nil { + t.Fatalf("did not expect error: %v from readSe", err) + } + + if test.want != got { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} + +// TestReadMe checks that readMe correctly parses a me(v) mapped +// Exp-Golomb-coded element. Expected behaviour is described in in sections 9.1 +// and 9.1.2 in Rec. ITU-T H.264 (04/2017). +func TestReadMe(t *testing.T) { + in := []byte{0x38} // Bit string: 00111, codeNum: 6. + inErr := []byte{0x07, 0xe0} // Bit string: 0000 0111 111, codeNum: 62 (will give invalid codeNum err) + + tests := []struct { + in []byte // Input data. + cat uint // Chroma array.. + mpm mbPartPredMode + want uint // Expected result from readMe. + err error // Expected value of err from readMe. + }{ + {in, 1, intra4x4, 29, nil}, + {in, 1, intra8x8, 29, nil}, + {in, 1, inter, 32, nil}, + {in, 2, intra4x4, 29, nil}, + {in, 2, intra8x8, 29, nil}, + {in, 2, inter, 32, nil}, + {in, 0, intra4x4, 3, nil}, + {in, 0, intra8x8, 3, nil}, + {in, 0, inter, 5, nil}, + {in, 3, intra4x4, 3, nil}, + {in, 3, intra8x8, 3, nil}, + {in, 3, inter, 5, nil}, + {inErr, 1, intra4x4, 0, errInvalidCodeNum}, + {in, 4, intra4x4, 0, errInvalidCAT}, + {in, 0, 4, 0, errInvalidMPM}, + } + + for i, test := range tests { + got, err := readMe(bitio.NewReader(bytes.NewReader(test.in)), test.cat, test.mpm) + if err != test.err { + t.Fatalf("did not expect to get error: %v for test: %v", err, i) + } + + if test.want != got { + t.Errorf("did not get expected result for test: %v\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} diff --git a/codec/h264/decode/pps.go b/codec/h264/decode/pps.go new file mode 100644 index 00000000..ebc3d45f --- /dev/null +++ b/codec/h264/decode/pps.go @@ -0,0 +1,239 @@ +package h264 + +import ( + "math" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +// import "strings" + +// Specification Page 46 7.3.2.2 + +type PPS struct { + ID, SPSID int + EntropyCodingMode int + NumSliceGroupsMinus1 int + BottomFieldPicOrderInFramePresent bool + NumSlicGroupsMinus1 int + SliceGroupMapType int + RunLengthMinus1 []int + TopLeft []int + BottomRight []int + SliceGroupChangeDirection bool + SliceGroupChangeRateMinus1 int + PicSizeInMapUnitsMinus1 int + SliceGroupId []int + NumRefIdxL0DefaultActiveMinus1 int + NumRefIdxL1DefaultActiveMinus1 int + WeightedPred bool + WeightedBipred int + PicInitQpMinus26 int + PicInitQsMinus26 int + ChromaQpIndexOffset int + DeblockingFilterControlPresent bool + ConstrainedIntraPred bool + RedundantPicCntPresent bool + Transform8x8Mode int + PicScalingMatrixPresent bool + PicScalingListPresent []bool + SecondChromaQpIndexOffset int +} + +func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { + logger.Printf("debug: PPS RBSP %d bytes %d bits == \n", len(rbsp), len(rbsp)*8) + logger.Printf("debug: \t%#v\n", rbsp[0:8]) + pps := PPS{} + // TODO: give this io.Reader + br := bits.NewBitReader(nil) + + var err error + pps.ID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ID") + } + + pps.SPSID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SPS ID") + } + + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read EntropyCodingMode") + } + pps.EntropyCodingMode = int(b) + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read BottomFieldPicOrderInFramePresent") + } + pps.BottomFieldPicOrderInFramePresent = b == 1 + + pps.NumSliceGroupsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumSliceGroupsMinus1") + } + + if pps.NumSliceGroupsMinus1 > 0 { + pps.SliceGroupMapType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceGroupMapType") + } + + if pps.SliceGroupMapType == 0 { + for iGroup := 0; iGroup <= pps.NumSliceGroupsMinus1; iGroup++ { + pps.RunLengthMinus1[iGroup], err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse RunLengthMinus1") + } + } + } else if pps.SliceGroupMapType == 2 { + for iGroup := 0; iGroup < pps.NumSliceGroupsMinus1; iGroup++ { + pps.TopLeft[iGroup], err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse TopLeft[iGroup]") + } + if err != nil { + return nil, errors.Wrap(err, "could not parse TopLeft[iGroup]") + } + + pps.BottomRight[iGroup], err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse BottomRight[iGroup]") + } + } + } else if pps.SliceGroupMapType > 2 && pps.SliceGroupMapType < 6 { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SliceGroupChangeDirection") + } + pps.SliceGroupChangeDirection = b == 1 + + pps.SliceGroupChangeRateMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceGroupChangeRateMinus1") + } + } else if pps.SliceGroupMapType == 6 { + pps.PicSizeInMapUnitsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicSizeInMapUnitsMinus1") + } + + for i := 0; i <= pps.PicSizeInMapUnitsMinus1; i++ { + b, err = br.ReadBits(int(math.Ceil(math.Log2(float64(pps.NumSliceGroupsMinus1 + 1))))) + if err != nil { + return nil, errors.Wrap(err, "coult not read SliceGroupId") + } + pps.SliceGroupId[i] = int(b) + } + } + + } + pps.NumRefIdxL0DefaultActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.New("could not parse NumRefIdxL0DefaultActiveMinus1") + } + + pps.NumRefIdxL1DefaultActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.New("could not parse NumRefIdxL1DefaultActiveMinus1") + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read WeightedPred") + } + pps.WeightedPred = b == 1 + + b, err = br.ReadBits(2) + if err != nil { + return nil, errors.Wrap(err, "could not read WeightedBipred") + } + pps.WeightedBipred = int(b) + + pps.PicInitQpMinus26, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse PicInitQpMinus26") + } + + pps.PicInitQsMinus26, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse PicInitQsMinus26") + } + + pps.ChromaQpIndexOffset, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse ChromaQpIndexOffset") + } + + err = readFlags(br, []flag{ + {&pps.DeblockingFilterControlPresent, "DeblockingFilterControlPresent"}, + {&pps.ConstrainedIntraPred, "ConstrainedIntraPred"}, + {&pps.RedundantPicCntPresent, "RedundantPicCntPresent"}, + }) + if err != nil { + return nil, err + } + + logger.Printf("debug: \tChecking for more PPS data") + if moreRBSPData(br) { + logger.Printf("debug: \tProcessing additional PPS data") + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read Transform8x8Mode") + } + pps.Transform8x8Mode = int(b) + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read PicScalingMatrixPresent") + } + pps.PicScalingMatrixPresent = b == 1 + + if pps.PicScalingMatrixPresent { + v := 6 + if sps.ChromaFormat != chroma444 { + v = 2 + } + for i := 0; i < 6+(v*pps.Transform8x8Mode); i++ { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read PicScalingListPresent") + } + pps.PicScalingListPresent[i] = b == 1 + if pps.PicScalingListPresent[i] { + if i < 6 { + scalingList( + br, + ScalingList4x4[i], + 16, + DefaultScalingMatrix4x4[i]) + + } else { + scalingList( + br, + ScalingList8x8[i], + 64, + DefaultScalingMatrix8x8[i-6]) + + } + } + } + pps.SecondChromaQpIndexOffset, err = readSe(nil) + if err != nil { + return nil, errors.New("could not parse SecondChromaQpIndexOffset") + } + } + moreRBSPData(br) + // rbspTrailingBits() + } + + if showPacket { + debugPacket("PPS", pps) + } + return &pps, nil + +} diff --git a/codec/h264/decode/rangeTabLPS.go b/codec/h264/decode/rangeTabLPS.go new file mode 100644 index 00000000..3bcfb000 --- /dev/null +++ b/codec/h264/decode/rangeTabLPS.go @@ -0,0 +1,100 @@ +package h264 + +import "errors" + +// Number of columns and rows for rangeTabLPS. +const ( + rangeTabLPSColumns = 4 + rangeTabLPSRows = 64 +) + +// rangeTabLPS provides values of codIRangeLPS as defined in section 9.3.3.2.1.1, +// tab 9-44. Rows correspond to pStateIdx, and columns to qCodIRangeIdx, i.e. +// codIRangeLPS = rangeTabLPS[pStateIdx][qCodIRangeIdx]. +var rangeTabLPS = [rangeTabLPSRows][rangeTabLPSColumns]int{ + 0: {128, 176, 208, 240}, + 1: {128, 167, 197, 227}, + 2: {128, 158, 187, 216}, + 3: {123, 150, 178, 205}, + 4: {116, 142, 169, 195}, + 5: {111, 135, 160, 185}, + 6: {105, 128, 152, 175}, + 7: {100, 122, 144, 166}, + 8: {95, 116, 137, 158}, + 9: {90, 110, 130, 150}, + 10: {85, 104, 123, 142}, + 11: {81, 99, 117, 135}, + 12: {77, 94, 111, 128}, + 13: {73, 89, 105, 122}, + 14: {69, 85, 100, 116}, + 15: {66, 80, 95, 110}, + 16: {62, 76, 90, 104}, + 17: {59, 72, 86, 99}, + 18: {56, 69, 81, 94}, + 19: {53, 65, 77, 89}, + 20: {51, 62, 73, 85}, + 21: {48, 59, 69, 80}, + 22: {46, 56, 66, 76}, + 23: {43, 53, 63, 72}, + 24: {41, 50, 59, 69}, + 25: {39, 48, 56, 65}, + 26: {37, 45, 54, 62}, + 27: {35, 43, 51, 59}, + 28: {33, 41, 48, 56}, + 29: {32, 39, 46, 53}, + 30: {30, 37, 43, 50}, + 31: {29, 35, 41, 48}, + 32: {27, 33, 39, 45}, + 33: {26, 61, 67, 43}, + 34: {24, 30, 35, 41}, + 35: {23, 28, 33, 39}, + 36: {22, 27, 32, 37}, + 37: {21, 26, 30, 35}, + 38: {20, 24, 29, 33}, + 39: {19, 23, 27, 31}, + 40: {18, 22, 26, 30}, + 41: {17, 21, 25, 28}, + 42: {16, 20, 23, 27}, + 43: {15, 19, 22, 25}, + 44: {14, 18, 21, 24}, + 45: {14, 17, 20, 23}, + 46: {13, 16, 19, 22}, + 47: {12, 15, 18, 21}, + 48: {12, 14, 17, 20}, + 49: {11, 14, 16, 19}, + 50: {11, 13, 15, 18}, + 51: {10, 12, 15, 17}, + 52: {10, 12, 14, 16}, + 53: {9, 11, 13, 15}, + 54: {9, 11, 12, 14}, + 55: {8, 10, 12, 14}, + 56: {8, 9, 11, 13}, + 57: {7, 9, 11, 12}, + 58: {7, 9, 10, 12}, + 59: {7, 8, 10, 11}, + 60: {6, 8, 9, 11}, + 61: {6, 7, 9, 10}, + 62: {6, 7, 8, 9}, + 63: {2, 2, 2, 2}, +} + +// Errors returnable by retCodIRangeLPS. +var ( + errPStateIdx = errors.New("invalid pStateIdx") + errQCodIRangeIdx = errors.New("invalid qCodIRangeIdx") +) + +// retCodIRangeLPS retrieves the codIRangeLPS for a given pStateIdx and +// qCodIRangeIdx using the rangeTabLPS as specified in section 9.3.3.2.1.1, +// tab 9-44. +func retCodIRangeLPS(pStateIdx, qCodIRangeIdx int) (int, error) { + if pStateIdx < 0 || rangeTabLPSRows <= pStateIdx { + return 0, errPStateIdx + } + + if qCodIRangeIdx < 0 || rangeTabLPSColumns <= qCodIRangeIdx { + return 0, errQCodIRangeIdx + } + + return rangeTabLPS[pStateIdx][qCodIRangeIdx], nil +} diff --git a/codec/h264/decode/rbsp.go b/codec/h264/decode/rbsp.go new file mode 100644 index 00000000..57826439 --- /dev/null +++ b/codec/h264/decode/rbsp.go @@ -0,0 +1,86 @@ +package h264 + +import ( + "fmt" + + "github.com/ausocean/h264decode/h264/bits" +) + +const ( + profileBaseline = 66 + profileMain = 77 + profileExtended = 88 + profileHigh = 100 + profileHigh10 = 110 + profileHigh422 = 122 + profileHigh444Predictive = 244 +) + +var ( + ProfileIDC = map[int]string{ + profileBaseline: "Baseline", + profileMain: "Main", + profileExtended: "Extended", + profileHigh: "High", + profileHigh10: "High 10", + profileHigh422: "High 4:2:2", + profileHigh444Predictive: "High 4:4:4", + } +) + +// 7.3.2.11 +func rbspTrailingBits(br *bits.BitReader) { + _, err := br.ReadBits(1) + if err != nil { + fmt.Printf("error reading StopOneBit: %v\n", err) + } + // 7.2 + for !br.ByteAligned() { + // RBSPAlignmentZeroBit + _, err := br.ReadBits(1) + if err != nil { + fmt.Printf("error reading AligntmentZeroBit: %v\n", err) + break + } + } +} +func NewRBSP(frame []byte) []byte { + // TODO: NALUType 14,20,21 add padding to 3rd or 4th byte + return frame[5:] +} + +// TODO: Should be base-ten big endian bit arrays, not bytes +// ITU A.2.1.1 - Bit 9 is 1 +func isConstrainedBaselineProfile(profile int, b []byte) bool { + if profile != profileBaseline { + return false + } + if len(b) > 8 && b[8] == 1 { + return true + } + return false +} + +// ITU A2.4.2 - Bit 12 and 13 are 1 +func isConstrainedHighProfile(profile int, b []byte) bool { + if profile != profileHigh { + return false + } + if len(b) > 13 { + if b[12] == 1 && b[13] == 1 { + return true + } + } + return false +} + +// ITU A2.8 - Bit 11 is 1 +func isHigh10IntraProfile(profile int, b []byte) bool { + if profile != profileHigh10 { + return false + } + if len(b) > 11 && b[11] == 1 { + return true + } + return false +} diff --git a/codec/h264/decode/read.go b/codec/h264/decode/read.go new file mode 100644 index 00000000..c50180dd --- /dev/null +++ b/codec/h264/decode/read.go @@ -0,0 +1,230 @@ +package h264 + +import ( + "fmt" + "io" + "os" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +type H264Reader struct { + IsStarted bool + Stream io.Reader + NalUnits []*bits.BitReader + VideoStreams []*VideoStream + DebugFile *os.File + bytes []byte + byteOffset int + *bits.BitReader +} + +func (h *H264Reader) BufferToReader(cntBytes int) error { + buf := make([]byte, cntBytes) + if _, err := h.Stream.Read(buf); err != nil { + logger.Printf("error: while reading %d bytes: %v\n", cntBytes, err) + return err + } + h.bytes = append(h.bytes, buf...) + if h.DebugFile != nil { + h.DebugFile.Write(buf) + } + h.byteOffset += cntBytes + return nil +} + +func (h *H264Reader) Discard(cntBytes int) error { + buf := make([]byte, cntBytes) + if _, err := h.Stream.Read(buf); err != nil { + logger.Printf("error: while discarding %d bytes: %v\n", cntBytes, err) + return err + } + h.byteOffset += cntBytes + return nil +} + +// TODO: what does this do ? +func bitVal(bits []int) int { + t := 0 + for i, b := range bits { + if b == 1 { + t += 1 << uint((len(bits)-1)-i) + } + } + // fmt.Printf("\t bitVal: %d\n", t) + return t +} + +func (h *H264Reader) Start() { + for { + // TODO: need to handle error from this. + nalUnit, _, _ := h.readNalUnit() + switch nalUnit.Type { + case naluTypeSPS: + // TODO: handle this error + sps, _ := NewSPS(nalUnit.rbsp, false) + h.VideoStreams = append( + h.VideoStreams, + &VideoStream{SPS: sps}, + ) + case naluTypePPS: + videoStream := h.VideoStreams[len(h.VideoStreams)-1] + // TODO: handle this error + videoStream.PPS, _ = NewPPS(videoStream.SPS, nalUnit.RBSP(), false) + case naluTypeSliceIDRPicture: + fallthrough + case naluTypeSliceNonIDRPicture: + videoStream := h.VideoStreams[len(h.VideoStreams)-1] + logger.Printf("info: frame number %d\n", len(videoStream.Slices)) + // TODO: handle this error + sliceContext, _ := NewSliceContext(videoStream, nalUnit, nalUnit.RBSP(), true) + videoStream.Slices = append(videoStream.Slices, sliceContext) + } + } +} + +func (r *H264Reader) readNalUnit() (*NalUnit, *bits.BitReader, error) { + // Read to start of NAL + logger.Printf("debug: Seeking NAL %d start\n", len(r.NalUnits)) + + // TODO: Fix this. + for !isStartSequence(nil) { + if err := r.BufferToReader(1); err != nil { + // TODO: should this return an error here. + return nil, nil, nil + } + } + /* + if !r.IsStarted { + logger.Printf("debug: skipping initial NAL zero byte spaces\n") + r.LogStreamPosition() + // Annex B.2 Step 1 + if err := r.Discard(1); err != nil { + logger.Printf("error: while discarding empty byte (Annex B.2:1): %v\n", err) + return nil + } + if err := r.Discard(2); err != nil { + logger.Printf("error: while discarding start code prefix one 3bytes (Annex B.2:2): %v\n", err) + return nil + } + } + */ + startOffset := r.BytesRead() + logger.Printf("debug: Seeking next NAL start\n") + // Read to start of next NAL + so := r.BytesRead() + for so == startOffset || !isStartSequence(nil) { + so = r.BytesRead() + if err := r.BufferToReader(1); err != nil { + // TODO: should this return an error here? + return nil, nil, nil + } + } + // logger.Printf("debug: PreRewind %#v\n", r.Bytes()) + // Rewind back the length of the start sequence + // r.RewindBytes(4) + // logger.Printf("debug: PostRewind %#v\n", r.Bytes()) + endOffset := r.BytesRead() + logger.Printf("debug: found NAL unit with %d bytes from %d to %d\n", endOffset-startOffset, startOffset, endOffset) + nalUnitReader := bits.NewBitReader(nil) + r.NalUnits = append(r.NalUnits, nalUnitReader) + // TODO: this should really take an io.Reader rather than []byte. Need to fix nil + // once this is fixed. + nalUnit, err := NewNalUnit(nil, 0) + if err != nil { + return nil, nil, errors.Wrap(err, "cannot create new nal unit") + } + return nalUnit, nalUnitReader, nil +} + +func isStartSequence(packet []byte) bool { + if len(packet) < len(InitialNALU) { + return false + } + naluSegment := packet[len(packet)-4:] + for i := range InitialNALU { + if naluSegment[i] != InitialNALU[i] { + return false + } + } + return true +} + +func isStartCodeOnePrefix(buf []byte) bool { + for i, b := range buf { + if i < 2 && b != byte(0) { + return false + } + // byte 3 may be 0 or 1 + if i == 3 && b != byte(0) || b != byte(1) { + return false + } + } + logger.Printf("debug: found start code one prefix byte\n") + return true +} + +func isEmpty3Byte(buf []byte) bool { + if len(buf) < 3 { + return false + } + for _, i := range buf[len(buf)-3:] { + if i != 0 { + return false + } + } + return true +} + +// TODO: complete this. +func moreRBSPData(br *bits.BitReader) bool { + // Read until the least significant bit of any remaining bytes + // If the least significant bit is 1, that marks the first bit + // of the rbspTrailingBits() struct. If the bits read is more + // than 0, then there is more RBSP data + var bits uint64 + cnt := 0 + for bits != 1 { + if _, err := br.ReadBits(8); err != nil { + logger.Printf("moreRBSPData error: %v\n", err) + return false + } + cnt++ + } + logger.Printf("moreRBSPData: read %d additional bits\n", cnt) + return cnt > 0 +} + +type field struct { + loc *int + name string + n int +} + +func readFields(br *bits.BitReader, fields []field) error { + for _, f := range fields { + b, err := br.ReadBits(f.n) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not read %s", f.name)) + } + *f.loc = int(b) + } + return nil +} + +type flag struct { + loc *bool + name string +} + +func readFlags(br *bits.BitReader, flags []flag) error { + for _, f := range flags { + b, err := br.ReadBits(1) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not read %s", f.name)) + } + *f.loc = b == 1 + } + return nil +} diff --git a/codec/h264/decode/server.go b/codec/h264/decode/server.go new file mode 100644 index 00000000..f702c1eb --- /dev/null +++ b/codec/h264/decode/server.go @@ -0,0 +1,70 @@ +package h264 + +import ( + // "github.com/nareix/joy4/av" + // "github.com/nareix/joy4/codec/h264parser" + // "github.com/nareix/joy4/format/ts" + "io" + "log" + "net" + "os" + "os/signal" + + "github.com/ausocean/h264decode/h264/bits" +) + +// InitialNALU indicates the start of a h264 packet +// 0 - Forbidden 0 bit; always 0 +// 1,2 - NRI +// 3,4,5,6,7 - Type +var ( + InitialNALU = []byte{0, 0, 0, 1} + Initial3BNALU = []byte{0, 0, 1} + logger *log.Logger + streamOffset = 0 +) + +func init() { + logger = log.New(os.Stderr, "streamer ", log.Lshortfile|log.Lmicroseconds) +} + +func ByteStreamReader(connection net.Conn) { + logger.Printf("opened bytestream\n") + defer connection.Close() + handleConnection(connection) +} + +func handleConnection(connection io.Reader) { + logger.Printf("debug: handling connection\n") + streamFilename := "/home/bruce/devel/go/src/github.com/mrmod/cvnightlife/output.mp4" + _ = os.Remove(streamFilename) + debugFile, err := os.Create(streamFilename) + if err != nil { + panic(err) + } + streamReader := &H264Reader{ + Stream: connection, + // TODO: need to give this an io.Reader, not nil. + BitReader: bits.NewBitReader(nil), + DebugFile: debugFile, + } + c := make(chan os.Signal, 1) + signal.Notify(c) + go func() { + logger.Printf("debug: waiting on signals\n") + s := <-c + logger.Printf("info: %v received, closing stream file\n", s) + streamReader.DebugFile.Close() + os.Exit(0) + }() + + defer func() { + if r := recover(); r != nil { + logger.Printf("fatal: recovered: %v\n", r) + logger.Printf("info: closing streamfile\n") + streamReader.DebugFile.Close() + os.Exit(1) + } + }() + streamReader.Start() +} diff --git a/codec/h264/decode/slice.go b/codec/h264/decode/slice.go new file mode 100644 index 00000000..251989cc --- /dev/null +++ b/codec/h264/decode/slice.go @@ -0,0 +1,1369 @@ +package h264 + +import ( + "bytes" + "fmt" + "math" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +// Chroma formats as defined in section 6.2, tab 6-1. +const ( + chromaMonochrome = iota + chroma420 + chroma422 + chroma444 +) + +type VideoStream struct { + SPS *SPS + PPS *PPS + Slices []*SliceContext +} +type SliceContext struct { + *NalUnit + *SPS + *PPS + *Slice +} +type Slice struct { + Header *SliceHeader + Data *SliceData +} +type SliceHeader struct { + FirstMbInSlice int + SliceType int + PPSID int + ColorPlaneID int + FrameNum int + FieldPic bool + BottomField bool + IDRPicID int + PicOrderCntLsb int + DeltaPicOrderCntBottom int + DeltaPicOrderCnt []int + RedundantPicCnt int + DirectSpatialMvPred bool + NumRefIdxActiveOverride bool + NumRefIdxL0ActiveMinus1 int + NumRefIdxL1ActiveMinus1 int + CabacInit int + SliceQpDelta int + SpForSwitch bool + SliceQsDelta int + DisableDeblockingFilter int + SliceAlphaC0OffsetDiv2 int + SliceBetaOffsetDiv2 int + SliceGroupChangeCycle int + RefPicListModificationFlagL0 bool + ModificationOfPicNums int + AbsDiffPicNumMinus1 int + LongTermPicNum int + RefPicListModificationFlagL1 bool + LumaLog2WeightDenom int + ChromaLog2WeightDenom int + ChromaArrayType int + LumaWeightL0Flag bool + LumaWeightL0 []int + LumaOffsetL0 []int + ChromaWeightL0Flag bool + ChromaWeightL0 [][]int + ChromaOffsetL0 [][]int + LumaWeightL1Flag bool + LumaWeightL1 []int + LumaOffsetL1 []int + ChromaWeightL1Flag bool + ChromaWeightL1 [][]int + ChromaOffsetL1 [][]int + NoOutputOfPriorPicsFlag bool + LongTermReferenceFlag bool + AdaptiveRefPicMarkingModeFlag bool + MemoryManagementControlOperation int + DifferenceOfPicNumsMinus1 int + LongTermFrameIdx int + MaxLongTermFrameIdxPlus1 int +} + +type SliceData struct { + BitReader *bits.BitReader + CabacAlignmentOneBit int + MbSkipRun int + MbSkipFlag bool + MbFieldDecodingFlag bool + EndOfSliceFlag bool + MbType int + MbTypeName string + SliceTypeName string + PcmAlignmentZeroBit int + PcmSampleLuma []int + PcmSampleChroma []int + TransformSize8x8Flag bool + CodedBlockPattern int + MbQpDelta int + PrevIntra4x4PredModeFlag []int + RemIntra4x4PredMode []int + PrevIntra8x8PredModeFlag []int + RemIntra8x8PredMode []int + IntraChromaPredMode int + RefIdxL0 []int + RefIdxL1 []int + MvdL0 [][][]int + MvdL1 [][][]int +} + +// Table 7-6 +var sliceTypeMap = map[int]string{ + 0: "P", + 1: "B", + 2: "I", + 3: "SP", + 4: "SI", + 5: "P", + 6: "B", + 7: "I", + 8: "SP", + 9: "SI", +} + +func flagVal(b bool) int { + if b { + return 1 + } + return 0 +} + +// context-adaptive arithmetic entropy-coded element (CABAC) +// 9.3 +// When parsing the slice date of a slice (7.3.4) the initialization is 9.3.1 +func (d SliceData) ae(v int) int { + // 9.3.1.1 : CABAC context initialization ctxIdx + return 0 +} + +// 8.2.2 +func MbToSliceGroupMap(sps *SPS, pps *PPS, header *SliceHeader) []int { + mbaffFrameFlag := 0 + if sps.MBAdaptiveFrameField && !header.FieldPic { + mbaffFrameFlag = 1 + } + mapUnitToSliceGroupMap := MapUnitToSliceGroupMap(sps, pps, header) + mbToSliceGroupMap := []int{} + for i := 0; i <= PicSizeInMbs(sps, header)-1; i++ { + if sps.FrameMbsOnly || header.FieldPic { + mbToSliceGroupMap = append(mbToSliceGroupMap, mapUnitToSliceGroupMap[i]) + continue + } + if mbaffFrameFlag == 1 { + mbToSliceGroupMap = append(mbToSliceGroupMap, mapUnitToSliceGroupMap[i/2]) + continue + } + if !sps.FrameMbsOnly && !sps.MBAdaptiveFrameField && !header.FieldPic { + mbToSliceGroupMap = append( + mbToSliceGroupMap, + mapUnitToSliceGroupMap[(i/(2*PicWidthInMbs(sps)))*PicWidthInMbs(sps)+(i%PicWidthInMbs(sps))]) + } + } + return mbToSliceGroupMap + +} +func PicWidthInMbs(sps *SPS) int { + return sps.PicWidthInMbsMinus1 + 1 +} +func PicHeightInMapUnits(sps *SPS) int { + return sps.PicHeightInMapUnitsMinus1 + 1 +} +func PicSizeInMapUnits(sps *SPS) int { + return PicWidthInMbs(sps) * PicHeightInMapUnits(sps) +} +func FrameHeightInMbs(sps *SPS) int { + return (2 - flagVal(sps.FrameMbsOnly)) * PicHeightInMapUnits(sps) +} +func PicHeightInMbs(sps *SPS, header *SliceHeader) int { + return FrameHeightInMbs(sps) / (1 + flagVal(header.FieldPic)) +} +func PicSizeInMbs(sps *SPS, header *SliceHeader) int { + return PicWidthInMbs(sps) * PicHeightInMbs(sps, header) +} + +// table 6-1 +func SubWidthC(sps *SPS) int { + n := 17 + if sps.UseSeparateColorPlane { + if sps.ChromaFormat == chroma444 { + return n + } + } + + switch sps.ChromaFormat { + case chromaMonochrome: + return n + case chroma420: + n = 2 + case chroma422: + n = 2 + case chroma444: + n = 1 + + } + return n +} +func SubHeightC(sps *SPS) int { + n := 17 + if sps.UseSeparateColorPlane { + if sps.ChromaFormat == chroma444 { + return n + } + } + switch sps.ChromaFormat { + case chromaMonochrome: + return n + case chroma420: + n = 2 + case chroma422: + n = 1 + case chroma444: + n = 1 + + } + return n +} + +// 7-36 +func CodedBlockPatternLuma(data *SliceData) int { + return data.CodedBlockPattern % 16 +} +func CodedBlockPatternChroma(data *SliceData) int { + return data.CodedBlockPattern / 16 +} + +// dependencyId see Annex G.8.8.1 +// Also G7.3.1.1 nal_unit_header_svc_extension +func DQId(nalUnit *NalUnit) int { + return (nalUnit.DependencyId << 4) + nalUnit.QualityId +} + +// Annex G p527 +func NumMbPart(nalUnit *NalUnit, sps *SPS, header *SliceHeader, data *SliceData) int { + sliceType := sliceTypeMap[header.SliceType] + numMbPart := 0 + if MbTypeName(sliceType, CurrMbAddr(sps, header)) == "B_SKIP" || MbTypeName(sliceType, CurrMbAddr(sps, header)) == "B_Direct_16x16" { + if DQId(nalUnit) == 0 && nalUnit.Type != 20 { + numMbPart = 4 + } else if DQId(nalUnit) > 0 && nalUnit.Type == 20 { + numMbPart = 1 + } + } else if MbTypeName(sliceType, CurrMbAddr(sps, header)) != "B_SKIP" && MbTypeName(sliceType, CurrMbAddr(sps, header)) != "B_Direct_16x16" { + numMbPart = CurrMbAddr(sps, header) + + } + return numMbPart +} + +func MbPred(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) + if err != nil { + return errors.Wrap(err, "could not get mbPartPredMode") + } + if mbPartPredMode == intra4x4 || mbPartPredMode == intra8x8 || mbPartPredMode == intra16x16 { + if mbPartPredMode == intra4x4 { + for luma4x4BlkIdx := 0; luma4x4BlkIdx < 16; luma4x4BlkIdx++ { + var v int + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 1 bit or ae(v) + binarization := NewBinarization( + "PrevIntra4x4PredModeFlag", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + cabac = initCabac(binarization, sliceContext) + _ = cabac + logger.Printf("TODO: ae for PevIntra4x4PredModeFlag[%d]\n", luma4x4BlkIdx) + } else { + b, err := br.ReadBits(1) + if err != nil { + return errors.Wrap(err, "could not read PrevIntra4x4PredModeFlag") + } + v = int(b) + } + sliceContext.Slice.Data.PrevIntra4x4PredModeFlag = append( + sliceContext.Slice.Data.PrevIntra4x4PredModeFlag, + v) + if sliceContext.Slice.Data.PrevIntra4x4PredModeFlag[luma4x4BlkIdx] == 0 { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 3 bits or ae(v) + binarization := NewBinarization( + "RemIntra4x4PredMode", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for RemIntra4x4PredMode[%d]\n", luma4x4BlkIdx) + } else { + b, err := br.ReadBits(3) + if err != nil { + return errors.Wrap(err, "could not read RemIntra4x4PredMode") + } + v = int(b) + } + if len(sliceContext.Slice.Data.RemIntra4x4PredMode) < luma4x4BlkIdx { + sliceContext.Slice.Data.RemIntra4x4PredMode = append( + sliceContext.Slice.Data.RemIntra4x4PredMode, + make([]int, luma4x4BlkIdx-len(sliceContext.Slice.Data.RemIntra4x4PredMode)+1)...) + } + sliceContext.Slice.Data.RemIntra4x4PredMode[luma4x4BlkIdx] = v + } + } + } + if mbPartPredMode == intra8x8 { + for luma8x8BlkIdx := 0; luma8x8BlkIdx < 4; luma8x8BlkIdx++ { + sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) + var v int + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 1 bit or ae(v) + binarization := NewBinarization("PrevIntra8x8PredModeFlag", sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for PrevIntra8x8PredModeFlag[%d]\n", luma8x8BlkIdx) + } else { + b, err := br.ReadBits(1) + if err != nil { + return errors.Wrap(err, "could not read PrevIntra8x8PredModeFlag") + } + v = int(b) + } + sliceContext.Slice.Data.PrevIntra8x8PredModeFlag = append( + sliceContext.Slice.Data.PrevIntra8x8PredModeFlag, v) + if sliceContext.Slice.Data.PrevIntra8x8PredModeFlag[luma8x8BlkIdx] == 0 { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: 3 bits or ae(v) + binarization := NewBinarization( + "RemIntra8x8PredMode", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for RemIntra8x8PredMode[%d]\n", luma8x8BlkIdx) + } else { + b, err := br.ReadBits(3) + if err != nil { + return errors.Wrap(err, "could not read RemIntra8x8PredMode") + } + v = int(b) + } + if len(sliceContext.Slice.Data.RemIntra8x8PredMode) < luma8x8BlkIdx { + sliceContext.Slice.Data.RemIntra8x8PredMode = append( + sliceContext.Slice.Data.RemIntra8x8PredMode, + make([]int, luma8x8BlkIdx-len(sliceContext.Slice.Data.RemIntra8x8PredMode)+1)...) + } + sliceContext.Slice.Data.RemIntra8x8PredMode[luma8x8BlkIdx] = v + } + } + + } + if sliceContext.Slice.Header.ChromaArrayType == 1 || sliceContext.Slice.Header.ChromaArrayType == 2 { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: ue(v) or ae(v) + binarization := NewBinarization( + "IntraChromaPredMode", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for IntraChromaPredMode\n") + } else { + var err error + sliceContext.Slice.Data.IntraChromaPredMode, err = readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse IntraChromaPredMode") + } + } + } + + } else if mbPartPredMode != direct { + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get mbPartPredMode for loop 1 mbPartIdx: %d", mbPartIdx)) + } + if (sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1 > 0 || sliceContext.Slice.Data.MbFieldDecodingFlag != sliceContext.Slice.Header.FieldPic) && m != predL1 { + logger.Printf("\tTODO: refIdxL0[%d] te or ae(v)\n", mbPartIdx) + if len(sliceContext.Slice.Data.RefIdxL0) < mbPartIdx { + sliceContext.Slice.Data.RefIdxL0 = append( + sliceContext.Slice.Data.RefIdxL0, make([]int, mbPartIdx-len(sliceContext.Slice.Data.RefIdxL0)+1)...) + } + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: te(v) or ae(v) + binarization := NewBinarization( + "RefIdxL0", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + logger.Printf("TODO: ae for RefIdxL0[%d]\n", mbPartIdx) + } else { + // TODO: Only one reference picture is used for inter-prediction, + // then the value should be 0 + if MbaffFrameFlag(sliceContext.SPS, sliceContext.Slice.Header) == 0 || !sliceContext.Slice.Data.MbFieldDecodingFlag { + sliceContext.Slice.Data.RefIdxL0[mbPartIdx], _ = readTe( + nil, + uint(sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1)) + } else { + rangeMax := 2*sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1 + 1 + sliceContext.Slice.Data.RefIdxL0[mbPartIdx], _ = readTe( + nil, + uint(rangeMax)) + } + } + } + } + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get mbPartPredMode for loop 2 mbPartIdx: %d", mbPartIdx)) + } + if m != predL1 { + for compIdx := 0; compIdx < 2; compIdx++ { + if len(sliceContext.Slice.Data.MvdL0) < mbPartIdx { + sliceContext.Slice.Data.MvdL0 = append( + sliceContext.Slice.Data.MvdL0, + make([][][]int, mbPartIdx-len(sliceContext.Slice.Data.MvdL0)+1)...) + } + if len(sliceContext.Slice.Data.MvdL0[mbPartIdx][0]) < compIdx { + sliceContext.Slice.Data.MvdL0[mbPartIdx][0] = append( + sliceContext.Slice.Data.MvdL0[mbPartIdx][0], + make([]int, compIdx-len(sliceContext.Slice.Data.MvdL0[mbPartIdx][0])+1)...) + } + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: se(v) or ae(v) + if compIdx == 0 { + binarization := NewBinarization( + "MvdLnEnd0", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } else if compIdx == 1 { + binarization := NewBinarization( + "MvdLnEnd1", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } + logger.Printf("TODO: ae for MvdL0[%d][0][%d]\n", mbPartIdx, compIdx) + } else { + sliceContext.Slice.Data.MvdL0[mbPartIdx][0][compIdx], _ = readSe(nil) + } + } + } + } + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("could not get mbPartPredMode for loop 3 mbPartIdx: %d", mbPartIdx)) + } + if m != predL0 { + for compIdx := 0; compIdx < 2; compIdx++ { + if len(sliceContext.Slice.Data.MvdL1) < mbPartIdx { + sliceContext.Slice.Data.MvdL1 = append( + sliceContext.Slice.Data.MvdL1, + make([][][]int, mbPartIdx-len(sliceContext.Slice.Data.MvdL1)+1)...) + } + if len(sliceContext.Slice.Data.MvdL1[mbPartIdx][0]) < compIdx { + sliceContext.Slice.Data.MvdL1[mbPartIdx][0] = append( + sliceContext.Slice.Data.MvdL0[mbPartIdx][0], + make([]int, compIdx-len(sliceContext.Slice.Data.MvdL1[mbPartIdx][0])+1)...) + } + if sliceContext.PPS.EntropyCodingMode == 1 { + if compIdx == 0 { + binarization := NewBinarization( + "MvdLnEnd0", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } else if compIdx == 1 { + binarization := NewBinarization( + "MvdLnEnd1", + sliceContext.Slice.Data) + binarization.Decode(sliceContext, br, rbsp) + + } + // TODO: se(v) or ae(v) + logger.Printf("TODO: ae for MvdL1[%d][0][%d]\n", mbPartIdx, compIdx) + } else { + sliceContext.Slice.Data.MvdL1[mbPartIdx][0][compIdx], _ = readSe(nil) + } + } + } + } + } + return nil +} + +// 8.2.2.1 +func MapUnitToSliceGroupMap(sps *SPS, pps *PPS, header *SliceHeader) []int { + mapUnitToSliceGroupMap := []int{} + picSizeInMapUnits := PicSizeInMapUnits(sps) + if pps.NumSliceGroupsMinus1 == 0 { + // 0 to PicSizeInMapUnits -1 inclusive + for i := 0; i <= picSizeInMapUnits-1; i++ { + mapUnitToSliceGroupMap = append(mapUnitToSliceGroupMap, 0) + } + } else { + switch pps.SliceGroupMapType { + case 0: + // 8.2.2.1 + i := 0 + for i < picSizeInMapUnits { + // iGroup should be incremented in the pps.RunLengthMinus1 index operation. There may be a bug here + for iGroup := 0; iGroup <= pps.NumSliceGroupsMinus1 && i < picSizeInMapUnits; i += pps.RunLengthMinus1[iGroup+1] + 1 { + for j := 0; j < pps.RunLengthMinus1[iGroup] && i+j < picSizeInMapUnits; j++ { + if len(mapUnitToSliceGroupMap) < i+j { + mapUnitToSliceGroupMap = append( + mapUnitToSliceGroupMap, + make([]int, (i+j)-len(mapUnitToSliceGroupMap)+1)...) + } + mapUnitToSliceGroupMap[i+j] = iGroup + } + } + } + case 1: + // 8.2.2.2 + for i := 0; i < picSizeInMapUnits; i++ { + v := ((i % PicWidthInMbs(sps)) + (((i / PicWidthInMbs(sps)) * (pps.NumSliceGroupsMinus1 + 1)) / 2)) % (pps.NumSliceGroupsMinus1 + 1) + mapUnitToSliceGroupMap = append(mapUnitToSliceGroupMap, v) + } + case 2: + // 8.2.2.3 + for i := 0; i < picSizeInMapUnits; i++ { + mapUnitToSliceGroupMap = append(mapUnitToSliceGroupMap, pps.NumSliceGroupsMinus1) + } + for iGroup := pps.NumSliceGroupsMinus1 - 1; iGroup >= 0; iGroup-- { + yTopLeft := pps.TopLeft[iGroup] / PicWidthInMbs(sps) + xTopLeft := pps.TopLeft[iGroup] % PicWidthInMbs(sps) + yBottomRight := pps.BottomRight[iGroup] / PicWidthInMbs(sps) + xBottomRight := pps.BottomRight[iGroup] % PicWidthInMbs(sps) + for y := yTopLeft; y <= yBottomRight; y++ { + for x := xTopLeft; x <= xBottomRight; x++ { + idx := y*PicWidthInMbs(sps) + x + if len(mapUnitToSliceGroupMap) < idx { + mapUnitToSliceGroupMap = append( + mapUnitToSliceGroupMap, + make([]int, idx-len(mapUnitToSliceGroupMap)+1)...) + mapUnitToSliceGroupMap[idx] = iGroup + } + } + } + } + + case 3: + // 8.2.2.4 + // TODO + case 4: + // 8.2.2.5 + // TODO + case 5: + // 8.2.2.6 + // TODO + case 6: + // 8.2.2.7 + // TODO + } + } + // 8.2.2.8 + // Convert mapUnitToSliceGroupMap to MbToSliceGroupMap + return mapUnitToSliceGroupMap +} +func nextMbAddress(n int, sps *SPS, pps *PPS, header *SliceHeader) int { + i := n + 1 + // picSizeInMbs is the number of macroblocks in picture 0 + // 7-13 + // PicWidthInMbs = sps.PicWidthInMbsMinus1 + 1 + // PicHeightInMapUnits = sps.PicHeightInMapUnitsMinus1 + 1 + // 7-29 + // picSizeInMbs = PicWidthInMbs * PicHeightInMbs + // 7-26 + // PicHeightInMbs = FrameHeightInMbs / (1 + header.fieldPicFlag) + // 7-18 + // FrameHeightInMbs = (2 - ps.FrameMbsOnly) * PicHeightInMapUnits + picWidthInMbs := sps.PicWidthInMbsMinus1 + 1 + picHeightInMapUnits := sps.PicHeightInMapUnitsMinus1 + 1 + frameHeightInMbs := (2 - flagVal(sps.FrameMbsOnly)) * picHeightInMapUnits + picHeightInMbs := frameHeightInMbs / (1 + flagVal(header.FieldPic)) + picSizeInMbs := picWidthInMbs * picHeightInMbs + mbToSliceGroupMap := MbToSliceGroupMap(sps, pps, header) + for i < picSizeInMbs && mbToSliceGroupMap[i] != mbToSliceGroupMap[i] { + i++ + } + return i +} + +func CurrMbAddr(sps *SPS, header *SliceHeader) int { + mbaffFrameFlag := 0 + if sps.MBAdaptiveFrameField && !header.FieldPic { + mbaffFrameFlag = 1 + } + + return header.FirstMbInSlice * (1 * mbaffFrameFlag) +} + +func MbaffFrameFlag(sps *SPS, header *SliceHeader) int { + if sps.MBAdaptiveFrameField && !header.FieldPic { + return 1 + } + return 0 +} + +func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, error) { + var cabac *CABAC + var err error + sliceContext.Slice.Data = &SliceData{BitReader: br} + // TODO: Why is this being initialized here? + // initCabac(sliceContext) + if sliceContext.PPS.EntropyCodingMode == 1 { + for !br.ByteAligned() { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read CabacAlignmentOneBit") + } + sliceContext.Slice.Data.CabacAlignmentOneBit = int(b) + } + } + mbaffFrameFlag := 0 + if sliceContext.SPS.MBAdaptiveFrameField && !sliceContext.Slice.Header.FieldPic { + mbaffFrameFlag = 1 + } + currMbAddr := sliceContext.Slice.Header.FirstMbInSlice * (1 * mbaffFrameFlag) + + moreDataFlag := true + prevMbSkipped := 0 + sliceContext.Slice.Data.SliceTypeName = sliceTypeMap[sliceContext.Slice.Header.SliceType] + sliceContext.Slice.Data.MbTypeName = MbTypeName(sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType) + logger.Printf("debug: \tSliceData: Processing moreData: %v\n", moreDataFlag) + for moreDataFlag { + logger.Printf("debug: \tLooking for more sliceContext.Slice.Data in slice type %s\n", sliceContext.Slice.Data.SliceTypeName) + if sliceContext.Slice.Data.SliceTypeName != "I" && sliceContext.Slice.Data.SliceTypeName != "SI" { + logger.Printf("debug: \tNonI/SI slice, processing moreData\n") + if sliceContext.PPS.EntropyCodingMode == 0 { + sliceContext.Slice.Data.MbSkipRun, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MbSkipRun") + } + + if sliceContext.Slice.Data.MbSkipRun > 0 { + prevMbSkipped = 1 + } + for i := 0; i < sliceContext.Slice.Data.MbSkipRun; i++ { + // nextMbAddress(currMbAdd + currMbAddr = nextMbAddress(currMbAddr, sliceContext.SPS, sliceContext.PPS, sliceContext.Slice.Header) + } + if sliceContext.Slice.Data.MbSkipRun > 0 { + moreDataFlag = moreRBSPData(br) + } + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MbSkipFlag") + } + sliceContext.Slice.Data.MbSkipFlag = b == 1 + + moreDataFlag = !sliceContext.Slice.Data.MbSkipFlag + } + } + if moreDataFlag { + if mbaffFrameFlag == 1 && (currMbAddr%2 == 0 || (currMbAddr%2 == 1 && prevMbSkipped == 1)) { + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: ae implementation + binarization := NewBinarization("MbFieldDecodingFlag", sliceContext.Slice.Data) + // TODO: this should take a BitReader where the nil is. + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for MbFieldDecodingFlag\n") + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MbFieldDecodingFlag") + } + sliceContext.Slice.Data.MbFieldDecodingFlag = b == 1 + } + } + + // BEGIN: macroblockLayer() + if sliceContext.PPS.EntropyCodingMode == 1 { + // TODO: ae implementation + binarization := NewBinarization("MbType", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + _ = cabac + // TODO: remove bytes parameter from this function. + binarization.Decode(sliceContext, br, nil) + if binarization.PrefixSuffix { + logger.Printf("debug: MBType binarization has prefix and suffix\n") + } + bits := []int{} + for binIdx := 0; binarization.IsBinStringMatch(bits); binIdx++ { + newBit, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read bit") + } + if binarization.UseDecodeBypass == 1 { + // DecodeBypass + logger.Printf("TODO: decodeBypass is set: 9.3.3.2.3") + codIRange, codIOffset, err := initDecodingEngine(sliceContext.Slice.Data.BitReader) + if err != nil { + return nil, errors.Wrap(err, "could not initialise decoding engine") + } + // Initialize the decoder + // TODO: When should the suffix of MaxBinIdxCtx be used and when just the prefix? + // TODO: When should the suffix of CtxIdxOffset be used? + arithmeticDecoder, err := NewArithmeticDecoding( + sliceContext, + binarization, + CtxIdx( + binarization.binIdx, + binarization.MaxBinIdxCtx.Prefix, + binarization.CtxIdxOffset.Prefix, + ), + codIRange, + codIOffset, + ) + if err != nil { + return nil, errors.Wrap(err, "error from NewArithmeticDecoding") + } + // Bypass decoding + codIOffset, _, err = arithmeticDecoder.DecodeBypass( + sliceContext.Slice.Data, + codIRange, + codIOffset, + ) + if err != nil { + return nil, errors.Wrap(err, "could not DecodeBypass") + } + // End DecodeBypass + + } else { + // DO 9.3.3.1 + ctxIdx := CtxIdx( + binIdx, + binarization.MaxBinIdxCtx.Prefix, + binarization.CtxIdxOffset.Prefix) + if binarization.MaxBinIdxCtx.IsPrefixSuffix { + logger.Printf("TODO: Handle PrefixSuffix binarization\n") + } + logger.Printf("debug: MBType ctxIdx for %d is %d\n", binIdx, ctxIdx) + // Then 9.3.3.2 + codIRange, codIOffset, err := initDecodingEngine(br) + if err != nil { + return nil, errors.Wrap(err, "error from initDecodingEngine") + } + logger.Printf("debug: coding engine initialized: %d/%d\n", codIRange, codIOffset) + } + bits = append(bits, int(newBit)) + } + + logger.Printf("TODO: ae for MBType\n") + } else { + sliceContext.Slice.Data.MbType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MbType") + } + } + if sliceContext.Slice.Data.MbTypeName == "I_PCM" { + for !br.ByteAligned() { + _, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read PCMAlignmentZeroBit") + } + } + // 7-3 p95 + bitDepthY := 8 + sliceContext.SPS.BitDepthLumaMinus8 + for i := 0; i < 256; i++ { + s, err := br.ReadBits(bitDepthY) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("could not read PcmSampleLuma[%d]", i)) + } + sliceContext.Slice.Data.PcmSampleLuma = append( + sliceContext.Slice.Data.PcmSampleLuma, + int(s)) + } + // 9.3.1 p 246 + // cabac = initCabac(binarization, sliceContext) + // 6-1 p 47 + mbWidthC := 16 / SubWidthC(sliceContext.SPS) + mbHeightC := 16 / SubHeightC(sliceContext.SPS) + // if monochrome + if sliceContext.SPS.ChromaFormat == chromaMonochrome || sliceContext.SPS.UseSeparateColorPlane { + mbWidthC = 0 + mbHeightC = 0 + } + + bitDepthC := 8 + sliceContext.SPS.BitDepthChromaMinus8 + for i := 0; i < 2*mbWidthC*mbHeightC; i++ { + s, err := br.ReadBits(bitDepthC) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("could not read PcmSampleChroma[%d]", i)) + } + sliceContext.Slice.Data.PcmSampleChroma = append( + sliceContext.Slice.Data.PcmSampleChroma, + int(s)) + } + // 9.3.1 p 246 + // cabac = initCabac(binarization, sliceContext) + + } else { + noSubMbPartSizeLessThan8x8Flag := 1 + m, err := MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) + if err != nil { + return nil, errors.Wrap(err, "could not get mbPartPredMode") + } + if sliceContext.Slice.Data.MbTypeName == "I_NxN" && m != intra16x16 && NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data) == 4 { + logger.Printf("\tTODO: subMbPred\n") + /* + subMbType := SubMbPred(sliceContext.Slice.Data.MbType) + for mbPartIdx := 0; mbPartIdx < 4; mbPartIdx++ { + if subMbType[mbPartIdx] != "B_Direct_8x8" { + if NumbSubMbPart(subMbType[mbPartIdx]) > 1 { + noSubMbPartSizeLessThan8x8Flag = 0 + } + } else if !sliceContext.SPS.Direct8x8Inference { + noSubMbPartSizeLessThan8x8Flag = 0 + } + } + */ + } else { + if sliceContext.PPS.Transform8x8Mode == 1 && sliceContext.Slice.Data.MbTypeName == "I_NxN" { + // TODO + // 1 bit or ae(v) + // If sliceContext.PPS.EntropyCodingMode == 1, use ae(v) + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("TransformSize8x8Flag", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + binarization.Decode(sliceContext, br, nil) + + logger.Println("TODO: ae(v) for TransformSize8x8Flag") + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read TransformSize8x8Flag") + } + sliceContext.Slice.Data.TransformSize8x8Flag = b == 1 + } + } + // TODO: fix nil argument for. + MbPred(sliceContext, br, nil) + } + m, err = MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) + if err != nil { + return nil, errors.Wrap(err, "could not get mbPartPredMode") + } + if m != intra16x16 { + // TODO: me, ae + logger.Printf("TODO: CodedBlockPattern pending me/ae implementation\n") + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("CodedBlockPattern", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + // TODO: fix nil argument. + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for CodedBlockPattern\n") + } else { + me, _ := readMe( + nil, + uint(sliceContext.Slice.Header.ChromaArrayType), + // TODO: fix this + //MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0))) + 0) + sliceContext.Slice.Data.CodedBlockPattern = int(me) + } + + // sliceContext.Slice.Data.CodedBlockPattern = me(v) | ae(v) + if CodedBlockPatternLuma(sliceContext.Slice.Data) > 0 && sliceContext.PPS.Transform8x8Mode == 1 && sliceContext.Slice.Data.MbTypeName != "I_NxN" && noSubMbPartSizeLessThan8x8Flag == 1 && (sliceContext.Slice.Data.MbTypeName != "B_Direct_16x16" || sliceContext.SPS.Direct8x8Inference) { + // TODO: 1 bit or ae(v) + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("Transform8x8Flag", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + // TODO: fix nil argument. + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for TranformSize8x8Flag\n") + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "coult not read TransformSize8x8Flag") + } + sliceContext.Slice.Data.TransformSize8x8Flag = b == 1 + } + } + } + m, err = MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0) + if err != nil { + return nil, errors.Wrap(err, "could not get mbPartPredMode") + } + if CodedBlockPatternLuma(sliceContext.Slice.Data) > 0 || CodedBlockPatternChroma(sliceContext.Slice.Data) > 0 || m == intra16x16 { + // TODO: se or ae(v) + if sliceContext.PPS.EntropyCodingMode == 1 { + binarization := NewBinarization("MbQpDelta", sliceContext.Slice.Data) + cabac = initCabac(binarization, sliceContext) + // TODO; fix nil argument + binarization.Decode(sliceContext, br, nil) + + logger.Printf("TODO: ae for MbQpDelta\n") + } else { + sliceContext.Slice.Data.MbQpDelta, _ = readSe(nil) + } + + } + } + + } // END MacroblockLayer + if sliceContext.PPS.EntropyCodingMode == 0 { + moreDataFlag = moreRBSPData(br) + } else { + if sliceContext.Slice.Data.SliceTypeName != "I" && sliceContext.Slice.Data.SliceTypeName != "SI" { + if sliceContext.Slice.Data.MbSkipFlag { + prevMbSkipped = 1 + } else { + prevMbSkipped = 0 + } + } + if mbaffFrameFlag == 1 && currMbAddr%2 == 0 { + moreDataFlag = true + } else { + // TODO: ae implementation + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read EndOfSliceFlag") + } + sliceContext.Slice.Data.EndOfSliceFlag = b == 1 + moreDataFlag = !sliceContext.Slice.Data.EndOfSliceFlag + } + } + currMbAddr = nextMbAddress(currMbAddr, sliceContext.SPS, sliceContext.PPS, sliceContext.Slice.Header) + } // END while moreDataFlag + return sliceContext.Slice.Data, nil +} + +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) { + var err error + sps := videoStream.SPS + pps := videoStream.PPS + logger.Printf("debug: %s RBSP %d bytes %d bits == \n", NALUnitType[nalUnit.Type], len(rbsp), len(rbsp)*8) + logger.Printf("debug: \t%#v\n", rbsp[0:8]) + var idrPic bool + if nalUnit.Type == 5 { + idrPic = true + } + header := SliceHeader{} + if sps.UseSeparateColorPlane { + header.ChromaArrayType = 0 + } else { + header.ChromaArrayType = sps.ChromaFormat + } + br := bits.NewBitReader(bytes.NewReader(rbsp)) + + header.FirstMbInSlice, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FirstMbInSlice") + } + + header.SliceType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceType") + } + + sliceType := sliceTypeMap[header.SliceType] + logger.Printf("debug: %s (%s) slice of %d bytes\n", NALUnitType[nalUnit.Type], sliceType, len(rbsp)) + header.PPSID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PPSID") + } + + if sps.UseSeparateColorPlane { + b, err := br.ReadBits(2) + if err != nil { + return nil, errors.Wrap(err, "could not read ColorPlaneID") + } + header.ColorPlaneID = int(b) + } + // TODO: See 7.4.3 + // header.FrameNum = b.NextField("FrameNum", 0) + if !sps.FrameMbsOnly { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read FieldPic") + } + header.FieldPic = b == 1 + if header.FieldPic { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read BottomField") + } + header.BottomField = b == 1 + } + } + if idrPic { + header.IDRPicID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse IDRPicID") + } + } + if sps.PicOrderCountType == 0 { + b, err := br.ReadBits(sps.Log2MaxPicOrderCntLSBMin4 + 4) + if err != nil { + return nil, errors.Wrap(err, "could not read PicOrderCntLsb") + } + header.PicOrderCntLsb = int(b) + + if pps.BottomFieldPicOrderInFramePresent && !header.FieldPic { + header.DeltaPicOrderCntBottom, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DeltaPicOrderCntBottom") + } + } + } + if sps.PicOrderCountType == 1 && !sps.DeltaPicOrderAlwaysZero { + header.DeltaPicOrderCnt[0], err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DeltaPicOrderCnt") + } + + if pps.BottomFieldPicOrderInFramePresent && !header.FieldPic { + header.DeltaPicOrderCnt[1], err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DeltaPicOrderCnt") + } + } + } + if pps.RedundantPicCntPresent { + header.RedundantPicCnt, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse RedundantPicCnt") + } + } + if sliceType == "B" { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read DirectSpatialMvPred") + } + header.DirectSpatialMvPred = b == 1 + } + if sliceType == "B" || sliceType == "SP" { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read NumRefIdxActiveOverride") + } + header.NumRefIdxActiveOverride = b == 1 + + if header.NumRefIdxActiveOverride { + header.NumRefIdxL0ActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumRefIdxL0ActiveMinus1") + } + if sliceType == "B" { + header.NumRefIdxL1ActiveMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumRefIdxL1ActiveMinus1") + } + } + } + } + + if nalUnit.Type == 20 || nalUnit.Type == 21 { + // Annex H + // H.7.3.3.1.1 + // refPicListMvcModifications() + } else { + // 7.3.3.1 + if header.SliceType%5 != 2 && header.SliceType%5 != 4 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL0") + } + header.RefPicListModificationFlagL0 = b == 1 + + if header.RefPicListModificationFlagL0 { + for header.ModificationOfPicNums != 3 { + header.ModificationOfPicNums, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") + } + + if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { + header.AbsDiffPicNumMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") + } + } else if header.ModificationOfPicNums == 2 { + header.LongTermPicNum, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermPicNum") + } + } + } + } + + } + if header.SliceType%5 == 1 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL1") + } + header.RefPicListModificationFlagL1 = b == 1 + + if header.RefPicListModificationFlagL1 { + for header.ModificationOfPicNums != 3 { + header.ModificationOfPicNums, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") + } + + if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { + header.AbsDiffPicNumMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") + } + } else if header.ModificationOfPicNums == 2 { + header.LongTermPicNum, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermPicNum") + } + } + } + } + } + // refPicListModification() + } + + if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") { + // predWeightTable() + header.LumaLog2WeightDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaLog2WeightDenom") + } + + if header.ChromaArrayType != 0 { + header.ChromaLog2WeightDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaLog2WeightDenom") + } + } + for i := 0; i <= header.NumRefIdxL0ActiveMinus1; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LumaWeightL0Flag") + } + header.LumaWeightL0Flag = b == 1 + + if header.LumaWeightL0Flag { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaWeightL0") + } + header.LumaWeightL0 = append(header.LumaWeightL0, se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaOffsetL0") + } + header.LumaOffsetL0 = append(header.LumaOffsetL0, se) + } + if header.ChromaArrayType != 0 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaWeightL0Flag") + } + header.ChromaWeightL0Flag = b == 1 + + if header.ChromaWeightL0Flag { + header.ChromaWeightL0 = append(header.ChromaWeightL0, []int{}) + header.ChromaOffsetL0 = append(header.ChromaOffsetL0, []int{}) + for j := 0; j < 2; j++ { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaWeightL0") + } + header.ChromaWeightL0[i] = append(header.ChromaWeightL0[i], se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaOffsetL0") + } + header.ChromaOffsetL0[i] = append(header.ChromaOffsetL0[i], se) + } + } + } + } + if header.SliceType%5 == 1 { + for i := 0; i <= header.NumRefIdxL1ActiveMinus1; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LumaWeightL1Flag") + } + header.LumaWeightL1Flag = b == 1 + + if header.LumaWeightL1Flag { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaWeightL1") + } + header.LumaWeightL1 = append(header.LumaWeightL1, se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaOffsetL1") + } + header.LumaOffsetL1 = append(header.LumaOffsetL1, se) + } + if header.ChromaArrayType != 0 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaWeightL1Flag") + } + header.ChromaWeightL1Flag = b == 1 + + if header.ChromaWeightL1Flag { + header.ChromaWeightL1 = append(header.ChromaWeightL1, []int{}) + header.ChromaOffsetL1 = append(header.ChromaOffsetL1, []int{}) + for j := 0; j < 2; j++ { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaWeightL1") + } + header.ChromaWeightL1[i] = append(header.ChromaWeightL1[i], se) + + se, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaOffsetL1") + } + header.ChromaOffsetL1[i] = append(header.ChromaOffsetL1[i], se) + } + } + } + } + } + } // end predWeightTable + if nalUnit.RefIdc != 0 { + // devRefPicMarking() + if idrPic { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read NoOutputOfPriorPicsFlag") + } + header.NoOutputOfPriorPicsFlag = b == 1 + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LongTermReferenceFlag") + } + header.LongTermReferenceFlag = b == 1 + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read AdaptiveRefPicMarkingModeFlag") + } + header.AdaptiveRefPicMarkingModeFlag = b == 1 + + if header.AdaptiveRefPicMarkingModeFlag { + header.MemoryManagementControlOperation, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") + } + for header.MemoryManagementControlOperation != 0 { + if header.MemoryManagementControlOperation == 1 || header.MemoryManagementControlOperation == 3 { + header.DifferenceOfPicNumsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") + } + } + if header.MemoryManagementControlOperation == 2 { + header.LongTermPicNum, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermPicNum") + } + } + if header.MemoryManagementControlOperation == 3 || header.MemoryManagementControlOperation == 6 { + header.LongTermFrameIdx, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse LongTermFrameIdx") + } + } + if header.MemoryManagementControlOperation == 4 { + header.MaxLongTermFrameIdxPlus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxLongTermFrameIdxPlus1") + } + } + } + } + } // end decRefPicMarking + } + if pps.EntropyCodingMode == 1 && sliceType != "I" && sliceType != "SI" { + header.CabacInit, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse CabacInit") + } + } + header.SliceQpDelta, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceQpDelta") + } + + if sliceType == "SP" || sliceType == "SI" { + if sliceType == "SP" { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SpForSwitch") + } + header.SpForSwitch = b == 1 + } + header.SliceQsDelta, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceQsDelta") + } + } + if pps.DeblockingFilterControlPresent { + header.DisableDeblockingFilter, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse DisableDeblockingFilter") + } + + if header.DisableDeblockingFilter != 1 { + header.SliceAlphaC0OffsetDiv2, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceAlphaC0OffsetDiv2") + } + + header.SliceBetaOffsetDiv2, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse SliceBetaOffsetDiv2") + } + } + } + if pps.NumSliceGroupsMinus1 > 0 && pps.SliceGroupMapType >= 3 && pps.SliceGroupMapType <= 5 { + b, err := br.ReadBits(int(math.Ceil(math.Log2(float64(pps.PicSizeInMapUnitsMinus1/pps.SliceGroupChangeRateMinus1 + 1))))) + if err != nil { + return nil, errors.Wrap(err, "could not read SliceGruopChangeCycle") + } + header.SliceGroupChangeCycle = int(b) + } + + sliceContext := &SliceContext{ + NalUnit: nalUnit, + SPS: sps, + PPS: pps, + Slice: &Slice{ + Header: &header, + }, + } + sliceContext.Slice.Data, err = NewSliceData(sliceContext, br) + if err != nil { + return nil, errors.Wrap(err, "could not create slice data") + } + if showPacket { + debugPacket("debug: Header", sliceContext.Slice.Header) + debugPacket("debug: Data", sliceContext.Slice.Data) + } + return sliceContext, nil +} diff --git a/codec/h264/decode/slice_test.go b/codec/h264/decode/slice_test.go new file mode 100644 index 00000000..98b052ef --- /dev/null +++ b/codec/h264/decode/slice_test.go @@ -0,0 +1,49 @@ +package h264 + +import "testing" + +var subWidthCTests = []struct { + in SPS + want int +}{ + {SPS{}, 17}, + {SPS{ChromaFormat: 0}, 17}, + {SPS{ChromaFormat: 1}, 2}, + {SPS{ChromaFormat: 2}, 2}, + {SPS{ChromaFormat: 3}, 1}, + {SPS{ChromaFormat: 3, UseSeparateColorPlane: true}, 17}, + {SPS{ChromaFormat: 999}, 17}, +} + +// TestSubWidthC tests that the correct SubWidthC is returned given +// SPS inputs with various chroma formats. +func TestSubWidthC(t *testing.T) { + for _, tt := range subWidthCTests { + if got := SubWidthC(&tt.in); got != tt.want { + t.Errorf("SubWidthC(%#v) = %d, want %d", tt.in, got, tt.want) + } + } +} + +var subHeightCTests = []struct { + in SPS + want int +}{ + {SPS{}, 17}, + {SPS{ChromaFormat: 0}, 17}, + {SPS{ChromaFormat: 1}, 2}, + {SPS{ChromaFormat: 2}, 1}, + {SPS{ChromaFormat: 3}, 1}, + {SPS{ChromaFormat: 3, UseSeparateColorPlane: true}, 17}, + {SPS{ChromaFormat: 999}, 17}, +} + +// TestSubHeightC tests that the correct SubHeightC is returned given +// SPS inputs with various chroma formats. +func TestSubHeightC(t *testing.T) { + for _, tt := range subHeightCTests { + if got := SubHeightC(&tt.in); got != tt.want { + t.Errorf("SubHeight(%#v) = %d, want %d", tt.in, got, tt.want) + } + } +} diff --git a/codec/h264/decode/sps.go b/codec/h264/decode/sps.go new file mode 100644 index 00000000..20d93c99 --- /dev/null +++ b/codec/h264/decode/sps.go @@ -0,0 +1,690 @@ +package h264 + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ausocean/h264decode/h264/bits" + "github.com/pkg/errors" +) + +// Specification Page 43 7.3.2.1.1 +// Range is always inclusive +// XRange is always exclusive +type SPS struct { + // 8 bits + Profile int + // 6 bits + Constraint0, Constraint1 int + Constraint2, Constraint3 int + Constraint4, Constraint5 int + // 2 bit reserved 0 bits + // 8 bits + Level int + // Range 0 - 31 ; 6 bits + ID int + ChromaFormat int + UseSeparateColorPlane bool + BitDepthLumaMinus8 int + BitDepthChromaMinus8 int + QPrimeYZeroTransformBypass bool + SeqScalingMatrixPresent bool + // Delta is (0-12)-1 ; 4 bits + SeqScalingList []bool // se + // Range 0 - 12; 4 bits + Log2MaxFrameNumMinus4 int + // Range 0 - 2; 2 bits + PicOrderCountType int + // Range 0 - 12; 4 bits + Log2MaxPicOrderCntLSBMin4 int + DeltaPicOrderAlwaysZero bool + // Range (-2^31)+1 to (2^31)-1 ; 31 bits + OffsetForNonRefPic int // Value - 1 (se) + // Range (-2^31)+1 to (2^31)-1 ; 31 bits + OffsetForTopToBottomField int // Value - 1 (se) + // Range 0 - 255 ; 8 bits + NumRefFramesInPicOrderCntCycle int + // Range (-2^31)+1 to (2^31)-1 ; 31 bits + OffsetForRefFrameList []int // Value - 1 ([]se) + // Range 0 - MaxDpbFrames + MaxNumRefFrames int + GapsInFrameNumValueAllowed bool + // Page 77 + PicWidthInMbsMinus1 int + // Page 77 + PicHeightInMapUnitsMinus1 int + FrameMbsOnly bool + MBAdaptiveFrameField bool + Direct8x8Inference bool + FrameCropping bool + FrameCropLeftOffset int + FrameCropRightOffset int + FrameCropTopOffset int + FrameCropBottomOffset int + VuiParametersPresent bool + VuiParameters []int + AspectRatioInfoPresent bool + AspectRatio int + SarWidth int + SarHeight int + OverscanInfoPresent bool + OverscanAppropriate bool + VideoSignalTypePresent bool + VideoFormat int + VideoFullRange bool + ColorDescriptionPresent bool + ColorPrimaries int + TransferCharacteristics int + MatrixCoefficients int + ChromaLocInfoPresent bool + ChromaSampleLocTypeTopField int + ChromaSampleLocTypeBottomField int + CpbCntMinus1 int + BitRateScale int + CpbSizeScale int + BitRateValueMinus1 []int + Cbr []bool + InitialCpbRemovalDelayLengthMinus1 int + CpbRemovalDelayLengthMinus1 int + CpbSizeValueMinus1 []int + DpbOutputDelayLengthMinus1 int + TimeOffsetLength int + TimingInfoPresent bool + NumUnitsInTick int + TimeScale int + NalHrdParametersPresent bool + FixedFrameRate bool + VclHrdParametersPresent bool + LowHrdDelay bool + PicStructPresent bool + BitstreamRestriction bool + MotionVectorsOverPicBoundaries bool + MaxBytesPerPicDenom int + MaxBitsPerMbDenom int + Log2MaxMvLengthHorizontal int + Log2MaxMvLengthVertical int + MaxDecFrameBuffering int + MaxNumReorderFrames int +} + +var ( + DefaultScalingMatrix4x4 = [][]int{ + {6, 13, 20, 28, 13, 20, 28, 32, 20, 28, 32, 37, 28, 32, 37, 42}, + {10, 14, 20, 24, 14, 20, 24, 27, 20, 24, 27, 30, 24, 27, 30, 34}, + } + + DefaultScalingMatrix8x8 = [][]int{ + {6, 10, 13, 16, 18, 23, 25, 27, + 10, 11, 16, 18, 23, 25, 27, 29, + 13, 16, 18, 23, 25, 27, 29, 31, + 16, 18, 23, 25, 27, 29, 31, 33, + 18, 23, 25, 27, 29, 31, 33, 36, + 23, 25, 27, 29, 31, 33, 36, 38, + 25, 27, 29, 31, 33, 36, 38, 40, + 27, 29, 31, 33, 36, 38, 40, 42}, + {9, 13, 15, 17, 19, 21, 22, 24, + 13, 13, 17, 19, 21, 22, 24, 25, + 15, 17, 19, 21, 22, 24, 25, 27, + 17, 19, 21, 22, 24, 25, 27, 28, + 19, 21, 22, 24, 25, 27, 28, 30, + 21, 22, 24, 25, 27, 28, 30, 32, + 22, 24, 25, 27, 28, 30, 32, 33, + 24, 25, 27, 28, 30, 32, 33, 35}, + } + Default4x4IntraList = []int{6, 13, 13, 20, 20, 20, 38, 38, 38, 38, 32, 32, 32, 37, 37, 42} + Default4x4InterList = []int{10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34} + Default8x8IntraList = []int{ + 6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, + 23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, + 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31, + 31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42} + Default8x8InterList = []int{ + 9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, + 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, + 27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35} + ScalingList4x4 = map[int][]int{ + 0: Default4x4IntraList, + 1: Default4x4IntraList, + 2: Default4x4IntraList, + 3: Default4x4InterList, + 4: Default4x4InterList, + 5: Default4x4InterList, + 6: Default8x8IntraList, + 7: Default8x8InterList, + 8: Default8x8IntraList, + 9: Default8x8InterList, + 10: Default8x8IntraList, + 11: Default8x8InterList, + } + ScalingList8x8 = ScalingList4x4 +) + +func isInList(l []int, term int) bool { + for _, m := range l { + if m == term { + return true + } + } + return false +} +func debugPacket(name string, packet interface{}) { + logger.Printf("debug: %s packet\n", name) + for _, line := range strings.Split(fmt.Sprintf("%+v", packet), " ") { + logger.Printf("debug: \t%#v\n", line) + } +} +func scalingList(b *bits.BitReader, scalingList []int, sizeOfScalingList int, defaultScalingMatrix []int) error { + lastScale := 8 + nextScale := 8 + for i := 0; i < sizeOfScalingList; i++ { + if nextScale != 0 { + deltaScale, err := readSe(nil) + if err != nil { + return errors.Wrap(err, "could not parse deltaScale") + } + nextScale = (lastScale + deltaScale + 256) % 256 + if i == 0 && nextScale == 0 { + // Scaling list should use the default list for this point in the matrix + _ = defaultScalingMatrix + } + } + if nextScale == 0 { + scalingList[i] = lastScale + } else { + scalingList[i] = nextScale + } + lastScale = scalingList[i] + } + return nil +} +func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { + logger.Printf("debug: SPS RBSP %d bytes %d bits\n", len(rbsp), len(rbsp)*8) + logger.Printf("debug: \t%#v\n", rbsp[0:8]) + sps := SPS{} + br := bits.NewBitReader(bytes.NewReader(rbsp)) + var err error + hrdParameters := func() error { + sps.CpbCntMinus1, err = readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse CpbCntMinus1") + } + + err := readFields(br, []field{ + {&sps.BitRateScale, "BitRateScale", 4}, + {&sps.CpbSizeScale, "CpbSizeScale", 4}, + }) + if err != nil { + return err + } + + // SchedSelIdx E1.2 + for sseli := 0; sseli <= sps.CpbCntMinus1; sseli++ { + ue, err := readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse BitRateValueMinus1") + } + sps.BitRateValueMinus1 = append(sps.BitRateValueMinus1, ue) + + ue, err = readUe(nil) + if err != nil { + return errors.Wrap(err, "could not parse CpbSizeValueMinus1") + } + sps.CpbSizeValueMinus1 = append(sps.CpbSizeValueMinus1, ue) + + if v, _ := br.ReadBits(1); v == 1 { + sps.Cbr = append(sps.Cbr, true) + } else { + sps.Cbr = append(sps.Cbr, false) + } + + err = readFields(br, + []field{ + {&sps.InitialCpbRemovalDelayLengthMinus1, "InitialCpbRemovalDelayLengthMinus1", 5}, + {&sps.CpbRemovalDelayLengthMinus1, "CpbRemovalDelayLengthMinus1", 5}, + {&sps.DpbOutputDelayLengthMinus1, "DpbOutputDelayLengthMinus1", 5}, + {&sps.TimeOffsetLength, "TimeOffsetLength", 5}, + }, + ) + if err != nil { + return err + } + } + return nil + } + + err = readFields(br, + []field{ + {&sps.Profile, "ProfileIDC", 8}, + {&sps.Constraint0, "Constraint0", 1}, + {&sps.Constraint1, "Constraint1", 1}, + {&sps.Constraint2, "Constraint2", 1}, + {&sps.Constraint3, "Constraint3", 1}, + {&sps.Constraint4, "Constraint4", 1}, + {&sps.Constraint5, "Constraint5", 1}, + }, + ) + + _, err = br.ReadBits(2) + if err != nil { + return nil, errors.Wrap(err, "could not read ReservedZeroBits") + } + + b, err := br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read Level") + } + sps.Level = int(b) + + // sps.ID = b.NextField("SPSID", 6) // proper + sps.ID, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ID") + } + + sps.ChromaFormat, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaFormat") + } + + // This should be done only for certain ProfileIDC: + isProfileIDC := []int{100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135} + // SpecialProfileCase1 + if isInList(isProfileIDC, sps.Profile) { + if sps.ChromaFormat == chroma444 { + // TODO: should probably deal with error here. + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read UseSeparateColorPlaneFlag") + } + sps.UseSeparateColorPlane = b == 1 + } + + sps.BitDepthLumaMinus8, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse BitDepthLumaMinus8") + } + + sps.BitDepthChromaMinus8, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse BitDepthChromaMinus8") + } + + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read QPrimeYZeroTransformBypass") + } + sps.QPrimeYZeroTransformBypass = b == 1 + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SeqScalingMatrixPresent") + } + sps.SeqScalingMatrixPresent = b == 1 + + if sps.SeqScalingMatrixPresent { + max := 12 + if sps.ChromaFormat != chroma444 { + max = 8 + } + logger.Printf("debug: \tbuilding Scaling matrix for %d elements\n", max) + for i := 0; i < max; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read SeqScalingList") + } + sps.SeqScalingList = append(sps.SeqScalingList, b == 1) + + if sps.SeqScalingList[i] { + if i < 6 { + scalingList( + br, + ScalingList4x4[i], + 16, + DefaultScalingMatrix4x4[i]) + // 4x4: Page 75 bottom + } else { + // 8x8 Page 76 top + scalingList( + br, + ScalingList8x8[i], + 64, + DefaultScalingMatrix8x8[i-6]) + } + } + } + } + } // End SpecialProfileCase1 + + // showSPS() + // return sps + // Possibly wrong due to no scaling list being built + sps.Log2MaxFrameNumMinus4, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxFrameNumMinus4") + } + + sps.PicOrderCountType, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicOrderCountType") + } + + if sps.PicOrderCountType == 0 { + sps.Log2MaxPicOrderCntLSBMin4, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxPicOrderCntLSBMin4") + } + } else if sps.PicOrderCountType == 1 { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read DeltaPicOrderAlwaysZero") + } + sps.DeltaPicOrderAlwaysZero = b == 1 + + sps.OffsetForNonRefPic, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse OffsetForNonRefPic") + } + + sps.OffsetForTopToBottomField, err = readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse OffsetForTopToBottomField") + } + + sps.NumRefFramesInPicOrderCntCycle, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse NumRefFramesInPicOrderCntCycle") + } + + for i := 0; i < sps.NumRefFramesInPicOrderCntCycle; i++ { + se, err := readSe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse OffsetForRefFrameList") + } + sps.OffsetForRefFrameList = append( + sps.OffsetForRefFrameList, + se) + } + + } + + sps.MaxNumRefFrames, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxNumRefFrames") + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read GapsInFrameNumValueAllowed") + } + sps.GapsInFrameNumValueAllowed = b == 1 + + sps.PicWidthInMbsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicWidthInMbsMinus1") + } + + sps.PicHeightInMapUnitsMinus1, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse PicHeightInMapUnitsMinus1") + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read FrameMbsOnly") + } + sps.FrameMbsOnly = b == 1 + + if !sps.FrameMbsOnly { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MBAdaptiveFrameField") + } + sps.MBAdaptiveFrameField = b == 1 + } + + err = readFlags(br, []flag{ + {&sps.Direct8x8Inference, "Direct8x8Inference"}, + {&sps.FrameCropping, "FrameCropping"}, + }) + if err != nil { + return nil, err + } + + if sps.FrameCropping { + sps.FrameCropLeftOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropLeftOffset") + } + + sps.FrameCropRightOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropRightOffset") + } + + sps.FrameCropTopOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropTopOffset") + } + + sps.FrameCropBottomOffset, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse FrameCropBottomOffset") + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VuiParametersPresent") + } + sps.VuiParametersPresent = b == 1 + + if sps.VuiParametersPresent { + // vui_parameters + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read AspectRatioInfoPresent") + } + sps.AspectRatioInfoPresent = b == 1 + + if sps.AspectRatioInfoPresent { + b, err = br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read AspectRatio") + } + sps.AspectRatio = int(b) + + EXTENDED_SAR := 999 + if sps.AspectRatio == EXTENDED_SAR { + b, err = br.ReadBits(16) + if err != nil { + return nil, errors.Wrap(err, "could not read SarWidth") + } + sps.SarWidth = int(b) + + b, err = br.ReadBits(16) + if err != nil { + return nil, errors.Wrap(err, "could not read SarHeight") + } + sps.SarHeight = int(b) + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read OverscanInfoPresent") + } + sps.OverscanInfoPresent = b == 1 + + if sps.OverscanInfoPresent { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read OverscanAppropriate") + } + sps.OverscanAppropriate = b == 1 + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VideoSignalTypePresent") + } + sps.VideoSignalTypePresent = b == 1 + + if sps.VideoSignalTypePresent { + b, err = br.ReadBits(3) + if err != nil { + return nil, errors.Wrap(err, "could not read VideoFormat") + } + sps.VideoFormat = int(b) + } + + if sps.VideoSignalTypePresent { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VideoFullRange") + } + sps.VideoFullRange = b == 1 + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ColorDescriptionPresent") + } + sps.ColorDescriptionPresent = b == 1 + + if sps.ColorDescriptionPresent { + err = readFields(br, + []field{ + {&sps.ColorPrimaries, "ColorPrimaries", 8}, + {&sps.TransferCharacteristics, "TransferCharacteristics", 8}, + {&sps.MatrixCoefficients, "MatrixCoefficients", 8}, + }, + ) + if err != nil { + return nil, err + } + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaLocInfoPresent") + } + sps.ChromaLocInfoPresent = b == 1 + + if sps.ChromaLocInfoPresent { + sps.ChromaSampleLocTypeTopField, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaSampleLocTypeTopField") + } + + sps.ChromaSampleLocTypeBottomField, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaSampleLocTypeBottomField") + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read TimingInfoPresent") + } + sps.TimingInfoPresent = b == 1 + + if sps.TimingInfoPresent { + err := readFields(br, []field{ + {&sps.NumUnitsInTick, "NumUnitsInTick", 32}, + {&sps.TimeScale, "TimeScale", 32}, + }) + if err != nil { + return nil, err + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read FixedFrameRate") + } + sps.FixedFrameRate = b == 1 + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read NalHrdParametersPresent") + } + sps.NalHrdParametersPresent = b == 1 + + if sps.NalHrdParametersPresent { + err = hrdParameters() + if err != nil { + return nil, errors.Wrap(err, "could not get hrdParameters") + } + } + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read VclHrdParametersPresent") + } + sps.VclHrdParametersPresent = b == 1 + + if sps.VclHrdParametersPresent { + err = hrdParameters() + if err != nil { + return nil, errors.Wrap(err, "could not get hrdParameters") + } + } + if sps.NalHrdParametersPresent || sps.VclHrdParametersPresent { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LowHrdDelay") + } + sps.LowHrdDelay = b == 1 + } + + err := readFlags(br, []flag{ + {&sps.PicStructPresent, "PicStructPresent"}, + {&sps.BitstreamRestriction, "BitStreamRestriction"}, + }) + + if sps.BitstreamRestriction { + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read MotionVectorsOverPicBoundaries") + } + sps.MotionVectorsOverPicBoundaries = b == 1 + + sps.MaxBytesPerPicDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxBytesPerPicDenom") + } + + sps.MaxBitsPerMbDenom, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxBitsPerMbDenom") + } + + sps.Log2MaxMvLengthHorizontal, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthHorizontal") + } + + sps.Log2MaxMvLengthVertical, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthVertical") + } + + sps.MaxNumReorderFrames, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxNumReorderFrames") + } + + sps.MaxDecFrameBuffering, err = readUe(nil) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxDecFrameBuffering") + } + } + + } // End VuiParameters Annex E.1.1 + if showPacket { + debugPacket("SPS", sps) + } + return &sps, nil +} diff --git a/codec/h264/decode/stateTransxTab.go b/codec/h264/decode/stateTransxTab.go new file mode 100644 index 00000000..31f15fcd --- /dev/null +++ b/codec/h264/decode/stateTransxTab.go @@ -0,0 +1,74 @@ +package h264 + +type StateTransx struct { + TransIdxLPS, TransIdxMPS int +} + +// 9-45 +// [pStateIdx]{TransIdxLPS, TransIdxMPS} +var stateTransxTab = map[int]StateTransx{ + 0: {0, 1}, + 1: {0, 2}, + 2: {1, 3}, + 3: {2, 4}, + 4: {2, 5}, + 5: {4, 6}, + 6: {4, 7}, + 7: {5, 8}, + 8: {6, 9}, + 9: {7, 10}, + 10: {8, 11}, + 11: {9, 12}, + 12: {9, 13}, + 13: {11, 14}, + 14: {11, 15}, + 15: {12, 16}, + 16: {13, 17}, + 17: {13, 18}, + 18: {15, 19}, + 19: {15, 20}, + 20: {16, 21}, + 21: {16, 22}, + 22: {18, 23}, + 23: {18, 24}, + 24: {19, 25}, + 25: {19, 26}, + 26: {21, 27}, + 27: {21, 28}, + 28: {22, 29}, + 29: {22, 30}, + 30: {23, 31}, + 31: {24, 32}, + 32: {24, 33}, + 33: {25, 34}, + 34: {26, 35}, + 35: {26, 36}, + 36: {27, 37}, + 37: {27, 38}, + 38: {28, 39}, + 39: {29, 40}, + 40: {29, 41}, + 41: {30, 42}, + 42: {30, 43}, + 43: {30, 44}, + 44: {31, 45}, + 45: {32, 46}, + 46: {32, 47}, + 47: {33, 48}, + 48: {33, 49}, + 49: {33, 50}, + 50: {34, 51}, + 51: {34, 52}, + 52: {35, 53}, + 53: {35, 54}, + 54: {35, 55}, + 55: {36, 56}, + 56: {36, 57}, + 57: {36, 58}, + 58: {37, 59}, + 59: {37, 61}, + 60: {37, 61}, + 61: {38, 62}, + 62: {38, 62}, + 63: {63, 63}, +} From 04de967aa89c24411c2e37f655d28790a5eb18f1 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 12:09:30 +0930 Subject: [PATCH 56/93] h264: renamed files to make more go like --- codec/h264/decode/{mbType.go => mbtype.go} | 0 codec/h264/decode/{mn_vars.go => mnvars.go} | 0 codec/h264/decode/{nalUnit.go => nalunit.go} | 0 codec/h264/decode/{rangeTabLPS.go => rangetablps.go} | 0 codec/h264/decode/{stateTransxTab.go => statetransxtab.go} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename codec/h264/decode/{mbType.go => mbtype.go} (100%) rename codec/h264/decode/{mn_vars.go => mnvars.go} (100%) rename codec/h264/decode/{nalUnit.go => nalunit.go} (100%) rename codec/h264/decode/{rangeTabLPS.go => rangetablps.go} (100%) rename codec/h264/decode/{stateTransxTab.go => statetransxtab.go} (100%) diff --git a/codec/h264/decode/mbType.go b/codec/h264/decode/mbtype.go similarity index 100% rename from codec/h264/decode/mbType.go rename to codec/h264/decode/mbtype.go diff --git a/codec/h264/decode/mn_vars.go b/codec/h264/decode/mnvars.go similarity index 100% rename from codec/h264/decode/mn_vars.go rename to codec/h264/decode/mnvars.go diff --git a/codec/h264/decode/nalUnit.go b/codec/h264/decode/nalunit.go similarity index 100% rename from codec/h264/decode/nalUnit.go rename to codec/h264/decode/nalunit.go diff --git a/codec/h264/decode/rangeTabLPS.go b/codec/h264/decode/rangetablps.go similarity index 100% rename from codec/h264/decode/rangeTabLPS.go rename to codec/h264/decode/rangetablps.go diff --git a/codec/h264/decode/stateTransxTab.go b/codec/h264/decode/statetransxtab.go similarity index 100% rename from codec/h264/decode/stateTransxTab.go rename to codec/h264/decode/statetransxtab.go From 4a9da74ff00b6fd63c67e48e13f80ed84578bcc1 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 13:29:10 +0930 Subject: [PATCH 57/93] codec/h264/decode: did not copy over most up to date version from old repo, so fixing that --- codec/h264/decode/.gitignore | 3 - codec/h264/decode/mbtype.go | 16 +++-- codec/h264/decode/mbtype_test.go | 112 +++++++++++++++++++++++++++++++ codec/h264/decode/parse.go | 15 +++-- codec/h264/decode/parse_test.go | 10 +-- 5 files changed, 138 insertions(+), 18 deletions(-) delete mode 100644 codec/h264/decode/.gitignore create mode 100644 codec/h264/decode/mbtype_test.go diff --git a/codec/h264/decode/.gitignore b/codec/h264/decode/.gitignore deleted file mode 100644 index 86ede22e..00000000 --- a/codec/h264/decode/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.*.swp -*.mp4 -*.h264 diff --git a/codec/h264/decode/mbtype.go b/codec/h264/decode/mbtype.go index 1e266446..2c3bc281 100644 --- a/codec/h264/decode/mbtype.go +++ b/codec/h264/decode/mbtype.go @@ -93,6 +93,7 @@ func MbTypeName(sliceType string, mbType int) string { var ( errNaMode = errors.New("no mode for given slice and mb type") errPartition = errors.New("partition must be 0") + errSliceType = errors.New("bad sliceType") ) // MbPartPredMode returns a macroblock partition prediction mode for the given @@ -111,8 +112,11 @@ func MbPartPredMode(data *SliceData, sliceType string, mbType, partition int) (m if mbType > 0 && mbType < 25 { return intra16x16, nil } - return -1, errNaMode + return naMbPartPredMode, errNaMode case "SI": + if mbType != 0 { + return naMbPartPredMode, errNaMode + } return intra4x4, nil case "P": fallthrough @@ -120,7 +124,7 @@ func MbPartPredMode(data *SliceData, sliceType string, mbType, partition int) (m if mbType >= 0 && mbType < 3 { return predL0, nil } else if mbType == 3 || mbType == 4 { - return -1, errNaMode + return naMbPartPredMode, errNaMode } else { return predL0, nil } @@ -128,6 +132,8 @@ func MbPartPredMode(data *SliceData, sliceType string, mbType, partition int) (m switch mbType { case 0: return direct, nil + case 3: + return biPred, nil case 1: fallthrough case 4: @@ -157,14 +163,16 @@ func MbPartPredMode(data *SliceData, sliceType string, mbType, partition int) (m case 15: return predL1, nil case 22: - return -1, errNaMode + return naMbPartPredMode, errNaMode default: if mbType > 15 && mbType < 22 { return biPred, nil } return direct, nil } + default: + return naMbPartPredMode, errSliceType } } - return -1, errPartition + return naMbPartPredMode, errPartition } diff --git a/codec/h264/decode/mbtype_test.go b/codec/h264/decode/mbtype_test.go new file mode 100644 index 00000000..d735f9c8 --- /dev/null +++ b/codec/h264/decode/mbtype_test.go @@ -0,0 +1,112 @@ +/* +NAME + parse.go + +DESCRIPTION + mbtype_test.go provides testing for functions provided in mbtype.go. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) +*/ +package h264 + +import ( + "testing" +) + +func TestMbPartPredMode(t *testing.T) { + tests := []struct { + sliceType string + mbType int + data *SliceData + want mbPartPredMode + err error + }{ + // Table 7-11 (I-slices). + 0: {"I", 0, &SliceData{TransformSize8x8Flag: false}, intra4x4, nil}, + 1: {"I", 0, &SliceData{TransformSize8x8Flag: true}, intra8x8, nil}, + 2: {"I", 1, nil, intra16x16, nil}, + 3: {"I", 2, nil, intra16x16, nil}, + 4: {"I", 3, nil, intra16x16, nil}, + 5: {"I", 4, nil, intra16x16, nil}, + 6: {"I", 5, nil, intra16x16, nil}, + 7: {"I", 6, nil, intra16x16, nil}, + 8: {"I", 7, nil, intra16x16, nil}, + 9: {"I", 8, nil, intra16x16, nil}, + 10: {"I", 9, nil, intra16x16, nil}, + 11: {"I", 10, nil, intra16x16, nil}, + 12: {"I", 11, nil, intra16x16, nil}, + 13: {"I", 12, nil, intra16x16, nil}, + 14: {"I", 13, nil, intra16x16, nil}, + 15: {"I", 14, nil, intra16x16, nil}, + 16: {"I", 15, nil, intra16x16, nil}, + 17: {"I", 16, nil, intra16x16, nil}, + 18: {"I", 17, nil, intra16x16, nil}, + 19: {"I", 18, nil, intra16x16, nil}, + 20: {"I", 19, nil, intra16x16, nil}, + 21: {"I", 20, nil, intra16x16, nil}, + 22: {"I", 21, nil, intra16x16, nil}, + 23: {"I", 22, nil, intra16x16, nil}, + 24: {"I", 23, nil, intra16x16, nil}, + 25: {"I", 24, nil, intra16x16, nil}, + 26: {"I", 25, nil, naMbPartPredMode, errNaMode}, + + // Table 7-12 (SI-slices). + 27: {"SI", 0, nil, intra4x4, nil}, + + // Table 7-13 (SP-slices). + 28: {"SP", 0, nil, predL0, nil}, + 29: {"SP", 1, nil, predL0, nil}, + 30: {"SP", 2, nil, predL0, nil}, + 31: {"SP", 3, nil, naMbPartPredMode, errNaMode}, + 32: {"SP", 4, nil, naMbPartPredMode, errNaMode}, + + // Table 7-14 (B-slices). + 33: {"B", 0, nil, direct, nil}, + 34: {"B", 1, nil, predL0, nil}, + 35: {"B", 2, nil, predL1, nil}, + 36: {"B", 3, nil, biPred, nil}, + 37: {"B", 4, nil, predL0, nil}, + 38: {"B", 5, nil, predL0, nil}, + 39: {"B", 6, nil, predL1, nil}, + 40: {"B", 7, nil, predL1, nil}, + 41: {"B", 8, nil, predL0, nil}, + 42: {"B", 9, nil, predL0, nil}, + 43: {"B", 10, nil, predL1, nil}, + 44: {"B", 11, nil, predL1, nil}, + 45: {"B", 12, nil, predL0, nil}, + 46: {"B", 13, nil, predL0, nil}, + 47: {"B", 14, nil, predL1, nil}, + 48: {"B", 15, nil, predL1, nil}, + 49: {"B", 16, nil, biPred, nil}, + 50: {"B", 17, nil, biPred, nil}, + 51: {"B", 18, nil, biPred, nil}, + 52: {"B", 19, nil, biPred, nil}, + 53: {"B", 20, nil, biPred, nil}, + 54: {"B", 21, nil, biPred, nil}, + 55: {"B", 22, nil, naMbPartPredMode, errNaMode}, + + // Test some weird cases where we expect error. + 56: {"O", 0, nil, naMbPartPredMode, errSliceType}, + 57: {"I", 26, nil, naMbPartPredMode, errNaMode}, + 58: {"I", -1, nil, naMbPartPredMode, errNaMode}, + 59: {"SI", 1, nil, naMbPartPredMode, errNaMode}, + + // Cases for inferred mbtype. + 60: {"SP", 5, nil, predL0, nil}, + 61: {"SP", -1, nil, predL0, nil}, + 62: {"B", -1, nil, direct, nil}, + 63: {"B", 23, nil, direct, nil}, + } + + for i, test := range tests { + m, err := MbPartPredMode(test.data, test.sliceType, test.mbType, 0) + if err != test.err { + t.Errorf("unexpected error from test %d.\nGot: %v\nWant: %v\n", i, err, test.err) + } + + if m != test.want { + t.Errorf("did not get expected result for test %d.\nGot: %v\nWant: %v\n", i, m, test.want) + } + } +} diff --git a/codec/h264/decode/parse.go b/codec/h264/decode/parse.go index 18245f3e..24ff222b 100644 --- a/codec/h264/decode/parse.go +++ b/codec/h264/decode/parse.go @@ -16,10 +16,12 @@ package h264 import ( "math" - "github.com/icza/bitio" + "github.com/ausocean/h264decode/h264/bits" "github.com/pkg/errors" ) +// mbPartPredMode represents a macroblock partition prediction mode. +// Modes are defined as consts below. These modes are in section 7.4.5. type mbPartPredMode int8 const ( @@ -31,6 +33,7 @@ const ( direct biPred inter + naMbPartPredMode ) // readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer @@ -38,7 +41,7 @@ const ( // // TODO: this should return uint, but rest of code needs to be changed for this // to happen. -func readUe(r bitio.Reader) (int, error) { +func readUe(r *bits.BitReader) (int, error) { nZeros := -1 var err error for b := uint64(0); b == 0; nZeros++ { @@ -47,7 +50,7 @@ func readUe(r bitio.Reader) (int, error) { return 0, err } } - rem, err := r.ReadBits(byte(nZeros)) + rem, err := r.ReadBits(int(nZeros)) if err != nil { return 0, err } @@ -59,7 +62,7 @@ func readUe(r bitio.Reader) (int, error) { // Rec. ITU-T H.264 (04/2017). // // TODO: this should also return uint. -func readTe(r bitio.Reader, x uint) (int, error) { +func readTe(r *bits.BitReader, x uint) (int, error) { if x > 1 { return readUe(r) } @@ -83,7 +86,7 @@ var errReadTeBadX = errors.New("x must be more than or equal to 1") // readSe parses a syntax element with descriptor se(v), i.e. a signed integer // Exp-Golomb-coded syntax element, using the method described in sections // 9.1 and 9.1.1 in Rec. ITU-T H.264 (04/2017). -func readSe(r bitio.Reader) (int, error) { +func readSe(r *bits.BitReader) (int, error) { codeNum, err := readUe(r) if err != nil { return 0, errors.Wrap(err, "error reading ue(v)") @@ -95,7 +98,7 @@ func readSe(r bitio.Reader) (int, error) { // readMe parses a syntax element of me(v) descriptor, i.e. mapped // Exp-Golomb-coded element, using methods described in sections 9.1 and 9.1.2 // in Rec. ITU-T H.264 (04/2017). -func readMe(r bitio.Reader, chromaArrayType uint, mpm mbPartPredMode) (uint, error) { +func readMe(r *bits.BitReader, chromaArrayType uint, mpm mbPartPredMode) (uint, error) { // Indexes to codedBlockPattern map. var i1, i2, i3 int diff --git a/codec/h264/decode/parse_test.go b/codec/h264/decode/parse_test.go index 4356fa50..cc2d47e0 100644 --- a/codec/h264/decode/parse_test.go +++ b/codec/h264/decode/parse_test.go @@ -15,7 +15,7 @@ import ( "bytes" "testing" - "github.com/icza/bitio" + "github.com/ausocean/h264decode/h264/bits" ) // TestReadUe checks that readUe correctly parses an Exp-Golomb-coded element @@ -41,7 +41,7 @@ func TestReadUe(t *testing.T) { } for i, test := range tests { - got, err := readUe(bitio.NewReader(bytes.NewReader(test.in))) + got, err := readUe(bits.NewBitReader(bytes.NewReader(test.in))) if err != nil { t.Fatalf("did not expect error: %v from readUe", err) } @@ -69,7 +69,7 @@ func TestReadTe(t *testing.T) { } for i, test := range tests { - got, err := readTe(bitio.NewReader(bytes.NewReader(test.in)), test.x) + got, err := readTe(bits.NewBitReader(bytes.NewReader(test.in)), test.x) if err != test.err { t.Fatalf("did not get expected error for test: %v\nGot: %v\nWant: %v\n", i, err, test.err) } @@ -99,7 +99,7 @@ func TestReadSe(t *testing.T) { } for i, test := range tests { - got, err := readSe(bitio.NewReader(bytes.NewReader(test.in))) + got, err := readSe(bits.NewBitReader(bytes.NewReader(test.in))) if err != nil { t.Fatalf("did not expect error: %v from readSe", err) } @@ -142,7 +142,7 @@ func TestReadMe(t *testing.T) { } for i, test := range tests { - got, err := readMe(bitio.NewReader(bytes.NewReader(test.in)), test.cat, test.mpm) + got, err := readMe(bits.NewBitReader(bytes.NewReader(test.in)), test.cat, test.mpm) if err != test.err { t.Fatalf("did not expect to get error: %v for test: %v", err, i) } From ca717dddafb9c31088951a289f59e866e40e9a93 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 14:36:50 +0930 Subject: [PATCH 58/93] codec/h264/decode: giving parsing function calls BitReaders instead of nil --- codec/h264/decode/pps.go | 30 ++++++------- codec/h264/decode/slice.go | 92 +++++++++++++++++++------------------- codec/h264/decode/sps.go | 62 ++++++++++++------------- 3 files changed, 92 insertions(+), 92 deletions(-) diff --git a/codec/h264/decode/pps.go b/codec/h264/decode/pps.go index ebc3d45f..4e659649 100644 --- a/codec/h264/decode/pps.go +++ b/codec/h264/decode/pps.go @@ -49,12 +49,12 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { br := bits.NewBitReader(nil) var err error - pps.ID, err = readUe(nil) + pps.ID, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ID") } - pps.SPSID, err = readUe(nil) + pps.SPSID, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SPS ID") } @@ -71,27 +71,27 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { } pps.BottomFieldPicOrderInFramePresent = b == 1 - pps.NumSliceGroupsMinus1, err = readUe(nil) + pps.NumSliceGroupsMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse NumSliceGroupsMinus1") } if pps.NumSliceGroupsMinus1 > 0 { - pps.SliceGroupMapType, err = readUe(nil) + pps.SliceGroupMapType, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SliceGroupMapType") } if pps.SliceGroupMapType == 0 { for iGroup := 0; iGroup <= pps.NumSliceGroupsMinus1; iGroup++ { - pps.RunLengthMinus1[iGroup], err = readUe(nil) + pps.RunLengthMinus1[iGroup], err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse RunLengthMinus1") } } } else if pps.SliceGroupMapType == 2 { for iGroup := 0; iGroup < pps.NumSliceGroupsMinus1; iGroup++ { - pps.TopLeft[iGroup], err = readUe(nil) + pps.TopLeft[iGroup], err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse TopLeft[iGroup]") } @@ -99,7 +99,7 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { return nil, errors.Wrap(err, "could not parse TopLeft[iGroup]") } - pps.BottomRight[iGroup], err = readUe(nil) + pps.BottomRight[iGroup], err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse BottomRight[iGroup]") } @@ -111,12 +111,12 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { } pps.SliceGroupChangeDirection = b == 1 - pps.SliceGroupChangeRateMinus1, err = readUe(nil) + pps.SliceGroupChangeRateMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SliceGroupChangeRateMinus1") } } else if pps.SliceGroupMapType == 6 { - pps.PicSizeInMapUnitsMinus1, err = readUe(nil) + pps.PicSizeInMapUnitsMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse PicSizeInMapUnitsMinus1") } @@ -131,12 +131,12 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { } } - pps.NumRefIdxL0DefaultActiveMinus1, err = readUe(nil) + pps.NumRefIdxL0DefaultActiveMinus1, err = readUe(br) if err != nil { return nil, errors.New("could not parse NumRefIdxL0DefaultActiveMinus1") } - pps.NumRefIdxL1DefaultActiveMinus1, err = readUe(nil) + pps.NumRefIdxL1DefaultActiveMinus1, err = readUe(br) if err != nil { return nil, errors.New("could not parse NumRefIdxL1DefaultActiveMinus1") } @@ -153,17 +153,17 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { } pps.WeightedBipred = int(b) - pps.PicInitQpMinus26, err = readSe(nil) + pps.PicInitQpMinus26, err = readSe(br) if err != nil { return nil, errors.New("could not parse PicInitQpMinus26") } - pps.PicInitQsMinus26, err = readSe(nil) + pps.PicInitQsMinus26, err = readSe(br) if err != nil { return nil, errors.New("could not parse PicInitQsMinus26") } - pps.ChromaQpIndexOffset, err = readSe(nil) + pps.ChromaQpIndexOffset, err = readSe(br) if err != nil { return nil, errors.New("could not parse ChromaQpIndexOffset") } @@ -222,7 +222,7 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { } } } - pps.SecondChromaQpIndexOffset, err = readSe(nil) + pps.SecondChromaQpIndexOffset, err = readSe(br) if err != nil { return nil, errors.New("could not parse SecondChromaQpIndexOffset") } diff --git a/codec/h264/decode/slice.go b/codec/h264/decode/slice.go index 251989cc..4e669917 100644 --- a/codec/h264/decode/slice.go +++ b/codec/h264/decode/slice.go @@ -373,7 +373,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { logger.Printf("TODO: ae for IntraChromaPredMode\n") } else { var err error - sliceContext.Slice.Data.IntraChromaPredMode, err = readUe(nil) + sliceContext.Slice.Data.IntraChromaPredMode, err = readUe(br) if err != nil { return errors.Wrap(err, "could not parse IntraChromaPredMode") } @@ -406,12 +406,12 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { // then the value should be 0 if MbaffFrameFlag(sliceContext.SPS, sliceContext.Slice.Header) == 0 || !sliceContext.Slice.Data.MbFieldDecodingFlag { sliceContext.Slice.Data.RefIdxL0[mbPartIdx], _ = readTe( - nil, + br, uint(sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1)) } else { rangeMax := 2*sliceContext.Slice.Header.NumRefIdxL0ActiveMinus1 + 1 sliceContext.Slice.Data.RefIdxL0[mbPartIdx], _ = readTe( - nil, + br, uint(rangeMax)) } } @@ -451,7 +451,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { } logger.Printf("TODO: ae for MvdL0[%d][0][%d]\n", mbPartIdx, compIdx) } else { - sliceContext.Slice.Data.MvdL0[mbPartIdx][0][compIdx], _ = readSe(nil) + sliceContext.Slice.Data.MvdL0[mbPartIdx][0][compIdx], _ = readSe(br) } } } @@ -491,7 +491,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { // TODO: se(v) or ae(v) logger.Printf("TODO: ae for MvdL1[%d][0][%d]\n", mbPartIdx, compIdx) } else { - sliceContext.Slice.Data.MvdL1[mbPartIdx][0][compIdx], _ = readSe(nil) + sliceContext.Slice.Data.MvdL1[mbPartIdx][0][compIdx], _ = readSe(br) } } } @@ -645,7 +645,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e if sliceContext.Slice.Data.SliceTypeName != "I" && sliceContext.Slice.Data.SliceTypeName != "SI" { logger.Printf("debug: \tNonI/SI slice, processing moreData\n") if sliceContext.PPS.EntropyCodingMode == 0 { - sliceContext.Slice.Data.MbSkipRun, err = readUe(nil) + sliceContext.Slice.Data.MbSkipRun, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MbSkipRun") } @@ -762,7 +762,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e logger.Printf("TODO: ae for MBType\n") } else { - sliceContext.Slice.Data.MbType, err = readUe(nil) + sliceContext.Slice.Data.MbType, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MbType") } @@ -867,7 +867,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e logger.Printf("TODO: ae for CodedBlockPattern\n") } else { me, _ := readMe( - nil, + br, uint(sliceContext.Slice.Header.ChromaArrayType), // TODO: fix this //MbPartPredMode(sliceContext.Slice.Data, sliceContext.Slice.Data.SliceTypeName, sliceContext.Slice.Data.MbType, 0))) @@ -908,7 +908,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e logger.Printf("TODO: ae for MbQpDelta\n") } else { - sliceContext.Slice.Data.MbQpDelta, _ = readSe(nil) + sliceContext.Slice.Data.MbQpDelta, _ = readSe(br) } } @@ -963,19 +963,19 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } br := bits.NewBitReader(bytes.NewReader(rbsp)) - header.FirstMbInSlice, err = readUe(nil) + header.FirstMbInSlice, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse FirstMbInSlice") } - header.SliceType, err = readUe(nil) + header.SliceType, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SliceType") } sliceType := sliceTypeMap[header.SliceType] logger.Printf("debug: %s (%s) slice of %d bytes\n", NALUnitType[nalUnit.Type], sliceType, len(rbsp)) - header.PPSID, err = readUe(nil) + header.PPSID, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse PPSID") } @@ -1004,7 +1004,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } } if idrPic { - header.IDRPicID, err = readUe(nil) + header.IDRPicID, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse IDRPicID") } @@ -1017,27 +1017,27 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh header.PicOrderCntLsb = int(b) if pps.BottomFieldPicOrderInFramePresent && !header.FieldPic { - header.DeltaPicOrderCntBottom, err = readSe(nil) + header.DeltaPicOrderCntBottom, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse DeltaPicOrderCntBottom") } } } if sps.PicOrderCountType == 1 && !sps.DeltaPicOrderAlwaysZero { - header.DeltaPicOrderCnt[0], err = readSe(nil) + header.DeltaPicOrderCnt[0], err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse DeltaPicOrderCnt") } if pps.BottomFieldPicOrderInFramePresent && !header.FieldPic { - header.DeltaPicOrderCnt[1], err = readSe(nil) + header.DeltaPicOrderCnt[1], err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse DeltaPicOrderCnt") } } } if pps.RedundantPicCntPresent { - header.RedundantPicCnt, err = readUe(nil) + header.RedundantPicCnt, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse RedundantPicCnt") } @@ -1057,12 +1057,12 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh header.NumRefIdxActiveOverride = b == 1 if header.NumRefIdxActiveOverride { - header.NumRefIdxL0ActiveMinus1, err = readUe(nil) + header.NumRefIdxL0ActiveMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse NumRefIdxL0ActiveMinus1") } if sliceType == "B" { - header.NumRefIdxL1ActiveMinus1, err = readUe(nil) + header.NumRefIdxL1ActiveMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse NumRefIdxL1ActiveMinus1") } @@ -1085,18 +1085,18 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh if header.RefPicListModificationFlagL0 { for header.ModificationOfPicNums != 3 { - header.ModificationOfPicNums, err = readUe(nil) + header.ModificationOfPicNums, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") } if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { - header.AbsDiffPicNumMinus1, err = readUe(nil) + header.AbsDiffPicNumMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") } } else if header.ModificationOfPicNums == 2 { - header.LongTermPicNum, err = readUe(nil) + header.LongTermPicNum, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermPicNum") } @@ -1114,18 +1114,18 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh if header.RefPicListModificationFlagL1 { for header.ModificationOfPicNums != 3 { - header.ModificationOfPicNums, err = readUe(nil) + header.ModificationOfPicNums, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") } if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { - header.AbsDiffPicNumMinus1, err = readUe(nil) + header.AbsDiffPicNumMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") } } else if header.ModificationOfPicNums == 2 { - header.LongTermPicNum, err = readUe(nil) + header.LongTermPicNum, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermPicNum") } @@ -1138,13 +1138,13 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") { // predWeightTable() - header.LumaLog2WeightDenom, err = readUe(nil) + header.LumaLog2WeightDenom, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LumaLog2WeightDenom") } if header.ChromaArrayType != 0 { - header.ChromaLog2WeightDenom, err = readUe(nil) + header.ChromaLog2WeightDenom, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaLog2WeightDenom") } @@ -1157,13 +1157,13 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh header.LumaWeightL0Flag = b == 1 if header.LumaWeightL0Flag { - se, err := readSe(nil) + se, err := readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LumaWeightL0") } header.LumaWeightL0 = append(header.LumaWeightL0, se) - se, err = readSe(nil) + se, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LumaOffsetL0") } @@ -1180,13 +1180,13 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh header.ChromaWeightL0 = append(header.ChromaWeightL0, []int{}) header.ChromaOffsetL0 = append(header.ChromaOffsetL0, []int{}) for j := 0; j < 2; j++ { - se, err := readSe(nil) + se, err := readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaWeightL0") } header.ChromaWeightL0[i] = append(header.ChromaWeightL0[i], se) - se, err = readSe(nil) + se, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaOffsetL0") } @@ -1204,13 +1204,13 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh header.LumaWeightL1Flag = b == 1 if header.LumaWeightL1Flag { - se, err := readSe(nil) + se, err := readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LumaWeightL1") } header.LumaWeightL1 = append(header.LumaWeightL1, se) - se, err = readSe(nil) + se, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LumaOffsetL1") } @@ -1227,13 +1227,13 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh header.ChromaWeightL1 = append(header.ChromaWeightL1, []int{}) header.ChromaOffsetL1 = append(header.ChromaOffsetL1, []int{}) for j := 0; j < 2; j++ { - se, err := readSe(nil) + se, err := readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaWeightL1") } header.ChromaWeightL1[i] = append(header.ChromaWeightL1[i], se) - se, err = readSe(nil) + se, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaOffsetL1") } @@ -1266,31 +1266,31 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh header.AdaptiveRefPicMarkingModeFlag = b == 1 if header.AdaptiveRefPicMarkingModeFlag { - header.MemoryManagementControlOperation, err = readUe(nil) + header.MemoryManagementControlOperation, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") } for header.MemoryManagementControlOperation != 0 { if header.MemoryManagementControlOperation == 1 || header.MemoryManagementControlOperation == 3 { - header.DifferenceOfPicNumsMinus1, err = readUe(nil) + header.DifferenceOfPicNumsMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") } } if header.MemoryManagementControlOperation == 2 { - header.LongTermPicNum, err = readUe(nil) + header.LongTermPicNum, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermPicNum") } } if header.MemoryManagementControlOperation == 3 || header.MemoryManagementControlOperation == 6 { - header.LongTermFrameIdx, err = readUe(nil) + header.LongTermFrameIdx, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermFrameIdx") } } if header.MemoryManagementControlOperation == 4 { - header.MaxLongTermFrameIdxPlus1, err = readUe(nil) + header.MaxLongTermFrameIdxPlus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MaxLongTermFrameIdxPlus1") } @@ -1300,12 +1300,12 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } // end decRefPicMarking } if pps.EntropyCodingMode == 1 && sliceType != "I" && sliceType != "SI" { - header.CabacInit, err = readUe(nil) + header.CabacInit, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse CabacInit") } } - header.SliceQpDelta, err = readSe(nil) + header.SliceQpDelta, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SliceQpDelta") } @@ -1318,24 +1318,24 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } header.SpForSwitch = b == 1 } - header.SliceQsDelta, err = readSe(nil) + header.SliceQsDelta, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SliceQsDelta") } } if pps.DeblockingFilterControlPresent { - header.DisableDeblockingFilter, err = readUe(nil) + header.DisableDeblockingFilter, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse DisableDeblockingFilter") } if header.DisableDeblockingFilter != 1 { - header.SliceAlphaC0OffsetDiv2, err = readSe(nil) + header.SliceAlphaC0OffsetDiv2, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SliceAlphaC0OffsetDiv2") } - header.SliceBetaOffsetDiv2, err = readSe(nil) + header.SliceBetaOffsetDiv2, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse SliceBetaOffsetDiv2") } diff --git a/codec/h264/decode/sps.go b/codec/h264/decode/sps.go index 20d93c99..5258d0f4 100644 --- a/codec/h264/decode/sps.go +++ b/codec/h264/decode/sps.go @@ -175,12 +175,12 @@ func debugPacket(name string, packet interface{}) { logger.Printf("debug: \t%#v\n", line) } } -func scalingList(b *bits.BitReader, scalingList []int, sizeOfScalingList int, defaultScalingMatrix []int) error { +func scalingList(br *bits.BitReader, scalingList []int, sizeOfScalingList int, defaultScalingMatrix []int) error { lastScale := 8 nextScale := 8 for i := 0; i < sizeOfScalingList; i++ { if nextScale != 0 { - deltaScale, err := readSe(nil) + deltaScale, err := readSe(br) if err != nil { return errors.Wrap(err, "could not parse deltaScale") } @@ -206,7 +206,7 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { br := bits.NewBitReader(bytes.NewReader(rbsp)) var err error hrdParameters := func() error { - sps.CpbCntMinus1, err = readUe(nil) + sps.CpbCntMinus1, err = readUe(br) if err != nil { return errors.Wrap(err, "could not parse CpbCntMinus1") } @@ -221,13 +221,13 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { // SchedSelIdx E1.2 for sseli := 0; sseli <= sps.CpbCntMinus1; sseli++ { - ue, err := readUe(nil) + ue, err := readUe(br) if err != nil { return errors.Wrap(err, "could not parse BitRateValueMinus1") } sps.BitRateValueMinus1 = append(sps.BitRateValueMinus1, ue) - ue, err = readUe(nil) + ue, err = readUe(br) if err != nil { return errors.Wrap(err, "could not parse CpbSizeValueMinus1") } @@ -278,12 +278,12 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { sps.Level = int(b) // sps.ID = b.NextField("SPSID", 6) // proper - sps.ID, err = readUe(nil) + sps.ID, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ID") } - sps.ChromaFormat, err = readUe(nil) + sps.ChromaFormat, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaFormat") } @@ -301,12 +301,12 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { sps.UseSeparateColorPlane = b == 1 } - sps.BitDepthLumaMinus8, err = readUe(nil) + sps.BitDepthLumaMinus8, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse BitDepthLumaMinus8") } - sps.BitDepthChromaMinus8, err = readUe(nil) + sps.BitDepthChromaMinus8, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse BitDepthChromaMinus8") } @@ -360,18 +360,18 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { // showSPS() // return sps // Possibly wrong due to no scaling list being built - sps.Log2MaxFrameNumMinus4, err = readUe(nil) + sps.Log2MaxFrameNumMinus4, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse Log2MaxFrameNumMinus4") } - sps.PicOrderCountType, err = readUe(nil) + sps.PicOrderCountType, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse PicOrderCountType") } if sps.PicOrderCountType == 0 { - sps.Log2MaxPicOrderCntLSBMin4, err = readUe(nil) + sps.Log2MaxPicOrderCntLSBMin4, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse Log2MaxPicOrderCntLSBMin4") } @@ -382,23 +382,23 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { } sps.DeltaPicOrderAlwaysZero = b == 1 - sps.OffsetForNonRefPic, err = readSe(nil) + sps.OffsetForNonRefPic, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse OffsetForNonRefPic") } - sps.OffsetForTopToBottomField, err = readSe(nil) + sps.OffsetForTopToBottomField, err = readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse OffsetForTopToBottomField") } - sps.NumRefFramesInPicOrderCntCycle, err = readUe(nil) + sps.NumRefFramesInPicOrderCntCycle, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse NumRefFramesInPicOrderCntCycle") } for i := 0; i < sps.NumRefFramesInPicOrderCntCycle; i++ { - se, err := readSe(nil) + se, err := readSe(br) if err != nil { return nil, errors.Wrap(err, "could not parse OffsetForRefFrameList") } @@ -409,7 +409,7 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { } - sps.MaxNumRefFrames, err = readUe(nil) + sps.MaxNumRefFrames, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MaxNumRefFrames") } @@ -420,12 +420,12 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { } sps.GapsInFrameNumValueAllowed = b == 1 - sps.PicWidthInMbsMinus1, err = readUe(nil) + sps.PicWidthInMbsMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse PicWidthInMbsMinus1") } - sps.PicHeightInMapUnitsMinus1, err = readUe(nil) + sps.PicHeightInMapUnitsMinus1, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse PicHeightInMapUnitsMinus1") } @@ -453,22 +453,22 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { } if sps.FrameCropping { - sps.FrameCropLeftOffset, err = readUe(nil) + sps.FrameCropLeftOffset, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse FrameCropLeftOffset") } - sps.FrameCropRightOffset, err = readUe(nil) + sps.FrameCropRightOffset, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse FrameCropRightOffset") } - sps.FrameCropTopOffset, err = readUe(nil) + sps.FrameCropTopOffset, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse FrameCropTopOffset") } - sps.FrameCropBottomOffset, err = readUe(nil) + sps.FrameCropBottomOffset, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse FrameCropBottomOffset") } @@ -573,12 +573,12 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { sps.ChromaLocInfoPresent = b == 1 if sps.ChromaLocInfoPresent { - sps.ChromaSampleLocTypeTopField, err = readUe(nil) + sps.ChromaSampleLocTypeTopField, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaSampleLocTypeTopField") } - sps.ChromaSampleLocTypeBottomField, err = readUe(nil) + sps.ChromaSampleLocTypeBottomField, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ChromaSampleLocTypeBottomField") } @@ -651,32 +651,32 @@ func NewSPS(rbsp []byte, showPacket bool) (*SPS, error) { } sps.MotionVectorsOverPicBoundaries = b == 1 - sps.MaxBytesPerPicDenom, err = readUe(nil) + sps.MaxBytesPerPicDenom, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MaxBytesPerPicDenom") } - sps.MaxBitsPerMbDenom, err = readUe(nil) + sps.MaxBitsPerMbDenom, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MaxBitsPerMbDenom") } - sps.Log2MaxMvLengthHorizontal, err = readUe(nil) + sps.Log2MaxMvLengthHorizontal, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthHorizontal") } - sps.Log2MaxMvLengthVertical, err = readUe(nil) + sps.Log2MaxMvLengthVertical, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse Log2MaxMvLengthVertical") } - sps.MaxNumReorderFrames, err = readUe(nil) + sps.MaxNumReorderFrames, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MaxNumReorderFrames") } - sps.MaxDecFrameBuffering, err = readUe(nil) + sps.MaxDecFrameBuffering, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse MaxDecFrameBuffering") } From f7b1f2f7b834cd1980d18636b3c9425a4dcd4b0f Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 15:19:15 +0930 Subject: [PATCH 59/93] codec/h264: decode pakage renamed to h264dec --- codec/h264/{decode => h264dec}/CODEOWNERS | 0 codec/h264/{decode => h264dec}/LICENSE | 0 codec/h264/{decode => h264dec}/README.md | 0 codec/h264/{decode => h264dec}/bits/bitreader.go | 0 codec/h264/{decode => h264dec}/bits/bitreader_test.go | 0 codec/h264/{decode => h264dec}/cabac.go | 0 codec/h264/{decode => h264dec}/cabac_test.go | 0 codec/h264/{decode => h264dec}/frame.go | 0 codec/h264/{decode => h264dec}/go.mod | 0 codec/h264/{decode => h264dec}/go.sum | 0 codec/h264/{decode => h264dec}/mbtype.go | 0 codec/h264/{decode => h264dec}/mbtype_test.go | 0 codec/h264/{decode => h264dec}/mnvars.go | 0 codec/h264/{decode => h264dec}/nalunit.go | 0 codec/h264/{decode => h264dec}/parse.go | 0 codec/h264/{decode => h264dec}/parse_test.go | 0 codec/h264/{decode => h264dec}/pps.go | 0 codec/h264/{decode => h264dec}/rangetablps.go | 0 codec/h264/{decode => h264dec}/rbsp.go | 0 codec/h264/{decode => h264dec}/read.go | 0 codec/h264/{decode => h264dec}/server.go | 0 codec/h264/{decode => h264dec}/slice.go | 0 codec/h264/{decode => h264dec}/slice_test.go | 0 codec/h264/{decode => h264dec}/sps.go | 0 codec/h264/{decode => h264dec}/statetransxtab.go | 0 25 files changed, 0 insertions(+), 0 deletions(-) rename codec/h264/{decode => h264dec}/CODEOWNERS (100%) rename codec/h264/{decode => h264dec}/LICENSE (100%) rename codec/h264/{decode => h264dec}/README.md (100%) rename codec/h264/{decode => h264dec}/bits/bitreader.go (100%) rename codec/h264/{decode => h264dec}/bits/bitreader_test.go (100%) rename codec/h264/{decode => h264dec}/cabac.go (100%) rename codec/h264/{decode => h264dec}/cabac_test.go (100%) rename codec/h264/{decode => h264dec}/frame.go (100%) rename codec/h264/{decode => h264dec}/go.mod (100%) rename codec/h264/{decode => h264dec}/go.sum (100%) rename codec/h264/{decode => h264dec}/mbtype.go (100%) rename codec/h264/{decode => h264dec}/mbtype_test.go (100%) rename codec/h264/{decode => h264dec}/mnvars.go (100%) rename codec/h264/{decode => h264dec}/nalunit.go (100%) rename codec/h264/{decode => h264dec}/parse.go (100%) rename codec/h264/{decode => h264dec}/parse_test.go (100%) rename codec/h264/{decode => h264dec}/pps.go (100%) rename codec/h264/{decode => h264dec}/rangetablps.go (100%) rename codec/h264/{decode => h264dec}/rbsp.go (100%) rename codec/h264/{decode => h264dec}/read.go (100%) rename codec/h264/{decode => h264dec}/server.go (100%) rename codec/h264/{decode => h264dec}/slice.go (100%) rename codec/h264/{decode => h264dec}/slice_test.go (100%) rename codec/h264/{decode => h264dec}/sps.go (100%) rename codec/h264/{decode => h264dec}/statetransxtab.go (100%) diff --git a/codec/h264/decode/CODEOWNERS b/codec/h264/h264dec/CODEOWNERS similarity index 100% rename from codec/h264/decode/CODEOWNERS rename to codec/h264/h264dec/CODEOWNERS diff --git a/codec/h264/decode/LICENSE b/codec/h264/h264dec/LICENSE similarity index 100% rename from codec/h264/decode/LICENSE rename to codec/h264/h264dec/LICENSE diff --git a/codec/h264/decode/README.md b/codec/h264/h264dec/README.md similarity index 100% rename from codec/h264/decode/README.md rename to codec/h264/h264dec/README.md diff --git a/codec/h264/decode/bits/bitreader.go b/codec/h264/h264dec/bits/bitreader.go similarity index 100% rename from codec/h264/decode/bits/bitreader.go rename to codec/h264/h264dec/bits/bitreader.go diff --git a/codec/h264/decode/bits/bitreader_test.go b/codec/h264/h264dec/bits/bitreader_test.go similarity index 100% rename from codec/h264/decode/bits/bitreader_test.go rename to codec/h264/h264dec/bits/bitreader_test.go diff --git a/codec/h264/decode/cabac.go b/codec/h264/h264dec/cabac.go similarity index 100% rename from codec/h264/decode/cabac.go rename to codec/h264/h264dec/cabac.go diff --git a/codec/h264/decode/cabac_test.go b/codec/h264/h264dec/cabac_test.go similarity index 100% rename from codec/h264/decode/cabac_test.go rename to codec/h264/h264dec/cabac_test.go diff --git a/codec/h264/decode/frame.go b/codec/h264/h264dec/frame.go similarity index 100% rename from codec/h264/decode/frame.go rename to codec/h264/h264dec/frame.go diff --git a/codec/h264/decode/go.mod b/codec/h264/h264dec/go.mod similarity index 100% rename from codec/h264/decode/go.mod rename to codec/h264/h264dec/go.mod diff --git a/codec/h264/decode/go.sum b/codec/h264/h264dec/go.sum similarity index 100% rename from codec/h264/decode/go.sum rename to codec/h264/h264dec/go.sum diff --git a/codec/h264/decode/mbtype.go b/codec/h264/h264dec/mbtype.go similarity index 100% rename from codec/h264/decode/mbtype.go rename to codec/h264/h264dec/mbtype.go diff --git a/codec/h264/decode/mbtype_test.go b/codec/h264/h264dec/mbtype_test.go similarity index 100% rename from codec/h264/decode/mbtype_test.go rename to codec/h264/h264dec/mbtype_test.go diff --git a/codec/h264/decode/mnvars.go b/codec/h264/h264dec/mnvars.go similarity index 100% rename from codec/h264/decode/mnvars.go rename to codec/h264/h264dec/mnvars.go diff --git a/codec/h264/decode/nalunit.go b/codec/h264/h264dec/nalunit.go similarity index 100% rename from codec/h264/decode/nalunit.go rename to codec/h264/h264dec/nalunit.go diff --git a/codec/h264/decode/parse.go b/codec/h264/h264dec/parse.go similarity index 100% rename from codec/h264/decode/parse.go rename to codec/h264/h264dec/parse.go diff --git a/codec/h264/decode/parse_test.go b/codec/h264/h264dec/parse_test.go similarity index 100% rename from codec/h264/decode/parse_test.go rename to codec/h264/h264dec/parse_test.go diff --git a/codec/h264/decode/pps.go b/codec/h264/h264dec/pps.go similarity index 100% rename from codec/h264/decode/pps.go rename to codec/h264/h264dec/pps.go diff --git a/codec/h264/decode/rangetablps.go b/codec/h264/h264dec/rangetablps.go similarity index 100% rename from codec/h264/decode/rangetablps.go rename to codec/h264/h264dec/rangetablps.go diff --git a/codec/h264/decode/rbsp.go b/codec/h264/h264dec/rbsp.go similarity index 100% rename from codec/h264/decode/rbsp.go rename to codec/h264/h264dec/rbsp.go diff --git a/codec/h264/decode/read.go b/codec/h264/h264dec/read.go similarity index 100% rename from codec/h264/decode/read.go rename to codec/h264/h264dec/read.go diff --git a/codec/h264/decode/server.go b/codec/h264/h264dec/server.go similarity index 100% rename from codec/h264/decode/server.go rename to codec/h264/h264dec/server.go diff --git a/codec/h264/decode/slice.go b/codec/h264/h264dec/slice.go similarity index 100% rename from codec/h264/decode/slice.go rename to codec/h264/h264dec/slice.go diff --git a/codec/h264/decode/slice_test.go b/codec/h264/h264dec/slice_test.go similarity index 100% rename from codec/h264/decode/slice_test.go rename to codec/h264/h264dec/slice_test.go diff --git a/codec/h264/decode/sps.go b/codec/h264/h264dec/sps.go similarity index 100% rename from codec/h264/decode/sps.go rename to codec/h264/h264dec/sps.go diff --git a/codec/h264/decode/statetransxtab.go b/codec/h264/h264dec/statetransxtab.go similarity index 100% rename from codec/h264/decode/statetransxtab.go rename to codec/h264/h264dec/statetransxtab.go From ce3d98bd3b38ed374452c2d2bb115a71eb308481 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 15:20:39 +0930 Subject: [PATCH 60/93] codec/h264/h264dec: changed packages from h264 to h264dec --- codec/h264/h264dec/cabac.go | 2 +- codec/h264/h264dec/cabac_test.go | 2 +- codec/h264/h264dec/frame.go | 2 +- codec/h264/h264dec/mbtype.go | 2 +- codec/h264/h264dec/mbtype_test.go | 2 +- codec/h264/h264dec/mnvars.go | 2 +- codec/h264/h264dec/nalunit.go | 2 +- codec/h264/h264dec/parse.go | 2 +- codec/h264/h264dec/parse_test.go | 2 +- codec/h264/h264dec/pps.go | 2 +- codec/h264/h264dec/rangetablps.go | 2 +- codec/h264/h264dec/rbsp.go | 2 +- codec/h264/h264dec/read.go | 2 +- codec/h264/h264dec/server.go | 2 +- codec/h264/h264dec/slice.go | 2 +- codec/h264/h264dec/slice_test.go | 2 +- codec/h264/h264dec/sps.go | 2 +- codec/h264/h264dec/statetransxtab.go | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 3697f630..72ebc9f3 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "github.com/ausocean/h264decode/h264/bits" diff --git a/codec/h264/h264dec/cabac_test.go b/codec/h264/h264dec/cabac_test.go index 1c78b4a6..66d82a93 100644 --- a/codec/h264/h264dec/cabac_test.go +++ b/codec/h264/h264dec/cabac_test.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import "testing" diff --git a/codec/h264/h264dec/frame.go b/codec/h264/h264dec/frame.go index 94143f53..9e240a45 100644 --- a/codec/h264/h264dec/frame.go +++ b/codec/h264/h264dec/frame.go @@ -1,4 +1,4 @@ -package h264 +package h264dec // NALU types, as defined in table 7-1 in specifications. const ( diff --git a/codec/h264/h264dec/mbtype.go b/codec/h264/h264dec/mbtype.go index 2c3bc281..e335e756 100644 --- a/codec/h264/h264dec/mbtype.go +++ b/codec/h264/h264dec/mbtype.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "errors" diff --git a/codec/h264/h264dec/mbtype_test.go b/codec/h264/h264dec/mbtype_test.go index d735f9c8..a9fd988b 100644 --- a/codec/h264/h264dec/mbtype_test.go +++ b/codec/h264/h264dec/mbtype_test.go @@ -8,7 +8,7 @@ DESCRIPTION AUTHORS Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) */ -package h264 +package h264dec import ( "testing" diff --git a/codec/h264/h264dec/mnvars.go b/codec/h264/h264dec/mnvars.go index 3aba8d5e..2de1abf9 100644 --- a/codec/h264/h264dec/mnvars.go +++ b/codec/h264/h264dec/mnvars.go @@ -1,4 +1,4 @@ -package h264 +package h264dec type MN struct { M, N int diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 35eeab76..f0ecbb32 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "github.com/ausocean/h264decode/h264/bits" diff --git a/codec/h264/h264dec/parse.go b/codec/h264/h264dec/parse.go index 24ff222b..72a74e80 100644 --- a/codec/h264/h264dec/parse.go +++ b/codec/h264/h264dec/parse.go @@ -11,7 +11,7 @@ AUTHORS mrmod */ -package h264 +package h264dec import ( "math" diff --git a/codec/h264/h264dec/parse_test.go b/codec/h264/h264dec/parse_test.go index cc2d47e0..42c74a6b 100644 --- a/codec/h264/h264dec/parse_test.go +++ b/codec/h264/h264dec/parse_test.go @@ -9,7 +9,7 @@ AUTHOR Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) */ -package h264 +package h264dec import ( "bytes" diff --git a/codec/h264/h264dec/pps.go b/codec/h264/h264dec/pps.go index 4e659649..646e4762 100644 --- a/codec/h264/h264dec/pps.go +++ b/codec/h264/h264dec/pps.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "math" diff --git a/codec/h264/h264dec/rangetablps.go b/codec/h264/h264dec/rangetablps.go index 3bcfb000..56f759ae 100644 --- a/codec/h264/h264dec/rangetablps.go +++ b/codec/h264/h264dec/rangetablps.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import "errors" diff --git a/codec/h264/h264dec/rbsp.go b/codec/h264/h264dec/rbsp.go index 57826439..cad5faef 100644 --- a/codec/h264/h264dec/rbsp.go +++ b/codec/h264/h264dec/rbsp.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "fmt" diff --git a/codec/h264/h264dec/read.go b/codec/h264/h264dec/read.go index c50180dd..3f15e188 100644 --- a/codec/h264/h264dec/read.go +++ b/codec/h264/h264dec/read.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "fmt" diff --git a/codec/h264/h264dec/server.go b/codec/h264/h264dec/server.go index f702c1eb..2b439d96 100644 --- a/codec/h264/h264dec/server.go +++ b/codec/h264/h264dec/server.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( // "github.com/nareix/joy4/av" diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 4e669917..488303c8 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "bytes" diff --git a/codec/h264/h264dec/slice_test.go b/codec/h264/h264dec/slice_test.go index 98b052ef..e7988a7e 100644 --- a/codec/h264/h264dec/slice_test.go +++ b/codec/h264/h264dec/slice_test.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import "testing" diff --git a/codec/h264/h264dec/sps.go b/codec/h264/h264dec/sps.go index 5258d0f4..03b511f2 100644 --- a/codec/h264/h264dec/sps.go +++ b/codec/h264/h264dec/sps.go @@ -1,4 +1,4 @@ -package h264 +package h264dec import ( "bytes" diff --git a/codec/h264/h264dec/statetransxtab.go b/codec/h264/h264dec/statetransxtab.go index 31f15fcd..5338e5e4 100644 --- a/codec/h264/h264dec/statetransxtab.go +++ b/codec/h264/h264dec/statetransxtab.go @@ -1,4 +1,4 @@ -package h264 +package h264dec type StateTransx struct { TransIdxLPS, TransIdxMPS int From 5d31c4936015b84aa94dc38cc85e8b3b80be5605 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 15:21:17 +0930 Subject: [PATCH 61/93] code/h264/h264dec: removed go module stuff --- codec/h264/h264dec/go.mod | 8 -------- codec/h264/h264dec/go.sum | 4 ---- 2 files changed, 12 deletions(-) delete mode 100644 codec/h264/h264dec/go.mod delete mode 100644 codec/h264/h264dec/go.sum diff --git a/codec/h264/h264dec/go.mod b/codec/h264/h264dec/go.mod deleted file mode 100644 index ffa61433..00000000 --- a/codec/h264/h264dec/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module github.com/ausocean/h264decode - -go 1.12 - -require ( - github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508 - github.com/pkg/errors v0.8.1 -) diff --git a/codec/h264/h264dec/go.sum b/codec/h264/h264dec/go.sum deleted file mode 100644 index 15c64706..00000000 --- a/codec/h264/h264dec/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508 h1:2LdkN1icT8cEFyB95fUbPE0TmQL9ZOjUv9MNJ1kg3XE= -github.com/icza/bitio v0.0.0-20180221120200-b25b30b42508/go.mod h1:1+iKpsBoI5fsqBTrjxjM81vidVQcxXCmDrM9vc6EU2w= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 3914bc1422d00befbacd313f68b8402f2c11d4d6 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 15:44:45 +0930 Subject: [PATCH 62/93] codec/h264/h264dec: fixed import paths --- codec/h264/h264dec/cabac.go | 2 +- codec/h264/h264dec/nalunit.go | 2 +- codec/h264/h264dec/parse.go | 2 +- codec/h264/h264dec/parse_test.go | 2 +- codec/h264/h264dec/pps.go | 2 +- codec/h264/h264dec/rbsp.go | 2 +- codec/h264/h264dec/read.go | 2 +- codec/h264/h264dec/server.go | 2 +- codec/h264/h264dec/slice.go | 2 +- codec/h264/h264dec/sps.go | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/codec/h264/h264dec/cabac.go b/codec/h264/h264dec/cabac.go index 72ebc9f3..47d90835 100644 --- a/codec/h264/h264dec/cabac.go +++ b/codec/h264/h264dec/cabac.go @@ -1,7 +1,7 @@ package h264dec import ( - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index f0ecbb32..49d34aea 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -1,7 +1,7 @@ package h264dec import ( - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) diff --git a/codec/h264/h264dec/parse.go b/codec/h264/h264dec/parse.go index 72a74e80..86baaece 100644 --- a/codec/h264/h264dec/parse.go +++ b/codec/h264/h264dec/parse.go @@ -16,7 +16,7 @@ package h264dec import ( "math" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) diff --git a/codec/h264/h264dec/parse_test.go b/codec/h264/h264dec/parse_test.go index 42c74a6b..c936ca3d 100644 --- a/codec/h264/h264dec/parse_test.go +++ b/codec/h264/h264dec/parse_test.go @@ -15,7 +15,7 @@ import ( "bytes" "testing" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" ) // TestReadUe checks that readUe correctly parses an Exp-Golomb-coded element diff --git a/codec/h264/h264dec/pps.go b/codec/h264/h264dec/pps.go index 646e4762..139306c6 100644 --- a/codec/h264/h264dec/pps.go +++ b/codec/h264/h264dec/pps.go @@ -3,7 +3,7 @@ package h264dec import ( "math" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) diff --git a/codec/h264/h264dec/rbsp.go b/codec/h264/h264dec/rbsp.go index cad5faef..271076ae 100644 --- a/codec/h264/h264dec/rbsp.go +++ b/codec/h264/h264dec/rbsp.go @@ -3,7 +3,7 @@ package h264dec import ( "fmt" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" ) const ( diff --git a/codec/h264/h264dec/read.go b/codec/h264/h264dec/read.go index 3f15e188..92a12ed9 100644 --- a/codec/h264/h264dec/read.go +++ b/codec/h264/h264dec/read.go @@ -5,7 +5,7 @@ import ( "io" "os" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) diff --git a/codec/h264/h264dec/server.go b/codec/h264/h264dec/server.go index 2b439d96..301bfae5 100644 --- a/codec/h264/h264dec/server.go +++ b/codec/h264/h264dec/server.go @@ -10,7 +10,7 @@ import ( "os" "os/signal" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" ) // InitialNALU indicates the start of a h264 packet diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 488303c8..b25b4502 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -5,7 +5,7 @@ import ( "fmt" "math" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) diff --git a/codec/h264/h264dec/sps.go b/codec/h264/h264dec/sps.go index 03b511f2..c4aabd22 100644 --- a/codec/h264/h264dec/sps.go +++ b/codec/h264/h264dec/sps.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" "github.com/pkg/errors" ) From 16b4d570b6a3c6343ca6e5bcbb5f2acf24be2a8d Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 17:25:57 +0930 Subject: [PATCH 63/93] codec/h264/h264dec/bits/bitreader.go: added ReadBool and ReadBitsInt methods --- codec/h264/decode/bits/bitreader.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/codec/h264/decode/bits/bitreader.go b/codec/h264/decode/bits/bitreader.go index 7e17f407..08cc5d41 100644 --- a/codec/h264/decode/bits/bitreader.go +++ b/codec/h264/decode/bits/bitreader.go @@ -128,6 +128,24 @@ func (br *BitReader) ReadBits(n int) (uint64, error) { return r, nil } +// ReadBitsInt reads n bits from the source, and returns as an int. +func (br *BitReader) ReadBitsInt(n int) (int, error) { + b, err := br.ReadBits(n) + if err != nil { + return 0, err + } + return int(b), nil +} + +// ReadBool reads a single bit from the source, converts to a bool and returns. +func (br *BitReader) ReadBool() (bool, error) { + b, err := br.ReadBits(1) + if err != nil { + return false, err + } + return b == 1, nil +} + // PeekBits provides the next n bits returning them in the least-significant // part of a uint64, without advancing through the source. // For example, with a source as []byte{0x8f,0xe3} (1000 1111, 1110 0011), we From 0d9861c49eddf506fec0c9f84b7c5c78160ecd63 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 17:37:54 +0930 Subject: [PATCH 64/93] codec/h264/decode: added SVCExtension struct with NewSVCExtension function --- codec/h264/decode/nalunit.go | 94 +++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/codec/h264/decode/nalunit.go b/codec/h264/decode/nalunit.go index 35eeab76..0101f750 100644 --- a/codec/h264/decode/nalunit.go +++ b/codec/h264/decode/nalunit.go @@ -1,7 +1,7 @@ package h264 import ( - "github.com/ausocean/h264decode/h264/bits" + "bitbucket.org/ausocean/av/codec/h264/decode/bits" "github.com/pkg/errors" ) @@ -12,16 +12,7 @@ type NalUnit struct { Type int SvcExtensionFlag int Avc3dExtensionFlag int - IdrFlag int - PriorityId int - NoInterLayerPredFlag int - DependencyId int - QualityId int - TemporalId int - UseRefBasePicFlag int - DiscardableFlag int - OutputFlag int - ReservedThree2Bits int + SVCExtension *SVCExtension HeaderBytes int NonIdrFlag int ViewId int @@ -34,19 +25,74 @@ type NalUnit struct { rbsp []byte } -func NalUnitHeaderSvcExtension(nalUnit *NalUnit, br *bits.BitReader) error { - return readFields(br, []field{ - {&nalUnit.IdrFlag, "IdrFlag", 1}, - {&nalUnit.PriorityId, "PriorityId", 6}, - {&nalUnit.NoInterLayerPredFlag, "NoInterLayerPredFlag", 1}, - {&nalUnit.DependencyId, "DependencyId", 3}, - {&nalUnit.QualityId, "QualityId", 4}, - {&nalUnit.TemporalId, "TemporalId", 3}, - {&nalUnit.UseRefBasePicFlag, "UseRefBasePicFlag", 1}, - {&nalUnit.DiscardableFlag, "DiscardableFlag", 1}, - {&nalUnit.OutputFlag, "OutputFlag", 1}, - {&nalUnit.ReservedThree2Bits, "ReservedThree2Bits", 2}, - }) +type SVCExtension struct { + IdrFlag bool + PriorityId int + NoInterLayerPredFlag bool + DependencyId int + QualityId int + TemporalId int + UseRefBasePicFlag bool + DiscardableFlag bool + OutputFlag bool + ReservedThree2Bits int +} + +func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { + e := &SVCExtension{} + var err error + + e.IdrFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read IdrFlag") + } + + e.PriorityId, err = br.ReadBitsInt(6) + if err != nil { + return nil, errors.Wrap(err, "could not read PriorityId") + } + + e.NoInterLayerPredFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read NoInterLayerPredFlag") + } + + e.DependencyId, err = br.ReadBitsInt(3) + if err != nil { + return nil, errors.Wrap(err, "could not read DependencyId") + } + + e.QualityId, err = br.ReadBitsInt(4) + if err != nil { + return nil, errors.Wrap(err, "could not read QualityId") + } + + e.TemporalId, err = br.ReadBitsInt(3) + if err != nil { + return nil, errors.Wrap(err, "could not read TemporalId") + } + + e.UseRefBasePicFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read UseRefBasePicFlag") + } + + e.DiscardableFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read DiscardableFlag") + } + + e.OutputFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read OutputFlag") + } + + e.ReservedThree2Bits, err = br.ReadBitsInt(2) + if err != nil { + return nil, errors.Wrap(err, "could not read ReservedThree2Bits") + } + + return e, nil } func NalUnitHeader3davcExtension(nalUnit *NalUnit, br *bits.BitReader) error { From e54bc234c87881f1df90049731adb6a647e9060e Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 17:50:45 +0930 Subject: [PATCH 65/93] codec/h264/decode: added ThreeDAVCExtenstion type and NewThreeDAVCExtension function --- codec/h264/decode/nalunit.go | 66 +++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/codec/h264/decode/nalunit.go b/codec/h264/decode/nalunit.go index 0101f750..db983909 100644 --- a/codec/h264/decode/nalunit.go +++ b/codec/h264/decode/nalunit.go @@ -13,18 +13,57 @@ type NalUnit struct { SvcExtensionFlag int Avc3dExtensionFlag int SVCExtension *SVCExtension - HeaderBytes int - NonIdrFlag int - ViewId int - AnchorPicFlag int - InterViewFlag int - ReservedOneBit int - ViewIdx int - DepthFlag int + ThreeDAVCExtension *ThreeDAVCExtension EmulationPreventionThreeByte byte rbsp []byte } +type ThreeDAVCExtension struct { + ViewIdx int + DepthFlag bool + NonIdrFlag bool + TemporalID int + AnchorPicFlag bool + InterViewFlag bool +} + +func NewThreeDAVCExtension(br *bits.BitReader) (*ThreeDAVCExtension, error) { + e := &ThreeDAVCExtension{} + var err error + + e.ViewIdx, err = br.ReadBitsInt(8) + if err != nil { + return nil, errors.Wrap(err, "could not read ViewIdx") + } + + e.DepthFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read DepthFlag") + } + + e.NonIdrFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read NonIdrFlag") + } + + e.TemporalID, err = br.ReadBitsInt(3) + if err != nil { + return nil, errors.Wrap(err, "could not read TemporalId") + } + + e.AnchorPicFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read AnchorPicFlag") + } + + e.InterViewFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read InterViewFlag") + } + + return e, nil +} + type SVCExtension struct { IdrFlag bool PriorityId int @@ -95,17 +134,6 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { return e, nil } -func NalUnitHeader3davcExtension(nalUnit *NalUnit, br *bits.BitReader) error { - return readFields(br, []field{ - {&nalUnit.ViewIdx, "ViewIdx", 8}, - {&nalUnit.DepthFlag, "DepthFlag", 1}, - {&nalUnit.NonIdrFlag, "NonIdrFlag", 1}, - {&nalUnit.TemporalId, "TemporalId", 3}, - {&nalUnit.AnchorPicFlag, "AnchorPicFlag", 1}, - {&nalUnit.InterViewFlag, "InterViewFlag", 1}, - }) -} - func NalUnitHeaderMvcExtension(nalUnit *NalUnit, br *bits.BitReader) error { return readFields(br, []field{ {&nalUnit.NonIdrFlag, "NonIdrFlag", 1}, From 24f6b2d12f27d7ee0df2b8577908a05532c49686 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 18:04:51 +0930 Subject: [PATCH 66/93] codec/h264/decode: added MVCExtension type and NewMVCExtension func --- codec/h264/decode/nalunit.go | 65 +++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/codec/h264/decode/nalunit.go b/codec/h264/decode/nalunit.go index db983909..9969cf91 100644 --- a/codec/h264/decode/nalunit.go +++ b/codec/h264/decode/nalunit.go @@ -14,10 +14,63 @@ type NalUnit struct { Avc3dExtensionFlag int SVCExtension *SVCExtension ThreeDAVCExtension *ThreeDAVCExtension + MVCExtension *MVCExtension EmulationPreventionThreeByte byte rbsp []byte } +type MVCExtension struct { + NonIdrFlag bool + PriorityID int + ViewID int + TemporalID int + AnchorPicFlag bool + InterViewFlag bool + ReservedOneBit int +} + +func NewMVCExtension(br *bits.BitReader) (*MVCExtension, error) { + e := &MVCExtension{} + var err error + + e.NonIdrFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read NonIdrFlag") + } + + e.PriorityID, err = br.ReadBitsInt(6) + if err != nil { + return nil, errors.Wrap(err, "could not read PriorityId") + } + + e.ViewID, err = br.ReadBitsInt(10) + if err != nil { + return nil, errors.Wrap(err, "could not read ViewId") + } + + e.TemporalID, err = br.ReadBitsInt(3) + if err != nil { + return nil, errors.Wrap(err, "could not read TemporalId") + } + + e.AnchorPicFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read AnchorPicFlag") + } + + e.InterViewFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read InterViewFlag") + } + + e.ReservedOneBit, err = br.ReadBitsInt(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ReservedOneBit") + } + + return e, nil +} + type ThreeDAVCExtension struct { ViewIdx int DepthFlag bool @@ -134,18 +187,6 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { return e, nil } -func NalUnitHeaderMvcExtension(nalUnit *NalUnit, br *bits.BitReader) error { - return readFields(br, []field{ - {&nalUnit.NonIdrFlag, "NonIdrFlag", 1}, - {&nalUnit.PriorityId, "PriorityId", 6}, - {&nalUnit.ViewId, "ViewId", 10}, - {&nalUnit.TemporalId, "TemporalId", 3}, - {&nalUnit.AnchorPicFlag, "AnchorPicFlag", 1}, - {&nalUnit.InterViewFlag, "InterViewFlag", 1}, - {&nalUnit.ReservedOneBit, "ReservedOneBit", 1}, - }) -} - func (n *NalUnit) RBSP() []byte { return n.rbsp } From f11ba2b43375b9e0250bc415dbc1df847eb6e469 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 20:20:33 +0930 Subject: [PATCH 67/93] codec/h264/h264dec: renamed NalUnit function to NewNALUnit and cleaned it up --- codec/h264/decode/nalunit.go | 134 +++++++++++++++++------------------ 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/codec/h264/decode/nalunit.go b/codec/h264/decode/nalunit.go index 9969cf91..2adf3165 100644 --- a/codec/h264/decode/nalunit.go +++ b/codec/h264/decode/nalunit.go @@ -1,23 +1,12 @@ package h264 import ( - "bitbucket.org/ausocean/av/codec/h264/decode/bits" - "github.com/pkg/errors" -) + "io" -type NalUnit struct { - NumBytes int - ForbiddenZeroBit int - RefIdc int - Type int - SvcExtensionFlag int - Avc3dExtensionFlag int - SVCExtension *SVCExtension - ThreeDAVCExtension *ThreeDAVCExtension - MVCExtension *MVCExtension - EmulationPreventionThreeByte byte - rbsp []byte -} + "github.com/pkg/errors" + + "bitbucket.org/ausocean/av/codec/h264/decode/bits" +) type MVCExtension struct { NonIdrFlag bool @@ -187,70 +176,80 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { return e, nil } -func (n *NalUnit) RBSP() []byte { - return n.rbsp +type NALUnit struct { + ForbiddenZeroBit int + RefIdc int + Type int + SVCExtensionFlag int + AVC3dExtensionFlag int + SVCExtension *SVCExtension + ThreeDAVCExtension *ThreeDAVCExtension + MVCExtension *MVCExtension + EmulationPreventionThreeByte byte + RBSP []byte } -func NewNalUnit(frame []byte, numBytesInNal int) (*NalUnit, error) { - logger.Printf("debug: reading %d byte NAL\n", numBytesInNal) - nalUnit := NalUnit{ - NumBytes: numBytesInNal, - HeaderBytes: 1, - } - // TODO: pass in actual io.Reader to NewBitReader - br := bits.NewBitReader(nil) +func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { + n := &NALUnit{} err := readFields(br, []field{ - {&nalUnit.ForbiddenZeroBit, "ForbiddenZeroBit", 1}, - {&nalUnit.RefIdc, "NalRefIdc", 2}, - {&nalUnit.Type, "NalUnitType", 5}, + {&n.ForbiddenZeroBit, "ForbiddenZeroBit", 1}, + {&n.RefIdc, "NalRefIdc", 2}, + {&n.Type, "NalUnitType", 5}, }) if err != nil { return nil, err } - if nalUnit.Type == 14 || nalUnit.Type == 20 || nalUnit.Type == 21 { - if nalUnit.Type != 21 { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read SvcExtensionFlag") - } - nalUnit.SvcExtensionFlag = int(b) - } else { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read Avc3dExtensionFlag") - } - nalUnit.Avc3dExtensionFlag = int(b) - } - if nalUnit.SvcExtensionFlag == 1 { - NalUnitHeaderSvcExtension(&nalUnit, br) - nalUnit.HeaderBytes += 3 - } else if nalUnit.Avc3dExtensionFlag == 1 { - NalUnitHeader3davcExtension(&nalUnit, br) - nalUnit.HeaderBytes += 2 - } else { - NalUnitHeaderMvcExtension(&nalUnit, br) - nalUnit.HeaderBytes += 3 + var headBytes, rbspBytes = 1, 0 + // TODO: use consts for the NAL types here + if n.Type == 14 || n.Type == 20 || n.Type == 21 { + if n.Type != 21 { + n.SVCExtensionFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read SVCExtensionFlag") + } + } else { + n.AVC3DExtensionFlag, err = br.ReadBool() + if err != nil { + return nil, errors.Wrap(err, "could not read AVC3DExtensionFlag") + } + } + if n.SVCExtensionFlag { + n.SVCExtension, err = NewSVCExtension(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse SVCExtension") + } + n.HeaderBytes += 3 + } else if n.AVC3DExtensionFlag { + n.ThreeDAVCExtension, err = NewThreeDAVCExtension(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse ThreeDAVCExtension") + } + n.HeaderBytes += 2 + } else { + n.MVCExtension, err = NewMVCExtension(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse MVCExtension") + } + n.HeaderBytes += 3 } } - logger.Printf("debug: found %d byte header. Reading body\n", nalUnit.HeaderBytes) - for i := nalUnit.HeaderBytes; i < nalUnit.NumBytes; i++ { + for moreRBSPData(br) { next3Bytes, err := br.PeekBits(24) - if err != nil { - logger.Printf("error: while reading next 3 NAL bytes: %v\n", err) - break + if err != nil && errors.Cause(err) != io.EOF { + return nil, errors.Wrap("could not Peek next 3 bytes") } - // Little odd, the err above and the i+2 check might be synonyms - if i+2 < nalUnit.NumBytes && next3Bytes == 0x000003 { + + if next3Bytes == 0x000003 { for j := 0; j < 2; j++ { rbspByte, err := br.ReadBits(8) if err != nil { return nil, errors.Wrap(err, "could not read rbspByte") } - nalUnit.rbsp = append(nalUnit.rbsp, byte(rbspByte)) + n.rbsp = append(n.rbsp, byte(rbspByte)) } i += 2 @@ -259,18 +258,15 @@ func NewNalUnit(frame []byte, numBytesInNal int) (*NalUnit, error) { if err != nil { return nil, errors.Wrap(err, "could not read eptByte") } - nalUnit.EmulationPreventionThreeByte = byte(eptByte) + n.EmulationPreventionThreeByte = byte(eptByte) } else { - if b, err := br.ReadBits(8); err == nil { - nalUnit.rbsp = append(nalUnit.rbsp, byte(b)) - } else { - logger.Printf("error: while reading byte %d of %d nal bytes: %v\n", i, nalUnit.NumBytes, err) - break + b, err := br.ReadBits(8) + if err != nil { + return nil, errors.Wrap(err, "could not read RBSP byte") } + n.rbsp = append(n.rbsp, byte(b)) } } - // nalUnit.rbsp = frame[nalUnit.HeaderBytes:] - logger.Printf("info: decoded %s NAL with %d RBSP bytes\n", NALUnitType[nalUnit.Type], len(nalUnit.rbsp)) - return &nalUnit, nil + return n, nil } From bdf3b37fef494ac86f9dcd3f7e79983baa6e836c Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 20:23:30 +0930 Subject: [PATCH 68/93] codec/h264/h264dec: fixed import path for bits package --- codec/h264/h264dec/nalunit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 4ecf8e99..592d747f 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -5,7 +5,7 @@ import ( "github.com/pkg/errors" - "bitbucket.org/ausocean/av/codec/h264/decode/bits" + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" ) type MVCExtension struct { From 278c6f2ef17b1d809c7f6040b82685fa1fc4eace Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 20:51:48 +0930 Subject: [PATCH 69/93] codec/h264/h264dec/nalunit.go: added commenting --- codec/h264/h264dec/nalunit.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 592d747f..dc76fb43 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -1,3 +1,12 @@ +/* +DESCRIPTION + nalunit.go provides structures for a NAL unit as well as it's extensions. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) + mrmod +*/ + package h264dec import ( @@ -8,6 +17,8 @@ import ( "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" ) +// MVCExtension describes a NAL unit header multiview video coding extension, as +// defined in section H.7.3.1.1 of the specifications. type MVCExtension struct { NonIdrFlag bool PriorityID int @@ -18,6 +29,9 @@ type MVCExtension struct { ReservedOneBit int } +// NewMVCExtension parses a NAL unit header multiview video coding extension +// from br following the syntax structure specified in section H.7.3.1.1, and +// returns as a new MVCExtension. func NewMVCExtension(br *bits.BitReader) (*MVCExtension, error) { e := &MVCExtension{} var err error @@ -60,6 +74,8 @@ func NewMVCExtension(br *bits.BitReader) (*MVCExtension, error) { return e, nil } +// ThreeDAVCExtension describes a NAL unit header 3D advanced video coding +// extension, as defined in section J.7.3.1.1 of the specifications. type ThreeDAVCExtension struct { ViewIdx int DepthFlag bool @@ -69,6 +85,9 @@ type ThreeDAVCExtension struct { InterViewFlag bool } +// NewThreeDAVCExtension parses a NAL unit header 3D advanced video coding +// extension from br following the syntax structure specified in section +// J.7.3.1.1, and returns as a new ThreeDAVCExtension. func NewThreeDAVCExtension(br *bits.BitReader) (*ThreeDAVCExtension, error) { e := &ThreeDAVCExtension{} var err error @@ -106,6 +125,8 @@ func NewThreeDAVCExtension(br *bits.BitReader) (*ThreeDAVCExtension, error) { return e, nil } +// SVCExtension describes a NAL unit header scalable video coding extension, as +// defined in section G.7.3.1.1 of the specifications. type SVCExtension struct { IdrFlag bool PriorityId int @@ -119,6 +140,9 @@ type SVCExtension struct { ReservedThree2Bits int } +// NewSVCExtension parses a NAL unit header scalable video coding extension from +// br following the syntax structure specified in section G.7.3.1.1, and returns +// as a new SVCExtension. func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { e := &SVCExtension{} var err error @@ -176,6 +200,8 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { return e, nil } +// NALUnit describes a network abstraction layer unit, as defined in section +// 7.3.1 of the specifications. type NALUnit struct { ForbiddenZeroBit int RefIdc int @@ -189,6 +215,8 @@ type NALUnit struct { RBSP []byte } +// NewNALUnit parses a network abstraction layer unit from br following the +// syntax structure specified in section 7.3.1, and returns as a new NALUnit. func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { n := &NALUnit{} From 4d65d0d433bb735bb3f6c42b857f409b2286973e Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 19 Jul 2019 20:58:01 +0930 Subject: [PATCH 70/93] codec/h264/h264dec: fixed random build errors --- codec/h264/h264dec/nalunit.go | 32 +++++++++++++------------------- codec/h264/h264dec/read.go | 10 +++++----- codec/h264/h264dec/slice.go | 20 ++++++++++---------- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index dc76fb43..9f4ecab3 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -129,11 +129,11 @@ func NewThreeDAVCExtension(br *bits.BitReader) (*ThreeDAVCExtension, error) { // defined in section G.7.3.1.1 of the specifications. type SVCExtension struct { IdrFlag bool - PriorityId int + PriorityID int NoInterLayerPredFlag bool - DependencyId int - QualityId int - TemporalId int + DependencyID int + QualityID int + TemporalID int UseRefBasePicFlag bool DiscardableFlag bool OutputFlag bool @@ -152,7 +152,7 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { return nil, errors.Wrap(err, "could not read IdrFlag") } - e.PriorityId, err = br.ReadBitsInt(6) + e.PriorityID, err = br.ReadBitsInt(6) if err != nil { return nil, errors.Wrap(err, "could not read PriorityId") } @@ -162,17 +162,17 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { return nil, errors.Wrap(err, "could not read NoInterLayerPredFlag") } - e.DependencyId, err = br.ReadBitsInt(3) + e.DependencyID, err = br.ReadBitsInt(3) if err != nil { return nil, errors.Wrap(err, "could not read DependencyId") } - e.QualityId, err = br.ReadBitsInt(4) + e.QualityID, err = br.ReadBitsInt(4) if err != nil { return nil, errors.Wrap(err, "could not read QualityId") } - e.TemporalId, err = br.ReadBitsInt(3) + e.TemporalID, err = br.ReadBitsInt(3) if err != nil { return nil, errors.Wrap(err, "could not read TemporalId") } @@ -206,8 +206,8 @@ type NALUnit struct { ForbiddenZeroBit int RefIdc int Type int - SVCExtensionFlag int - AVC3dExtensionFlag int + SVCExtensionFlag bool + AVC3DExtensionFlag bool SVCExtension *SVCExtension ThreeDAVCExtension *ThreeDAVCExtension MVCExtension *MVCExtension @@ -229,8 +229,6 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { return nil, err } - var headBytes, rbspBytes = 1, 0 - // TODO: use consts for the NAL types here if n.Type == 14 || n.Type == 20 || n.Type == 21 { if n.Type != 21 { @@ -249,26 +247,23 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { if err != nil { return nil, errors.Wrap(err, "could not parse SVCExtension") } - n.HeaderBytes += 3 } else if n.AVC3DExtensionFlag { n.ThreeDAVCExtension, err = NewThreeDAVCExtension(br) if err != nil { return nil, errors.Wrap(err, "could not parse ThreeDAVCExtension") } - n.HeaderBytes += 2 } else { n.MVCExtension, err = NewMVCExtension(br) if err != nil { return nil, errors.Wrap(err, "could not parse MVCExtension") } - n.HeaderBytes += 3 } } for moreRBSPData(br) { next3Bytes, err := br.PeekBits(24) if err != nil && errors.Cause(err) != io.EOF { - return nil, errors.Wrap("could not Peek next 3 bytes") + return nil, errors.Wrap(err, "could not Peek next 3 bytes") } if next3Bytes == 0x000003 { @@ -277,9 +272,8 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { if err != nil { return nil, errors.Wrap(err, "could not read rbspByte") } - n.rbsp = append(n.rbsp, byte(rbspByte)) + n.RBSP = append(n.RBSP, byte(rbspByte)) } - i += 2 // Read Emulation prevention three byte. eptByte, err := br.ReadBits(8) @@ -292,7 +286,7 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { if err != nil { return nil, errors.Wrap(err, "could not read RBSP byte") } - n.rbsp = append(n.rbsp, byte(b)) + n.RBSP = append(n.RBSP, byte(b)) } } diff --git a/codec/h264/h264dec/read.go b/codec/h264/h264dec/read.go index 92a12ed9..bf45bd1b 100644 --- a/codec/h264/h264dec/read.go +++ b/codec/h264/h264dec/read.go @@ -63,7 +63,7 @@ func (h *H264Reader) Start() { switch nalUnit.Type { case naluTypeSPS: // TODO: handle this error - sps, _ := NewSPS(nalUnit.rbsp, false) + sps, _ := NewSPS(nalUnit.RBSP, false) h.VideoStreams = append( h.VideoStreams, &VideoStream{SPS: sps}, @@ -71,20 +71,20 @@ func (h *H264Reader) Start() { case naluTypePPS: videoStream := h.VideoStreams[len(h.VideoStreams)-1] // TODO: handle this error - videoStream.PPS, _ = NewPPS(videoStream.SPS, nalUnit.RBSP(), false) + videoStream.PPS, _ = NewPPS(videoStream.SPS, nalUnit.RBSP, false) case naluTypeSliceIDRPicture: fallthrough case naluTypeSliceNonIDRPicture: videoStream := h.VideoStreams[len(h.VideoStreams)-1] logger.Printf("info: frame number %d\n", len(videoStream.Slices)) // TODO: handle this error - sliceContext, _ := NewSliceContext(videoStream, nalUnit, nalUnit.RBSP(), true) + sliceContext, _ := NewSliceContext(videoStream, nalUnit, nalUnit.RBSP, true) videoStream.Slices = append(videoStream.Slices, sliceContext) } } } -func (r *H264Reader) readNalUnit() (*NalUnit, *bits.BitReader, error) { +func (r *H264Reader) readNalUnit() (*NALUnit, *bits.BitReader, error) { // Read to start of NAL logger.Printf("debug: Seeking NAL %d start\n", len(r.NalUnits)) @@ -131,7 +131,7 @@ func (r *H264Reader) readNalUnit() (*NalUnit, *bits.BitReader, error) { r.NalUnits = append(r.NalUnits, nalUnitReader) // TODO: this should really take an io.Reader rather than []byte. Need to fix nil // once this is fixed. - nalUnit, err := NewNalUnit(nil, 0) + nalUnit, err := NewNALUnit(nil) if err != nil { return nil, nil, errors.Wrap(err, "cannot create new nal unit") } diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index b25b4502..360282a6 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -23,7 +23,7 @@ type VideoStream struct { Slices []*SliceContext } type SliceContext struct { - *NalUnit + *NALUnit *SPS *PPS *Slice @@ -240,12 +240,12 @@ func CodedBlockPatternChroma(data *SliceData) int { // dependencyId see Annex G.8.8.1 // Also G7.3.1.1 nal_unit_header_svc_extension -func DQId(nalUnit *NalUnit) int { - return (nalUnit.DependencyId << 4) + nalUnit.QualityId +func DQId(nalUnit *NALUnit) int { + return (nalUnit.SVCExtension.DependencyID << 4) + nalUnit.SVCExtension.QualityID } // Annex G p527 -func NumMbPart(nalUnit *NalUnit, sps *SPS, header *SliceHeader, data *SliceData) int { +func NumMbPart(nalUnit *NALUnit, sps *SPS, header *SliceHeader, data *SliceData) int { sliceType := sliceTypeMap[header.SliceType] numMbPart := 0 if MbTypeName(sliceType, CurrMbAddr(sps, header)) == "B_SKIP" || MbTypeName(sliceType, CurrMbAddr(sps, header)) == "B_Direct_16x16" { @@ -381,7 +381,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { } } else if mbPartPredMode != direct { - for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NALUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) if err != nil { @@ -417,7 +417,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { } } } - for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NALUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) if err != nil { return errors.Wrap(err, fmt.Sprintf("could not get mbPartPredMode for loop 2 mbPartIdx: %d", mbPartIdx)) @@ -456,7 +456,7 @@ func MbPred(sliceContext *SliceContext, br *bits.BitReader, rbsp []byte) error { } } } - for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { + for mbPartIdx := 0; mbPartIdx < NumMbPart(sliceContext.NALUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data); mbPartIdx++ { sliceContext.Update(sliceContext.Slice.Header, sliceContext.Slice.Data) m, err := MbPartPredMode(sliceContext.Slice.Data, sliceType, sliceContext.Slice.Data.MbType, mbPartIdx) if err != nil { @@ -815,7 +815,7 @@ func NewSliceData(sliceContext *SliceContext, br *bits.BitReader) (*SliceData, e if err != nil { return nil, errors.Wrap(err, "could not get mbPartPredMode") } - if sliceContext.Slice.Data.MbTypeName == "I_NxN" && m != intra16x16 && NumMbPart(sliceContext.NalUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data) == 4 { + if sliceContext.Slice.Data.MbTypeName == "I_NxN" && m != intra16x16 && NumMbPart(sliceContext.NALUnit, sliceContext.SPS, sliceContext.Slice.Header, sliceContext.Slice.Data) == 4 { logger.Printf("\tTODO: subMbPred\n") /* subMbType := SubMbPred(sliceContext.Slice.Data.MbType) @@ -945,7 +945,7 @@ 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(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, showPacket bool) (*SliceContext, error) { var err error sps := videoStream.SPS pps := videoStream.PPS @@ -1350,7 +1350,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } sliceContext := &SliceContext{ - NalUnit: nalUnit, + NALUnit: nalUnit, SPS: sps, PPS: pps, Slice: &Slice{ From 865c1676da44e6a8e63a2eda4cb014a02a6a6b10 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sun, 21 Jul 2019 21:22:55 +0930 Subject: [PATCH 71/93] codec/h264/h264dec: wrote explantory comment for neglectance of io.EOF error when peeking in RBSP parsing process --- codec/h264/h264dec/nalunit.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 9f4ecab3..4f5725cc 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -262,6 +262,11 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { for moreRBSPData(br) { 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 { return nil, errors.Wrap(err, "could not Peek next 3 bytes") } From 47d0c300fc4fe1077e336f87f273e7bcbd9afe90 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 22 Jul 2019 12:56:37 +0930 Subject: [PATCH 72/93] codec/h264/h264dec: created RefPicListModification type with constructors and separated from SliceHeader type --- codec/h264/h264dec/slice.go | 239 ++++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 103 deletions(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index b25b4502..eaf0be79 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -32,51 +32,130 @@ type Slice struct { Header *SliceHeader Data *SliceData } + +// RefPicListModification provides elements of a ref_pic_list_modification syntax +// (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 +} + +// TODO: need to complete this. +// NewRefPicListMVCModification parses elements of a ref_pic_list_mvc_modification +// following the syntax structure defined in section H.7.3.3.1.1, and returns as +// a new RefPicListModification. +func NewRefPicListMVCModifiation(br *bits.BitReader) (*RefPicListModification, error) { + return nil, nil +} + +// 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) { + r := &RefPicListModification{} + // 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 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.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") + } + } + } + } + } + // refPicListModification() + return nil, nil +} + type SliceHeader struct { - FirstMbInSlice int - SliceType int - PPSID int - ColorPlaneID int - FrameNum int - FieldPic bool - BottomField bool - IDRPicID int - PicOrderCntLsb int - DeltaPicOrderCntBottom int - DeltaPicOrderCnt []int - RedundantPicCnt int - DirectSpatialMvPred bool - NumRefIdxActiveOverride bool - NumRefIdxL0ActiveMinus1 int - NumRefIdxL1ActiveMinus1 int - CabacInit int - SliceQpDelta int - SpForSwitch bool - SliceQsDelta int - DisableDeblockingFilter int - SliceAlphaC0OffsetDiv2 int - SliceBetaOffsetDiv2 int - SliceGroupChangeCycle int - RefPicListModificationFlagL0 bool - ModificationOfPicNums int - AbsDiffPicNumMinus1 int - LongTermPicNum int - RefPicListModificationFlagL1 bool - LumaLog2WeightDenom int - ChromaLog2WeightDenom int - ChromaArrayType int - LumaWeightL0Flag bool - LumaWeightL0 []int - LumaOffsetL0 []int - ChromaWeightL0Flag bool - ChromaWeightL0 [][]int - ChromaOffsetL0 [][]int - LumaWeightL1Flag bool - LumaWeightL1 []int - LumaOffsetL1 []int - ChromaWeightL1Flag bool - ChromaWeightL1 [][]int - ChromaOffsetL1 [][]int + FirstMbInSlice int + SliceType int + PPSID int + ColorPlaneID int + FrameNum int + FieldPic bool + BottomField bool + IDRPicID int + PicOrderCntLsb int + DeltaPicOrderCntBottom int + DeltaPicOrderCnt []int + RedundantPicCnt int + DirectSpatialMvPred bool + NumRefIdxActiveOverride bool + NumRefIdxL0ActiveMinus1 int + NumRefIdxL1ActiveMinus1 int + RefPicListModification *RefPicListModification + + // pred_weight_table + LumaLog2WeightDenom int + ChromaLog2WeightDenom int + ChromaArrayType int + LumaWeightL0Flag bool + LumaWeightL0 []int + LumaOffsetL0 []int + ChromaWeightL0Flag bool + ChromaWeightL0 [][]int + ChromaOffsetL0 [][]int + LumaWeightL1Flag bool + LumaWeightL1 []int + LumaOffsetL1 []int + ChromaWeightL1Flag bool + ChromaWeightL1 [][]int + ChromaOffsetL1 [][]int + + // dec_ref_pic_marking NoOutputOfPriorPicsFlag bool LongTermReferenceFlag bool AdaptiveRefPicMarkingModeFlag bool @@ -84,6 +163,15 @@ type SliceHeader struct { DifferenceOfPicNumsMinus1 int LongTermFrameIdx int MaxLongTermFrameIdxPlus1 int + + CabacInit int + SliceQpDelta int + SpForSwitch bool + SliceQsDelta int + DisableDeblockingFilter int + SliceAlphaC0OffsetDiv2 int + SliceBetaOffsetDiv2 int + SliceGroupChangeCycle int } type SliceData struct { @@ -1075,65 +1163,10 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh // H.7.3.3.1.1 // refPicListMvcModifications() } else { - // 7.3.3.1 - if header.SliceType%5 != 2 && header.SliceType%5 != 4 { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL0") - } - header.RefPicListModificationFlagL0 = b == 1 - - if header.RefPicListModificationFlagL0 { - for header.ModificationOfPicNums != 3 { - header.ModificationOfPicNums, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") - } - - if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { - header.AbsDiffPicNumMinus1, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") - } - } else if header.ModificationOfPicNums == 2 { - header.LongTermPicNum, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LongTermPicNum") - } - } - } - } - + header.RefPicListModification, err = NewRefPicListModification(br, &header) + if err != nil { + return nil, errors.Wrap(err, "could not parse RefPicListModification") } - if header.SliceType%5 == 1 { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read RefPicListModificationFlagL1") - } - header.RefPicListModificationFlagL1 = b == 1 - - if header.RefPicListModificationFlagL1 { - for header.ModificationOfPicNums != 3 { - header.ModificationOfPicNums, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ModificationOfPicNums") - } - - if header.ModificationOfPicNums == 0 || header.ModificationOfPicNums == 1 { - header.AbsDiffPicNumMinus1, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse AbsDiffPicNumMinus1") - } - } else if header.ModificationOfPicNums == 2 { - header.LongTermPicNum, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LongTermPicNum") - } - } - } - } - } - // refPicListModification() } if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") { @@ -1278,7 +1311,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } } if header.MemoryManagementControlOperation == 2 { - header.LongTermPicNum, err = readUe(br) + header.RefPicListModification.LongTermPicNum, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse LongTermPicNum") } From c19385301547027299a55caa0971f904c7bd4d97 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 22 Jul 2019 14:01:23 +0930 Subject: [PATCH 73/93] codec/h264/h264dec: wrote PredWeightTable type with constructor and gave to SliceHeader struct --- codec/h264/h264dec/slice.go | 262 +++++++++++++++++++----------------- 1 file changed, 140 insertions(+), 122 deletions(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index eaf0be79..5fa12c82 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -119,6 +119,141 @@ func NewRefPicListModification(br *bits.BitReader, h *SliceHeader) (*RefPicListM return nil, nil } +// PredWeightTable provides elements of a pred_weight_table syntax structure +// as defined in section 7.3.3.2 of the specifications. +type PredWeightTable struct { + LumaLog2WeightDenom int + ChromaLog2WeightDenom int + ChromaArrayType int + LumaWeightL0Flag bool + LumaWeightL0 []int + LumaOffsetL0 []int + ChromaWeightL0Flag bool + ChromaWeightL0 [][]int + ChromaOffsetL0 [][]int + LumaWeightL1Flag bool + LumaWeightL1 []int + LumaOffsetL1 []int + ChromaWeightL1Flag bool + ChromaWeightL1 [][]int + ChromaOffsetL1 [][]int +} + +// 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) { + p := &PredWeightTable{} + var err error + + p.LumaLog2WeightDenom, err = readUe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaLog2WeightDenom") + } + + if p.ChromaArrayType != 0 { + p.ChromaLog2WeightDenom, err = readUe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaLog2WeightDenom") + } + } + for i := 0; i <= h.NumRefIdxL0ActiveMinus1; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LumaWeightL0Flag") + } + p.LumaWeightL0Flag = b == 1 + + if p.LumaWeightL0Flag { + se, err := readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaWeightL0") + } + p.LumaWeightL0 = append(p.LumaWeightL0, se) + + se, err = readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaOffsetL0") + } + p.LumaOffsetL0 = append(p.LumaOffsetL0, se) + } + if p.ChromaArrayType != 0 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaWeightL0Flag") + } + p.ChromaWeightL0Flag = b == 1 + + if p.ChromaWeightL0Flag { + p.ChromaWeightL0 = append(p.ChromaWeightL0, []int{}) + p.ChromaOffsetL0 = append(p.ChromaOffsetL0, []int{}) + for j := 0; j < 2; j++ { + se, err := readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaWeightL0") + } + p.ChromaWeightL0[i] = append(p.ChromaWeightL0[i], se) + + se, err = readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaOffsetL0") + } + p.ChromaOffsetL0[i] = append(p.ChromaOffsetL0[i], se) + } + } + } + } + if h.SliceType%5 == 1 { + for i := 0; i <= h.NumRefIdxL1ActiveMinus1; i++ { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LumaWeightL1Flag") + } + p.LumaWeightL1Flag = b == 1 + + if p.LumaWeightL1Flag { + se, err := readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaWeightL1") + } + p.LumaWeightL1 = append(p.LumaWeightL1, se) + + se, err = readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse LumaOffsetL1") + } + p.LumaOffsetL1 = append(p.LumaOffsetL1, se) + } + if p.ChromaArrayType != 0 { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read ChromaWeightL1Flag") + } + p.ChromaWeightL1Flag = b == 1 + + if p.ChromaWeightL1Flag { + p.ChromaWeightL1 = append(p.ChromaWeightL1, []int{}) + p.ChromaOffsetL1 = append(p.ChromaOffsetL1, []int{}) + for j := 0; j < 2; j++ { + se, err := readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaWeightL1") + } + p.ChromaWeightL1[i] = append(p.ChromaWeightL1[i], se) + + se, err = readSe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse ChromaOffsetL1") + } + p.ChromaOffsetL1[i] = append(p.ChromaOffsetL1[i], se) + } + } + } + } + } + return p, nil +} + type SliceHeader struct { FirstMbInSlice int SliceType int @@ -136,24 +271,9 @@ type SliceHeader struct { NumRefIdxActiveOverride bool NumRefIdxL0ActiveMinus1 int NumRefIdxL1ActiveMinus1 int - RefPicListModification *RefPicListModification - // pred_weight_table - LumaLog2WeightDenom int - ChromaLog2WeightDenom int - ChromaArrayType int - LumaWeightL0Flag bool - LumaWeightL0 []int - LumaOffsetL0 []int - ChromaWeightL0Flag bool - ChromaWeightL0 [][]int - ChromaOffsetL0 [][]int - LumaWeightL1Flag bool - LumaWeightL1 []int - LumaOffsetL1 []int - ChromaWeightL1Flag bool - ChromaWeightL1 [][]int - ChromaOffsetL1 [][]int + *RefPicListModification + *PredWeightTable // dec_ref_pic_marking NoOutputOfPriorPicsFlag bool @@ -1170,113 +1290,11 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } if (pps.WeightedPred && (sliceType == "P" || sliceType == "SP")) || (pps.WeightedBipred == 1 && sliceType == "B") { - // predWeightTable() - header.LumaLog2WeightDenom, err = readUe(br) + header.PredWeightTable, err = NewPredWeightTable(br, &header) if err != nil { - return nil, errors.Wrap(err, "could not parse LumaLog2WeightDenom") + return nil, errors.Wrap(err, "could not parse PredWeightTable") } - - if header.ChromaArrayType != 0 { - header.ChromaLog2WeightDenom, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ChromaLog2WeightDenom") - } - } - for i := 0; i <= header.NumRefIdxL0ActiveMinus1; i++ { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read LumaWeightL0Flag") - } - header.LumaWeightL0Flag = b == 1 - - if header.LumaWeightL0Flag { - se, err := readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LumaWeightL0") - } - header.LumaWeightL0 = append(header.LumaWeightL0, se) - - se, err = readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LumaOffsetL0") - } - header.LumaOffsetL0 = append(header.LumaOffsetL0, se) - } - if header.ChromaArrayType != 0 { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read ChromaWeightL0Flag") - } - header.ChromaWeightL0Flag = b == 1 - - if header.ChromaWeightL0Flag { - header.ChromaWeightL0 = append(header.ChromaWeightL0, []int{}) - header.ChromaOffsetL0 = append(header.ChromaOffsetL0, []int{}) - for j := 0; j < 2; j++ { - se, err := readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ChromaWeightL0") - } - header.ChromaWeightL0[i] = append(header.ChromaWeightL0[i], se) - - se, err = readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ChromaOffsetL0") - } - header.ChromaOffsetL0[i] = append(header.ChromaOffsetL0[i], se) - } - } - } - } - if header.SliceType%5 == 1 { - for i := 0; i <= header.NumRefIdxL1ActiveMinus1; i++ { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read LumaWeightL1Flag") - } - header.LumaWeightL1Flag = b == 1 - - if header.LumaWeightL1Flag { - se, err := readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LumaWeightL1") - } - header.LumaWeightL1 = append(header.LumaWeightL1, se) - - se, err = readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LumaOffsetL1") - } - header.LumaOffsetL1 = append(header.LumaOffsetL1, se) - } - if header.ChromaArrayType != 0 { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read ChromaWeightL1Flag") - } - header.ChromaWeightL1Flag = b == 1 - - if header.ChromaWeightL1Flag { - header.ChromaWeightL1 = append(header.ChromaWeightL1, []int{}) - header.ChromaOffsetL1 = append(header.ChromaOffsetL1, []int{}) - for j := 0; j < 2; j++ { - se, err := readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ChromaWeightL1") - } - header.ChromaWeightL1[i] = append(header.ChromaWeightL1[i], se) - - se, err = readSe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse ChromaOffsetL1") - } - header.ChromaOffsetL1[i] = append(header.ChromaOffsetL1[i], se) - } - } - } - } - } - } // end predWeightTable + } if nalUnit.RefIdc != 0 { // devRefPicMarking() if idrPic { From 0240c4c5f790392dd0efbaf998b6d4321c50d36b Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 22 Jul 2019 15:13:18 +0930 Subject: [PATCH 74/93] codec/h264/h264dec: wrote DecRefPicMarking type with constructor and gave to SliceHeader type --- codec/h264/h264dec/slice.go | 140 ++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 5fa12c82..531e4f8e 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -254,6 +254,78 @@ func NewPredWeightTable(br *bits.BitReader, h *SliceHeader) (*PredWeightTable, e return p, nil } +// DecRefPicMarking provides elements of a dec_ref_pic_marking syntax structure +// as defined in section 7.3.3.3 of the specifications. +type DecRefPicMarking struct { + NoOutputOfPriorPicsFlag bool + LongTermReferenceFlag bool + AdaptiveRefPicMarkingModeFlag bool + MemoryManagementControlOperation int + DifferenceOfPicNumsMinus1 int + LongTermFrameIdx int + MaxLongTermFrameIdxPlus1 int +} + +// 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) { + d := &DecRefPicMarking{} + if idrPic { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read NoOutputOfPriorPicsFlag") + } + d.NoOutputOfPriorPicsFlag = b == 1 + + b, err = br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read LongTermReferenceFlag") + } + d.LongTermReferenceFlag = b == 1 + } else { + b, err := br.ReadBits(1) + if err != nil { + return nil, errors.Wrap(err, "could not read AdaptiveRefPicMarkingModeFlag") + } + 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) + if err != nil { + return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") + } + } + if d.MemoryManagementControlOperation == 2 { + h.RefPicListModification.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 err != nil { + return nil, errors.Wrap(err, "could not parse LongTermFrameIdx") + } + } + if d.MemoryManagementControlOperation == 4 { + d.MaxLongTermFrameIdxPlus1, err = readUe(br) + if err != nil { + return nil, errors.Wrap(err, "could not parse MaxLongTermFrameIdxPlus1") + } + } + } + } + } + return d, nil +} + type SliceHeader struct { FirstMbInSlice int SliceType int @@ -271,19 +343,9 @@ type SliceHeader struct { NumRefIdxActiveOverride bool NumRefIdxL0ActiveMinus1 int NumRefIdxL1ActiveMinus1 int - *RefPicListModification *PredWeightTable - - // dec_ref_pic_marking - NoOutputOfPriorPicsFlag bool - LongTermReferenceFlag bool - AdaptiveRefPicMarkingModeFlag bool - MemoryManagementControlOperation int - DifferenceOfPicNumsMinus1 int - LongTermFrameIdx int - MaxLongTermFrameIdxPlus1 int - + *DecRefPicMarking CabacInit int SliceQpDelta int SpForSwitch bool @@ -1297,58 +1359,10 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NalUnit, rbsp []byte, sh } if nalUnit.RefIdc != 0 { // devRefPicMarking() - if idrPic { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read NoOutputOfPriorPicsFlag") - } - header.NoOutputOfPriorPicsFlag = b == 1 - - b, err = br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read LongTermReferenceFlag") - } - header.LongTermReferenceFlag = b == 1 - } else { - b, err := br.ReadBits(1) - if err != nil { - return nil, errors.Wrap(err, "could not read AdaptiveRefPicMarkingModeFlag") - } - header.AdaptiveRefPicMarkingModeFlag = b == 1 - - if header.AdaptiveRefPicMarkingModeFlag { - header.MemoryManagementControlOperation, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") - } - for header.MemoryManagementControlOperation != 0 { - if header.MemoryManagementControlOperation == 1 || header.MemoryManagementControlOperation == 3 { - header.DifferenceOfPicNumsMinus1, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse MemoryManagementControlOperation") - } - } - if header.MemoryManagementControlOperation == 2 { - header.RefPicListModification.LongTermPicNum, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LongTermPicNum") - } - } - if header.MemoryManagementControlOperation == 3 || header.MemoryManagementControlOperation == 6 { - header.LongTermFrameIdx, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse LongTermFrameIdx") - } - } - if header.MemoryManagementControlOperation == 4 { - header.MaxLongTermFrameIdxPlus1, err = readUe(br) - if err != nil { - return nil, errors.Wrap(err, "could not parse MaxLongTermFrameIdxPlus1") - } - } - } - } - } // end decRefPicMarking + header.DecRefPicMarking, err = NewDecRefPicMarking(br, idrPic, &header) + if err != nil { + return nil, errors.Wrap(err, "could not parse DecRefPicMarking") + } } if pps.EntropyCodingMode == 1 && sliceType != "I" && sliceType != "SI" { header.CabacInit, err = readUe(br) From 513ac67ad989edd2bc9bf65348c43113f077c1e3 Mon Sep 17 00:00:00 2001 From: Saxon Date: Tue, 23 Jul 2019 16:37:08 +0930 Subject: [PATCH 75/93] codec/h264/h264dec: added tests for pps parsing found in pps.go and made necessary changes Changes to get successful parsing included modification to moreRBSPData, and as a result the Off function from the bits.BitReader. A couple of basic PPS tests have been added, but more should be added once we know the scaling list parsing works. --- codec/h264/h264dec/bits/bitreader.go | 5 + codec/h264/h264dec/parse_test.go | 14 +-- codec/h264/h264dec/pps.go | 32 ++---- codec/h264/h264dec/pps_test.go | 147 +++++++++++++++++++++++++++ codec/h264/h264dec/read.go | 72 ++++++++++--- codec/h264/h264dec/read_test.go | 52 ++++++++++ 6 files changed, 278 insertions(+), 44 deletions(-) create mode 100644 codec/h264/h264dec/pps_test.go create mode 100644 codec/h264/h264dec/read_test.go diff --git a/codec/h264/h264dec/bits/bitreader.go b/codec/h264/h264dec/bits/bitreader.go index 7e17f407..bed0ac7c 100644 --- a/codec/h264/h264dec/bits/bitreader.go +++ b/codec/h264/h264dec/bits/bitreader.go @@ -164,6 +164,11 @@ func (br *BitReader) ByteAligned() bool { return br.bits == 0 } +// Off returns the current offset from the starting bit of the current byte. +func (br *BitReader) Off() int { + return br.bits +} + // BytesRead returns the number of bytes that have been read by the BitReader. func (br *BitReader) BytesRead() int { return br.nRead diff --git a/codec/h264/h264dec/parse_test.go b/codec/h264/h264dec/parse_test.go index c936ca3d..9bec492d 100644 --- a/codec/h264/h264dec/parse_test.go +++ b/codec/h264/h264dec/parse_test.go @@ -89,13 +89,13 @@ func TestReadSe(t *testing.T) { in []byte // Bitstring to read. want int // Expected value from se(v) parsing process. }{ - {[]byte{0x80}, 0}, - {[]byte{0x40}, 1}, - {[]byte{0x60}, -1}, - {[]byte{0x20}, 2}, - {[]byte{0x28}, -2}, - {[]byte{0x30}, 3}, - {[]byte{0x38}, -3}, + {[]byte{0x80}, 0}, // Bit string: 1, codeNum: 0, syntax element val: 0 + {[]byte{0x40}, 1}, // Bit string: 010, codeNum: 1, syntax element val: 1 + {[]byte{0x60}, -1}, // Bit string: 011, codeNum: 2, syntax element val: -1 + {[]byte{0x20}, 2}, // Bit string: 00100, codeNum: 3, syntax element val: 2 + {[]byte{0x28}, -2}, // Bit string: 00101, codeNum: 4, syntax element val: -2 + {[]byte{0x30}, 3}, // Bit string: 00110, codeNum: 5, syntax element val: 3 + {[]byte{0x38}, -3}, // Bit string: 00111, codeNum: 6, syntax element val: -3 } for i, test := range tests { diff --git a/codec/h264/h264dec/pps.go b/codec/h264/h264dec/pps.go index 139306c6..51f508b5 100644 --- a/codec/h264/h264dec/pps.go +++ b/codec/h264/h264dec/pps.go @@ -14,9 +14,8 @@ import ( type PPS struct { ID, SPSID int EntropyCodingMode int - NumSliceGroupsMinus1 int BottomFieldPicOrderInFramePresent bool - NumSlicGroupsMinus1 int + NumSliceGroupsMinus1 int SliceGroupMapType int RunLengthMinus1 []int TopLeft []int @@ -41,14 +40,10 @@ type PPS struct { SecondChromaQpIndexOffset int } -func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { - logger.Printf("debug: PPS RBSP %d bytes %d bits == \n", len(rbsp), len(rbsp)*8) - logger.Printf("debug: \t%#v\n", rbsp[0:8]) +func NewPPS(br *bits.BitReader, chromaFormat int) (*PPS, error) { pps := PPS{} - // TODO: give this io.Reader - br := bits.NewBitReader(nil) - var err error + pps.ID, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse ID") @@ -84,10 +79,11 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { if pps.SliceGroupMapType == 0 { for iGroup := 0; iGroup <= pps.NumSliceGroupsMinus1; iGroup++ { - pps.RunLengthMinus1[iGroup], err = readUe(br) + b, err := readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse RunLengthMinus1") } + pps.RunLengthMinus1 = append(pps.RunLengthMinus1, b) } } else if pps.SliceGroupMapType == 2 { for iGroup := 0; iGroup < pps.NumSliceGroupsMinus1; iGroup++ { @@ -195,7 +191,7 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { if pps.PicScalingMatrixPresent { v := 6 - if sps.ChromaFormat != chroma444 { + if chromaFormat != chroma444 { v = 2 } for i := 0; i < 6+(v*pps.Transform8x8Mode); i++ { @@ -222,18 +218,12 @@ func NewPPS(sps *SPS, rbsp []byte, showPacket bool) (*PPS, error) { } } } - pps.SecondChromaQpIndexOffset, err = readSe(br) - if err != nil { - return nil, errors.New("could not parse SecondChromaQpIndexOffset") - } } - moreRBSPData(br) - // rbspTrailingBits() - } - - if showPacket { - debugPacket("PPS", pps) + pps.SecondChromaQpIndexOffset, err = readSe(br) + if err != nil { + return nil, errors.New("could not parse SecondChromaQpIndexOffset") + } } + moreRBSPData(br) return &pps, nil - } diff --git a/codec/h264/h264dec/pps_test.go b/codec/h264/h264dec/pps_test.go new file mode 100644 index 00000000..7ac357c8 --- /dev/null +++ b/codec/h264/h264dec/pps_test.go @@ -0,0 +1,147 @@ +package h264dec + +import ( + "bytes" + "errors" + "reflect" + "testing" + + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" +) + +func TestNewPPS(t *testing.T) { + // TODO: add test with scaling list once we have a test for scalingList func. + tests := []struct { + in string + chromaFormat int + want PPS + }{ + { + in: "1" + // ue(v) pic_parameter_set_id = 0 + "1" + // ue(v) seq_parameter_set_id = 0 + "1" + // u(1) entropy_coding_mode_flag = 1 + "0" + // u(1) pic_order_present_flag = 0 + "1" + // ue(v) num_slice_groups_minus1 = 0 + "1" + // ue(v) num_ref_idx_L0_active_minus1 = 0 + "1" + // ue(v) num_ref_idx_L1_active_minus1 = 0 + "1" + // u(1) weighted_pred_flag = 1 + "00" + // u(2) weighted_bipred_idc = 0 + "1" + // se(v) pic_init_qp_minus26 = 0 + "1" + // se(v) pic_init_qs_minus26 = 0 + "1" + // se(v) chroma_qp_index_offset = 0 + "1" + // u(1) deblocking_filter_control_present_flag = 1 + "0" + // u(1) constrained_intra_pred_flag = 0 + "0" + // u(1) redundant_pic_cnt_present_flag = 0 + "10000000", // rbspTrailingBits + want: PPS{ + ID: 0, + SPSID: 0, + EntropyCodingMode: 1, + BottomFieldPicOrderInFramePresent: false, + NumSliceGroupsMinus1: 0, + NumRefIdxL0DefaultActiveMinus1: 0, + NumRefIdxL1DefaultActiveMinus1: 0, + WeightedPred: true, + WeightedBipred: 0, + PicInitQpMinus26: 0, + PicInitQsMinus26: 0, + ChromaQpIndexOffset: 0, + DeblockingFilterControlPresent: true, + ConstrainedIntraPred: false, + RedundantPicCntPresent: false, + }, + }, + { + in: "1" + // ue(v) pic_parameter_set_id = 0 + "1" + // ue(v) seq_parameter_set_id = 0 + "1" + // u(1) entropy_coding_mode_flag = 1 + "1" + // u(1) bottom_field_pic_order_in_frame_present_flag = 1 + "010" + // ue(v) num_slice_groups_minus1 = 1 + "1" + // ue(v) slice_group_map_type = 0 + "1" + // ue(v) run_length_minus1[0] = 0 + "1" + // ue(v) run_length_minus1[1] = 0 + "1" + // ue(v) num_ref_idx_L0_active_minus1 = 0 + "1" + // ue(v) num_ref_idx_L1_active_minus1 = 0 + "1" + // u(1) weighted_pred_flag = 0 + "00" + // u(2) weighted_bipred_idc = 0 + "011" + // se(v) pic_init_qp_minus26 = -1 + "010" + // se(v) pic_init_qs_minus26 = 1 + "00100" + // se(v) chroma_qp_index_offset = 2 + "0" + // u(1) deblocking_filter_control_present_flag =0 + "0" + // u(1) constrained_intra_pred_flag=0 + "0" + // u(1) redundant_pic_cnt_present_flag=0 + "0" + // u(1) transform_8x8_mode_flag=0 + "0" + // u(1) pic_scaling_matrix_present_flag=0 + "00100" + // se(v) second_chroma_qp_index_offset=2 + "10000", // stop bit and trailing bits + want: PPS{ + ID: 0, + SPSID: 0, + EntropyCodingMode: 1, + BottomFieldPicOrderInFramePresent: true, + NumSliceGroupsMinus1: 1, + RunLengthMinus1: []int{0, 0}, + NumRefIdxL0DefaultActiveMinus1: 0, + NumRefIdxL1DefaultActiveMinus1: 0, + WeightedPred: true, + WeightedBipred: 0, + PicInitQpMinus26: -1, + PicInitQsMinus26: 1, + ChromaQpIndexOffset: 2, + DeblockingFilterControlPresent: false, + ConstrainedIntraPred: false, + RedundantPicCntPresent: false, + Transform8x8Mode: 0, + PicScalingMatrixPresent: false, + SecondChromaQpIndexOffset: 2, + }, + }, + } + + for i, test := range tests { + bin, err := binToSlice(test.in) + if err != nil { + t.Fatalf("error: %v converting binary string to slice for test: %d", err, i) + } + + pps, err := NewPPS(bits.NewBitReader(bytes.NewReader(bin)), test.chromaFormat) + if err != nil { + t.Fatalf("did not expect error: %v for test: %d", err, i) + } + + if !reflect.DeepEqual(test.want, *pps) { + t.Errorf("did not get expected result for test: %d.\nGot: %+v\nWant: %+v\n", i, *pps, test.want) + } + } +} + +// binToSlice is a helper function to convert a string of binary into a +// corresponding byte slice, e.g. "0100 0001 1000 1100" => {0x41,0x8c}. +// Spaces in the string are ignored. +func binToSlice(s string) ([]byte, error) { + var ( + a byte = 0x80 + cur byte + bytes []byte + ) + + for _, c := range s { + switch c { + case ' ': + continue + case '1': + cur |= a + case '0': + default: + return nil, errors.New("invalid binary string") + } + + a >>= 1 + if a == 0 { + bytes = append(bytes, cur) + cur = 0 + a = 0x80 + } + } + return bytes, nil +} diff --git a/codec/h264/h264dec/read.go b/codec/h264/h264dec/read.go index 92a12ed9..fe0ab176 100644 --- a/codec/h264/h264dec/read.go +++ b/codec/h264/h264dec/read.go @@ -71,7 +71,7 @@ func (h *H264Reader) Start() { case naluTypePPS: videoStream := h.VideoStreams[len(h.VideoStreams)-1] // TODO: handle this error - videoStream.PPS, _ = NewPPS(videoStream.SPS, nalUnit.RBSP(), false) + videoStream.PPS, _ = NewPPS(nil, videoStream.SPS.ChromaFormat) case naluTypeSliceIDRPicture: fallthrough case naluTypeSliceNonIDRPicture: @@ -177,23 +177,63 @@ func isEmpty3Byte(buf []byte) bool { return true } -// TODO: complete this. func moreRBSPData(br *bits.BitReader) bool { - // Read until the least significant bit of any remaining bytes - // If the least significant bit is 1, that marks the first bit - // of the rbspTrailingBits() struct. If the bits read is more - // than 0, then there is more RBSP data - var bits uint64 - cnt := 0 - for bits != 1 { - if _, err := br.ReadBits(8); err != nil { - logger.Printf("moreRBSPData error: %v\n", err) - return false - } - cnt++ + // If we get an error then we must at end of NAL unit or end of stream, so + // return false. + b, err := br.PeekBits(1) + if err != nil { + return false } - logger.Printf("moreRBSPData: read %d additional bits\n", cnt) - return cnt > 0 + + // If b is not 1, then we don't have a stop bit and therefore there is more + // data so return true. + if b == 0 { + return true + } + + // If we have a stop bit and trailing zeros then we're okay, otherwise return + // now, we haven't found the end. + b, err = br.PeekBits(8 - br.Off()) + if err != nil { + return false + } + rem := 0x01 << uint(7-br.Off()) + if int(b) != rem { + return true + } + + // If we try to read another bit but get EOF then we must be at the end of the + // NAL or stream. + _, err = br.PeekBits(9 - br.Off()) + if err != nil { + return false + } + + // Do we have some trailing 0 bits, and then a 24-bit start code ? If so, it + // there must not be any more RBSP data left. + // If we get an error from the Peek, then there must not be another NAL, and + // there must be some more RBSP, because trailing bits do not extend past the + // byte in which the stop bit is found. + b, err = br.PeekBits(8 - br.Off() + 24) + if err != nil { + return true + } + rem = (0x01 << uint((7-br.Off())+24)) | 0x01 + if int(b) == rem { + return false + } + + // Similar check to above, but this time checking for 32-bit start code. + b, err = br.PeekBits(8 - br.Off() + 32) + if err != nil { + return true + } + rem = (0x01 << uint((7-br.Off())+32)) | 0x01 + if int(b) == rem { + return false + } + + return true } type field struct { diff --git a/codec/h264/h264dec/read_test.go b/codec/h264/h264dec/read_test.go new file mode 100644 index 00000000..f6fe7ddd --- /dev/null +++ b/codec/h264/h264dec/read_test.go @@ -0,0 +1,52 @@ +package h264dec + +import ( + "bytes" + "testing" + + "bitbucket.org/ausocean/av/codec/h264/h264dec/bits" +) + +func TestMoreRBSPData(t *testing.T) { + tests := []struct { + in string + want bool + }{ + { + in: "00000100", + want: true, + }, + { + in: "10000100", + want: true, + }, + { + in: "10000000", + want: false, + }, + { + in: "10000000 00000000 00000000 00000001", + want: false, + }, + { + in: "10000000 00000000 00000000 00000000 00000001", + want: false, + }, + { + in: "10000000 00000000", + want: true, + }, + } + + for i, test := range tests { + b, err := binToSlice(test.in) + if err != nil { + t.Fatalf("unexpected binToSlice error: %v for test: %d", err, i) + } + + got := moreRBSPData(bits.NewBitReader(bytes.NewReader(b))) + if got != test.want { + t.Errorf("unexpected result for test: %d\nGot: %v\nWant: %v\n", i, got, test.want) + } + } +} From bdc3b4cfc5c7e402a1c8148f2e4a27a9bbcdd264 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 24 Jul 2019 00:04:10 +0930 Subject: [PATCH 76/93] container/mts/mpegts.go: added Programs, Streams and MediaStreams functions --- container/mts/mpegts.go | 85 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index 0b356770..f2a9ae0b 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -33,6 +33,7 @@ import ( "fmt" "github.com/Comcast/gots/packet" + gotspsi "github.com/Comcast/gots/psi" "github.com/pkg/errors" "bitbucket.org/ausocean/av/container/mts/meta" @@ -420,9 +421,9 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { } var ( - errNoPesPayload = errors.New("no PES payload") - errNoPesPTS = errors.New("no PES PTS") - errInvalidPesHeader = errors.New("invalid PES header") + errNoPesPayload = errors.New("no PES payload") + errNoPesPTS = errors.New("no PES PTS") + errInvalidPesHeader = errors.New("invalid PES header") errInvalidPesPayload = errors.New("invalid PES payload") ) @@ -592,3 +593,81 @@ func SegmentForMeta(d []byte, key, val string) ([][]byte, error) { return res, nil } + +// pid returns the packet identifier for the given packet. +func pid(p []byte) uint16 { + return uint16(p[1]&0x1f)<<8 | uint16(p[2]) +} + +// Programs returns a map of program numbers and corresponding PMT PIDs for a +// given MPEG-TS PAT packet. +func Programs(p []byte) (map[uint16]uint16, error) { + pat, err := gotspsi.NewPAT(p) + if err != nil { + return nil, err + } + return pat.ProgramMap(), nil +} + +// Streams returns elementary streams defined in a given MPEG-TS PMT packet. +// A gotspsi.PmtElementaryStream will give stream type from +// gotspsi.PmtElementaryStream.StreamType() and PID from +// gotspsi.PmtElementaryStream.ElementaryPid(). +// +// PmtStreamTypes from gots/psi are defined as follows: +// PmtStreamTypeMpeg2VideoH262 uint8 = 2 // H262 +// PmtStreamTypeMpeg4Video uint8 = 27 // H264 +// PmtStreamTypeMpeg4VideoH264 uint8 = 27 // H264 +// PmtStreamTypeMpeg4VideoH265 uint8 = 36 // H265 +// PmtStreamTypeAac uint8 = 15 // AAC +// PmtStreamTypeAc3 uint8 = 129 // DD +// PmtStreamTypeEc3 uint8 = 135 // DD+ +// PmtStreamTypeScte35 uint8 = 134 // SCTE-35 +func Streams(p []byte) ([]gotspsi.PmtElementaryStream, error) { + pmt, err := gotspsi.NewPMT(p) + if err != nil { + return nil, err + } + return pmt.ElementaryStreams(), nil +} + +// MediaStreams retrieves the PmtElementaryStreams from the given PSI. This +// function currently assumes that PSI contain a PAT followed by a PMT directly +// after. We also assume that this MPEG-TS stream contains just one program, +// but this program may contain different streams, i.e. a video stream + audio +// stream. +func MediaStreams(p []byte) ([]gotspsi.PmtElementaryStream, error) { + pat := p[:PacketSize] + pmt := p[PacketSize : 2*PacketSize] + + if pid(pat) != PatPid { + return nil, errors.New("first packet is not a PAT") + } + + m, err := Programs(pat) + if err != nil { + return nil, errors.Wrap(err, "could not get programs from PAT") + } + + if len(m) == 0 { + return nil, errors.New("no programs contained in PAT") + } + + if len(m) > 1 { + return nil, errors.New("more than one program not yet supported") + } + + var v uint16 + for _, v = range m { + } + + if pid(pmt) != v { + return nil, errors.New("second packet is not desired PMT") + } + + s, err := Streams(pmt) + if err != nil { + return nil, errors.Wrap(err, "could not get streams from PMT") + } + return s, nil +} From 3c1965d93826b8ba77c4715cbd96ed6dc5e86877 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 26 Jul 2019 18:55:56 +0930 Subject: [PATCH 77/93] Propagate netsender client error. --- cmd/revid-cli/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 46f39905..cf2ee174 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -262,7 +262,7 @@ func run(cfg revid.Config) { ns, err := netsender.New(log, nil, readPin, nil) if err != nil { - log.Log(logger.Fatal, pkg+"could not initialise netsender client") + log.Log(logger.Fatal, pkg+"could not initialise netsender client: "+err.Error()) } var vs int From 6779fa3cb64e7c1e3984e4eafb36e365597e8c99 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 27 Jul 2019 12:26:30 +0930 Subject: [PATCH 78/93] codec/h264/h264dec: commenting for NAL uni syntax structure fields --- codec/h264/h264dec/nalunit.go | 143 +++++++++++++++++++++++++++------- 1 file changed, 115 insertions(+), 28 deletions(-) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 4f5725cc..2e076567 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -19,13 +19,29 @@ import ( // MVCExtension describes a NAL unit header multiview video coding extension, as // defined in section H.7.3.1.1 of the specifications. +// Semantics of fields are specified in section H.7.4.1.1. type MVCExtension struct { - NonIdrFlag bool - PriorityID int - ViewID int - TemporalID int - AnchorPicFlag bool - InterViewFlag bool + // non_idr_flag, if true indicates that access unit is not IDR. + NonIdrFlag bool + + // priority_id, indicates priority of NAL unit. A lower value => higher priority. + PriorityID int + + // view_id, specifies a view identifier for the unit. Units with identical + // view_id are in the same view. + ViewID int + + // temporal_id, temporal identifier for the unit. + TemporalID int + + // anchor_pic_flag, if true access unit is an anchor access unit. + AnchorPicFlag bool + + // inter_view_flag, if false current view component not used for inter-view + // prediction elsewhere in access unit. + InterViewFlag bool + + // reserved_one_bit, always 1 (ignored by decoders) ReservedOneBit int } @@ -76,12 +92,26 @@ func NewMVCExtension(br *bits.BitReader) (*MVCExtension, error) { // ThreeDAVCExtension describes a NAL unit header 3D advanced video coding // extension, as defined in section J.7.3.1.1 of the specifications. +// For field semantics see section J.7.4.1.1. type ThreeDAVCExtension struct { - ViewIdx int - DepthFlag bool - NonIdrFlag bool - TemporalID int + // view_idx, specifies the order index for the NAL i.e. view_id = view_id[view_idx]. + ViewIdx int + + // dpeth_flag, if true indicates NAL part of a depth view component, otherwise + // a texture view component. + DepthFlag bool + + // non_idr_flag, if true indicates that access unit is not IDR. + NonIdrFlag bool + + // temporal_id, temporal identifier for the unit. + TemporalID int + + // anchor_pic_flag, if true access unit is an anchor access unit. AnchorPicFlag bool + + // inter_view_flag, if false current view component not used for inter-view + // prediction elsewhere in access unit. InterViewFlag bool } @@ -127,17 +157,44 @@ func NewThreeDAVCExtension(br *bits.BitReader) (*ThreeDAVCExtension, error) { // SVCExtension describes a NAL unit header scalable video coding extension, as // defined in section G.7.3.1.1 of the specifications. +// For field semantics see section G.7.4.1.1. type SVCExtension struct { - IdrFlag bool - PriorityID int + // idr_flag, if true the current coded picture is an IDR picture when + // dependency_id == max(dependency_id) in the coded picture. + IdrFlag bool + + // priority_id, specifies priority identifier for unit. + PriorityID int + + // no_inter_layer_pred_flag, if true inter-layer prediction can't be used for + // decoding slice. NoInterLayerPredFlag bool - DependencyID int - QualityID int - TemporalID int - UseRefBasePicFlag bool - DiscardableFlag bool - OutputFlag bool - ReservedThree2Bits int + + // dependency_id, specifies a dependency identifier for the NAL. + DependencyID int + + // quality_id, specifies a quality identifier for the NAL. + QualityID int + + // temporal_id, specifiesa temporal identifier for the NAL. + TemporalID int + + // use_ref_base_pic_flag, if true indicates reference base pictures and + // decoded pictures are used as references for inter prediction. + UseRefBasePicFlag bool + + // discardable_flag, if true, indicates current NAL is not used for decoding + // dependency representations that are part of the current coded picture or + // any subsequent coded picture in decoding order and have a greater + // dependency_id value than current NAL. + DiscardableFlag bool + + // output_flag, affects the decoded picture output and removal processes as + // specified in Annex C. + OutputFlag bool + + // reserved_three_2bits, equal to 3. Decoders ignore. + ReservedThree2Bits int } // NewSVCExtension parses a NAL unit header scalable video coding extension from @@ -202,17 +259,47 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { // NALUnit describes a network abstraction layer unit, as defined in section // 7.3.1 of the specifications. +// Field semantics are defined in section 7.4.1. type NALUnit struct { - ForbiddenZeroBit int - RefIdc int - Type int - SVCExtensionFlag bool - AVC3DExtensionFlag bool - SVCExtension *SVCExtension - ThreeDAVCExtension *ThreeDAVCExtension - MVCExtension *MVCExtension + // forbidden_zero_bit, always 0. + ForbiddenZeroBit int + + // nal_ref_idc, if not 0 indicates content of NAL contains a sequence parameter + // set, a sequence parameter set extension, a subset sequence parameter set, + // a picture parameter set, a slice of a reference picture, a slice data + // partition of a reference picture, or a prefix NAL preceding a slice of + // a reference picture. + RefIdc int + + // nal_unit_type, specifies the type of RBSP data contained in the NAL as + // defined in Table 7-1. + Type int + + // svc_extension_flag, indicates whether a nal_unit_header_svc_extension() + // (G.7.3.1.1) or nal_unit_header_mvc_extension() (H.7.3.1.1) will follow next + // in the syntax structure. + SVCExtensionFlag bool + + // avc_3d_extension_flag, for nal_unit_type = 21, indicates that a + // nal_unit_header_mvc_extension() (H.7.3.1.1) or + // nal_unit_header_3davc_extension() (J.7.3.1.1) will follow next in syntax + // structure. + AVC3DExtensionFlag bool + + // nal_unit_header_svc_extension() as defined in section G.7.3.1.1. + SVCExtension *SVCExtension + + // nal_unit_header_3davc_extension() as defined in section J.7.3.1.1 + ThreeDAVCExtension *ThreeDAVCExtension + + // nal_unit_header_mvc_extension() as defined in section H.7.3.1.1). + MVCExtension *MVCExtension + + // emulation_prevention_three_byte, equal to 0x03 and is discarded by decoder. EmulationPreventionThreeByte byte - RBSP []byte + + // rbsp_byte, the raw byte sequence payload data for the NAL. + RBSP []byte } // NewNALUnit parses a network abstraction layer unit from br following the From 23c53d78f5900f0f8d44148dc7bb6d546f2b0d3e Mon Sep 17 00:00:00 2001 From: scruzin Date: Mon, 15 Jul 2019 08:50:45 +0930 Subject: [PATCH 79/93] Use ausocean/iot v1.2.5. --- go.mod | 9 ++------- go.sum | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index c3d766c5..37a7a31f 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,12 @@ module bitbucket.org/ausocean/av go 1.12 require ( - bitbucket.org/ausocean/iot v1.2.4 + bitbucket.org/ausocean/iot v1.2.5 bitbucket.org/ausocean/utils v1.2.6 - github.com/BurntSushi/toml v0.3.1 // indirect github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 - github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect - github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 // indirect github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 github.com/mewkiz/flac v1.0.5 - github.com/sergi/go-diff v1.0.0 // indirect + github.com/pkg/errors v0.8.1 github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index cd09945f..5ee9f294 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ 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/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= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -8,16 +11,21 @@ github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXSh github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7/go.mod h1:O5HA0jgDXkBp+jw0770QNBT8fsRJCbH7JXmM7wxLUBU= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/adrianmo/go-nmea v1.1.1-0.20190109062325-c448653979f7/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-audio/aiff v0.0.0-20180403003018-6c3a8a6aff12/go.mod h1:AMSAp6W1zd0koOdX6QDgGIuBDTUvLa2SLQtm7d9eM3c= github.com/go-audio/audio v0.0.0-20180206231410-b697a35b5608/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 h1:4sGU+UABMMsRJyD+Y2yzMYxq0GJFUsRRESI0P1gZ2ig= github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs= +github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ= github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21 h1:Hc1iKlyxNHp3CV59G2E/qabUkHvEwOIJxDK0CJ7CRjA= github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21/go.mod h1:LlQmBGkOuV/SKzEDXBPKauvN2UqCgzXO2XjecTGj40s= github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc= @@ -29,16 +37,19 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e h1:3NIzz7weXhh3NToPgbtlQtKiVgerEaG4/nY2skGoGG0= github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e/go.mod h1:CaowXBWOiSGWEpBBV8LoVnQTVPV4ycyviC9IBLj8dRw= +github.com/yryz/ds18b20 v0.0.0-20180211073435-3cf383a40624/go.mod h1:MqFju5qeLDFh+S9PqxYT7TEla8xeW7bgGr/69q3oki0= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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= +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= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= From 46c709404f7213499eb1672d55bc900f94d28e14 Mon Sep 17 00:00:00 2001 From: scruzin Date: Fri, 26 Jul 2019 18:55:56 +0930 Subject: [PATCH 80/93] Propagate netsender client error. --- cmd/revid-cli/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 46f39905..cf2ee174 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -262,7 +262,7 @@ func run(cfg revid.Config) { ns, err := netsender.New(log, nil, readPin, nil) if err != nil { - log.Log(logger.Fatal, pkg+"could not initialise netsender client") + log.Log(logger.Fatal, pkg+"could not initialise netsender client: "+err.Error()) } var vs int From 404e6493b609d533e3583c8b300563b65554074f Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 27 Jul 2019 13:35:05 +0930 Subject: [PATCH 81/93] codec/h264/h26dec/read.go: added fieldReader type to provide sticky error and specific methods for bool and int reading --- codec/h264/h264dec/read.go | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/codec/h264/h264dec/read.go b/codec/h264/h264dec/read.go index 92a12ed9..f0a65500 100644 --- a/codec/h264/h264dec/read.go +++ b/codec/h264/h264dec/read.go @@ -228,3 +228,43 @@ func readFlags(br *bits.BitReader, flags []flag) error { } return nil } + +// fieldReader provides methods for reading bool and int fields from a +// bits.BitReader with a sticky error that may be checked after a series of +// parsing read calls. +type fieldReader struct { + e error + br *bits.BitReader +} + +// newFieldReader returns a new fieldReader. +func newFieldReader(br *bits.BitReader) fieldReader { + return fieldReader{br: br} +} + +// readBool returns a bool from reading one bit from br. If we have an error +// already, we do not continue with the read. +func (r fieldReader) readBool() bool { + if r.e != nil { + return false + } + var b uint64 + b, r.e = r.br.ReadBits(1) + return b == 1 +} + +// readBitsInt returns an int from reading n bits from br. If we have an error +// already, we do not continue with the read. +func (r fieldReader) readBitsInt(n int) int { + if r.e != nil { + return 0 + } + var b uint64 + b, r.e = r.br.ReadBits(n) + return int(b) +} + +// err returns the fieldReader's error e. +func (r fieldReader) err() error { + return r.e +} From 9f47b22a84b82dc09579852238d45a14f5815b81 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 27 Jul 2019 13:59:12 +0930 Subject: [PATCH 82/93] codec/h264/h264dec: moved fieldReader to parse.go and wrote methods for reading other descriptor types like ue and te. --- codec/h264/h264dec/parse.go | 92 +++++++++++++++++++++++++++++++++++++ codec/h264/h264dec/read.go | 40 ---------------- 2 files changed, 92 insertions(+), 40 deletions(-) diff --git a/codec/h264/h264dec/parse.go b/codec/h264/h264dec/parse.go index 86baaece..e6fba287 100644 --- a/codec/h264/h264dec/parse.go +++ b/codec/h264/h264dec/parse.go @@ -36,6 +36,98 @@ const ( naMbPartPredMode ) +// fieldReader provides methods for reading bool and int fields from a +// bits.BitReader with a sticky error that may be checked after a series of +// parsing read calls. +type fieldReader struct { + e error + br *bits.BitReader +} + +// newFieldReader returns a new fieldReader. +func newFieldReader(br *bits.BitReader) fieldReader { + return fieldReader{br: br} +} + +// readBool returns a bool from reading one bit from br. If we have an error +// already, we do not continue with the read. +func (r fieldReader) readBool() bool { + if r.e != nil { + return false + } + var b uint64 + b, r.e = r.br.ReadBits(1) + return b == 1 +} + +// readBitsInt returns an int from reading n bits from br. If we have an error +// already, we do not continue with the read. +func (r fieldReader) readBitsInt(n int) int { + if r.e != nil { + return 0 + } + var b uint64 + b, r.e = r.br.ReadBits(n) + return int(b) +} + +// readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer +// Exp-Golomb-coded element using method as specified in section 9.1 of ITU-T +// H.264 and return as an int. The read does not happen if the fieldReader +// has a non-nil error. +func (r fieldReader) readUe() int { + if r.e != nil { + return 0 + } + var i int + i, r.e = readUe(r.br) + return i +} + +// readTe parses a syntax element of te(v) descriptor i.e, truncated +// Exp-Golomb-coded syntax element using method as specified in section 9.1 +// and returns as an int. The read does not happen if the fieldReader +// has a non-nil error. +func (r fieldReader) readTe(x uint) int { + if r.e != nil { + return 0 + } + var i int + i, r.e = readTe(r.br, x) + return i +} + +// readSe parses a syntax element with descriptor se(v), i.e. a signed integer +// Exp-Golomb-coded syntax element, using the method described in sections +// 9.1 and 9.1.1 and returns as int. The read does not happen if the fieldReader +// has a non-nil error. +func (r fieldReader) readSe() int { + if r.e != nil { + return 0 + } + var i int + i, r.e = readSe(r.br) + return i +} + +// readMe parses a syntax element of me(v) descriptor, i.e. mapped +// Exp-Golomb-coded element, using methods described in sections 9.1 and 9.1.2 +// and returns as int. The read does not happen if the fieldReader has a +// non-nil error. +func (r fieldReader) readMe(chromaArrayType uint, mpm mbPartPredMode) int { + if r.e != nil { + return 0 + } + var i uint + i, r.e = readMe(r.br, chromaArrayType, mpm) + return int(i) +} + +// err returns the fieldReader's error e. +func (r fieldReader) err() error { + return r.e +} + // readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer // Exp-Golomb-coded element using method as specified in section 9.1 of ITU-T H.264. // diff --git a/codec/h264/h264dec/read.go b/codec/h264/h264dec/read.go index f0a65500..92a12ed9 100644 --- a/codec/h264/h264dec/read.go +++ b/codec/h264/h264dec/read.go @@ -228,43 +228,3 @@ func readFlags(br *bits.BitReader, flags []flag) error { } return nil } - -// fieldReader provides methods for reading bool and int fields from a -// bits.BitReader with a sticky error that may be checked after a series of -// parsing read calls. -type fieldReader struct { - e error - br *bits.BitReader -} - -// newFieldReader returns a new fieldReader. -func newFieldReader(br *bits.BitReader) fieldReader { - return fieldReader{br: br} -} - -// readBool returns a bool from reading one bit from br. If we have an error -// already, we do not continue with the read. -func (r fieldReader) readBool() bool { - if r.e != nil { - return false - } - var b uint64 - b, r.e = r.br.ReadBits(1) - return b == 1 -} - -// readBitsInt returns an int from reading n bits from br. If we have an error -// already, we do not continue with the read. -func (r fieldReader) readBitsInt(n int) int { - if r.e != nil { - return 0 - } - var b uint64 - b, r.e = r.br.ReadBits(n) - return int(b) -} - -// err returns the fieldReader's error e. -func (r fieldReader) err() error { - return r.e -} From 75a6df5da5a15426c7a9c3d9e0ae3b477e920560 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 29 Jul 2019 12:33:14 +0930 Subject: [PATCH 83/93] codec/h264/h264dec: removed readBitsInt and readBool --- codec/h264/h264dec/parse.go | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/codec/h264/h264dec/parse.go b/codec/h264/h264dec/parse.go index e6fba287..68abe8c6 100644 --- a/codec/h264/h264dec/parse.go +++ b/codec/h264/h264dec/parse.go @@ -49,20 +49,9 @@ func newFieldReader(br *bits.BitReader) fieldReader { return fieldReader{br: br} } -// readBool returns a bool from reading one bit from br. If we have an error -// already, we do not continue with the read. -func (r fieldReader) readBool() bool { - if r.e != nil { - return false - } - var b uint64 - b, r.e = r.br.ReadBits(1) - return b == 1 -} - // readBitsInt returns an int from reading n bits from br. If we have an error // already, we do not continue with the read. -func (r fieldReader) readBitsInt(n int) int { +func (r fieldReader) readBits(n int) int { if r.e != nil { return 0 } From 430e56e22bd477be5954223d1092a48bf721e870 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 29 Jul 2019 12:35:01 +0930 Subject: [PATCH 84/93] codec/h264/h264dec: fixed readBits return type --- codec/h264/h264dec/parse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codec/h264/h264dec/parse.go b/codec/h264/h264dec/parse.go index 68abe8c6..0763be27 100644 --- a/codec/h264/h264dec/parse.go +++ b/codec/h264/h264dec/parse.go @@ -51,13 +51,13 @@ func newFieldReader(br *bits.BitReader) fieldReader { // readBitsInt returns an int from reading n bits from br. If we have an error // already, we do not continue with the read. -func (r fieldReader) readBits(n int) int { +func (r fieldReader) readBits(n int) uint64 { if r.e != nil { return 0 } var b uint64 b, r.e = r.br.ReadBits(n) - return int(b) + return b } // readUe parses a syntax element of ue(v) descriptor, i.e. an unsigned integer From 23d9f289dd8638604ebe6a7a24adf7d420f98708 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 29 Jul 2019 13:04:59 +0930 Subject: [PATCH 85/93] codec/h264/h264dec: fixed field types for NalUnit and now using fieldReader in NewNALUnit --- codec/h264/h264dec/nalunit.go | 51 +++++++++++++---------------------- codec/h264/h264dec/slice.go | 4 +-- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 2e076567..2e754f12 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -10,6 +10,7 @@ AUTHORS package h264dec import ( + "fmt" "io" "github.com/pkg/errors" @@ -262,18 +263,18 @@ func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { // Field semantics are defined in section 7.4.1. type NALUnit struct { // forbidden_zero_bit, always 0. - ForbiddenZeroBit int + ForbiddenZeroBit uint8 // nal_ref_idc, if not 0 indicates content of NAL contains a sequence parameter // set, a sequence parameter set extension, a subset sequence parameter set, // a picture parameter set, a slice of a reference picture, a slice data // partition of a reference picture, or a prefix NAL preceding a slice of // a reference picture. - RefIdc int + RefIdc uint8 // nal_unit_type, specifies the type of RBSP data contained in the NAL as // defined in Table 7-1. - Type int + Type uint8 // svc_extension_flag, indicates whether a nal_unit_header_svc_extension() // (G.7.3.1.1) or nal_unit_header_mvc_extension() (H.7.3.1.1) will follow next @@ -306,28 +307,19 @@ type NALUnit struct { // syntax structure specified in section 7.3.1, and returns as a new NALUnit. func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { n := &NALUnit{} + r := newFieldReader(br) - err := readFields(br, []field{ - {&n.ForbiddenZeroBit, "ForbiddenZeroBit", 1}, - {&n.RefIdc, "NalRefIdc", 2}, - {&n.Type, "NalUnitType", 5}, - }) - if err != nil { - return nil, err - } + n.ForbiddenZeroBit = uint8(r.readBits(1)) + n.RefIdc = uint8(r.readBits(2)) + n.Type = uint8(r.readBits(5)) // TODO: use consts for the NAL types here + var err error if n.Type == 14 || n.Type == 20 || n.Type == 21 { if n.Type != 21 { - n.SVCExtensionFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read SVCExtensionFlag") - } + n.SVCExtensionFlag = r.readBits(1) == 1 } else { - n.AVC3DExtensionFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read AVC3DExtensionFlag") - } + n.AVC3DExtensionFlag = r.readBits(1) == 1 } if n.SVCExtensionFlag { n.SVCExtension, err = NewSVCExtension(br) @@ -360,27 +352,20 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { if next3Bytes == 0x000003 { for j := 0; j < 2; j++ { - rbspByte, err := br.ReadBits(8) - if err != nil { - return nil, errors.Wrap(err, "could not read rbspByte") - } + rbspByte := byte(r.readBits(8)) n.RBSP = append(n.RBSP, byte(rbspByte)) } // Read Emulation prevention three byte. - eptByte, err := br.ReadBits(8) - if err != nil { - return nil, errors.Wrap(err, "could not read eptByte") - } - n.EmulationPreventionThreeByte = byte(eptByte) + n.EmulationPreventionThreeByte = byte(r.readBits(8)) } else { - b, err := br.ReadBits(8) - if err != nil { - return nil, errors.Wrap(err, "could not read RBSP byte") - } - n.RBSP = append(n.RBSP, byte(b)) + n.RBSP = append(n.RBSP, byte(r.readBits(8))) } } + if r.err() != nil { + return nil, fmt.Errorf("fieldReader error: %v", r.err()) + } + return n, nil } diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 360282a6..77e369d9 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -949,7 +949,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh var err error sps := videoStream.SPS pps := videoStream.PPS - logger.Printf("debug: %s RBSP %d bytes %d bits == \n", NALUnitType[nalUnit.Type], len(rbsp), len(rbsp)*8) + 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 if nalUnit.Type == 5 { @@ -974,7 +974,7 @@ func NewSliceContext(videoStream *VideoStream, nalUnit *NALUnit, rbsp []byte, sh } sliceType := sliceTypeMap[header.SliceType] - logger.Printf("debug: %s (%s) slice of %d bytes\n", NALUnitType[nalUnit.Type], sliceType, len(rbsp)) + logger.Printf("debug: %s (%s) slice of %d bytes\n", NALUnitType[int(nalUnit.Type)], sliceType, len(rbsp)) header.PPSID, err = readUe(br) if err != nil { return nil, errors.Wrap(err, "could not parse PPSID") From 6c691743033cf84638b55cd0b831ffcd36acc731 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 29 Jul 2019 13:41:40 +0930 Subject: [PATCH 86/93] codec/h264/h264dec: change field types to types more consistent with specs and now using fieldReader to read fields of syntax structures --- codec/h264/h264dec/nalunit.go | 169 +++++++++------------------------- codec/h264/h264dec/slice.go | 2 +- 2 files changed, 46 insertions(+), 125 deletions(-) diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 2e754f12..656fa166 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -26,14 +26,14 @@ type MVCExtension struct { NonIdrFlag bool // priority_id, indicates priority of NAL unit. A lower value => higher priority. - PriorityID int + PriorityID uint8 // view_id, specifies a view identifier for the unit. Units with identical // view_id are in the same view. - ViewID int + ViewID uint32 // temporal_id, temporal identifier for the unit. - TemporalID int + TemporalID uint8 // anchor_pic_flag, if true access unit is an anchor access unit. AnchorPicFlag bool @@ -43,7 +43,7 @@ type MVCExtension struct { InterViewFlag bool // reserved_one_bit, always 1 (ignored by decoders) - ReservedOneBit int + ReservedOneBit uint8 } // NewMVCExtension parses a NAL unit header multiview video coding extension @@ -51,43 +51,19 @@ type MVCExtension struct { // returns as a new MVCExtension. func NewMVCExtension(br *bits.BitReader) (*MVCExtension, error) { e := &MVCExtension{} - var err error + r := newFieldReader(br) - e.NonIdrFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read NonIdrFlag") + e.NonIdrFlag = r.readBits(1) == 1 + e.PriorityID = uint8(r.readBits(6)) + e.ViewID = uint32(r.readBits(10)) + e.TemporalID = uint8(r.readBits(3)) + e.AnchorPicFlag = r.readBits(1) == 1 + e.InterViewFlag = r.readBits(1) == 1 + e.ReservedOneBit = uint8(r.readBits(1)) + + if r.err() != nil { + return nil, fmt.Errorf("error from fieldReader: %v", r.err()) } - - e.PriorityID, err = br.ReadBitsInt(6) - if err != nil { - return nil, errors.Wrap(err, "could not read PriorityId") - } - - e.ViewID, err = br.ReadBitsInt(10) - if err != nil { - return nil, errors.Wrap(err, "could not read ViewId") - } - - e.TemporalID, err = br.ReadBitsInt(3) - if err != nil { - return nil, errors.Wrap(err, "could not read TemporalId") - } - - e.AnchorPicFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read AnchorPicFlag") - } - - e.InterViewFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read InterViewFlag") - } - - e.ReservedOneBit, err = br.ReadBitsInt(1) - if err != nil { - return nil, errors.Wrap(err, "could not read ReservedOneBit") - } - return e, nil } @@ -96,7 +72,7 @@ func NewMVCExtension(br *bits.BitReader) (*MVCExtension, error) { // For field semantics see section J.7.4.1.1. type ThreeDAVCExtension struct { // view_idx, specifies the order index for the NAL i.e. view_id = view_id[view_idx]. - ViewIdx int + ViewIdx uint8 // dpeth_flag, if true indicates NAL part of a depth view component, otherwise // a texture view component. @@ -106,7 +82,7 @@ type ThreeDAVCExtension struct { NonIdrFlag bool // temporal_id, temporal identifier for the unit. - TemporalID int + TemporalID uint8 // anchor_pic_flag, if true access unit is an anchor access unit. AnchorPicFlag bool @@ -121,36 +97,17 @@ type ThreeDAVCExtension struct { // J.7.3.1.1, and returns as a new ThreeDAVCExtension. func NewThreeDAVCExtension(br *bits.BitReader) (*ThreeDAVCExtension, error) { e := &ThreeDAVCExtension{} - var err error + r := newFieldReader(br) - e.ViewIdx, err = br.ReadBitsInt(8) - if err != nil { - return nil, errors.Wrap(err, "could not read ViewIdx") - } + e.ViewIdx = uint8(r.readBits(8)) + e.DepthFlag = r.readBits(1) == 1 + e.NonIdrFlag = r.readBits(1) == 1 + e.TemporalID = uint8(r.readBits(3)) + e.AnchorPicFlag = r.readBits(1) == 1 + e.InterViewFlag = r.readBits(1) == 1 - e.DepthFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read DepthFlag") - } - - e.NonIdrFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read NonIdrFlag") - } - - e.TemporalID, err = br.ReadBitsInt(3) - if err != nil { - return nil, errors.Wrap(err, "could not read TemporalId") - } - - e.AnchorPicFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read AnchorPicFlag") - } - - e.InterViewFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read InterViewFlag") + if r.err() != nil { + return nil, fmt.Errorf("error from fieldReader: %v", r.err()) } return e, nil @@ -165,20 +122,20 @@ type SVCExtension struct { IdrFlag bool // priority_id, specifies priority identifier for unit. - PriorityID int + PriorityID uint8 // no_inter_layer_pred_flag, if true inter-layer prediction can't be used for // decoding slice. NoInterLayerPredFlag bool // dependency_id, specifies a dependency identifier for the NAL. - DependencyID int + DependencyID uint8 // quality_id, specifies a quality identifier for the NAL. - QualityID int + QualityID uint8 // temporal_id, specifiesa temporal identifier for the NAL. - TemporalID int + TemporalID uint8 // use_ref_base_pic_flag, if true indicates reference base pictures and // decoded pictures are used as references for inter prediction. @@ -195,7 +152,7 @@ type SVCExtension struct { OutputFlag bool // reserved_three_2bits, equal to 3. Decoders ignore. - ReservedThree2Bits int + ReservedThree2Bits uint8 } // NewSVCExtension parses a NAL unit header scalable video coding extension from @@ -203,58 +160,22 @@ type SVCExtension struct { // as a new SVCExtension. func NewSVCExtension(br *bits.BitReader) (*SVCExtension, error) { e := &SVCExtension{} - var err error + r := newFieldReader(br) - e.IdrFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read IdrFlag") + e.IdrFlag = r.readBits(1) == 1 + e.PriorityID = uint8(r.readBits(6)) + e.NoInterLayerPredFlag = r.readBits(1) == 1 + e.DependencyID = uint8(r.readBits(3)) + e.QualityID = uint8(r.readBits(4)) + e.TemporalID = uint8(r.readBits(3)) + e.UseRefBasePicFlag = r.readBits(1) == 1 + e.DiscardableFlag = r.readBits(1) == 1 + e.OutputFlag = r.readBits(1) == 1 + e.ReservedThree2Bits = uint8(r.readBits(2)) + + if r.err() != nil { + return nil, fmt.Errorf("error from fieldReader: %v", r.err()) } - - e.PriorityID, err = br.ReadBitsInt(6) - if err != nil { - return nil, errors.Wrap(err, "could not read PriorityId") - } - - e.NoInterLayerPredFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read NoInterLayerPredFlag") - } - - e.DependencyID, err = br.ReadBitsInt(3) - if err != nil { - return nil, errors.Wrap(err, "could not read DependencyId") - } - - e.QualityID, err = br.ReadBitsInt(4) - if err != nil { - return nil, errors.Wrap(err, "could not read QualityId") - } - - e.TemporalID, err = br.ReadBitsInt(3) - if err != nil { - return nil, errors.Wrap(err, "could not read TemporalId") - } - - e.UseRefBasePicFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read UseRefBasePicFlag") - } - - e.DiscardableFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read DiscardableFlag") - } - - e.OutputFlag, err = br.ReadBool() - if err != nil { - return nil, errors.Wrap(err, "could not read OutputFlag") - } - - e.ReservedThree2Bits, err = br.ReadBitsInt(2) - if err != nil { - return nil, errors.Wrap(err, "could not read ReservedThree2Bits") - } - return e, nil } diff --git a/codec/h264/h264dec/slice.go b/codec/h264/h264dec/slice.go index 77e369d9..93e2a6e4 100644 --- a/codec/h264/h264dec/slice.go +++ b/codec/h264/h264dec/slice.go @@ -241,7 +241,7 @@ func CodedBlockPatternChroma(data *SliceData) int { // dependencyId see Annex G.8.8.1 // Also G7.3.1.1 nal_unit_header_svc_extension func DQId(nalUnit *NALUnit) int { - return (nalUnit.SVCExtension.DependencyID << 4) + nalUnit.SVCExtension.QualityID + return int((nalUnit.SVCExtension.DependencyID << 4)) + int(nalUnit.SVCExtension.QualityID) } // Annex G p527 From 8e1e84d311069067b45f2841f2dbb4142a944b17 Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 29 Jul 2019 13:43:03 +0930 Subject: [PATCH 87/93] codec/h264/h264dec/bits: removed ReadBitsInt and ReadBool as not required anymore --- codec/h264/h264dec/bits/bitreader.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/codec/h264/h264dec/bits/bitreader.go b/codec/h264/h264dec/bits/bitreader.go index 08cc5d41..7e17f407 100644 --- a/codec/h264/h264dec/bits/bitreader.go +++ b/codec/h264/h264dec/bits/bitreader.go @@ -128,24 +128,6 @@ func (br *BitReader) ReadBits(n int) (uint64, error) { return r, nil } -// ReadBitsInt reads n bits from the source, and returns as an int. -func (br *BitReader) ReadBitsInt(n int) (int, error) { - b, err := br.ReadBits(n) - if err != nil { - return 0, err - } - return int(b), nil -} - -// ReadBool reads a single bit from the source, converts to a bool and returns. -func (br *BitReader) ReadBool() (bool, error) { - b, err := br.ReadBits(1) - if err != nil { - return false, err - } - return b == 1, nil -} - // PeekBits provides the next n bits returning them in the least-significant // part of a uint64, without advancing through the source. // For example, with a source as []byte{0x8f,0xe3} (1000 1111, 1110 0011), we From b71d8fdd8b7e15ffd6523fc325aebaa208f54a7e Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 29 Jul 2019 13:49:57 +0930 Subject: [PATCH 88/93] container/mts/mpegts.go: undoing changes to mpegts.go that shouldn't be there --- container/mts/mpegts.go | 85 ++--------------------------------------- 1 file changed, 3 insertions(+), 82 deletions(-) diff --git a/container/mts/mpegts.go b/container/mts/mpegts.go index f2a9ae0b..0b356770 100644 --- a/container/mts/mpegts.go +++ b/container/mts/mpegts.go @@ -33,7 +33,6 @@ import ( "fmt" "github.com/Comcast/gots/packet" - gotspsi "github.com/Comcast/gots/psi" "github.com/pkg/errors" "bitbucket.org/ausocean/av/container/mts/meta" @@ -421,9 +420,9 @@ func GetPTSRange(clip []byte, pid uint16) (pts [2]uint64, err error) { } var ( - errNoPesPayload = errors.New("no PES payload") - errNoPesPTS = errors.New("no PES PTS") - errInvalidPesHeader = errors.New("invalid PES header") + errNoPesPayload = errors.New("no PES payload") + errNoPesPTS = errors.New("no PES PTS") + errInvalidPesHeader = errors.New("invalid PES header") errInvalidPesPayload = errors.New("invalid PES payload") ) @@ -593,81 +592,3 @@ func SegmentForMeta(d []byte, key, val string) ([][]byte, error) { return res, nil } - -// pid returns the packet identifier for the given packet. -func pid(p []byte) uint16 { - return uint16(p[1]&0x1f)<<8 | uint16(p[2]) -} - -// Programs returns a map of program numbers and corresponding PMT PIDs for a -// given MPEG-TS PAT packet. -func Programs(p []byte) (map[uint16]uint16, error) { - pat, err := gotspsi.NewPAT(p) - if err != nil { - return nil, err - } - return pat.ProgramMap(), nil -} - -// Streams returns elementary streams defined in a given MPEG-TS PMT packet. -// A gotspsi.PmtElementaryStream will give stream type from -// gotspsi.PmtElementaryStream.StreamType() and PID from -// gotspsi.PmtElementaryStream.ElementaryPid(). -// -// PmtStreamTypes from gots/psi are defined as follows: -// PmtStreamTypeMpeg2VideoH262 uint8 = 2 // H262 -// PmtStreamTypeMpeg4Video uint8 = 27 // H264 -// PmtStreamTypeMpeg4VideoH264 uint8 = 27 // H264 -// PmtStreamTypeMpeg4VideoH265 uint8 = 36 // H265 -// PmtStreamTypeAac uint8 = 15 // AAC -// PmtStreamTypeAc3 uint8 = 129 // DD -// PmtStreamTypeEc3 uint8 = 135 // DD+ -// PmtStreamTypeScte35 uint8 = 134 // SCTE-35 -func Streams(p []byte) ([]gotspsi.PmtElementaryStream, error) { - pmt, err := gotspsi.NewPMT(p) - if err != nil { - return nil, err - } - return pmt.ElementaryStreams(), nil -} - -// MediaStreams retrieves the PmtElementaryStreams from the given PSI. This -// function currently assumes that PSI contain a PAT followed by a PMT directly -// after. We also assume that this MPEG-TS stream contains just one program, -// but this program may contain different streams, i.e. a video stream + audio -// stream. -func MediaStreams(p []byte) ([]gotspsi.PmtElementaryStream, error) { - pat := p[:PacketSize] - pmt := p[PacketSize : 2*PacketSize] - - if pid(pat) != PatPid { - return nil, errors.New("first packet is not a PAT") - } - - m, err := Programs(pat) - if err != nil { - return nil, errors.Wrap(err, "could not get programs from PAT") - } - - if len(m) == 0 { - return nil, errors.New("no programs contained in PAT") - } - - if len(m) > 1 { - return nil, errors.New("more than one program not yet supported") - } - - var v uint16 - for _, v = range m { - } - - if pid(pmt) != v { - return nil, errors.New("second packet is not desired PMT") - } - - s, err := Streams(pmt) - if err != nil { - return nil, errors.Wrap(err, "could not get streams from PMT") - } - return s, nil -} From 520ead0c6c5c0e32ae30936520ea529831da672b Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 29 Jul 2019 14:08:59 +0930 Subject: [PATCH 89/93] codec/h264/h264dec/nalunit.go: using consts for NAL unit types check in newNALUnit --- codec/h264/h264dec/frame.go | 2 ++ codec/h264/h264dec/nalunit.go | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/codec/h264/h264dec/frame.go b/codec/h264/h264dec/frame.go index 9e240a45..82b32ae5 100644 --- a/codec/h264/h264dec/frame.go +++ b/codec/h264/h264dec/frame.go @@ -19,6 +19,8 @@ const ( naluTypePrefixNALU naluTypeSubsetSPS naluTypeDepthParamSet + naluTypeSliceLayerExtRBSP = 20 + naluTypeSliceLayerExtRBSP2 = 21 ) var ( diff --git a/codec/h264/h264dec/nalunit.go b/codec/h264/h264dec/nalunit.go index 656fa166..f1535855 100644 --- a/codec/h264/h264dec/nalunit.go +++ b/codec/h264/h264dec/nalunit.go @@ -234,10 +234,9 @@ func NewNALUnit(br *bits.BitReader) (*NALUnit, error) { n.RefIdc = uint8(r.readBits(2)) n.Type = uint8(r.readBits(5)) - // TODO: use consts for the NAL types here var err error - if n.Type == 14 || n.Type == 20 || n.Type == 21 { - if n.Type != 21 { + if n.Type == naluTypePrefixNALU || n.Type == naluTypeSliceLayerExtRBSP || n.Type == naluTypeSliceLayerExtRBSP2 { + if n.Type != naluTypeSliceLayerExtRBSP2 { n.SVCExtensionFlag = r.readBits(1) == 1 } else { n.AVC3DExtensionFlag = r.readBits(1) == 1 From 2906e82adcd96605d35f4743ec846db53771e981 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 31 Jul 2019 22:08:03 +0930 Subject: [PATCH 90/93] codec/h264/h264dec/pps_test.go: added file header --- codec/h264/h264dec/pps_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/codec/h264/h264dec/pps_test.go b/codec/h264/h264dec/pps_test.go index 7ac357c8..52db2bd4 100644 --- a/codec/h264/h264dec/pps_test.go +++ b/codec/h264/h264dec/pps_test.go @@ -1,3 +1,11 @@ +/* +DESCRIPTION + pps_test.go provides testing for parsing functionality found in pps.go. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) +*/ + package h264dec import ( From 547e9f22ae0a84860cf9cf2cb2fb6c5c8706798f Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 31 Jul 2019 22:35:09 +0930 Subject: [PATCH 91/93] codec/h264/h264dec/helpers.go: added helpers.go file and binToSlice func for converting binary string to a []byte --- codec/h264/h264dec/helpers.go | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 codec/h264/h264dec/helpers.go diff --git a/codec/h264/h264dec/helpers.go b/codec/h264/h264dec/helpers.go new file mode 100644 index 00000000..22e9a5eb --- /dev/null +++ b/codec/h264/h264dec/helpers.go @@ -0,0 +1,41 @@ +/* +DESCRIPTION + helpers.go provides general helper utilities. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) +*/ +package h264dec + +import "errors" + +// binToSlice is a helper function to convert a string of binary into a +// corresponding byte slice, e.g. "0100 0001 1000 1100" => {0x41,0x8c}. +// Spaces in the string are ignored. +func binToSlice(s string) ([]byte, error) { + var ( + a byte = 0x80 + cur byte + bytes []byte + ) + + for _, c := range s { + switch c { + case ' ': + continue + case '1': + cur |= a + case '0': + default: + return nil, errors.New("invalid binary string") + } + + a >>= 1 + if a == 0 { + bytes = append(bytes, cur) + cur = 0 + a = 0x80 + } + } + return bytes, nil +} From 9239676214f494e3579d0cec539e7cb187299003 Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 31 Jul 2019 22:39:20 +0930 Subject: [PATCH 92/93] codec/h264/h264dec: merged in master and removed additional binToSlice func --- codec/h264/h264dec/pps_test.go | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/codec/h264/h264dec/pps_test.go b/codec/h264/h264dec/pps_test.go index 52db2bd4..b346040c 100644 --- a/codec/h264/h264dec/pps_test.go +++ b/codec/h264/h264dec/pps_test.go @@ -10,7 +10,6 @@ package h264dec import ( "bytes" - "errors" "reflect" "testing" @@ -122,34 +121,3 @@ func TestNewPPS(t *testing.T) { } } } - -// binToSlice is a helper function to convert a string of binary into a -// corresponding byte slice, e.g. "0100 0001 1000 1100" => {0x41,0x8c}. -// Spaces in the string are ignored. -func binToSlice(s string) ([]byte, error) { - var ( - a byte = 0x80 - cur byte - bytes []byte - ) - - for _, c := range s { - switch c { - case ' ': - continue - case '1': - cur |= a - case '0': - default: - return nil, errors.New("invalid binary string") - } - - a >>= 1 - if a == 0 { - bytes = append(bytes, cur) - cur = 0 - a = 0x80 - } - } - return bytes, nil -} From 0b21b7a8c48dacd89c05c7550608281a091585ec Mon Sep 17 00:00:00 2001 From: Saxon Date: Wed, 31 Jul 2019 22:40:10 +0930 Subject: [PATCH 93/93] codec/h264/h264dec/read_test.go: added file header --- codec/h264/h264dec/read_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/codec/h264/h264dec/read_test.go b/codec/h264/h264dec/read_test.go index f6fe7ddd..a56ce0cd 100644 --- a/codec/h264/h264dec/read_test.go +++ b/codec/h264/h264dec/read_test.go @@ -1,3 +1,10 @@ +/* +DESCRIPTION + read_test.go provides testing for utilities in read.go. + +AUTHORS + Saxon Nelson-Milton , The Australian Ocean Laboratory (AusOcean) +*/ package h264dec import (