Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/bluenviron/gortsplib/llms.txt

Use this file to discover all available pages before exploring further.

To publish a stream, you describe the formats you intend to send, connect to the server, and then write RTP packets in a loop.

Record workflow

1

Define a session description

A description.Session declares every media track and its format. The server uses this information during ANNOUNCE to understand what you will send.
forma := &format.H264{
    PayloadTyp:        96,
    PacketizationMode: 1,
}
desc := &description.Session{
    Medias: []*description.Media{{
        Type:    description.MediaTypeVideo,
        Formats: []format.Format{forma},
    }},
}
2

Start recording

StartRecording is a one-call shorthand that internally runs:
  1. c.Scheme = u.Scheme; c.Host = u.Host
  2. c.Start()
  3. c.Announce(u, desc)
  4. c.SetupAll(u, desc.Medias)
  5. c.Record()
c := gortsplib.Client{}
err := c.StartRecording("rtsp://myuser:mypass@localhost:8554/mystream", desc)
if err != nil {
    panic(err)
}
defer c.Close()
If you need finer control — for example, to inspect the ANNOUNCE response before proceeding — call Start, Announce, SetupAll, and Record individually instead of using the shorthand.
3

Write RTP packets

Call WritePacketRTP to push a packet to the server. Pass the *description.Media that the packet belongs to.
err = c.WritePacketRTP(desc.Medias[0], pkt)
if err != nil {
    panic(err)
}

Complete H264 example

This example announces an H264 format and sends encoded frames at 5 FPS. The encoder uses CGO (FFmpeg); the RTP layer is pure Go.
This file carries a //go:build cgo tag and requires the FFmpeg development libraries:
apt install -y libavcodec-dev libswscale-dev gcc pkg-config
client-record-format-h264/main.go
//go:build cgo

// Package main contains an example.
package main

import (
	"crypto/rand"
	"log"
	"time"

	"github.com/bluenviron/gortsplib/v5"
	"github.com/bluenviron/gortsplib/v5/pkg/description"
	"github.com/bluenviron/gortsplib/v5/pkg/format"
)

// This example shows how to:
// 1. connect to a RTSP server, announce an H264 format.
// 2. generate dummy RGBA images.
// 3. encode images with H264.
// 4. generate RTP packets from H264.
// 5. write RTP packets to the server.

func multiplyAndDivide(v, m, d int64) int64 {
	secs := v / d
	dec := v % d
	return (secs*m + dec*m/d)
}

func randUint32() (uint32, error) {
	var b [4]byte
	_, err := rand.Read(b[:])
	if err != nil {
		return 0, err
	}
	return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
}

func main() {
	// create a stream 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://myuser:mypass@localhost:8554/mystream", desc)
	if err != nil {
		panic(err)
	}
	defer c.Close()

	// setup RGBA -> H264 encoder
	h264enc := &h264Encoder{
		Width:  640,
		Height: 480,
		FPS:    5,
	}
	err = h264enc.initialize()
	if err != nil {
		panic(err)
	}
	defer h264enc.close()

	// setup H264 -> RTP encoder
	rtpEnc, err := forma.CreateEncoder()
	if err != nil {
		panic(err)
	}

	start := time.Now()

	randomStart, err := randUint32()
	if err != nil {
		panic(err)
	}

	// setup a ticker to sleep between frames
	ticker := time.NewTicker(200 * time.Millisecond)
	defer ticker.Stop()

	for range ticker.C {
		// get current timestamp
		pts := multiplyAndDivide(int64(time.Since(start)), int64(forma.ClockRate()), int64(time.Second))

		// create a dummy image
		img := createDummyImage()

		// encode the image with H264
		au, pts, err := h264enc.encode(img, pts)
		if err != nil {
			panic(err)
		}

		// wait for a H264 access unit
		if au == nil {
			continue
		}

		// generate RTP packets from the H264 access unit
		pkts, err := rtpEnc.Encode(au)
		if err != nil {
			panic(err)
		}

		log.Printf("writing RTP packets with PTS=%d, au size=%d, pkt count=%d", pts, len(au), len(pkts))

		// write RTP packets to the server
		for _, pkt := range pkts {
			pkt.Timestamp += uint32(int64(randomStart) + pts)

			err = c.WritePacketRTP(desc.Medias[0], pkt)
			if err != nil {
				panic(err)
			}
		}
	}
}

