5 changed files with 845 additions and 474 deletions
@ -0,0 +1,318 @@
@@ -0,0 +1,318 @@
|
||||
//nolint:dupl
|
||||
package fmp4 |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
gomp4 "github.com/abema/go-mp4" |
||||
"github.com/aler9/gortsplib" |
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func testMP4(t *testing.T, byts []byte, boxes []gomp4.BoxPath) { |
||||
i := 0 |
||||
_, err := gomp4.ReadBoxStructure(bytes.NewReader(byts), func(h *gomp4.ReadHandle) (interface{}, error) { |
||||
require.Equal(t, boxes[i], h.Path) |
||||
i++ |
||||
return h.Expand() |
||||
}) |
||||
require.NoError(t, err) |
||||
} |
||||
|
||||
var testSPS = []byte{ |
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, |
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, |
||||
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, |
||||
0x20, |
||||
} |
||||
|
||||
var testVideoTrack = &gortsplib.TrackH264{ |
||||
PayloadType: 96, |
||||
SPS: testSPS, |
||||
PPS: []byte{0x08}, |
||||
} |
||||
|
||||
var testAudioTrack = &gortsplib.TrackMPEG4Audio{ |
||||
PayloadType: 97, |
||||
Config: &mpeg4audio.Config{ |
||||
Type: 2, |
||||
SampleRate: 44100, |
||||
ChannelCount: 2, |
||||
}, |
||||
SizeLength: 13, |
||||
IndexLength: 3, |
||||
IndexDeltaLength: 3, |
||||
} |
||||
|
||||
func TestGenerateInit(t *testing.T) { |
||||
t.Run("video + audio", func(t *testing.T) { |
||||
byts, err := GenerateInit(testVideoTrack, testAudioTrack) |
||||
require.NoError(t, err) |
||||
|
||||
boxes := []gomp4.BoxPath{ |
||||
{gomp4.BoxTypeFtyp()}, |
||||
{gomp4.BoxTypeMoov()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeTkhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMdhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeHdlr()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), gomp4.BoxTypeVmhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), gomp4.BoxTypeDinf()}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), gomp4.BoxTypeUrl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeAvc1(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeAvc1(), gomp4.BoxTypeAvcC(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeAvc1(), gomp4.BoxTypeBtrt(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStts(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsc(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsz(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStco(), |
||||
}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeTkhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMdhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeHdlr()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf()}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeSmhd(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), gomp4.BoxTypeUrl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeMp4a(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeMp4a(), gomp4.BoxTypeEsds(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeMp4a(), gomp4.BoxTypeBtrt(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStts(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsc(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsz(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStco(), |
||||
}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvex()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvex(), gomp4.BoxTypeTrex()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvex(), gomp4.BoxTypeTrex()}, |
||||
} |
||||
testMP4(t, byts, boxes) |
||||
}) |
||||
|
||||
t.Run("video only", func(t *testing.T) { |
||||
byts, err := GenerateInit(testVideoTrack, nil) |
||||
require.NoError(t, err) |
||||
|
||||
boxes := []gomp4.BoxPath{ |
||||
{gomp4.BoxTypeFtyp()}, |
||||
{gomp4.BoxTypeMoov()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeTkhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMdhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeHdlr()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf()}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeVmhd(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), gomp4.BoxTypeUrl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeAvc1(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeAvc1(), gomp4.BoxTypeAvcC(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeAvc1(), gomp4.BoxTypeBtrt(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStts(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsc(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsz(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStco(), |
||||
}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvex()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvex(), gomp4.BoxTypeTrex()}, |
||||
} |
||||
testMP4(t, byts, boxes) |
||||
}) |
||||
|
||||
t.Run("audio only", func(t *testing.T) { |
||||
byts, err := GenerateInit(nil, testAudioTrack) |
||||
require.NoError(t, err) |
||||
|
||||
boxes := []gomp4.BoxPath{ |
||||
{gomp4.BoxTypeFtyp()}, |
||||
{gomp4.BoxTypeMoov()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeTkhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMdhd()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeHdlr()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf()}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeSmhd(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeDinf(), gomp4.BoxTypeDref(), gomp4.BoxTypeUrl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeMp4a(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeMp4a(), gomp4.BoxTypeEsds(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsd(), gomp4.BoxTypeMp4a(), gomp4.BoxTypeBtrt(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStts(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsc(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStsz(), |
||||
}, |
||||
{ |
||||
gomp4.BoxTypeMoov(), gomp4.BoxTypeTrak(), gomp4.BoxTypeMdia(), gomp4.BoxTypeMinf(), |
||||
gomp4.BoxTypeStbl(), gomp4.BoxTypeStco(), |
||||
}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvex()}, |
||||
{gomp4.BoxTypeMoov(), gomp4.BoxTypeMvex(), gomp4.BoxTypeTrex()}, |
||||
} |
||||
testMP4(t, byts, boxes) |
||||
}) |
||||
} |
@ -0,0 +1,143 @@
@@ -0,0 +1,143 @@
|
||||
package fmp4 |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
|
||||
gomp4 "github.com/abema/go-mp4" |
||||
"github.com/aler9/gortsplib/pkg/h264" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestGeneratePart(t *testing.T) { |
||||
testVideoSamples := []*VideoSample{ |
||||
{ |
||||
NALUs: [][]byte{ |
||||
{0x06}, |
||||
{0x07}, |
||||
}, |
||||
PTS: 0, |
||||
DTS: 0, |
||||
}, |
||||
{ |
||||
NALUs: [][]byte{ |
||||
testSPS, // SPS
|
||||
{8}, // PPS
|
||||
{5}, // IDR
|
||||
}, |
||||
PTS: 2 * time.Second, |
||||
DTS: 2 * time.Second, |
||||
}, |
||||
|
||||
{ |
||||
NALUs: [][]byte{ |
||||
{1}, // non-IDR
|
||||
}, |
||||
PTS: 4 * time.Second, |
||||
DTS: 4 * time.Second, |
||||
}, |
||||
|
||||
{ |
||||
NALUs: [][]byte{ |
||||
{1}, // non-IDR
|
||||
}, |
||||
PTS: 6 * time.Second, |
||||
DTS: 6 * time.Second, |
||||
}, |
||||
{ |
||||
NALUs: [][]byte{ |
||||
{5}, // IDR
|
||||
}, |
||||
PTS: 7 * time.Second, |
||||
DTS: 7 * time.Second, |
||||
}, |
||||
} |
||||
|
||||
testAudioSamples := []*AudioSample{ |
||||
{ |
||||
AU: []byte{ |
||||
0x01, 0x02, 0x03, 0x04, |
||||
}, |
||||
PTS: 3 * time.Second, |
||||
}, |
||||
{ |
||||
AU: []byte{ |
||||
0x01, 0x02, 0x03, 0x04, |
||||
}, |
||||
PTS: 3500 * time.Millisecond, |
||||
}, |
||||
{ |
||||
AU: []byte{ |
||||
0x01, 0x02, 0x03, 0x04, |
||||
}, |
||||
PTS: 4500 * time.Millisecond, |
||||
}, |
||||
} |
||||
|
||||
for i, sample := range testVideoSamples { |
||||
sample.IDRPresent = h264.IDRPresent(sample.NALUs) |
||||
if i != len(testVideoSamples)-1 { |
||||
sample.Next = testVideoSamples[i+1] |
||||
} |
||||
} |
||||
testVideoSamples = testVideoSamples[:len(testVideoSamples)-1] |
||||
|
||||
for i, sample := range testAudioSamples { |
||||
if i != len(testAudioSamples)-1 { |
||||
sample.Next = testAudioSamples[i+1] |
||||
} |
||||
} |
||||
testAudioSamples = testAudioSamples[:len(testAudioSamples)-1] |
||||
|
||||
t.Run("video + audio", func(t *testing.T) { |
||||
byts, err := GeneratePart(testVideoTrack, testAudioTrack, testVideoSamples, testAudioSamples) |
||||
require.NoError(t, err) |
||||
|
||||
boxes := []gomp4.BoxPath{ |
||||
{gomp4.BoxTypeMoof()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeMfhd()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()}, |
||||
{gomp4.BoxTypeMdat()}, |
||||
} |
||||
testMP4(t, byts, boxes) |
||||
}) |
||||
|
||||
t.Run("video only", func(t *testing.T) { |
||||
byts, err := GeneratePart(testVideoTrack, nil, testVideoSamples, nil) |
||||
require.NoError(t, err) |
||||
|
||||
boxes := []gomp4.BoxPath{ |
||||
{gomp4.BoxTypeMoof()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeMfhd()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()}, |
||||
{gomp4.BoxTypeMdat()}, |
||||
} |
||||
testMP4(t, byts, boxes) |
||||
}) |
||||
|
||||
t.Run("audio only", func(t *testing.T) { |
||||
byts, err := GeneratePart(nil, testAudioTrack, nil, testAudioSamples) |
||||
require.NoError(t, err) |
||||
|
||||
boxes := []gomp4.BoxPath{ |
||||
{gomp4.BoxTypeMoof()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeMfhd()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfhd()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTfdt()}, |
||||
{gomp4.BoxTypeMoof(), gomp4.BoxTypeTraf(), gomp4.BoxTypeTrun()}, |
||||
{gomp4.BoxTypeMdat()}, |
||||
} |
||||
testMP4(t, byts, boxes) |
||||
}) |
||||
} |
@ -0,0 +1,370 @@
@@ -0,0 +1,370 @@
|
||||
package mpegts |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/aler9/gortsplib" |
||||
"github.com/aler9/gortsplib/pkg/h264" |
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio" |
||||
"github.com/asticode/go-astits" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestWriter(t *testing.T) { |
||||
testSPS := []byte{ |
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, |
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, |
||||
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, |
||||
0x20, |
||||
} |
||||
|
||||
testVideoTrack := &gortsplib.TrackH264{ |
||||
PayloadType: 96, |
||||
SPS: testSPS, |
||||
PPS: []byte{0x08}, |
||||
} |
||||
|
||||
testAudioTrack := &gortsplib.TrackMPEG4Audio{ |
||||
PayloadType: 97, |
||||
Config: &mpeg4audio.Config{ |
||||
Type: 2, |
||||
SampleRate: 44100, |
||||
ChannelCount: 2, |
||||
}, |
||||
SizeLength: 13, |
||||
IndexLength: 3, |
||||
IndexDeltaLength: 3, |
||||
} |
||||
|
||||
type videoSample struct { |
||||
NALUs [][]byte |
||||
PTS time.Duration |
||||
DTS time.Duration |
||||
} |
||||
|
||||
type audioSample struct { |
||||
AU []byte |
||||
PTS time.Duration |
||||
} |
||||
|
||||
type sample interface{} |
||||
|
||||
testSamples := []sample{ |
||||
videoSample{ |
||||
NALUs: [][]byte{ |
||||
testSPS, // SPS
|
||||
{8}, // PPS
|
||||
{5}, // IDR
|
||||
}, |
||||
PTS: 2 * time.Second, |
||||
DTS: 2 * time.Second, |
||||
}, |
||||
audioSample{ |
||||
AU: []byte{ |
||||
0x01, 0x02, 0x03, 0x04, |
||||
}, |
||||
PTS: 3 * time.Second, |
||||
}, |
||||
audioSample{ |
||||
AU: []byte{ |
||||
0x01, 0x02, 0x03, 0x04, |
||||
}, |
||||
PTS: 3500 * time.Millisecond, |
||||
}, |
||||
videoSample{ |
||||
NALUs: [][]byte{ |
||||
{1}, // non-IDR
|
||||
}, |
||||
PTS: 4 * time.Second, |
||||
DTS: 4 * time.Second, |
||||
}, |
||||
audioSample{ |
||||
AU: []byte{ |
||||
0x01, 0x02, 0x03, 0x04, |
||||
}, |
||||
PTS: 4500 * time.Millisecond, |
||||
}, |
||||
videoSample{ |
||||
NALUs: [][]byte{ |
||||
{1}, // non-IDR
|
||||
}, |
||||
PTS: 6 * time.Second, |
||||
DTS: 6 * time.Second, |
||||
}, |
||||
} |
||||
|
||||
t.Run("video + audio", func(t *testing.T) { |
||||
w := NewWriter(testVideoTrack, testAudioTrack) |
||||
|
||||
for _, sample := range testSamples { |
||||
switch tsample := sample.(type) { |
||||
case videoSample: |
||||
err := w.WriteH264( |
||||
tsample.DTS-2*time.Second, |
||||
tsample.DTS, |
||||
tsample.PTS, |
||||
h264.IDRPresent(tsample.NALUs), |
||||
tsample.NALUs) |
||||
require.NoError(t, err) |
||||
|
||||
case audioSample: |
||||
err := w.WriteAAC( |
||||
tsample.PTS-2*time.Second, |
||||
tsample.PTS, |
||||
tsample.AU) |
||||
require.NoError(t, err) |
||||
} |
||||
} |
||||
|
||||
byts := w.GenerateSegment() |
||||
|
||||
dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts), |
||||
astits.DemuxerOptPacketSize(188)) |
||||
|
||||
// PMT
|
||||
pkt, err := dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
Header: &astits.PacketHeader{ |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 0, |
||||
}, |
||||
Payload: append([]byte{ |
||||
0x00, 0x00, 0xb0, 0x0d, 0x00, 0x00, 0xc1, 0x00, |
||||
0x00, 0x00, 0x01, 0xf0, 0x00, 0x71, 0x10, 0xd8, |
||||
0x78, |
||||
}, bytes.Repeat([]byte{0xff}, 167)...), |
||||
}, pkt) |
||||
|
||||
// PAT
|
||||
pkt, err = dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
Header: &astits.PacketHeader{ |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 4096, |
||||
}, |
||||
Payload: append([]byte{ |
||||
0x00, 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, |
||||
0x00, 0xe1, 0x00, 0xf0, 0x00, 0x1b, 0xe1, 0x00, |
||||
0xf0, 0x00, 0x0f, 0xe1, 0x01, 0xf0, 0x00, 0x2f, |
||||
0x44, 0xb9, 0x9b, |
||||
}, bytes.Repeat([]byte{0xff}, 157)...), |
||||
}, pkt) |
||||
|
||||
// PES (H264)
|
||||
pkt, err = dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
AdaptationField: &astits.PacketAdaptationField{ |
||||
Length: 124, |
||||
StuffingLength: 117, |
||||
HasPCR: true, |
||||
PCR: &astits.ClockReference{}, |
||||
RandomAccessIndicator: true, |
||||
}, |
||||
Header: &astits.PacketHeader{ |
||||
HasAdaptationField: true, |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 256, |
||||
}, |
||||
Payload: []byte{ |
||||
0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x80, |
||||
0x05, 0x21, 0x00, 0x0d, 0x97, 0x81, 0x00, 0x00, |
||||
0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01, |
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, |
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, |
||||
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, |
||||
0x20, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, |
||||
0x00, 0x01, 0x05, |
||||
}, |
||||
}, pkt) |
||||
|
||||
// PES (AAC)
|
||||
pkt, err = dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
AdaptationField: &astits.PacketAdaptationField{ |
||||
Length: 158, |
||||
StuffingLength: 157, |
||||
RandomAccessIndicator: true, |
||||
}, |
||||
Header: &astits.PacketHeader{ |
||||
HasAdaptationField: true, |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 257, |
||||
}, |
||||
Payload: []byte{ |
||||
0x00, 0x00, 0x01, 0xc0, 0x00, 0x13, 0x80, 0x80, |
||||
0x05, 0x21, 0x00, 0x13, 0x56, 0xa1, 0xff, 0xf1, |
||||
0x50, 0x80, 0x01, 0x7f, 0xfc, 0x01, 0x02, 0x03, |
||||
0x04, |
||||
}, |
||||
}, pkt) |
||||
}) |
||||
|
||||
t.Run("video only", func(t *testing.T) { |
||||
w := NewWriter(testVideoTrack, nil) |
||||
|
||||
for _, sample := range testSamples { |
||||
if tsample, ok := sample.(videoSample); ok { |
||||
err := w.WriteH264( |
||||
tsample.DTS-2*time.Second, |
||||
tsample.DTS, |
||||
tsample.PTS, |
||||
h264.IDRPresent(tsample.NALUs), |
||||
tsample.NALUs) |
||||
require.NoError(t, err) |
||||
} |
||||
} |
||||
|
||||
byts := w.GenerateSegment() |
||||
|
||||
dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts), |
||||
astits.DemuxerOptPacketSize(188)) |
||||
|
||||
// PMT
|
||||
pkt, err := dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
Header: &astits.PacketHeader{ |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 0, |
||||
}, |
||||
Payload: append([]byte{ |
||||
0x00, 0x00, 0xb0, 0x0d, 0x00, 0x00, 0xc1, 0x00, |
||||
0x00, 0x00, 0x01, 0xf0, 0x00, 0x71, 0x10, 0xd8, |
||||
0x78, |
||||
}, bytes.Repeat([]byte{0xff}, 167)...), |
||||
}, pkt) |
||||
|
||||
// PAT
|
||||
pkt, err = dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
Header: &astits.PacketHeader{ |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 4096, |
||||
}, |
||||
Payload: append([]byte{ |
||||
0x00, 0x02, 0xb0, 0x12, 0x00, 0x01, 0xc1, 0x00, |
||||
0x00, 0xe1, 0x00, 0xf0, 0x00, 0x1b, 0xe1, 0x00, |
||||
0xf0, 0x00, 0x15, 0xbd, 0x4d, 0x56, |
||||
}, bytes.Repeat([]byte{0xff}, 162)...), |
||||
}, pkt) |
||||
|
||||
// PES (H264)
|
||||
pkt, err = dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
AdaptationField: &astits.PacketAdaptationField{ |
||||
Length: 124, |
||||
StuffingLength: 117, |
||||
HasPCR: true, |
||||
PCR: &astits.ClockReference{}, |
||||
RandomAccessIndicator: true, |
||||
}, |
||||
Header: &astits.PacketHeader{ |
||||
HasAdaptationField: true, |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 256, |
||||
}, |
||||
Payload: []byte{ |
||||
0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x80, |
||||
0x05, 0x21, 0x00, 0x0d, 0x97, 0x81, 0x00, 0x00, |
||||
0x00, 0x01, 0x09, 0xf0, 0x00, 0x00, 0x00, 0x01, |
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, |
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, |
||||
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, |
||||
0x20, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, |
||||
0x00, 0x01, 0x05, |
||||
}, |
||||
}, pkt) |
||||
}) |
||||
|
||||
t.Run("audio only", func(t *testing.T) { |
||||
w := NewWriter(nil, testAudioTrack) |
||||
|
||||
for _, sample := range testSamples { |
||||
if tsample, ok := sample.(audioSample); ok { |
||||
err := w.WriteAAC( |
||||
tsample.PTS-2*time.Second, |
||||
tsample.PTS, |
||||
tsample.AU) |
||||
require.NoError(t, err) |
||||
} |
||||
} |
||||
|
||||
byts := w.GenerateSegment() |
||||
|
||||
dem := astits.NewDemuxer(context.Background(), bytes.NewReader(byts), |
||||
astits.DemuxerOptPacketSize(188)) |
||||
|
||||
// PMT
|
||||
pkt, err := dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
Header: &astits.PacketHeader{ |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 0, |
||||
}, |
||||
Payload: append([]byte{ |
||||
0x00, 0x00, 0xb0, 0x0d, 0x00, 0x00, 0xc1, 0x00, |
||||
0x00, 0x00, 0x01, 0xf0, 0x00, 0x71, 0x10, 0xd8, |
||||
0x78, |
||||
}, bytes.Repeat([]byte{0xff}, 167)...), |
||||
}, pkt) |
||||
|
||||
// PAT
|
||||
pkt, err = dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
Header: &astits.PacketHeader{ |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 4096, |
||||
}, |
||||
Payload: append([]byte{ |
||||
0x00, 0x02, 0xb0, 0x12, 0x00, 0x01, 0xc1, 0x00, |
||||
0x00, 0xe1, 0x01, 0xf0, 0x00, 0x0f, 0xe1, 0x01, |
||||
0xf0, 0x00, 0xec, 0xe2, 0xb0, 0x94, |
||||
}, bytes.Repeat([]byte{0xff}, 162)...), |
||||
}, pkt) |
||||
|
||||
// PES (AAC)
|
||||
pkt, err = dem.NextPacket() |
||||
require.NoError(t, err) |
||||
require.Equal(t, &astits.Packet{ |
||||
AdaptationField: &astits.PacketAdaptationField{ |
||||
Length: 158, |
||||
StuffingLength: 151, |
||||
RandomAccessIndicator: true, |
||||
HasPCR: true, |
||||
PCR: &astits.ClockReference{Base: 90000}, |
||||
}, |
||||
Header: &astits.PacketHeader{ |
||||
HasAdaptationField: true, |
||||
HasPayload: true, |
||||
PayloadUnitStartIndicator: true, |
||||
PID: 257, |
||||
}, |
||||
Payload: []byte{ |
||||
0x00, 0x00, 0x01, 0xc0, 0x00, 0x13, 0x80, 0x80, |
||||
0x05, 0x21, 0x00, 0x13, 0x56, 0xa1, 0xff, 0xf1, |
||||
0x50, 0x80, 0x01, 0x7f, 0xfc, 0x01, 0x02, 0x03, |
||||
0x04, |
||||
}, |
||||
}, pkt) |
||||
}) |
||||
} |
Loading…
Reference in new issue