Files
gortsplib/examples/server-h264-from-disk/main.go
2025-03-23 19:04:12 +01:00

267 lines
6.0 KiB
Go

package main
import (
"crypto/rand"
"errors"
"fmt"
"io"
"log"
"os"
"sync"
"time"
"github.com/asticode/go-astits"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
)
// This example shows how to
// 1. create a RTSP server which accepts plain connections
// 2. read from disk a MPEG-TS file which contains a H264 track
// 3. serve the content of the file to connected readers
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 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 routeFrames(f *os.File, stream *gortsplib.ServerStream) {
// setup H264 -> RTP encoder
rtpEnc, err := stream.Desc.Medias[0].Formats[0].(*format.H264).CreateEncoder()
if err != nil {
panic(err)
}
randomStart, err := randUint32()
if err != nil {
panic(err)
}
for {
// setup MPEG-TS parser
r := &mpegts.Reader{R: f}
err = r.Initialize()
if err != nil {
panic(err)
}
// find the H264 track inside the file
track, err := findTrack(r)
if err != nil {
panic(err)
}
timeDecoder := mpegts.TimeDecoder{}
timeDecoder.Initialize()
var firstDTS *int64
var firstTime time.Time
var lastRTPTime uint32
// setup a callback that is called when a H264 access unit is read from the file
r.OnDataH264(track, func(pts, dts int64, au [][]byte) error {
dts = timeDecoder.Decode(dts)
pts = timeDecoder.Decode(pts)
// sleep between access units
if firstDTS != nil {
timeDrift := time.Duration(dts-*firstDTS)*time.Second/90000 - time.Since(firstTime)
if timeDrift > 0 {
time.Sleep(timeDrift)
}
} else {
firstTime = 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 packet timestamp
// we don't have to perform any conversion
// since H264 clock rate is the same in both MPEG-TS and RTSP
lastRTPTime = uint32(int64(randomStart) + pts)
for _, packet := range packets {
packet.Timestamp = lastRTPTime
}
// write RTP packets to the server
for _, packet := range packets {
err := stream.WritePacketRTP(stream.Desc.Medias[0], packet)
if err != nil {
return err
}
}
return nil
})
// read the file
for {
err := r.Read()
if err != nil {
// file has ended
if errors.Is(err, astits.ErrNoMorePackets) {
log.Printf("file has ended, rewinding")
// rewind to start position
_, err = f.Seek(0, io.SeekStart)
if err != nil {
panic(err)
}
// keep current timestamp
randomStart = lastRTPTime + 1
break
}
panic(err)
}
}
}
}
type serverHandler struct {
server *gortsplib.Server
stream *gortsplib.ServerStream
mutex sync.RWMutex
}
// called when a connection is opened.
func (sh *serverHandler) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
log.Printf("conn opened")
}
// called when a connection is closed.
func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
log.Printf("conn closed (%v)", ctx.Error)
}
// called when a session is opened.
func (sh *serverHandler) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
log.Printf("session opened")
}
// called when a session is closed.
func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
log.Printf("session closed")
}
// called when receiving a DESCRIBE request.
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("DESCRIBE request")
sh.mutex.RLock()
defer sh.mutex.RUnlock()
return &base.Response{
StatusCode: base.StatusOK,
}, sh.stream, nil
}
// called when receiving a SETUP request.
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("SETUP request")
sh.mutex.RLock()
defer sh.mutex.RUnlock()
return &base.Response{
StatusCode: base.StatusOK,
}, sh.stream, nil
}
// called when receiving a PLAY request.
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
log.Printf("PLAY request")
return &base.Response{
StatusCode: base.StatusOK,
}, nil
}
func main() {
h := &serverHandler{}
// prevent clients from connecting to the server until the stream is properly set up
h.mutex.Lock()
// create the server
h.server = &gortsplib.Server{
Handler: h,
RTSPAddress: ":8554",
UDPRTPAddress: ":8000",
UDPRTCPAddress: ":8001",
MulticastIPRange: "224.1.0.0/16",
MulticastRTPPort: 8002,
MulticastRTCPPort: 8003,
}
// start the server
err := h.server.Start()
if err != nil {
panic(err)
}
defer h.server.Close()
// create a RTSP description that contains a H264 format
desc := &description.Session{
Medias: []*description.Media{{
Type: description.MediaTypeVideo,
Formats: []format.Format{&format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}},
}},
}
// create a server stream
h.stream = &gortsplib.ServerStream{
Server: h.server,
Desc: desc,
}
err = h.stream.Initialize()
if err != nil {
panic(err)
}
defer h.stream.Close()
// open a file in MPEG-TS format
f, err := os.Open("myvideo.ts")
if err != nil {
panic(err)
}
defer f.Close()
// in a separate routine, route frames from file to ServerStream
go routeFrames(f, h.stream)
// allow clients to connect
h.mutex.Unlock()
// wait until a fatal error
log.Printf("server is ready on %s", h.server.RTSPAddress)
panic(h.server.Wait())
}