From 29c6e18e8993609a742f739f52a45148a8fb895a Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Sat, 25 May 2024 19:04:41 +0200 Subject: [PATCH] add client-record-format-h264-from-disk (#578) --- README.md | 1 + examples/client-play-backchannel/main.go | 2 +- .../main.go | 2 +- .../main.go | 2 +- examples/client-play-format-h264/main.go | 2 +- .../main.go | 2 +- .../main.go | 2 +- examples/client-play-format-h265/main.go | 2 +- .../main.go | 2 +- .../main.go | 2 +- examples/client-record-format-g711/main.go | 2 +- examples/client-record-format-g722/main.go | 2 +- .../main.go | 132 ++++++++++++++++++ examples/client-record-format-h264/main.go | 2 +- examples/client-record-format-h265/main.go | 2 +- examples/client-record-format-lpcm/main.go | 2 +- .../main.go | 52 ++++--- examples/client-record-format-mjpeg/main.go | 2 +- .../client-record-format-mpeg4audio/main.go | 2 +- examples/client-record-format-opus/main.go | 2 +- examples/client-record-format-vp8/main.go | 2 +- examples/client-record-format-vp9/main.go | 2 +- examples/client-record-options/main.go | 2 +- examples/client-record-pause/main.go | 2 +- examples/server-h264-save-to-disk/main.go | 2 +- 25 files changed, 184 insertions(+), 45 deletions(-) create mode 100644 examples/client-record-format-h264-from-disk/main.go diff --git a/README.md b/README.md index 00e74d4d..ff7052c0 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Features: * [client-record-format-g711](examples/client-record-format-g711/main.go) * [client-record-format-g722](examples/client-record-format-g722/main.go) * [client-record-format-h264](examples/client-record-format-h264/main.go) +* [client-record-format-h264-from-disk](examples/client-record-format-h264-from-disk/main.go) * [client-record-format-h265](examples/client-record-format-h265/main.go) * [client-record-format-lpcm](examples/client-record-format-lpcm/main.go) * [client-record-format-mjpeg](examples/client-record-format-mjpeg/main.go) diff --git a/examples/client-play-backchannel/main.go b/examples/client-play-backchannel/main.go index bf491d3d..8043b358 100644 --- a/examples/client-play-backchannel/main.go +++ b/examples/client-play-backchannel/main.go @@ -12,7 +12,7 @@ import ( ) // This example shows how to -// 1. generate RTP/G711 packets with GStreamer +// 1. generate a G711 stream and RTP packets with GStreamer // 2. connect to a RTSP server, find a back channel that supports G711 // 3. route the packets from GStreamer to the channel diff --git a/examples/client-play-format-h264-convert-to-jpeg/main.go b/examples/client-play-format-h264-convert-to-jpeg/main.go index b87bef32..2f574348 100644 --- a/examples/client-play-format-h264-convert-to-jpeg/main.go +++ b/examples/client-play-format-h264-convert-to-jpeg/main.go @@ -71,7 +71,7 @@ func main() { panic("media not found") } - // setup RTP/H264 -> H264 decoder + // setup RTP -> H264 decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-play-format-h264-save-to-disk/main.go b/examples/client-play-format-h264-save-to-disk/main.go index b9beb41a..8f92fa39 100644 --- a/examples/client-play-format-h264-save-to-disk/main.go +++ b/examples/client-play-format-h264-save-to-disk/main.go @@ -44,7 +44,7 @@ func main() { panic("media not found") } - // setup RTP/H264 -> H264 decoder + // setup RTP -> H264 decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-play-format-h264/main.go b/examples/client-play-format-h264/main.go index 4be907a4..27682288 100644 --- a/examples/client-play-format-h264/main.go +++ b/examples/client-play-format-h264/main.go @@ -47,7 +47,7 @@ func main() { panic("media not found") } - // setup RTP/H264 -> H264 decoder + // setup RTP -> H264 decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-play-format-h265-convert-to-jpeg/main.go b/examples/client-play-format-h265-convert-to-jpeg/main.go index e706b347..8d80fb62 100644 --- a/examples/client-play-format-h265-convert-to-jpeg/main.go +++ b/examples/client-play-format-h265-convert-to-jpeg/main.go @@ -71,7 +71,7 @@ func main() { panic("media not found") } - // setup RTP/H265 -> H265 decoder + // setup RTP -> H265 decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-play-format-h265-save-to-disk/main.go b/examples/client-play-format-h265-save-to-disk/main.go index d9567d93..0481519a 100644 --- a/examples/client-play-format-h265-save-to-disk/main.go +++ b/examples/client-play-format-h265-save-to-disk/main.go @@ -44,7 +44,7 @@ func main() { panic("media not found") } - // setup RTP/H265 -> H265 decoder + // setup RTP -> H265 decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-play-format-h265/main.go b/examples/client-play-format-h265/main.go index 2891d8a0..920e3ab1 100644 --- a/examples/client-play-format-h265/main.go +++ b/examples/client-play-format-h265/main.go @@ -47,7 +47,7 @@ func main() { panic("media not found") } - // setup RTP/H265 -> H265 decoder + // setup RTP -> H265 decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-play-format-mpeg4audio-save-to-disk/main.go b/examples/client-play-format-mpeg4audio-save-to-disk/main.go index 64d29598..31f0e08e 100644 --- a/examples/client-play-format-mpeg4audio-save-to-disk/main.go +++ b/examples/client-play-format-mpeg4audio-save-to-disk/main.go @@ -44,7 +44,7 @@ func main() { panic("media not found") } - // setup RTP/MPEG-4 audio -> MPEG-4 audio decoder + // setup RTP -> MPEG-4 audio decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-play-format-opus-save-to-disk/main.go b/examples/client-play-format-opus-save-to-disk/main.go index 7097ad54..4e2184f7 100644 --- a/examples/client-play-format-opus-save-to-disk/main.go +++ b/examples/client-play-format-opus-save-to-disk/main.go @@ -44,7 +44,7 @@ func main() { panic("media not found") } - // setup RTP/Opus -> Opus decoder + // setup RTP -> Opus decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err) diff --git a/examples/client-record-format-g711/main.go b/examples/client-record-format-g711/main.go index 4f98bdbb..9207e241 100644 --- a/examples/client-record-format-g711/main.go +++ b/examples/client-record-format-g711/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/G711 packets with GStreamer +// 1. generate a G711 and RTP packets with GStreamer // 2. connect to a RTSP server, announce a G711 format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-g722/main.go b/examples/client-record-format-g722/main.go index a98397f7..367e302b 100644 --- a/examples/client-record-format-g722/main.go +++ b/examples/client-record-format-g722/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/G722 packets with GStreamer +// 1. generate a G722 stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce a G722 format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-h264-from-disk/main.go b/examples/client-record-format-h264-from-disk/main.go new file mode 100644 index 00000000..37fc261f --- /dev/null +++ b/examples/client-record-format-h264-from-disk/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/rtptime" + "github.com/bluenviron/mediacommon/pkg/formats/mpegts" +) + +// This example shows how to +// 1. read H264 frames from a video file in MPEG-TS format +// 2. connect to a RTSP server, announce a H264 format +// 3. wrap frames into RTP packets +// 4. write packets to the server + +func findTrack(r *mpegts.Reader) (*mpegts.Track, error) { + for _, track := range r.Tracks() { + if _, ok := track.Codec.(*mpegts.CodecH264); ok { + return track, nil + } + } + return nil, fmt.Errorf("H264 track not found") +} + +func main() { + // open a file in MPEG-TS format + f, err := os.Open("myvideo.ts") + if err != nil { + panic(err) + } + defer f.Close() + + // setup MPEG-TS parser + r, err := mpegts.NewReader(f) + if err != nil { + panic(err) + } + + // find the H264 track inside the file + track, err := findTrack(r) + if err != nil { + panic(err) + } + + // create a RTSP description that contains a H264 format + forma := &format.H264{ + PayloadTyp: 96, + PacketizationMode: 1, + } + desc := &description.Session{ + Medias: []*description.Media{{ + Type: description.MediaTypeVideo, + Formats: []format.Format{forma}, + }}, + } + + // connect to the server, announce the format and start recording + c := gortsplib.Client{} + err = c.StartRecording("rtsp://localhost:8554/mystream", desc) + if err != nil { + panic(err) + } + defer c.Close() + + // setup H264 -> RTP encoder + rtpEnc, err := forma.CreateEncoder() + if err != nil { + panic(err) + } + + // setup RTP timestamp generator + rtpTime := &rtptime.Encoder{ClockRate: forma.ClockRate()} + err = rtpTime.Initialize() + if err != nil { + panic(err) + } + + var firstDTS *int64 + var startTime time.Time + + // setup a callback that is called whenever a H264 access unit is read from the file + r.OnDataH264(track, func(pts, dts int64, au [][]byte) error { + // sleep between access units + if firstDTS != nil { + timeDrift := time.Duration(dts-*firstDTS)*time.Second/90000 - time.Since(startTime) + if timeDrift > 0 { + time.Sleep(timeDrift) + } + } else { + startTime = time.Now() + firstDTS = &dts + } + + log.Printf("writing access unit with pts=%d dts=%d", pts, dts) + + // wrap the access unit into RTP packets + packets, err := rtpEnc.Encode(au) + if err != nil { + return err + } + + // set timestamp + rtpTime := rtpTime.Encode(time.Duration(pts) * time.Second / 90000) + for _, packet := range packets { + packet.Timestamp = rtpTime + } + + // write packets to the server + for _, packet := range packets { + err := c.WritePacketRTP(desc.Medias[0], packet) + if err != nil { + return err + } + } + + return nil + }) + + // start reading the MPEG-TS file + for { + err := r.Read() + if err != nil { + panic(err) + } + } +} diff --git a/examples/client-record-format-h264/main.go b/examples/client-record-format-h264/main.go index 35a52aa9..82615ae5 100644 --- a/examples/client-record-format-h264/main.go +++ b/examples/client-record-format-h264/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/H264 packets with GStreamer +// 1. generate a H264 stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce an H264 format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-h265/main.go b/examples/client-record-format-h265/main.go index 319d6dfa..7f316098 100644 --- a/examples/client-record-format-h265/main.go +++ b/examples/client-record-format-h265/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/H265 packets with GStreamer +// 1. generate a H265 stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce an H265 format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-lpcm/main.go b/examples/client-record-format-lpcm/main.go index f451acc8..d2c8a6c2 100644 --- a/examples/client-record-format-lpcm/main.go +++ b/examples/client-record-format-lpcm/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/LPCM packets with GStreamer +// 1. generate a LPCM stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce an LPCM format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-mjpeg-from-image/main.go b/examples/client-record-format-mjpeg-from-image/main.go index c22e5ffe..2692724d 100644 --- a/examples/client-record-format-mjpeg-from-image/main.go +++ b/examples/client-record-format-mjpeg-from-image/main.go @@ -17,9 +17,31 @@ import ( // 1. connect to a RTSP server, announce a M-JPEG format // 2. generate an image // 3. encode the image with JPEG -// 4. generate RTP/M-JPEG packets from the JPEG image +// 4. generate RTP packets from the JPEG image // 5. write packets to the server +func createRandomImage(i int) *image.RGBA { + img := image.NewRGBA(image.Rect(0, 0, 640, 480)) + + var cl color.RGBA + switch i { + case 0: + cl = color.RGBA{255, 0, 0, 0} + case 1: + cl = color.RGBA{0, 255, 0, 0} + case 2: + cl = color.RGBA{0, 0, 255, 0} + } + + for y := 0; y < img.Rect.Dy(); y++ { + for x := 0; x < img.Rect.Dx(); x++ { + img.SetRGBA(x, y, cl) + } + } + + return img +} + func main() { // create a description that contains a M-JPEG format forma := &format.MJPEG{} @@ -38,13 +60,13 @@ func main() { } defer c.Close() - // setup JPEG -> RTP/M-JPEG encoder + // setup JPEG -> RTP encoder rtpEnc, err := forma.CreateEncoder() if err != nil { panic(err) } - // setup timestamp generator + // setup RTP timestamp generator rtpTime := &rtptime.Encoder{ClockRate: forma.ClockRate()} err = rtpTime.Initialize() if err != nil { @@ -59,34 +81,18 @@ func main() { i := 0 for range ticker.C { - // create a RGBA image - image := image.NewRGBA(image.Rect(0, 0, 640, 480)) - - // fill the image - var cl color.RGBA - switch i { - case 0: - cl = color.RGBA{255, 0, 0, 0} - case 1: - cl = color.RGBA{0, 255, 0, 0} - case 2: - cl = color.RGBA{0, 0, 255, 0} - } - for y := 0; y < image.Rect.Dy(); y++ { - for x := 0; x < image.Rect.Dx(); x++ { - image.SetRGBA(x, y, cl) - } - } + // create a random image + img := createRandomImage(i) i = (i + 1) % 3 // encode the image with JPEG var buf bytes.Buffer - err := jpeg.Encode(&buf, image, &jpeg.Options{Quality: 80}) + err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 80}) if err != nil { panic(err) } - // generate RTP/M-JPEG packets from the JPEG image + // generate RTP packets from the JPEG image pkts, err := rtpEnc.Encode(buf.Bytes()) if err != nil { panic(err) diff --git a/examples/client-record-format-mjpeg/main.go b/examples/client-record-format-mjpeg/main.go index 80140b2e..67ec2c94 100644 --- a/examples/client-record-format-mjpeg/main.go +++ b/examples/client-record-format-mjpeg/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/M-JPEG packets with GStreamer +// 1. generate a M-JPEG stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce a M-JPEG format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-mpeg4audio/main.go b/examples/client-record-format-mpeg4audio/main.go index 467396f2..bbb1d27e 100644 --- a/examples/client-record-format-mpeg4audio/main.go +++ b/examples/client-record-format-mpeg4audio/main.go @@ -12,7 +12,7 @@ import ( ) // This example shows how to -// 1. generate RTP/MPEG-4 audio packets with GStreamer +// 1. generate a MPEG-4 audio stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce an MPEG-4 audio format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-opus/main.go b/examples/client-record-format-opus/main.go index 6b30ccb8..b79921a6 100644 --- a/examples/client-record-format-opus/main.go +++ b/examples/client-record-format-opus/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/Opus packets with GStreamer +// 1. generate a Opus stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce an Opus format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-vp8/main.go b/examples/client-record-format-vp8/main.go index 35f47a53..6f64bae6 100644 --- a/examples/client-record-format-vp8/main.go +++ b/examples/client-record-format-vp8/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/VP8 packets with GStreamer +// 1. generate a VP8 stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce a VP8 format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-format-vp9/main.go b/examples/client-record-format-vp9/main.go index 1666c850..c21c3636 100644 --- a/examples/client-record-format-vp9/main.go +++ b/examples/client-record-format-vp9/main.go @@ -11,7 +11,7 @@ import ( ) // This example shows how to -// 1. generate RTP/VP9 packets with GStreamer +// 1. generate a VP9 stream and RTP packets with GStreamer // 2. connect to a RTSP server, announce a VP9 format // 3. route the packets from GStreamer to the server diff --git a/examples/client-record-options/main.go b/examples/client-record-options/main.go index 6ec18403..669bc9ca 100644 --- a/examples/client-record-options/main.go +++ b/examples/client-record-options/main.go @@ -13,7 +13,7 @@ import ( // This example shows how to // 1. set additional client options -// 2. generate RTP/H264 frames from a file with GStreamer +// 2. read H264 frames from a file and generate RTP packets with GStreamer // 3. connect to a RTSP server, announce an H264 format // 4. write the frames to the server diff --git a/examples/client-record-pause/main.go b/examples/client-record-pause/main.go index 9d16584b..c948984d 100644 --- a/examples/client-record-pause/main.go +++ b/examples/client-record-pause/main.go @@ -12,7 +12,7 @@ import ( ) // This example shows how to -// 1. generate RTP/H264 frames from a file with GStreamer +// 1. read H264 frames from a file and generate RTP packets with GStreamer // 2. connect to a RTSP server, announce an H264 format // 3. write the frames to the server for 5 seconds // 4. pause for 5 seconds diff --git a/examples/server-h264-save-to-disk/main.go b/examples/server-h264-save-to-disk/main.go index ac663364..f4ba5bec 100644 --- a/examples/server-h264-save-to-disk/main.go +++ b/examples/server-h264-save-to-disk/main.go @@ -76,7 +76,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) ( }, fmt.Errorf("H264 media not found") } - // setup RTP/H264 -> H264 decoder + // setup RTP -> H264 decoder rtpDec, err := forma.CreateDecoder() if err != nil { panic(err)