19 changed files with 13 additions and 667 deletions
@ -1,79 +0,0 @@
@@ -1,79 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
// DecodeAnnexB decodes NALUs from the Annex-B code stream format.
|
||||
func DecodeAnnexB(byts []byte) ([][]byte, error) { |
||||
bl := len(byts) |
||||
|
||||
// check initial delimiter
|
||||
n := func() int { |
||||
if bl < 3 || byts[0] != 0x00 || byts[1] != 0x00 { |
||||
return -1 |
||||
} |
||||
|
||||
if byts[2] == 0x01 { |
||||
return 3 |
||||
} |
||||
|
||||
if bl < 4 || byts[2] != 0x00 || byts[3] != 0x01 { |
||||
return -1 |
||||
} |
||||
|
||||
return 4 |
||||
}() |
||||
if n < 0 { |
||||
return nil, fmt.Errorf("input doesn't start with a delimiter") |
||||
} |
||||
|
||||
var ret [][]byte |
||||
zeros := 0 |
||||
start := n |
||||
delimStart := 0 |
||||
|
||||
for i := n; i < bl; i++ { |
||||
switch byts[i] { |
||||
case 0: |
||||
if zeros == 0 { |
||||
delimStart = i |
||||
} |
||||
zeros++ |
||||
|
||||
case 1: |
||||
if zeros == 2 || zeros == 3 { |
||||
nalu := byts[start:delimStart] |
||||
if len(nalu) == 0 { |
||||
return nil, fmt.Errorf("empty NALU") |
||||
} |
||||
ret = append(ret, nalu) |
||||
start = i + 1 |
||||
} |
||||
zeros = 0 |
||||
|
||||
default: |
||||
zeros = 0 |
||||
} |
||||
} |
||||
|
||||
nalu := byts[start:bl] |
||||
if len(nalu) == 0 { |
||||
return nil, fmt.Errorf("empty NALU") |
||||
} |
||||
ret = append(ret, nalu) |
||||
|
||||
return ret, nil |
||||
} |
||||
|
||||
// EncodeAnnexB encodes NALUs into the Annex-B code stream format.
|
||||
func EncodeAnnexB(nalus [][]byte) ([]byte, error) { |
||||
var ret []byte |
||||
|
||||
for _, nalu := range nalus { |
||||
ret = append(ret, []byte{0x00, 0x00, 0x00, 0x01}...) |
||||
ret = append(ret, nalu...) |
||||
} |
||||
|
||||
return ret, nil |
||||
} |
@ -1,115 +0,0 @@
@@ -1,115 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
var casesAnnexB = []struct { |
||||
name string |
||||
encin []byte |
||||
encout []byte |
||||
dec [][]byte |
||||
}{ |
||||
{ |
||||
"2 zeros, single", |
||||
[]byte{0x00, 0x00, 0x01, 0xaa, 0xbb}, |
||||
[]byte{0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb}, |
||||
[][]byte{ |
||||
{0xaa, 0xbb}, |
||||
}, |
||||
}, |
||||
{ |
||||
"2 zeros, multiple", |
||||
[]byte{ |
||||
0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, 0x01, |
||||
0xcc, 0xdd, 0x00, 0x00, 0x01, 0xee, 0xff, |
||||
}, |
||||
[]byte{ |
||||
0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, |
||||
0x00, 0x01, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x01, |
||||
0xee, 0xff, |
||||
}, |
||||
[][]byte{ |
||||
{0xaa, 0xbb}, |
||||
{0xcc, 0xdd}, |
||||
{0xee, 0xff}, |
||||
}, |
||||
}, |
||||
{ |
||||
"3 zeros, single", |
||||
[]byte{0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb}, |
||||
[]byte{0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb}, |
||||
[][]byte{ |
||||
{0xaa, 0xbb}, |
||||
}, |
||||
}, |
||||
{ |
||||
"3 zeros, multiple", |
||||
[]byte{ |
||||
0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, |
||||
0x00, 0x01, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x01, |
||||
0xee, 0xff, |
||||
}, |
||||
[]byte{ |
||||
0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, |
||||
0x00, 0x01, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x01, |
||||
0xee, 0xff, |
||||
}, |
||||
[][]byte{ |
||||
{0xaa, 0xbb}, |
||||
{0xcc, 0xdd}, |
||||
{0xee, 0xff}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
func TestAnnexBDecode(t *testing.T) { |
||||
for _, ca := range casesAnnexB { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
dec, err := DecodeAnnexB(ca.encin) |
||||
require.NoError(t, err) |
||||
require.Equal(t, ca.dec, dec) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAnnexBEncode(t *testing.T) { |
||||
for _, ca := range casesAnnexB { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
enc, err := EncodeAnnexB(ca.dec) |
||||
require.NoError(t, err) |
||||
require.Equal(t, ca.encout, enc) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAnnexBDecodeError(t *testing.T) { |
||||
for _, ca := range []struct { |
||||
name string |
||||
enc []byte |
||||
}{ |
||||
{ |
||||
"empty", |
||||
[]byte{}, |
||||
}, |
||||
{ |
||||
"missing initial delimiter", |
||||
[]byte{0xaa, 0xbb}, |
||||
}, |
||||
{ |
||||
"empty initial", |
||||
[]byte{0x00, 0x00, 0x01}, |
||||
}, |
||||
{ |
||||
"empty 2nd", |
||||
[]byte{0x00, 0x00, 0x01, 0xaa, 0x00, 0x00, 0x01}, |
||||
}, |
||||
} { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
_, err := DecodeAnnexB(ca.enc) |
||||
require.Error(t, err) |
||||
}) |
||||
} |
||||
} |
@ -1,90 +0,0 @@
@@ -1,90 +0,0 @@
|
||||
package h264 |
||||
|
||||
// AntiCompetitionAdd adds the anti-competition bytes to a NALU.
|
||||
func AntiCompetitionAdd(nalu []byte) []byte { |
||||
var ret []byte |
||||
step := 0 |
||||
start := 0 |
||||
|
||||
for i, b := range nalu { |
||||
switch step { |
||||
case 0: |
||||
if b == 0 { |
||||
step++ |
||||
} |
||||
|
||||
case 1: |
||||
if b == 0 { |
||||
step++ |
||||
} else { |
||||
step = 0 |
||||
} |
||||
|
||||
case 2: |
||||
switch b { |
||||
case 3, 2, 1, 0: |
||||
ret = append(ret, nalu[start:i-2]...) |
||||
ret = append(ret, []byte{0x00, 0x00, 0x03, b}...) |
||||
step = 0 |
||||
start = i + 1 |
||||
|
||||
default: |
||||
step = 0 |
||||
} |
||||
} |
||||
} |
||||
|
||||
ret = append(ret, nalu[start:]...) |
||||
return ret |
||||
} |
||||
|
||||
// AntiCompetitionRemove removes the anti-competition bytes from a NALU.
|
||||
func AntiCompetitionRemove(nalu []byte) []byte { |
||||
// 0x00 0x00 0x03 0x00 -> 0x00 0x00 0x00
|
||||
// 0x00 0x00 0x03 0x01 -> 0x00 0x00 0x01
|
||||
// 0x00 0x00 0x03 0x02 -> 0x00 0x00 0x02
|
||||
// 0x00 0x00 0x03 0x03 -> 0x00 0x00 0x03
|
||||
|
||||
var ret []byte |
||||
step := 0 |
||||
start := 0 |
||||
|
||||
for i, b := range nalu { |
||||
switch step { |
||||
case 0: |
||||
if b == 0 { |
||||
step++ |
||||
} |
||||
|
||||
case 1: |
||||
if b == 0 { |
||||
step++ |
||||
} else { |
||||
step = 0 |
||||
} |
||||
|
||||
case 2: |
||||
if b == 3 { |
||||
step++ |
||||
} else { |
||||
step = 0 |
||||
} |
||||
|
||||
case 3: |
||||
switch b { |
||||
case 3, 2, 1, 0: |
||||
ret = append(ret, nalu[start:i-3]...) |
||||
ret = append(ret, []byte{0x00, 0x00, b}...) |
||||
step = 0 |
||||
start = i + 1 |
||||
|
||||
default: |
||||
step = 0 |
||||
} |
||||
} |
||||
} |
||||
|
||||
ret = append(ret, nalu[start:]...) |
||||
|
||||
return ret |
||||
} |
@ -1,47 +0,0 @@
@@ -1,47 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
var casesAntiCompetition = []struct { |
||||
name string |
||||
unproc []byte |
||||
proc []byte |
||||
}{ |
||||
{ |
||||
"base", |
||||
[]byte{ |
||||
0x00, 0x00, 0x00, |
||||
0x00, 0x00, 0x01, |
||||
0x00, 0x00, 0x02, |
||||
0x00, 0x00, 0x03, |
||||
}, |
||||
[]byte{ |
||||
0x00, 0x00, 0x03, 0x00, |
||||
0x00, 0x00, 0x03, 0x01, |
||||
0x00, 0x00, 0x03, 0x02, |
||||
0x00, 0x00, 0x03, 0x03, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
func TestAntiCompetitionAdd(t *testing.T) { |
||||
for _, ca := range casesAntiCompetition { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
proc := AntiCompetitionAdd(ca.unproc) |
||||
require.Equal(t, ca.proc, proc) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAntiCompetitionRemove(t *testing.T) { |
||||
for _, ca := range casesAntiCompetition { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
unproc := AntiCompetitionRemove(ca.proc) |
||||
require.Equal(t, ca.unproc, unproc) |
||||
}) |
||||
} |
||||
} |
@ -1,55 +0,0 @@
@@ -1,55 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"fmt" |
||||
) |
||||
|
||||
// DecodeAVCC encodes NALUs from the AVCC code stream format.
|
||||
func DecodeAVCC(byts []byte) ([][]byte, error) { |
||||
var ret [][]byte |
||||
|
||||
for len(byts) > 0 { |
||||
if len(byts) < 4 { |
||||
return nil, fmt.Errorf("invalid length") |
||||
} |
||||
|
||||
le := binary.BigEndian.Uint32(byts) |
||||
byts = byts[4:] |
||||
|
||||
if len(byts) < int(le) { |
||||
return nil, fmt.Errorf("invalid length") |
||||
} |
||||
|
||||
ret = append(ret, byts[:le]) |
||||
byts = byts[le:] |
||||
} |
||||
|
||||
if len(ret) == 0 { |
||||
return nil, fmt.Errorf("no NALUs decoded") |
||||
} |
||||
|
||||
return ret, nil |
||||
} |
||||
|
||||
// EncodeAVCC encodes NALUs into the AVCC code stream format.
|
||||
func EncodeAVCC(nalus [][]byte) ([]byte, error) { |
||||
le := 0 |
||||
for _, nalu := range nalus { |
||||
le += 4 + len(nalu) |
||||
} |
||||
|
||||
ret := make([]byte, le) |
||||
pos := 0 |
||||
|
||||
for _, nalu := range nalus { |
||||
ln := len(nalu) |
||||
binary.BigEndian.PutUint32(ret[pos:], uint32(ln)) |
||||
pos += 4 |
||||
|
||||
copy(ret[pos:], nalu) |
||||
pos += ln |
||||
} |
||||
|
||||
return ret, nil |
||||
} |
@ -1,85 +0,0 @@
@@ -1,85 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
var casesAVCC = []struct { |
||||
name string |
||||
enc []byte |
||||
dec [][]byte |
||||
}{ |
||||
{ |
||||
"single", |
||||
[]byte{ |
||||
0x00, 0x00, 0x00, 0x03, |
||||
0xaa, 0xbb, 0xcc, |
||||
}, |
||||
[][]byte{ |
||||
{0xaa, 0xbb, 0xcc}, |
||||
}, |
||||
}, |
||||
{ |
||||
"multiple", |
||||
[]byte{ |
||||
0x00, 0x00, 0x00, 0x02, |
||||
0xaa, 0xbb, |
||||
0x00, 0x00, 0x00, 0x02, |
||||
0xcc, 0xdd, |
||||
0x00, 0x00, 0x00, 0x02, |
||||
0xee, 0xff, |
||||
}, |
||||
[][]byte{ |
||||
{0xaa, 0xbb}, |
||||
{0xcc, 0xdd}, |
||||
{0xee, 0xff}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
func TestAVCCDecode(t *testing.T) { |
||||
for _, ca := range casesAVCC { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
dec, err := DecodeAVCC(ca.enc) |
||||
require.NoError(t, err) |
||||
require.Equal(t, ca.dec, dec) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAVCCEncode(t *testing.T) { |
||||
for _, ca := range casesAVCC { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
enc, err := EncodeAVCC(ca.dec) |
||||
require.NoError(t, err) |
||||
require.Equal(t, ca.enc, enc) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAVCCDecodeError(t *testing.T) { |
||||
for _, ca := range []struct { |
||||
name string |
||||
enc []byte |
||||
}{ |
||||
{ |
||||
"empty", |
||||
[]byte{}, |
||||
}, |
||||
{ |
||||
"invalid length", |
||||
[]byte{0x01}, |
||||
}, |
||||
{ |
||||
"invalid length", |
||||
[]byte{0x00, 0x00, 0x00, 0x03}, |
||||
}, |
||||
} { |
||||
t.Run(ca.name, func(t *testing.T) { |
||||
_, err := DecodeAVCC(ca.enc) |
||||
require.Error(t, err) |
||||
}) |
||||
} |
||||
} |
@ -1,61 +0,0 @@
@@ -1,61 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
// DTSEstimator is a DTS estimator.
|
||||
type DTSEstimator struct { |
||||
initializing int |
||||
prevDTS time.Duration |
||||
prevPTS time.Duration |
||||
prevPrevPTS time.Duration |
||||
} |
||||
|
||||
// NewDTSEstimator allocates a DTSEstimator.
|
||||
func NewDTSEstimator() *DTSEstimator { |
||||
return &DTSEstimator{ |
||||
initializing: 2, |
||||
} |
||||
} |
||||
|
||||
// Feed provides PTS to the estimator, and returns the estimated DTS.
|
||||
func (d *DTSEstimator) Feed(pts time.Duration) time.Duration { |
||||
switch d.initializing { |
||||
case 2: |
||||
d.initializing-- |
||||
return 0 |
||||
|
||||
case 1: |
||||
d.initializing-- |
||||
d.prevPTS = pts |
||||
d.prevDTS = time.Millisecond |
||||
return time.Millisecond |
||||
} |
||||
|
||||
dts := func() time.Duration { |
||||
// P or I frame
|
||||
if pts > d.prevPTS { |
||||
// previous frame was B
|
||||
// use the DTS of the previous frame
|
||||
if d.prevPTS < d.prevPrevPTS { |
||||
return d.prevPTS |
||||
} |
||||
|
||||
// previous frame was P or I
|
||||
// use two frames ago plus a small quantity
|
||||
// to avoid non-monotonous DTS with B-frames
|
||||
return d.prevPrevPTS + time.Millisecond |
||||
} |
||||
|
||||
// B Frame
|
||||
// increase by a small quantity
|
||||
return d.prevDTS + time.Millisecond |
||||
}() |
||||
|
||||
d.prevPrevPTS = d.prevPTS |
||||
d.prevPTS = pts |
||||
d.prevDTS = dts |
||||
|
||||
return dts |
||||
} |
@ -1,32 +0,0 @@
@@ -1,32 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestDTSEstimator(t *testing.T) { |
||||
est := NewDTSEstimator() |
||||
|
||||
// initial state
|
||||
dts := est.Feed(0) |
||||
require.Equal(t, time.Duration(0), dts) |
||||
|
||||
// b-frame
|
||||
dts = est.Feed(1*time.Second - 200*time.Millisecond) |
||||
require.Equal(t, time.Millisecond, dts) |
||||
|
||||
// b-frame
|
||||
dts = est.Feed(1*time.Second - 400*time.Millisecond) |
||||
require.Equal(t, 2*time.Millisecond, dts) |
||||
|
||||
// p-frame
|
||||
dts = est.Feed(1 * time.Second) |
||||
require.Equal(t, 1*time.Second-400*time.Millisecond, dts) |
||||
|
||||
// p-frame
|
||||
dts = est.Feed(1*time.Second + 200*time.Millisecond) |
||||
require.Equal(t, 1*time.Second-399*time.Millisecond, dts) |
||||
} |
@ -1,88 +0,0 @@
@@ -1,88 +0,0 @@
|
||||
package h264 |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
// NALUType is the type of a NALU.
|
||||
type NALUType uint8 |
||||
|
||||
// standard NALU types.
|
||||
const ( |
||||
NALUTypeNonIDR NALUType = 1 |
||||
NALUTypeDataPartitionA NALUType = 2 |
||||
NALUTypeDataPartitionB NALUType = 3 |
||||
NALUTypeDataPartitionC NALUType = 4 |
||||
NALUTypeIDR NALUType = 5 |
||||
NALUTypeSEI NALUType = 6 |
||||
NALUTypeSPS NALUType = 7 |
||||
NALUTypePPS NALUType = 8 |
||||
NALUTypeAccessUnitDelimiter NALUType = 9 |
||||
NALUTypeEndOfSequence NALUType = 10 |
||||
NALUTypeEndOfStream NALUType = 11 |
||||
NALUTypeFillerData NALUType = 12 |
||||
NALUTypeSPSExtension NALUType = 13 |
||||
NALUTypePrefix NALUType = 14 |
||||
NALUTypeSubsetSPS NALUType = 15 |
||||
NALUTypeReserved16 NALUType = 16 |
||||
NALUTypeReserved17 NALUType = 17 |
||||
NALUTypeReserved18 NALUType = 18 |
||||
NALUTypeSliceLayerWithoutPartitioning NALUType = 19 |
||||
NALUTypeSliceExtension NALUType = 20 |
||||
NALUTypeSliceExtensionDepth NALUType = 21 |
||||
NALUTypeReserved22 NALUType = 22 |
||||
NALUTypeReserved23 NALUType = 23 |
||||
) |
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (nt NALUType) String() string { |
||||
switch nt { |
||||
case NALUTypeNonIDR: |
||||
return "NonIDR" |
||||
case NALUTypeDataPartitionA: |
||||
return "DataPartitionA" |
||||
case NALUTypeDataPartitionB: |
||||
return "DataPartitionB" |
||||
case NALUTypeDataPartitionC: |
||||
return "DataPartitionC" |
||||
case NALUTypeIDR: |
||||
return "IDR" |
||||
case NALUTypeSEI: |
||||
return "SEI" |
||||
case NALUTypeSPS: |
||||
return "SPS" |
||||
case NALUTypePPS: |
||||
return "PPS" |
||||
case NALUTypeAccessUnitDelimiter: |
||||
return "AccessUnitDelimiter" |
||||
case NALUTypeEndOfSequence: |
||||
return "EndOfSequence" |
||||
case NALUTypeEndOfStream: |
||||
return "EndOfStream" |
||||
case NALUTypeFillerData: |
||||
return "FillerData" |
||||
case NALUTypeSPSExtension: |
||||
return "SPSExtension" |
||||
case NALUTypePrefix: |
||||
return "Prefix" |
||||
case NALUTypeSubsetSPS: |
||||
return "SubsetSPS" |
||||
case NALUTypeReserved16: |
||||
return "Reserved16" |
||||
case NALUTypeReserved17: |
||||
return "Reserved17" |
||||
case NALUTypeReserved18: |
||||
return "Reserved18" |
||||
case NALUTypeSliceLayerWithoutPartitioning: |
||||
return "SliceLayerWithoutPartitioning" |
||||
case NALUTypeSliceExtension: |
||||
return "SliceExtension" |
||||
case NALUTypeSliceExtensionDepth: |
||||
return "SliceExtensionDepth" |
||||
case NALUTypeReserved22: |
||||
return "Reserved22" |
||||
case NALUTypeReserved23: |
||||
return "Reserved23" |
||||
} |
||||
return fmt.Sprintf("unknown (%d)", nt) |
||||
} |
Loading…
Reference in new issue