Skip to content

Commit e46ae59

Browse files
authored
add server-h264-from-disk example (#719) (#727)
1 parent 8c6495c commit e46ae59

File tree

10 files changed

+278
-35
lines changed

10 files changed

+278
-35
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Features:
9696
* [server-tls](examples/server-tls/main.go)
9797
* [server-auth](examples/server-auth/main.go)
9898
* [server-h264-to-disk](examples/server-h264-to-disk/main.go)
99+
* [server-h264-from-disk](examples/server-h264-from-disk/main.go)
99100
* [proxy](examples/proxy/main.go)
100101

101102
## API Documentation

examples/client-record-format-h264-from-disk/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func main() {
137137
return nil
138138
})
139139

140-
// start reading the MPEG-TS file
140+
// read the MPEG-TS file
141141
for {
142142
err := r.Read()
143143
if err != nil {

examples/proxy/client.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const (
1717
)
1818

1919
type client struct {
20-
s *server
20+
server *server
2121
}
2222

2323
func (c *client) initialize() {
@@ -62,8 +62,8 @@ func (c *client) read() error {
6262
return err
6363
}
6464

65-
stream := c.s.setStreamReady(desc)
66-
defer c.s.setStreamUnready()
65+
stream := c.server.setStreamReady(desc)
66+
defer c.server.setStreamUnready()
6767

6868
log.Printf("stream is ready and can be read from the server at rtsp://localhost:8554/stream\n")
6969

examples/proxy/main.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ func main() {
1414

1515
// allocate the client.
1616
// give client access to the server.
17-
c := &client{s: s}
17+
c := &client{server: s}
1818
c.initialize()
1919

2020
// start server and wait until a fatal error
21-
log.Printf("server is ready")
22-
s.s.StartAndWait()
21+
log.Printf("server is ready on %s", s.server.RTSPAddress)
22+
s.server.StartAndWait()
2323
}

examples/proxy/server.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import (
1010
)
1111

1212
type server struct {
13-
s *gortsplib.Server
13+
server *gortsplib.Server
1414
mutex sync.RWMutex
1515
stream *gortsplib.ServerStream
1616
}
1717

1818
func (s *server) initialize() {
1919
// configure the server
20-
s.s = &gortsplib.Server{
20+
s.server = &gortsplib.Server{
2121
Handler: s,
2222
RTSPAddress: ":8554",
2323
UDPRTPAddress: ":8000",
@@ -99,7 +99,7 @@ func (s *server) setStreamReady(desc *description.Session) *gortsplib.ServerStre
9999
s.mutex.Lock()
100100
defer s.mutex.Unlock()
101101
s.stream = &gortsplib.ServerStream{
102-
Server: s.s,
102+
Server: s.server,
103103
Desc: desc,
104104
}
105105
err := s.stream.Initialize()

examples/server-auth/main.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const (
2929
)
3030

3131
type serverHandler struct {
32-
s *gortsplib.Server
32+
server *gortsplib.Server
3333
mutex sync.RWMutex
3434
stream *gortsplib.ServerStream
3535
publisher *gortsplib.ServerSession
@@ -54,8 +54,8 @@ func (sh *serverHandler) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpen
5454
func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
5555
log.Printf("session closed")
5656

57-
sh.mutex.Lock()
58-
defer sh.mutex.Unlock()
57+
sh.mutex.RLock()
58+
defer sh.mutex.RUnlock()
5959

6060
// if the session is the publisher,
6161
// close the stream and disconnect any reader.
@@ -118,7 +118,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
118118

119119
// create the stream and save the publisher
120120
sh.stream = &gortsplib.ServerStream{
121-
Server: sh.s,
121+
Server: sh.server,
122122
Desc: ctx.Description,
123123
}
124124
err := sh.stream.Initialize()
@@ -194,7 +194,7 @@ func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*bas
194194
func main() {
195195
// configure the server
196196
h := &serverHandler{}
197-
h.s = &gortsplib.Server{
197+
h.server = &gortsplib.Server{
198198
Handler: h,
199199
RTSPAddress: ":8554",
200200
UDPRTPAddress: ":8000",
@@ -205,6 +205,6 @@ func main() {
205205
}
206206

207207
// start server and wait until a fatal error
208-
log.Printf("server is ready")
209-
panic(h.s.StartAndWait())
208+
log.Printf("server is ready on %s", h.server.RTSPAddress)
209+
panic(h.server.StartAndWait())
210210
}
+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package main
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
"log"
7+
"os"
8+
"sync"
9+
"time"
10+
11+
"github.com/bluenviron/gortsplib/v4"
12+
"github.com/bluenviron/gortsplib/v4/pkg/base"
13+
"github.com/bluenviron/gortsplib/v4/pkg/description"
14+
"github.com/bluenviron/gortsplib/v4/pkg/format"
15+
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
16+
)
17+
18+
// This example shows how to
19+
// 1. create a RTSP server which accepts plain connections
20+
// 2. read from disk a MPEG-TS file which contains a H264 track
21+
// 3. serve the content of the file to connected readers
22+
23+
func findTrack(r *mpegts.Reader) (*mpegts.Track, error) {
24+
for _, track := range r.Tracks() {
25+
if _, ok := track.Codec.(*mpegts.CodecH264); ok {
26+
return track, nil
27+
}
28+
}
29+
return nil, fmt.Errorf("H264 track not found")
30+
}
31+
32+
func randUint32() (uint32, error) {
33+
var b [4]byte
34+
_, err := rand.Read(b[:])
35+
if err != nil {
36+
return 0, err
37+
}
38+
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
39+
}
40+
41+
type serverHandler struct {
42+
server *gortsplib.Server
43+
stream *gortsplib.ServerStream
44+
mutex sync.RWMutex
45+
}
46+
47+
// called when a connection is opened.
48+
func (sh *serverHandler) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
49+
log.Printf("conn opened")
50+
}
51+
52+
// called when a connection is closed.
53+
func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
54+
log.Printf("conn closed (%v)", ctx.Error)
55+
}
56+
57+
// called when a session is opened.
58+
func (sh *serverHandler) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
59+
log.Printf("session opened")
60+
}
61+
62+
// called when a session is closed.
63+
func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
64+
log.Printf("session closed")
65+
}
66+
67+
// called when receiving a DESCRIBE request.
68+
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
69+
log.Printf("describe request")
70+
71+
sh.mutex.RLock()
72+
defer sh.mutex.RUnlock()
73+
74+
return &base.Response{
75+
StatusCode: base.StatusOK,
76+
}, sh.stream, nil
77+
}
78+
79+
// called when receiving a SETUP request.
80+
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
81+
log.Printf("setup request")
82+
83+
sh.mutex.RLock()
84+
defer sh.mutex.RUnlock()
85+
86+
return &base.Response{
87+
StatusCode: base.StatusOK,
88+
}, sh.stream, nil
89+
}
90+
91+
// called when receiving a PLAY request.
92+
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
93+
log.Printf("play request")
94+
95+
return &base.Response{
96+
StatusCode: base.StatusOK,
97+
}, nil
98+
}
99+
100+
func readVideoFile(stream *gortsplib.ServerStream, media *description.Media, forma *format.H264) {
101+
// open a file in MPEG-TS format
102+
f, err := os.Open("myvideo.ts")
103+
if err != nil {
104+
panic(err)
105+
}
106+
defer f.Close()
107+
108+
// setup MPEG-TS parser
109+
r := &mpegts.Reader{R: f}
110+
err = r.Initialize()
111+
if err != nil {
112+
panic(err)
113+
}
114+
115+
// find the H264 track inside the file
116+
track, err := findTrack(r)
117+
if err != nil {
118+
panic(err)
119+
}
120+
121+
// setup H264 -> RTP encoder
122+
rtpEnc, err := forma.CreateEncoder()
123+
if err != nil {
124+
panic(err)
125+
}
126+
127+
randomStart, err := randUint32()
128+
if err != nil {
129+
panic(err)
130+
}
131+
132+
timeDecoder := mpegts.TimeDecoder{}
133+
timeDecoder.Initialize()
134+
135+
var firstDTS *int64
136+
var startTime time.Time
137+
138+
// setup a callback that is called whenever a H264 access unit is read from the file
139+
r.OnDataH264(track, func(pts, dts int64, au [][]byte) error {
140+
dts = timeDecoder.Decode(dts)
141+
pts = timeDecoder.Decode(pts)
142+
143+
// sleep between access units
144+
if firstDTS != nil {
145+
timeDrift := time.Duration(dts-*firstDTS)*time.Second/90000 - time.Since(startTime)
146+
if timeDrift > 0 {
147+
time.Sleep(timeDrift)
148+
}
149+
} else {
150+
startTime = time.Now()
151+
firstDTS = &dts
152+
}
153+
154+
log.Printf("writing access unit with pts=%d dts=%d", pts, dts)
155+
156+
// wrap the access unit into RTP packets
157+
packets, err := rtpEnc.Encode(au)
158+
if err != nil {
159+
return err
160+
}
161+
162+
// set packet timestamp
163+
// we don't have to perform any conversion
164+
// since H264 clock rate is the same in both MPEG-TS and RTSP
165+
for _, packet := range packets {
166+
packet.Timestamp = uint32(int64(randomStart) + pts)
167+
}
168+
169+
// write RTP packets to the server
170+
for _, packet := range packets {
171+
err := stream.WritePacketRTP(media, packet)
172+
if err != nil {
173+
return err
174+
}
175+
}
176+
177+
return nil
178+
})
179+
180+
for {
181+
err := r.Read()
182+
if err != nil {
183+
panic(err)
184+
}
185+
}
186+
}
187+
188+
func main() {
189+
h := &serverHandler{}
190+
191+
// prevent clients from connecting to the server until the stream is properly set up
192+
h.mutex.Lock()
193+
194+
// create the server
195+
h.server = &gortsplib.Server{
196+
Handler: h,
197+
RTSPAddress: ":8554",
198+
UDPRTPAddress: ":8000",
199+
UDPRTCPAddress: ":8001",
200+
MulticastIPRange: "224.1.0.0/16",
201+
MulticastRTPPort: 8002,
202+
MulticastRTCPPort: 8003,
203+
}
204+
205+
// start the server
206+
err := h.server.Start()
207+
if err != nil {
208+
panic(err)
209+
}
210+
211+
// create a RTSP description that contains a H264 format
212+
forma := &format.H264{
213+
PayloadTyp: 96,
214+
PacketizationMode: 1,
215+
}
216+
desc := &description.Session{
217+
Medias: []*description.Media{{
218+
Type: description.MediaTypeVideo,
219+
Formats: []format.Format{forma},
220+
}},
221+
}
222+
223+
// create a server stream
224+
h.stream = &gortsplib.ServerStream{
225+
Server: h.server,
226+
Desc: desc,
227+
}
228+
err = h.stream.Initialize()
229+
if err != nil {
230+
panic(err)
231+
}
232+
233+
// in a separate routine, read the MPEG-TS file
234+
go readVideoFile(h.stream, desc.Medias[0], forma)
235+
236+
// allow clients to connect
237+
h.mutex.Unlock()
238+
239+
// wait until a fatal error
240+
log.Printf("server is ready on %s", h.server.RTSPAddress)
241+
panic(h.server.Wait())
242+
}

examples/server-h264-to-disk/main.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
// 3. save the content of the H264 media in a file in MPEG-TS format
2121

2222
type serverHandler struct {
23-
s *gortsplib.Server
23+
server *gortsplib.Server
2424
mutex sync.Mutex
2525
publisher *gortsplib.ServerSession
2626
media *description.Media
@@ -151,7 +151,7 @@ func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*bas
151151
func main() {
152152
// configure the server
153153
h := &serverHandler{}
154-
h.s = &gortsplib.Server{
154+
h.server = &gortsplib.Server{
155155
Handler: h,
156156
RTSPAddress: ":8554",
157157
UDPRTPAddress: ":8000",
@@ -162,6 +162,6 @@ func main() {
162162
}
163163

164164
// start server and wait until a fatal error
165-
log.Printf("server is ready")
166-
panic(h.s.StartAndWait())
165+
log.Printf("server is ready on %s", h.server.RTSPAddress)
166+
panic(h.server.StartAndWait())
167167
}

0 commit comments

Comments
 (0)