mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 07:06:58 +08:00
examples: restart video files when they end (#733)
This commit is contained in:
@@ -2,11 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/asticode/go-astits"
|
||||||
"github.com/bluenviron/gortsplib/v4"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
@@ -38,26 +41,6 @@ func randUint32() (uint32, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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 := &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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a RTSP description that contains a H264 format
|
// create a RTSP description that contains a H264 format
|
||||||
forma := &format.H264{
|
forma := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
@@ -72,12 +55,19 @@ func main() {
|
|||||||
|
|
||||||
// connect to the server, announce the format and start recording
|
// connect to the server, announce the format and start recording
|
||||||
c := gortsplib.Client{}
|
c := gortsplib.Client{}
|
||||||
err = c.StartRecording("rtsp://myuser:mypass@localhost:8554/mystream", desc)
|
err := c.StartRecording("rtsp://myuser:mypass@localhost:8554/mystream", desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
// open a file in MPEG-TS format
|
||||||
|
f, err := os.Open("myvideo.ts")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
// setup H264 -> RTP encoder
|
// setup H264 -> RTP encoder
|
||||||
rtpEnc, err := forma.CreateEncoder()
|
rtpEnc, err := forma.CreateEncoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -89,13 +79,28 @@ func main() {
|
|||||||
panic(err)
|
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 := mpegts.TimeDecoder{}
|
||||||
timeDecoder.Initialize()
|
timeDecoder.Initialize()
|
||||||
|
|
||||||
var firstDTS *int64
|
var firstDTS *int64
|
||||||
var startTime time.Time
|
var startTime time.Time
|
||||||
|
var lastRTPTime uint32
|
||||||
|
|
||||||
// setup a callback that is called whenever a H264 access unit is read from the file
|
// 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 {
|
r.OnDataH264(track, func(pts, dts int64, au [][]byte) error {
|
||||||
dts = timeDecoder.Decode(dts)
|
dts = timeDecoder.Decode(dts)
|
||||||
pts = timeDecoder.Decode(pts)
|
pts = timeDecoder.Decode(pts)
|
||||||
@@ -122,8 +127,9 @@ func main() {
|
|||||||
// set packet timestamp
|
// set packet timestamp
|
||||||
// we don't have to perform any conversion
|
// we don't have to perform any conversion
|
||||||
// since H264 clock rate is the same in both MPEG-TS and RTSP
|
// since H264 clock rate is the same in both MPEG-TS and RTSP
|
||||||
|
lastRTPTime = uint32(int64(randomStart) + pts)
|
||||||
for _, packet := range packets {
|
for _, packet := range packets {
|
||||||
packet.Timestamp = uint32(int64(randomStart) + pts)
|
packet.Timestamp = lastRTPTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// write RTP packets to the server
|
// write RTP packets to the server
|
||||||
@@ -137,11 +143,28 @@ func main() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
// read the MPEG-TS file
|
// read the file
|
||||||
for {
|
for {
|
||||||
err := r.Read()
|
err := r.Read()
|
||||||
if err != nil {
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,7 +50,7 @@ func (s *server) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
|
|||||||
|
|
||||||
// called when receiving a DESCRIBE request.
|
// called when receiving a DESCRIBE request.
|
||||||
func (s *server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (s *server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("describe request")
|
log.Printf("DESCRIBE request")
|
||||||
|
|
||||||
s.mutex.RLock()
|
s.mutex.RLock()
|
||||||
defer s.mutex.RUnlock()
|
defer s.mutex.RUnlock()
|
||||||
@@ -69,7 +69,7 @@ func (s *server) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Re
|
|||||||
|
|
||||||
// called when receiving a SETUP request.
|
// called when receiving a SETUP request.
|
||||||
func (s *server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (s *server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("setup request")
|
log.Printf("SETUP request")
|
||||||
|
|
||||||
s.mutex.RLock()
|
s.mutex.RLock()
|
||||||
defer s.mutex.RUnlock()
|
defer s.mutex.RUnlock()
|
||||||
@@ -88,7 +88,7 @@ func (s *server) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response
|
|||||||
|
|
||||||
// called when receiving a PLAY request.
|
// called when receiving a PLAY request.
|
||||||
func (s *server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
func (s *server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||||
log.Printf("play request")
|
log.Printf("PLAY request")
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
|
@@ -67,7 +67,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
|
|||||||
|
|
||||||
// called when receiving a DESCRIBE request.
|
// called when receiving a DESCRIBE request.
|
||||||
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("describe request")
|
log.Printf("DESCRIBE request")
|
||||||
|
|
||||||
// Verify reader credentials.
|
// Verify reader credentials.
|
||||||
// In case of readers, credentials have to be verified during DESCRIBE and SETUP.
|
// In case of readers, credentials have to be verified during DESCRIBE and SETUP.
|
||||||
@@ -96,7 +96,7 @@ func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (
|
|||||||
|
|
||||||
// called when receiving an ANNOUNCE request.
|
// called when receiving an ANNOUNCE request.
|
||||||
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
||||||
log.Printf("announce request")
|
log.Printf("ANNOUNCE request")
|
||||||
|
|
||||||
// Verify publisher credentials.
|
// Verify publisher credentials.
|
||||||
// In case of publishers, credentials have to be verified during ANNOUNCE.
|
// In case of publishers, credentials have to be verified during ANNOUNCE.
|
||||||
@@ -134,7 +134,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
|
|||||||
|
|
||||||
// called when receiving a SETUP request.
|
// called when receiving a SETUP request.
|
||||||
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("setup request")
|
log.Printf("SETUP request")
|
||||||
|
|
||||||
// SETUP is used by both readers and publishers. In case of publishers, just return StatusOK.
|
// SETUP is used by both readers and publishers. In case of publishers, just return StatusOK.
|
||||||
if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
|
if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
|
||||||
@@ -169,7 +169,7 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
|
|||||||
|
|
||||||
// called when receiving a PLAY request.
|
// called when receiving a PLAY request.
|
||||||
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||||
log.Printf("play request")
|
log.Printf("PLAY request")
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -178,7 +178,7 @@ func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Re
|
|||||||
|
|
||||||
// called when receiving a RECORD request.
|
// called when receiving a RECORD request.
|
||||||
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||||
log.Printf("record request")
|
log.Printf("RECORD request")
|
||||||
|
|
||||||
// called when receiving a RTP packet
|
// called when receiving a RTP packet
|
||||||
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
||||||
|
@@ -2,12 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/asticode/go-astits"
|
||||||
"github.com/bluenviron/gortsplib/v4"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
@@ -38,6 +41,107 @@ func randUint32() (uint32, error) {
|
|||||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
|
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 {
|
type serverHandler struct {
|
||||||
server *gortsplib.Server
|
server *gortsplib.Server
|
||||||
stream *gortsplib.ServerStream
|
stream *gortsplib.ServerStream
|
||||||
@@ -66,7 +170,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
|
|||||||
|
|
||||||
// called when receiving a DESCRIBE request.
|
// called when receiving a DESCRIBE request.
|
||||||
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("describe request")
|
log.Printf("DESCRIBE request")
|
||||||
|
|
||||||
sh.mutex.RLock()
|
sh.mutex.RLock()
|
||||||
defer sh.mutex.RUnlock()
|
defer sh.mutex.RUnlock()
|
||||||
@@ -78,7 +182,7 @@ func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (
|
|||||||
|
|
||||||
// called when receiving a SETUP request.
|
// called when receiving a SETUP request.
|
||||||
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("setup request")
|
log.Printf("SETUP request")
|
||||||
|
|
||||||
sh.mutex.RLock()
|
sh.mutex.RLock()
|
||||||
defer sh.mutex.RUnlock()
|
defer sh.mutex.RUnlock()
|
||||||
@@ -90,101 +194,13 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
|
|||||||
|
|
||||||
// called when receiving a PLAY request.
|
// called when receiving a PLAY request.
|
||||||
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||||
log.Printf("play request")
|
log.Printf("PLAY request")
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readVideoFile(stream *gortsplib.ServerStream, media *description.Media, forma *format.H264) {
|
|
||||||
// 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 := &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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup H264 -> RTP encoder
|
|
||||||
rtpEnc, err := forma.CreateEncoder()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
randomStart, err := randUint32()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeDecoder := mpegts.TimeDecoder{}
|
|
||||||
timeDecoder.Initialize()
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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(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 packet timestamp
|
|
||||||
// we don't have to perform any conversion
|
|
||||||
// since H264 clock rate is the same in both MPEG-TS and RTSP
|
|
||||||
for _, packet := range packets {
|
|
||||||
packet.Timestamp = uint32(int64(randomStart) + pts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write RTP packets to the server
|
|
||||||
for _, packet := range packets {
|
|
||||||
err := stream.WritePacketRTP(media, packet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := r.Read()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
h := &serverHandler{}
|
h := &serverHandler{}
|
||||||
|
|
||||||
@@ -207,16 +223,16 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
defer h.server.Close()
|
||||||
|
|
||||||
// create a RTSP description that contains a H264 format
|
// create a RTSP description that contains a H264 format
|
||||||
forma := &format.H264{
|
|
||||||
PayloadTyp: 96,
|
|
||||||
PacketizationMode: 1,
|
|
||||||
}
|
|
||||||
desc := &description.Session{
|
desc := &description.Session{
|
||||||
Medias: []*description.Media{{
|
Medias: []*description.Media{{
|
||||||
Type: description.MediaTypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []format.Format{forma},
|
Formats: []format.Format{&format.H264{
|
||||||
|
PayloadTyp: 96,
|
||||||
|
PacketizationMode: 1,
|
||||||
|
}},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,9 +245,17 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
defer h.stream.Close()
|
||||||
|
|
||||||
// in a separate routine, read the MPEG-TS file
|
// open a file in MPEG-TS format
|
||||||
go readVideoFile(h.stream, desc.Medias[0], forma)
|
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
|
// allow clients to connect
|
||||||
h.mutex.Unlock()
|
h.mutex.Unlock()
|
||||||
|
@@ -57,7 +57,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
|
|||||||
|
|
||||||
// called when receiving an ANNOUNCE request.
|
// called when receiving an ANNOUNCE request.
|
||||||
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
||||||
log.Printf("announce request")
|
log.Printf("ANNOUNCE request")
|
||||||
|
|
||||||
sh.mutex.Lock()
|
sh.mutex.Lock()
|
||||||
defer sh.mutex.Unlock()
|
defer sh.mutex.Unlock()
|
||||||
@@ -115,7 +115,7 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
|
|||||||
}, nil, nil
|
}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("setup request")
|
log.Printf("SETUP request")
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -124,7 +124,7 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
|
|||||||
|
|
||||||
// called when receiving a RECORD request.
|
// called when receiving a RECORD request.
|
||||||
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||||
log.Printf("record request")
|
log.Printf("RECORD request")
|
||||||
|
|
||||||
// called when receiving a RTP packet
|
// called when receiving a RTP packet
|
||||||
ctx.Session.OnPacketRTP(sh.media, sh.format, func(pkt *rtp.Packet) {
|
ctx.Session.OnPacketRTP(sh.media, sh.format, func(pkt *rtp.Packet) {
|
||||||
|
@@ -57,7 +57,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
|
|||||||
|
|
||||||
// called when receiving a DESCRIBE request.
|
// called when receiving a DESCRIBE request.
|
||||||
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("describe request")
|
log.Printf("DESCRIBE request")
|
||||||
|
|
||||||
sh.mutex.RLock()
|
sh.mutex.RLock()
|
||||||
defer sh.mutex.RUnlock()
|
defer sh.mutex.RUnlock()
|
||||||
@@ -77,7 +77,7 @@ func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (
|
|||||||
|
|
||||||
// called when receiving an ANNOUNCE request.
|
// called when receiving an ANNOUNCE request.
|
||||||
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
||||||
log.Printf("announce request")
|
log.Printf("ANNOUNCE request")
|
||||||
|
|
||||||
sh.mutex.RLock()
|
sh.mutex.RLock()
|
||||||
defer sh.mutex.RUnlock()
|
defer sh.mutex.RUnlock()
|
||||||
@@ -106,7 +106,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
|
|||||||
|
|
||||||
// called when receiving a SETUP request.
|
// called when receiving a SETUP request.
|
||||||
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("setup request")
|
log.Printf("SETUP request")
|
||||||
|
|
||||||
// SETUP is used by both readers and publishers. In case of publishers, just return StatusOK.
|
// SETUP is used by both readers and publishers. In case of publishers, just return StatusOK.
|
||||||
if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
|
if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
|
||||||
@@ -132,7 +132,7 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
|
|||||||
|
|
||||||
// called when receiving a PLAY request.
|
// called when receiving a PLAY request.
|
||||||
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||||
log.Printf("play request")
|
log.Printf("PLAY request")
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -141,7 +141,7 @@ func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Re
|
|||||||
|
|
||||||
// called when receiving a RECORD request.
|
// called when receiving a RECORD request.
|
||||||
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||||
log.Printf("record request")
|
log.Printf("RECORD request")
|
||||||
|
|
||||||
// called when receiving a RTP packet
|
// called when receiving a RTP packet
|
||||||
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
||||||
|
@@ -56,7 +56,7 @@ func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionClo
|
|||||||
|
|
||||||
// called when receiving a DESCRIBE request.
|
// called when receiving a DESCRIBE request.
|
||||||
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("describe request")
|
log.Printf("DESCRIBE request")
|
||||||
|
|
||||||
sh.mutex.RLock()
|
sh.mutex.RLock()
|
||||||
defer sh.mutex.RUnlock()
|
defer sh.mutex.RUnlock()
|
||||||
@@ -76,7 +76,7 @@ func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (
|
|||||||
|
|
||||||
// called when receiving an ANNOUNCE request.
|
// called when receiving an ANNOUNCE request.
|
||||||
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
|
||||||
log.Printf("announce request")
|
log.Printf("ANNOUNCE request")
|
||||||
|
|
||||||
sh.mutex.Lock()
|
sh.mutex.Lock()
|
||||||
defer sh.mutex.Unlock()
|
defer sh.mutex.Unlock()
|
||||||
@@ -105,7 +105,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
|
|||||||
|
|
||||||
// called when receiving a SETUP request.
|
// called when receiving a SETUP request.
|
||||||
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
||||||
log.Printf("setup request")
|
log.Printf("SETUP request")
|
||||||
|
|
||||||
// SETUP is used by both readers and publishers. In case of publishers, just return StatusOK.
|
// SETUP is used by both readers and publishers. In case of publishers, just return StatusOK.
|
||||||
if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
|
if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
|
||||||
@@ -131,7 +131,7 @@ func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.
|
|||||||
|
|
||||||
// called when receiving a PLAY request.
|
// called when receiving a PLAY request.
|
||||||
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||||
log.Printf("play request")
|
log.Printf("PLAY request")
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -140,7 +140,7 @@ func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Re
|
|||||||
|
|
||||||
// called when receiving a RECORD request.
|
// called when receiving a RECORD request.
|
||||||
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||||
log.Printf("record request")
|
log.Printf("RECORD request")
|
||||||
|
|
||||||
// called when receiving a RTP packet
|
// called when receiving a RTP packet
|
||||||
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
|
||||||
|
2
go.mod
2
go.mod
@@ -3,6 +3,7 @@ module github.com/bluenviron/gortsplib/v4
|
|||||||
go 1.21.0
|
go 1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/asticode/go-astits v1.13.0
|
||||||
github.com/bluenviron/mediacommon/v2 v2.0.1-0.20250222132106-205c4f7f3850
|
github.com/bluenviron/mediacommon/v2 v2.0.1-0.20250222132106-205c4f7f3850
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/pion/rtcp v1.2.15
|
github.com/pion/rtcp v1.2.15
|
||||||
@@ -14,7 +15,6 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/asticode/go-astikit v0.30.0 // indirect
|
github.com/asticode/go-astikit v0.30.0 // indirect
|
||||||
github.com/asticode/go-astits v1.13.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
Reference in New Issue
Block a user