From fcf5eb092d46064bccabae9252887244afd1615c Mon Sep 17 00:00:00 2001 From: eh Date: Thu, 30 Apr 2026 06:14:00 -0400 Subject: [PATCH] hls: support reading and writing KLV (#5604) --- docs/2-features/03-publish.md | 26 +- docs/2-features/04-read.md | 14 +- internal/protocols/hls/from_stream.go | 56 +++- internal/protocols/hls/from_stream_test.go | 50 ++++ internal/protocols/hls/to_stream.go | 17 ++ internal/protocols/hls/to_stream_test.go | 302 ++++++++++++--------- 6 files changed, 319 insertions(+), 146 deletions(-) diff --git a/docs/2-features/03-publish.md b/docs/2-features/03-publish.md index 5d372332..b1f59e62 100644 --- a/docs/2-features/03-publish.md +++ b/docs/2-features/03-publish.md @@ -2,19 +2,19 @@ Live streams can be published to the server with the following protocols and codecs: -| protocol | variants | codecs | -| ----------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [SRT clients](../3-publish/01-srt-clients.md) | | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | -| [SRT cameras and servers](../3-publish/02-srt-cameras-and-servers.md) | | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | -| [WebRTC clients](../3-publish/03-webrtc-clients.md) | WHIP | **Video**: AV1, VP9, VP8, H265, H264
**Audio**: Opus, G722, G711 (PCMA, PCMU) | -| [WebRTC servers](../3-publish/04-webrtc-servers.md) | WHEP | **Video**: AV1, VP9, VP8, H265, H264
**Audio**: Opus, G722, G711 (PCMA, PCMU) | -| [RTSP clients](../3-publish/05-rtsp-clients.md) | UDP, TCP, RTSPS | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, MJPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | -| [RTSP cameras and servers](../3-publish/06-rtsp-cameras-and-servers.md) | UDP, UDP-Multicast, TCP, RTSPS | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, MJPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | -| [RTMP clients](../3-publish/07-rtmp-clients.md) | RTMP, RTMPS, Enhanced RTMP | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711 (PCMA, PCMU), LPCM | -| [RTMP cameras and servers](../3-publish/08-rtmp-cameras-and-servers.md) | RTMP, RTMPS, Enhanced RTMP | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711 (PCMA, PCMU), LPCM | -| [HLS cameras and servers](../3-publish/09-hls-cameras-and-servers.md) | Low-Latency HLS, MP4-based HLS, legacy HLS | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC) | -| [MPEG-TS](../3-publish/10-mpeg-ts.md) | MPEG-TS over UDP, MPEG-TS over Unix socket | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | -| [RTP](../3-publish/11-rtp.md) | RTP over UDP | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | +| protocol | variants | codecs | +| ----------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [SRT clients](../3-publish/01-srt-clients.md) | | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | +| [SRT cameras and servers](../3-publish/02-srt-cameras-and-servers.md) | | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | +| [WebRTC clients](../3-publish/03-webrtc-clients.md) | WHIP | **Video**: AV1, VP9, VP8, H265, H264
**Audio**: Opus, G722, G711 (PCMA, PCMU) | +| [WebRTC servers](../3-publish/04-webrtc-servers.md) | WHEP | **Video**: AV1, VP9, VP8, H265, H264
**Audio**: Opus, G722, G711 (PCMA, PCMU) | +| [RTSP clients](../3-publish/05-rtsp-clients.md) | UDP, TCP, RTSPS | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, MJPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | +| [RTSP cameras and servers](../3-publish/06-rtsp-cameras-and-servers.md) | UDP, UDP-Multicast, TCP, RTSPS | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, MJPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | +| [RTMP clients](../3-publish/07-rtmp-clients.md) | RTMP, RTMPS, Enhanced RTMP | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711 (PCMA, PCMU), LPCM | +| [RTMP cameras and servers](../3-publish/08-rtmp-cameras-and-servers.md) | RTMP, RTMPS, Enhanced RTMP | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711 (PCMA, PCMU), LPCM | +| [HLS cameras and servers](../3-publish/09-hls-cameras-and-servers.md) | Low-Latency HLS, MP4-based HLS, MPEG-TS-based HLS | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC)
**Other**: KLV (MPEG-TS-based HLS only) | +| [MPEG-TS](../3-publish/10-mpeg-ts.md) | MPEG-TS over UDP, MPEG-TS over Unix socket | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | +| [RTP](../3-publish/11-rtp.md) | RTP over UDP | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | We provide instructions for publishing with the following devices: diff --git a/docs/2-features/04-read.md b/docs/2-features/04-read.md index 52371f30..8e03f223 100644 --- a/docs/2-features/04-read.md +++ b/docs/2-features/04-read.md @@ -2,13 +2,13 @@ Live streams can be read from the server with the following protocols and codecs: -| protocol | variants | codecs | -| ---------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [SRT clients](../4-read/01-srt.md) | | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | -| [WebRTC clients](../4-read/02-webrtc.md) | WHEP | **Video**: AV1, VP9, VP8, H265, H264
**Audio**: Opus, G722, G711 (PCMA, PCMU)
**Other**: KLV | -| [RTSP clients](../4-read/03-rtsp.md) | UDP, UDP-Multicast, TCP, RTSPS | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | -| [RTMP clients](../4-read/04-rtmp.md) | RTMP, RTMPS, Enhanced RTMP | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711 (PCMA, PCMU), LPCM | -| [HLS](../4-read/05-hls.md) | Low-Latency HLS, MP4-based HLS, legacy HLS | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC) | +| protocol | variants | codecs | +| ---------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [SRT clients](../4-read/01-srt.md) | | **Video**: H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3
**Other**: KLV | +| [WebRTC clients](../4-read/02-webrtc.md) | WHEP | **Video**: AV1, VP9, VP8, H265, H264
**Audio**: Opus, G722, G711 (PCMA, PCMU)
**Other**: KLV | +| [RTSP clients](../4-read/03-rtsp.md) | UDP, UDP-Multicast, TCP, RTSPS | **Video**: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM
**Other**: KLV, MPEG-TS, any RTP-compatible codec | +| [RTMP clients](../4-read/04-rtmp.md) | RTMP, RTMPS, Enhanced RTMP | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711 (PCMA, PCMU), LPCM | +| [HLS](../4-read/05-hls.md) | Low-Latency HLS, MP4-based HLS, MPEG-TS-based HLS | **Video**: AV1, VP9, H265, H264
**Audio**: Opus, MPEG-4 Audio (AAC)
**Other**: KLV (MPEG-TS-based HLS only) | We provide instructions for reading with the following software: diff --git a/internal/protocols/hls/from_stream.go b/internal/protocols/hls/from_stream.go index f94b3ac4..50ddd813 100644 --- a/internal/protocols/hls/from_stream.go +++ b/internal/protocols/hls/from_stream.go @@ -19,7 +19,7 @@ import ( // ErrNoSupportedCodecs is returned by FromStream when there are no supported codecs. var ErrNoSupportedCodecs = errors.New( - "the stream doesn't contain any supported codec, which are currently AV1, VP9, H265, H264, Opus, MPEG-4 Audio") + "the stream doesn't contain any supported codec, which are currently AV1, VP9, H265, H264, Opus, MPEG-4 Audio, KLV") func setupVideoTrack( desc *description.Session, @@ -287,6 +287,54 @@ func setupAudioTracks( } } +func setupDataTracks( + desc *description.Session, + r *stream.Reader, + muxer *gohlslib.Muxer, +) { + addTrack := func( + media *description.Media, + forma format.Format, + track *gohlslib.Track, + onData stream.OnDataFunc, + ) { + muxer.Tracks = append(muxer.Tracks, track) + r.OnData(media, forma, onData) + } + + for _, media := range desc.Medias { + for _, forma := range media.Formats { + if forma, ok := forma.(*format.KLV); ok && muxer.Variant == gohlslib.MuxerVariantMPEGTS { + track := &gohlslib.Track{ + Codec: &codecs.KLV{Synchronous: true}, + ClockRate: forma.ClockRate(), + } + + addTrack( + media, + forma, + track, + func(u *unit.Unit) error { + if u.NilPayload() { + return nil + } + + err := muxer.WriteKLV( + track, + u.NTP, + u.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate + u.Payload.(unit.PayloadKLV)) + if err != nil { + return fmt.Errorf("muxer error: %w", err) + } + + return nil + }) + } + } + } +} + // FromStream maps a MediaMTX stream to a HLS muxer. func FromStream( desc *description.Session, @@ -305,6 +353,12 @@ func FromStream( muxer, ) + setupDataTracks( + desc, + r, + muxer, + ) + if len(muxer.Tracks) == 0 { return ErrNoSupportedCodecs } diff --git a/internal/protocols/hls/from_stream_test.go b/internal/protocols/hls/from_stream_test.go index acad4711..6c7a2fe4 100644 --- a/internal/protocols/hls/from_stream_test.go +++ b/internal/protocols/hls/from_stream_test.go @@ -69,3 +69,53 @@ func TestFromStreamSkipUnsupportedTracks(t *testing.T) { require.Equal(t, 2, n) } + +func TestFromStreamKLVRequiresMPEGTSVariant(t *testing.T) { + t.Run("klv only, non-mpegts variant", func(t *testing.T) { + desc := &description.Session{Medias: []*description.Media{{ + Type: description.MediaTypeApplication, + Formats: []format.Format{&format.KLV{PayloadTyp: 96}}, + }}} + + r := &stream.Reader{ + Parent: test.Logger(func(logger.Level, string, ...any) { + t.Error("should not happen") + }), + } + + m := &gohlslib.Muxer{Variant: gohlslib.MuxerVariantFMP4} + + err := FromStream(desc, r, m) + require.Equal(t, ErrNoSupportedCodecs, err) + }) + + t.Run("klv alongside video, non-mpegts variant", func(t *testing.T) { + desc := &description.Session{Medias: []*description.Media{ + { + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{PayloadTyp: 96, PacketizationMode: 1}}, + }, + { + Type: description.MediaTypeApplication, + Formats: []format.Format{&format.KLV{PayloadTyp: 97}}, + }, + }} + + n := 0 + + r := &stream.Reader{ + Parent: test.Logger(func(l logger.Level, f string, args ...any) { + require.Equal(t, logger.Warn, l) + require.Equal(t, "skipping track 2 (KLV)", fmt.Sprintf(f, args...)) + n++ + }), + } + + m := &gohlslib.Muxer{Variant: gohlslib.MuxerVariantFMP4} + + err := FromStream(desc, r, m) + require.NoError(t, err) + require.Equal(t, 1, n) + require.Equal(t, 1, len(m.Tracks)) + }) +} diff --git a/internal/protocols/hls/to_stream.go b/internal/protocols/hls/to_stream.go index d84e71e5..cf051a08 100644 --- a/internal/protocols/hls/to_stream.go +++ b/internal/protocols/hls/to_stream.go @@ -199,6 +199,23 @@ func ToStream( }) }) + case *codecs.KLV: + medi = &description.Media{ + Type: description.MediaTypeApplication, + Formats: []format.Format{&format.KLV{ + PayloadTyp: 96, + }}, + } + newClockRate := medi.Formats[0].ClockRate() + + c.OnDataKLV(ctrack, func(pts int64, uni []byte) { + (*subStream).WriteUnit(medi, medi.Formats[0], &unit.Unit{ + NTP: handleNTP(pts), + PTS: multiplyAndDivide(pts, int64(newClockRate), int64(ctrack.ClockRate)), + Payload: unit.PayloadKLV(uni), + }) + }) + default: panic("should not happen") } diff --git a/internal/protocols/hls/to_stream_test.go b/internal/protocols/hls/to_stream_test.go index 103c0d6e..9ead53e3 100644 --- a/internal/protocols/hls/to_stream_test.go +++ b/internal/protocols/hls/to_stream_test.go @@ -28,143 +28,195 @@ func TestToStreamNoSupportedCodecs(t *testing.T) { // func TestToStreamSkipUnsupportedTracks(t *testing.T) func TestToStream(t *testing.T) { - track1 := &mpegts.Track{ - Codec: &tscodecs.H264{}, - } + for _, ca := range []string{ + "h264", + "klv", + } { + t.Run(ca, func(t *testing.T) { + var track1 *mpegts.Track - s := &http.Server{ - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch { - case r.Method == http.MethodGet && r.URL.Path == "/stream.m3u8": - w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) - w.Write([]byte("#EXTM3U\n" + - "#EXT-X-VERSION:3\n" + - "#EXT-X-ALLOW-CACHE:NO\n" + - "#EXT-X-TARGETDURATION:2\n" + - "#EXT-X-MEDIA-SEQUENCE:0\n" + - "#EXT-X-PROGRAM-DATE-TIME:2018-05-20T08:17:15Z\n" + - "#EXTINF:2,\n" + - "segment1.ts\n" + - "#EXTINF:2,\n" + - "segment2.ts\n" + - "#EXTINF:2,\n" + - "segment3.ts\n" + - "#EXT-X-ENDLIST\n")) - - case r.Method == http.MethodGet && r.URL.Path == "/segment1.ts": - w.Header().Set("Content-Type", `video/MP2T`) - - w := &mpegts.Writer{W: w, Tracks: []*mpegts.Track{track1}} - err := w.Initialize() - require.NoError(t, err) - - err = w.WriteH264(track1, 2*90000, 2*90000, [][]byte{ - {7, 1, 2, 3}, // SPS - {8}, // PPS - {5, 1}, - }) - require.NoError(t, err) - - case r.Method == http.MethodGet && r.URL.Path == "/segment2.ts": - w.Header().Set("Content-Type", `video/MP2T`) - - w := &mpegts.Writer{W: w, Tracks: []*mpegts.Track{track1}} - err := w.Initialize() - require.NoError(t, err) - - err = w.WriteH264(track1, 2*90000, 2*90000, [][]byte{ - {5, 2}, - }) - require.NoError(t, err) + switch ca { + case "h264": + track1 = &mpegts.Track{ + Codec: &tscodecs.H264{}, + } + case "klv": + track1 = &mpegts.Track{ + Codec: &tscodecs.KLV{Synchronous: true}, + } } - }), - } - ln, err := net.Listen("tcp", "localhost:5781") - require.NoError(t, err) + s := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/stream.m3u8": + w.Header().Set("Content-Type", `application/vnd.apple.mpegurl`) + w.Write([]byte("#EXTM3U\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-ALLOW-CACHE:NO\n" + + "#EXT-X-TARGETDURATION:2\n" + + "#EXT-X-MEDIA-SEQUENCE:0\n" + + "#EXT-X-PROGRAM-DATE-TIME:2018-05-20T08:17:15Z\n" + + "#EXTINF:2,\n" + + "segment1.ts\n" + + "#EXTINF:2,\n" + + "segment2.ts\n" + + "#EXTINF:2,\n" + + "segment3.ts\n" + + "#EXT-X-ENDLIST\n")) - go s.Serve(ln) - defer s.Shutdown(context.Background()) + case r.Method == http.MethodGet && r.URL.Path == "/segment1.ts": + w.Header().Set("Content-Type", `video/MP2T`) - var strm *stream.Stream - var subStream *stream.SubStream - done := make(chan struct{}) + mw := &mpegts.Writer{W: w, Tracks: []*mpegts.Track{track1}} + err := mw.Initialize() + require.NoError(t, err) - r := &stream.Reader{Parent: test.NilLogger} + switch ca { + case "h264": + err = mw.WriteH264(track1, 2*90000, 2*90000, [][]byte{ + {7, 1, 2, 3}, // SPS + {8}, // PPS + {5, 1}, + }) + case "klv": + err = mw.WriteKLV(track1, 2*90000, []byte{0x06, 0x0e, 0x2b, 0x34}) + } + require.NoError(t, err) - var c *gohlslib.Client - c = &gohlslib.Client{ - URI: "http://localhost:5781/stream.m3u8", - OnTracks: func(tracks []*gohlslib.Track) error { - medias, err2 := ToStream(c, tracks, &conf.Path{ - UseAbsoluteTimestamp: true, - }, &subStream) - require.NoError(t, err2) + case r.Method == http.MethodGet && r.URL.Path == "/segment2.ts": + w.Header().Set("Content-Type", `video/MP2T`) - require.Equal(t, []*description.Media{{ - Type: description.MediaTypeVideo, - Formats: []format.Format{&format.H264{ - PayloadTyp: 96, - PacketizationMode: 1, - }}, - }}, medias) + mw := &mpegts.Writer{W: w, Tracks: []*mpegts.Track{track1}} + err := mw.Initialize() + require.NoError(t, err) - strm = &stream.Stream{ - Desc: &description.Session{Medias: medias}, - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Parent: test.NilLogger, - } - err2 = strm.Initialize() - require.NoError(t, err2) - - subStream = &stream.SubStream{ - Stream: strm, - UseRTPPackets: false, - } - err2 = subStream.Initialize() - require.NoError(t, err2) - - n := 0 - - r.OnData( - medias[0], - medias[0].Formats[0], - func(u *unit.Unit) error { - switch n { - case 0: - require.Equal(t, unit.PayloadH264{ - {7, 1, 2, 3}, - {8}, - {5, 1}, - }, u.Payload) - require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP) - case 1: - require.Equal(t, unit.PayloadH264{ - {7, 1, 2, 3}, - {8}, - {5, 2}, - }, u.Payload) - require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP) - close(done) - default: - t.Error("should not happen") + switch ca { + case "h264": + err = mw.WriteH264(track1, 2*90000, 2*90000, [][]byte{ + {5, 2}, + }) + case "klv": + err = mw.WriteKLV(track1, 2*90000, []byte{0x01, 0x02, 0x03, 0x04}) + } + require.NoError(t, err) } - n++ + }), + } + + ln, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + + go s.Serve(ln) + defer s.Shutdown(context.Background()) + + var strm *stream.Stream + var subStream *stream.SubStream + done := make(chan struct{}) + + r := &stream.Reader{Parent: test.NilLogger} + + var c *gohlslib.Client + c = &gohlslib.Client{ + URI: "http://" + ln.Addr().String() + "/stream.m3u8", + OnTracks: func(tracks []*gohlslib.Track) error { + medias, err2 := ToStream(c, tracks, &conf.Path{ + UseAbsoluteTimestamp: true, + }, &subStream) + require.NoError(t, err2) + + switch ca { + case "h264": + require.Equal(t, []*description.Media{{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ + PayloadTyp: 96, + PacketizationMode: 1, + }}, + }}, medias) + case "klv": + require.Equal(t, []*description.Media{{ + Type: description.MediaTypeApplication, + Formats: []format.Format{&format.KLV{ + PayloadTyp: 96, + }}, + }}, medias) + } + + strm = &stream.Stream{ + Desc: &description.Session{Medias: medias}, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, + } + err2 = strm.Initialize() + require.NoError(t, err2) + + subStream = &stream.SubStream{ + Stream: strm, + UseRTPPackets: false, + } + err2 = subStream.Initialize() + require.NoError(t, err2) + + n := 0 + + r.OnData( + medias[0], + medias[0].Formats[0], + func(u *unit.Unit) error { + switch ca { + case "h264": + switch n { + case 0: + require.Equal(t, unit.PayloadH264{ + {7, 1, 2, 3}, + {8}, + {5, 1}, + }, u.Payload) + require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP) + case 1: + require.Equal(t, unit.PayloadH264{ + {7, 1, 2, 3}, + {8}, + {5, 2}, + }, u.Payload) + require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP) + close(done) + default: + t.Error("should not happen") + } + + case "klv": + switch n { + case 0: + require.Equal(t, unit.PayloadKLV{0x06, 0x0e, 0x2b, 0x34}, u.Payload) + require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP) + case 1: + require.Equal(t, unit.PayloadKLV{0x01, 0x02, 0x03, 0x04}, u.Payload) + require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP) + close(done) + default: + t.Error("should not happen") + } + } + n++ + return nil + }) + + strm.AddReader(r) + return nil - }) + }, + } + err = c.Start() + require.NoError(t, err) + defer c.Close() - strm.AddReader(r) + <-done - return nil - }, + strm.RemoveReader(r) + strm.Close() + }) } - err = c.Start() - require.NoError(t, err) - defer c.Close() - - <-done - - strm.RemoveReader(r) - strm.Close() }