From a018f214bce3f73899dedfe97f76107647560aaa Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sat, 27 Aug 2022 16:00:08 +0200 Subject: [PATCH] hls: add fmp4 tests --- internal/hls/muxer_primary_playlist.go | 29 +- internal/hls/muxer_test.go | 1047 +++++++++++++++++------- 2 files changed, 780 insertions(+), 296 deletions(-) diff --git a/internal/hls/muxer_primary_playlist.go b/internal/hls/muxer_primary_playlist.go index bb239309..bcb307a8 100644 --- a/internal/hls/muxer_primary_playlist.go +++ b/internal/hls/muxer_primary_playlist.go @@ -50,24 +50,19 @@ func (p *muxerPrimaryPlaylist) file() *MuxerFileResponse { codecs = append(codecs, "mp4a.40."+strconv.FormatInt(int64(p.audioTrack.Config.Type), 10)) } - switch { - case !p.fmp4: - return bytes.NewReader([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:3\n" + - "#EXT-X-INDEPENDENT-SEGMENTS\n" + - "\n" + - "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"" + strings.Join(codecs, ",") + "\"\n" + - "stream.m3u8\n")) - - default: - return bytes.NewReader([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:9\n" + - "#EXT-X-INDEPENDENT-SEGMENTS\n" + - "\n" + - "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"" + strings.Join(codecs, ",") + "\"\n" + - "stream.m3u8\n" + - "\n")) + var version int + if !p.fmp4 { + version = 3 + } else { + version = 9 } + + return bytes.NewReader([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:" + strconv.FormatInt(int64(version), 10) + "\n" + + "#EXT-X-INDEPENDENT-SEGMENTS\n" + + "\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"" + strings.Join(codecs, ",") + "\"\n" + + "stream.m3u8\n")) }(), } } diff --git a/internal/hls/muxer_test.go b/internal/hls/muxer_test.go index 43dc8a8b..3e44c5d7 100644 --- a/internal/hls/muxer_test.go +++ b/internal/hls/muxer_test.go @@ -3,11 +3,13 @@ package hls import ( "bytes" "context" + "io" "io/ioutil" "regexp" "testing" "time" + gomp4 "github.com/abema/go-mp4" "github.com/aler9/gortsplib" "github.com/aler9/gortsplib/pkg/mpeg4audio" "github.com/asticode/go-astits" @@ -24,6 +26,16 @@ var testSPS = []byte{ 0x20, } +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) +} + func TestMuxerVideoAudio(t *testing.T) { videoTrack := &gortsplib.TrackH264{ PayloadType: 96, @@ -43,154 +55,364 @@ func TestMuxerVideoAudio(t *testing.T) { IndexDeltaLength: 3, } - m, err := NewMuxer(MuxerVariantMPEGTS, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, audioTrack) - require.NoError(t, err) - defer m.Close() + for _, ca := range []string{ + "mpegts", + "fmp4", + } { + t.Run(ca, func(t *testing.T) { + var v MuxerVariant + if ca == "mpegts" { + v = MuxerVariantMPEGTS + } else { + v = MuxerVariantFMP4 + } - // group without IDR - err = m.WriteH264(testTime, 1*time.Second, [][]byte{ - {0x06}, - {0x07}, - }) - require.NoError(t, err) + m, err := NewMuxer(v, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, audioTrack) + require.NoError(t, err) + defer m.Close() - // group with IDR - err = m.WriteH264(testTime, 2*time.Second, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) - require.NoError(t, err) + // group without IDR + d := 1 * time.Second + err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + {0x06}, + {0x07}, + }) + require.NoError(t, err) - err = m.WriteAAC(testTime, 3*time.Second, []byte{ - 0x01, 0x02, 0x03, 0x04, - }) - require.NoError(t, err) + // group with IDR + d = 2 * time.Second + err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) + require.NoError(t, err) - // group without IDR - err = m.WriteH264(testTime, 4*time.Second, [][]byte{ - {1}, // non-IDR - }) - require.NoError(t, err) + d = 3 * time.Second + err = m.WriteAAC(testTime.Add(d-1*time.Second), d, []byte{ + 0x01, 0x02, 0x03, 0x04, + }) + require.NoError(t, err) - // group with IDR - err = m.WriteH264(testTime, 6*time.Second, [][]byte{ - {5}, // IDR - }) - require.NoError(t, err) + d = 3500 * time.Millisecond + err = m.WriteAAC(testTime.Add(d-1*time.Second), d, []byte{ + 0x01, 0x02, 0x03, 0x04, + }) + require.NoError(t, err) - byts, err := ioutil.ReadAll(m.File("index.m3u8", "", "", "").Body) - require.NoError(t, err) + // group without IDR + d = 4 * time.Second + err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + {1}, // non-IDR + }) + require.NoError(t, err) - require.Equal(t, "#EXTM3U\n"+ - "#EXT-X-VERSION:3\n"+ - "#EXT-X-INDEPENDENT-SEGMENTS\n"+ - "\n"+ - "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028,mp4a.40.2\"\n"+ - "stream.m3u8\n", string(byts)) + d = 4500 * time.Millisecond + err = m.WriteAAC(testTime.Add(d-1*time.Second), d, []byte{ + 0x01, 0x02, 0x03, 0x04, + }) + require.NoError(t, err) - byts, err = ioutil.ReadAll(m.File("stream.m3u8", "", "", "").Body) - require.NoError(t, err) + // group with IDR + d = 6 * time.Second + err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + {5}, // IDR + }) + require.NoError(t, err) - re := regexp.MustCompile(`^#EXTM3U\n` + - `#EXT-X-VERSION:3\n` + - `#EXT-X-ALLOW-CACHE:NO\n` + - `#EXT-X-TARGETDURATION:4\n` + - `#EXT-X-MEDIA-SEQUENCE:0\n` + - `\n` + - `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + - `#EXTINF:4,\n` + - `([0-9]+\.ts)\n$`) - ma := re.FindStringSubmatch(string(byts)) - require.NotEqual(t, 0, len(ma)) + // group with IDR + d = 7 * time.Second + err = m.WriteH264(testTime.Add(d-1*time.Second), d, [][]byte{ + {5}, // IDR + }) + require.NoError(t, err) - dem := astits.NewDemuxer(context.Background(), m.File(ma[2], "", "", "").Body, - astits.DemuxerOptPacketSize(188)) + byts, err := ioutil.ReadAll(m.File("index.m3u8", "", "", "").Body) + require.NoError(t, err) - // 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) + if ca == "mpegts" { + require.Equal(t, "#EXTM3U\n"+ + "#EXT-X-VERSION:3\n"+ + "#EXT-X-INDEPENDENT-SEGMENTS\n"+ + "\n"+ + "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028,mp4a.40.2\"\n"+ + "stream.m3u8\n", string(byts)) + } else { + require.Equal(t, "#EXTM3U\n"+ + "#EXT-X-VERSION:9\n"+ + "#EXT-X-INDEPENDENT-SEGMENTS\n"+ + "\n"+ + "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028,mp4a.40.2\"\n"+ + "stream.m3u8\n", string(byts)) + } - // 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) + byts, err = ioutil.ReadAll(m.File("stream.m3u8", "", "", "").Body) + require.NoError(t, err) - // 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, 0x03, 0x19, 0x41, 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) + var ma []string + if ca == "mpegts" { + re := regexp.MustCompile(`^#EXTM3U\n` + + `#EXT-X-VERSION:3\n` + + `#EXT-X-ALLOW-CACHE:NO\n` + + `#EXT-X-TARGETDURATION:4\n` + + `#EXT-X-MEDIA-SEQUENCE:0\n` + + `\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:4,\n` + + `([0-9]+\.ts)\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:1,\n` + + `([0-9]+\.ts)\n$`) + ma = re.FindStringSubmatch(string(byts)) + } else { + re := regexp.MustCompile(`^#EXTM3U\n` + + `#EXT-X-VERSION:9\n` + + `#EXT-X-TARGETDURATION:4\n` + + `#EXT-X-MEDIA-SEQUENCE:0\n` + + `#EXT-X-MAP:URI="init.mp4"\n` + + `\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:4.00000,\n` + + `(.+?\.mp4)\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:1.00000,\n` + + `(.+?\.mp4)\n$`) + ma = re.FindStringSubmatch(string(byts)) + } + require.NotEqual(t, 0, len(ma)) - // 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, 0x07, 0xd8, 0x5f, 0xff, 0xf1, - 0x50, 0x80, 0x01, 0x7f, 0xfc, 0x01, 0x02, 0x03, - 0x04, - }, - }, pkt) + if ca == "mpegts" { + dem := astits.NewDemuxer(context.Background(), m.File(ma[2], "", "", "").Body, + 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, 0x03, 0x19, 0x41, 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, 0x07, 0xd8, 0x5f, 0xff, 0xf1, + 0x50, 0x80, 0x01, 0x7f, 0xfc, 0x01, 0x02, 0x03, + 0x04, + }, + }, pkt) + } else { + byts, err := io.ReadAll(m.File("init.mp4", "", "", "").Body) + 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) + + byts, err = io.ReadAll(m.File(ma[2], "", "", "").Body) + 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) + } + }) + } } func TestMuxerVideoOnly(t *testing.T) { @@ -200,83 +422,221 @@ func TestMuxerVideoOnly(t *testing.T) { PPS: []byte{0x08}, } - m, err := NewMuxer(MuxerVariantMPEGTS, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, nil) - require.NoError(t, err) - defer m.Close() + for _, ca := range []string{ + "mpegts", + "fmp4", + } { + t.Run(ca, func(t *testing.T) { + var v MuxerVariant + if ca == "mpegts" { + v = MuxerVariantMPEGTS + } else { + v = MuxerVariantFMP4 + } - // group with IDR - err = m.WriteH264(testTime, 2*time.Second, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) - require.NoError(t, err) + m, err := NewMuxer(v, 3, 1*time.Second, 0, 50*1024*1024, videoTrack, nil) + require.NoError(t, err) + defer m.Close() - // group with IDR - err = m.WriteH264(testTime, 6*time.Second, [][]byte{ - {5}, // IDR - }) - require.NoError(t, err) + // group with IDR + d := 2 * time.Second + err = m.WriteH264(testTime.Add(d-2*time.Second), d, [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) + require.NoError(t, err) - byts, err := ioutil.ReadAll(m.File("index.m3u8", "", "", "").Body) - require.NoError(t, err) + // group with IDR + d = 6 * time.Second + err = m.WriteH264(testTime.Add(d-2*time.Second), d, [][]byte{ + {5}, // IDR + }) + require.NoError(t, err) - require.Equal(t, "#EXTM3U\n"+ - "#EXT-X-VERSION:3\n"+ - "#EXT-X-INDEPENDENT-SEGMENTS\n"+ - "\n"+ - "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028\"\n"+ - "stream.m3u8\n", string(byts)) + // group with IDR + d = 7 * time.Second + err = m.WriteH264(testTime.Add(d-2*time.Second), d, [][]byte{ + {5}, // IDR + }) + require.NoError(t, err) - byts, err = ioutil.ReadAll(m.File("stream.m3u8", "", "", "").Body) - require.NoError(t, err) + byts, err := ioutil.ReadAll(m.File("index.m3u8", "", "", "").Body) + require.NoError(t, err) - re := regexp.MustCompile(`^#EXTM3U\n` + - `#EXT-X-VERSION:3\n` + - `#EXT-X-ALLOW-CACHE:NO\n` + - `#EXT-X-TARGETDURATION:4\n` + - `#EXT-X-MEDIA-SEQUENCE:0\n` + - `\n` + - `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + - `#EXTINF:4,\n` + - `([0-9]+\.ts)\n$`) - ma := re.FindStringSubmatch(string(byts)) - require.NotEqual(t, 0, len(ma)) + if ca == "mpegts" { + require.Equal(t, "#EXTM3U\n"+ + "#EXT-X-VERSION:3\n"+ + "#EXT-X-INDEPENDENT-SEGMENTS\n"+ + "\n"+ + "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028\"\n"+ + "stream.m3u8\n", string(byts)) + } else { + require.Equal(t, "#EXTM3U\n"+ + "#EXT-X-VERSION:9\n"+ + "#EXT-X-INDEPENDENT-SEGMENTS\n"+ + "\n"+ + "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.42c028\"\n"+ + "stream.m3u8\n", string(byts)) + } - dem := astits.NewDemuxer(context.Background(), m.File(ma[2], "", "", "").Body, - astits.DemuxerOptPacketSize(188)) + byts, err = ioutil.ReadAll(m.File("stream.m3u8", "", "", "").Body) + require.NoError(t, err) - // 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) + var ma []string + if ca == "mpegts" { + re := regexp.MustCompile(`^#EXTM3U\n` + + `#EXT-X-VERSION:3\n` + + `#EXT-X-ALLOW-CACHE:NO\n` + + `#EXT-X-TARGETDURATION:4\n` + + `#EXT-X-MEDIA-SEQUENCE:0\n` + + `\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:4,\n` + + `([0-9]+\.ts)\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:1,\n` + + `([0-9]+\.ts)\n$`) + ma = re.FindStringSubmatch(string(byts)) + } else { + re := regexp.MustCompile(`^#EXTM3U\n` + + `#EXT-X-VERSION:9\n` + + `#EXT-X-TARGETDURATION:4\n` + + `#EXT-X-MEDIA-SEQUENCE:0\n` + + `#EXT-X-MAP:URI="init.mp4"\n` + + `\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:4.00000,\n` + + `(.+?\.mp4)\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:1.00000,\n` + + `(.+?\.mp4)\n$`) + ma = re.FindStringSubmatch(string(byts)) + } + require.NotEqual(t, 0, len(ma)) - // 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) + if ca == "mpegts" { //nolint:dupl + dem := astits.NewDemuxer(context.Background(), m.File(ma[2], "", "", "").Body, + 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) + } else { //nolint:dupl + byts, err := io.ReadAll(m.File("init.mp4", "", "", "").Body) + 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) + + byts, err = io.ReadAll(m.File(ma[2], "", "", "").Body) + 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) + } + }) + } } func TestMuxerAudioOnly(t *testing.T) { @@ -292,86 +652,215 @@ func TestMuxerAudioOnly(t *testing.T) { IndexDeltaLength: 3, } - m, err := NewMuxer(MuxerVariantMPEGTS, 3, 1*time.Second, 0, 50*1024*1024, nil, audioTrack) - require.NoError(t, err) - defer m.Close() + for _, ca := range []string{ + "mpegts", + "fmp4", + } { + t.Run(ca, func(t *testing.T) { + var v MuxerVariant + if ca == "mpegts" { + v = MuxerVariantMPEGTS + } else { + v = MuxerVariantFMP4 + } - for i := 0; i < 100; i++ { - err = m.WriteAAC(testTime, 1*time.Second, []byte{ - 0x01, 0x02, 0x03, 0x04, + m, err := NewMuxer(v, 3, 1*time.Second, 0, 50*1024*1024, nil, audioTrack) + require.NoError(t, err) + defer m.Close() + + for i := 0; i < 100; i++ { + d := 1 * time.Second + err = m.WriteAAC(testTime.Add(d-1*time.Second), d, []byte{ + 0x01, 0x02, 0x03, 0x04, + }) + require.NoError(t, err) + } + + d := 2 * time.Second + err = m.WriteAAC(testTime.Add(d-1*time.Second), d, []byte{ + 0x01, 0x02, 0x03, 0x04, + }) + require.NoError(t, err) + + d = 3 * time.Second + err = m.WriteAAC(testTime.Add(d-1*time.Second), d, []byte{ + 0x01, 0x02, 0x03, 0x04, + }) + require.NoError(t, err) + + byts, err := ioutil.ReadAll(m.File("index.m3u8", "", "", "").Body) + require.NoError(t, err) + + if ca == "mpegts" { + require.Equal(t, "#EXTM3U\n"+ + "#EXT-X-VERSION:3\n"+ + "#EXT-X-INDEPENDENT-SEGMENTS\n"+ + "\n"+ + "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"mp4a.40.2\"\n"+ + "stream.m3u8\n", string(byts)) + } else { + require.Equal(t, "#EXTM3U\n"+ + "#EXT-X-VERSION:9\n"+ + "#EXT-X-INDEPENDENT-SEGMENTS\n"+ + "\n"+ + "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"mp4a.40.2\"\n"+ + "stream.m3u8\n", string(byts)) + } + + byts, err = ioutil.ReadAll(m.File("stream.m3u8", "", "", "").Body) + require.NoError(t, err) + + var ma []string + if ca == "mpegts" { + re := regexp.MustCompile(`^#EXTM3U\n` + + `#EXT-X-VERSION:3\n` + + `#EXT-X-ALLOW-CACHE:NO\n` + + `#EXT-X-TARGETDURATION:1\n` + + `#EXT-X-MEDIA-SEQUENCE:0\n` + + `\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:1,\n` + + `([0-9]+\.ts)\n$`) + ma = re.FindStringSubmatch(string(byts)) + } else { + re := regexp.MustCompile(`^#EXTM3U\n` + + `#EXT-X-VERSION:9\n` + + `#EXT-X-TARGETDURATION:2\n` + + `#EXT-X-MEDIA-SEQUENCE:0\n` + + `#EXT-X-MAP:URI="init.mp4"\n` + + `\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:2.32200,\n` + + `(.+?\.mp4)\n` + + `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + + `#EXTINF:0.02322,\n` + + `(.+?\.mp4)\n$`) + ma = re.FindStringSubmatch(string(byts)) + } + require.NotEqual(t, 0, len(ma)) + + if ca == "mpegts" { //nolint:dupl + dem := astits.NewDemuxer(context.Background(), m.File(ma[2], "", "", "").Body, + 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) + } else { //nolint:dupl + byts, err := io.ReadAll(m.File("init.mp4", "", "", "").Body) + 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) + + byts, err = io.ReadAll(m.File(ma[2], "", "", "").Body) + 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) + } }) - require.NoError(t, err) } - - err = m.WriteAAC(testTime, 2*time.Second, []byte{ - 0x01, 0x02, 0x03, 0x04, - }) - require.NoError(t, err) - - err = m.WriteAAC(testTime, 3*time.Second, []byte{ - 0x01, 0x02, 0x03, 0x04, - }) - require.NoError(t, err) - - byts, err := ioutil.ReadAll(m.File("index.m3u8", "", "", "").Body) - require.NoError(t, err) - - require.Equal(t, "#EXTM3U\n"+ - "#EXT-X-VERSION:3\n"+ - "#EXT-X-INDEPENDENT-SEGMENTS\n"+ - "\n"+ - "#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"mp4a.40.2\"\n"+ - "stream.m3u8\n", string(byts)) - - byts, err = ioutil.ReadAll(m.File("stream.m3u8", "", "", "").Body) - require.NoError(t, err) - - re := regexp.MustCompile(`^#EXTM3U\n` + - `#EXT-X-VERSION:3\n` + - `#EXT-X-ALLOW-CACHE:NO\n` + - `#EXT-X-TARGETDURATION:1\n` + - `#EXT-X-MEDIA-SEQUENCE:0\n` + - `\n` + - `#EXT-X-PROGRAM-DATE-TIME:(.*?)\n` + - `#EXTINF:1,\n` + - `([0-9]+\.ts)\n$`) - ma := re.FindStringSubmatch(string(byts)) - require.NotEqual(t, 0, len(ma)) - - dem := astits.NewDemuxer(context.Background(), m.File(ma[2], "", "", "").Body, - 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) } func TestMuxerCloseBeforeFirstSegmentReader(t *testing.T) {