mirror of
				https://github.com/aler9/rtsp-simple-server
				synced 2025-10-31 19:13:22 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			403 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package hls
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"io/ioutil"
 | |
| 	"regexp"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/aler9/gortsplib"
 | |
| 	"github.com/asticode/go-astits"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func TestMuxerVideoAudio(t *testing.T) {
 | |
| 	videoTrack, err := gortsplib.NewTrackH264(96, []byte{0x07, 0x01, 0x02, 0x03}, []byte{0x08}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	audioTrack, err := gortsplib.NewTrackAAC(97, 2, 44100, 2, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	m, err := NewMuxer(3, 1*time.Second, 50*1024*1024, videoTrack, audioTrack)
 | |
| 	require.NoError(t, err)
 | |
| 	defer m.Close()
 | |
| 
 | |
| 	// group without IDR
 | |
| 	err = m.WriteH264(1*time.Second, [][]byte{
 | |
| 		{0x06},
 | |
| 		{0x07},
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	// group with IDR
 | |
| 	err = m.WriteH264(2*time.Second, [][]byte{
 | |
| 		{7}, // SPS
 | |
| 		{8}, // PPS
 | |
| 		{5}, // IDR
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	err = m.WriteAAC(3*time.Second, [][]byte{
 | |
| 		{0x01, 0x02, 0x03, 0x04},
 | |
| 		{0x05, 0x06, 0x07, 0x08},
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	// group without IDR
 | |
| 	err = m.WriteH264(4*time.Second, [][]byte{
 | |
| 		{6},
 | |
| 		{7},
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	time.Sleep(2 * time.Second)
 | |
| 
 | |
| 	// group with IDR
 | |
| 	err = m.WriteH264(6*time.Second, [][]byte{
 | |
| 		{5}, // IDR
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	byts, err := ioutil.ReadAll(m.PrimaryPlaylist())
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	require.Equal(t, "#EXTM3U\n"+
 | |
| 		"#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.010203,mp4a.40.2\"\n"+
 | |
| 		"stream.m3u8\n", string(byts))
 | |
| 
 | |
| 	byts, err = ioutil.ReadAll(m.StreamPlaylist())
 | |
| 	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))
 | |
| 
 | |
| 	dem := astits.NewDemuxer(context.Background(), m.Segment(ma[2]),
 | |
| 		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:                148,
 | |
| 			StuffingLength:        141,
 | |
| 			HasPCR:                true,
 | |
| 			PCR:                   &astits.ClockReference{},
 | |
| 			RandomAccessIndicator: true,
 | |
| 		},
 | |
| 		Header: &astits.PacketHeader{
 | |
| 			HasAdaptationField:        true,
 | |
| 			HasPayload:                true,
 | |
| 			PayloadUnitStartIndicator: true,
 | |
| 			PID:                       256,
 | |
| 		},
 | |
| 		Payload: append([]byte{
 | |
| 			0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x80,
 | |
| 			0x05, 0x21, 0x00, 0x03, 0x5f, 0x91,
 | |
| 			0, 0, 0, 1, 9, 240, // AUD
 | |
| 			0, 0, 0, 1, 7, // SPS
 | |
| 			0, 0, 0, 1, 8, // PPS
 | |
| 			0, 0, 0, 1, 5, // IDR
 | |
| 		}, bytes.Repeat([]byte{0xff}, 0)...),
 | |
| 	}, pkt)
 | |
| 
 | |
| 	// PES (AAC)
 | |
| 	pkt, err = dem.NextPacket()
 | |
| 	require.NoError(t, err)
 | |
| 	require.Equal(t, &astits.Packet{
 | |
| 		AdaptationField: &astits.PacketAdaptationField{
 | |
| 			Length:                147,
 | |
| 			StuffingLength:        146,
 | |
| 			RandomAccessIndicator: true,
 | |
| 		},
 | |
| 		Header: &astits.PacketHeader{
 | |
| 			HasAdaptationField:        true,
 | |
| 			HasPayload:                true,
 | |
| 			PayloadUnitStartIndicator: true,
 | |
| 			PID:                       257,
 | |
| 		},
 | |
| 		Payload: append([]byte{
 | |
| 			0x00, 0x00, 0x01, 0xc0, 0x00, 0x1e, 0x80, 0x80,
 | |
| 			0x05, 0x21, 0x00, 0x09, 0x1e, 0xb1, 0xff, 0xf1,
 | |
| 			0x50, 0x80, 0x01, 0x7f, 0xfc, 0x01, 0x02, 0x03,
 | |
| 			0x04, 0xff, 0xf1, 0x50, 0x80, 0x01, 0x7f, 0xfc,
 | |
| 			0x05, 0x06, 0x07, 0x08,
 | |
| 		}, bytes.Repeat([]byte{0xff}, 0)...),
 | |
| 	}, pkt)
 | |
| }
 | |
| 
 | |
| func TestMuxerVideoOnly(t *testing.T) {
 | |
| 	videoTrack, err := gortsplib.NewTrackH264(96, []byte{0x07, 0x01, 0x02, 0x03}, []byte{0x08}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	m, err := NewMuxer(3, 1*time.Second, 50*1024*1024, videoTrack, nil)
 | |
| 	require.NoError(t, err)
 | |
| 	defer m.Close()
 | |
| 
 | |
| 	// group with IDR
 | |
| 	err = m.WriteH264(2*time.Second, [][]byte{
 | |
| 		{5}, // IDR
 | |
| 		{9}, // AUD
 | |
| 		{8}, // PPS
 | |
| 		{7}, // SPS
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	// group with IDR
 | |
| 	err = m.WriteH264(6*time.Second, [][]byte{
 | |
| 		{5}, // IDR
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	byts, err := ioutil.ReadAll(m.PrimaryPlaylist())
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	require.Equal(t, "#EXTM3U\n"+
 | |
| 		"#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"avc1.010203\"\n"+
 | |
| 		"stream.m3u8\n", string(byts))
 | |
| 
 | |
| 	byts, err = ioutil.ReadAll(m.StreamPlaylist())
 | |
| 	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))
 | |
| 
 | |
| 	dem := astits.NewDemuxer(context.Background(), m.Segment(ma[2]),
 | |
| 		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)
 | |
| }
 | |
| 
 | |
| func TestMuxerAudioOnly(t *testing.T) {
 | |
| 	audioTrack, err := gortsplib.NewTrackAAC(97, 2, 44100, 2, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	m, err := NewMuxer(3, 1*time.Second, 50*1024*1024, nil, audioTrack)
 | |
| 	require.NoError(t, err)
 | |
| 	defer m.Close()
 | |
| 
 | |
| 	for i := 0; i < 100; i++ {
 | |
| 		err = m.WriteAAC(1*time.Second, [][]byte{
 | |
| 			{0x01, 0x02, 0x03, 0x04},
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 	}
 | |
| 
 | |
| 	err = m.WriteAAC(2*time.Second, [][]byte{
 | |
| 		{0x01, 0x02, 0x03, 0x04},
 | |
| 		{0x05, 0x06, 0x07, 0x08},
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	err = m.WriteAAC(3*time.Second, [][]byte{
 | |
| 		{0x01, 0x02, 0x03, 0x04},
 | |
| 		{0x05, 0x06, 0x07, 0x08},
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	byts, err := ioutil.ReadAll(m.PrimaryPlaylist())
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	require.Equal(t, "#EXTM3U\n"+
 | |
| 		"#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS=\"mp4a.40.2\"\n"+
 | |
| 		"stream.m3u8\n", string(byts))
 | |
| 
 | |
| 	byts, err = ioutil.ReadAll(m.StreamPlaylist())
 | |
| 	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.Segment(ma[2]),
 | |
| 		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 TestMuxerCloseBeforeFirstSegment(t *testing.T) {
 | |
| 	videoTrack, err := gortsplib.NewTrackH264(96, []byte{0x07, 0x01, 0x02, 0x03}, []byte{0x08}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	m, err := NewMuxer(3, 1*time.Second, 50*1024*1024, videoTrack, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	// group with IDR
 | |
| 	err = m.WriteH264(2*time.Second, [][]byte{
 | |
| 		{5}, // IDR
 | |
| 		{9}, // AUD
 | |
| 		{8}, // PPS
 | |
| 		{7}, // SPS
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	m.Close()
 | |
| 
 | |
| 	byts, err := ioutil.ReadAll(m.StreamPlaylist())
 | |
| 	require.NoError(t, err)
 | |
| 	require.Equal(t, []byte{}, byts)
 | |
| }
 | |
| 
 | |
| func TestMuxerMaxSegmentSize(t *testing.T) {
 | |
| 	videoTrack, err := gortsplib.NewTrackH264(96, []byte{0x07, 0x01, 0x02, 0x03}, []byte{0x08}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	m, err := NewMuxer(3, 1*time.Second, 0, videoTrack, nil)
 | |
| 	require.NoError(t, err)
 | |
| 	defer m.Close()
 | |
| 
 | |
| 	err = m.WriteH264(2*time.Second, [][]byte{
 | |
| 		{5},
 | |
| 	})
 | |
| 	require.EqualError(t, err, "reached maximum segment size")
 | |
| }
 | |
| 
 | |
| func TestMuxerDoubleRead(t *testing.T) {
 | |
| 	videoTrack, err := gortsplib.NewTrackH264(96, []byte{0x07, 0x01, 0x02, 0x03}, []byte{0x08}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	m, err := NewMuxer(3, 1*time.Second, 50*1024*1024, videoTrack, nil)
 | |
| 	require.NoError(t, err)
 | |
| 	defer m.Close()
 | |
| 
 | |
| 	err = m.WriteH264(0, [][]byte{
 | |
| 		{5},
 | |
| 		{1},
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	err = m.WriteH264(2*time.Second, [][]byte{
 | |
| 		{5},
 | |
| 		{2},
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	byts1, err := ioutil.ReadAll(m.streamPlaylist.segments[0].reader())
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	byts2, err := ioutil.ReadAll(m.streamPlaylist.segments[0].reader())
 | |
| 	require.NoError(t, err)
 | |
| 	require.Equal(t, byts1, byts2)
 | |
| }
 | 