Setting client options for recording

You can configure timeouts and transport before calling StartRecording by initializing the Client struct directly.
client-record-options/main.go
// Client allows to set additional client options
c := &gortsplib.Client{
    // transport protocol (UDP or TCP). If nil, it is chosen automatically
    Protocol: nil,
    // timeout of read operations
    ReadTimeout: 10 * time.Second,
    // timeout of write operations
    WriteTimeout: 10 * time.Second,
}

// connect to the server, announce the format and start recording
err := c.StartRecording("rtsp://myuser:mypass@localhost:8554/mystream", desc)
if err != nil {
    panic(err)
}
defer c.Close()

Pause and resume

Call c.Pause() to send a PAUSE request that suspends transmission without disconnecting. Call c.Record() to resume.
client-record-pause/main.go
for {
    // wait
    time.Sleep(5 * time.Second)

    log.Println("pausing")

    // pause
    _, err := c.Pause()
    if err != nil {
        panic(err)
    }

    // wait
    time.Sleep(5 * time.Second)

    log.Println("recording")

    // record again
    _, err = c.Record()
    if err != nil {
        panic(err)
    }
}
Packet writes to the server while in the paused state will return an error. Ensure your write goroutine handles or gates on the paused state.

Play to record (re-publish)

You can relay a stream from one path to another by combining a reader client and a publisher client. Set up the reader first — including SetupAll — before calling StartRecording on the publisher, because StartRecording resets the control attribute of the medias.
client-play-to-record/main.go
// Package main contains an example.
package main

import (
	"log"

	"github.com/bluenviron/gortsplib/v5"
	"github.com/bluenviron/gortsplib/v5/pkg/base"
	"github.com/bluenviron/gortsplib/v5/pkg/description"
	"github.com/bluenviron/gortsplib/v5/pkg/format"
	"github.com/pion/rtp"
)

// This example shows how to:
// 1. connect to a RTSP server.
// 2. read all medias on a path.
// 3. re-publish all medias on another path.

func main() {
	// parse source URL
	sourceURL, err := base.ParseURL("rtsp://myuser:mypass@localhost:8554/mystream")
	if err != nil {
		panic(err)
	}

	reader := gortsplib.Client{
		Scheme: sourceURL.Scheme,
		Host:   sourceURL.Host,
	}

	// connect to the server
	err = reader.Start()
	if err != nil {
		panic(err)
	}
	defer reader.Close()

	// find available medias
	desc, _, err := reader.Describe(sourceURL)
	if err != nil {
		panic(err)
	}

	log.Printf("republishing %d medias", len(desc.Medias))

	// setup all medias
	// this must be called before StartRecording(), since it overrides the control attribute.
	err = reader.SetupAll(desc.BaseURL, desc.Medias)
	if err != nil {
		panic(err)
	}

	// connect to the server and start recording the same medias
	publisher := gortsplib.Client{}
	err = publisher.StartRecording("rtsp://myuser:mypass@localhost:8554/mystream2", desc)
	if err != nil {
		panic(err)
	}
	defer publisher.Close()

	// read RTP packets from the reader and route them to the publisher
	reader.OnPacketRTPAny(func(media *description.Media, _ format.Format, pkt *rtp.Packet) {
		err2 := publisher.WritePacketRTP(media, pkt)
		if err2 != nil {
			log.Printf("ERR: %v", err2)
		}
	})

	// start playing
	_, err = reader.Play(nil)
	if err != nil {
		panic(err)
	}

	// wait until a fatal error
	panic(reader.Wait())
}
Always call reader.SetupAll before publisher.StartRecording. StartRecording mutates the Control attribute of each description.Media in-place; calling it before SetupAll would break the reader’s SETUP URLs.

Build docs developers (and LLMs) love