> ## 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.

# Recording streams

> Publish RTP media streams to an RTSP server.

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

<Steps>
  <Step title="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.

    ```go theme={null}
    forma := &format.H264{
        PayloadTyp:        96,
        PacketizationMode: 1,
    }
    desc := &description.Session{
        Medias: []*description.Media{{
            Type:    description.MediaTypeVideo,
            Formats: []format.Format{forma},
        }},
    }
    ```
  </Step>

  <Step title="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()`

    ```go theme={null}
    c := gortsplib.Client{}
    err := c.StartRecording("rtsp://myuser:mypass@localhost:8554/mystream", desc)
    if err != nil {
        panic(err)
    }
    defer c.Close()
    ```

    <Info>
      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.
    </Info>
  </Step>

  <Step title="Write RTP packets">
    Call `WritePacketRTP` to push a packet to the server. Pass the `*description.Media` that the packet belongs to.

    ```go theme={null}
    err = c.WritePacketRTP(desc.Medias[0], pkt)
    if err != nil {
        panic(err)
    }
    ```
  </Step>
</Steps>

## 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.

<Note>
  This file carries a `//go:build cgo` tag and requires the FFmpeg development libraries:

  ```bash theme={null}
  apt install -y libavcodec-dev libswscale-dev gcc pkg-config
  ```
</Note>

```go client-record-format-h264/main.go theme={null}
//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.

```go client-record-options/main.go theme={null}
// 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.

```go client-record-pause/main.go theme={null}
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)
    }
}
```

<Note>
  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.
</Note>

## 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.

```go client-play-to-record/main.go theme={null}
// 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())
}
```

<Warning>
  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.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.