mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 23:26:54 +08:00
perform frame readings and writings in separate routines, in order to increase UDP throughput and avoid freezes caused by a single laggy reader (https://github.com/aler9/rtsp-simple-server/issues/125) (https://github.com/aler9/rtsp-simple-server/issues/162)
This commit is contained in:
@@ -55,7 +55,7 @@ type ClientConf struct {
|
|||||||
// If greater than 1, allows to pass buffers to routines different than the one
|
// If greater than 1, allows to pass buffers to routines different than the one
|
||||||
// that is reading frames.
|
// that is reading frames.
|
||||||
// It defaults to 1.
|
// It defaults to 1.
|
||||||
ReadBufferCount int
|
ReadBufferCount uint64
|
||||||
|
|
||||||
// callback called before every request.
|
// callback called before every request.
|
||||||
OnRequest func(req *base.Request)
|
OnRequest func(req *base.Request)
|
||||||
|
@@ -33,7 +33,7 @@ const (
|
|||||||
clientConnSenderReportPeriod = 10 * time.Second
|
clientConnSenderReportPeriod = 10 * time.Second
|
||||||
clientConnUDPCheckStreamPeriod = 5 * time.Second
|
clientConnUDPCheckStreamPeriod = 5 * time.Second
|
||||||
clientConnUDPKeepalivePeriod = 30 * time.Second
|
clientConnUDPKeepalivePeriod = 30 * time.Second
|
||||||
clientConnTCPFrameReadBufferSize = 128 * 1024
|
clientConnTCPFrameReadBufferSize = 2048
|
||||||
)
|
)
|
||||||
|
|
||||||
type clientConnState int
|
type clientConnState int
|
||||||
|
@@ -2,17 +2,17 @@
|
|||||||
package multibuffer
|
package multibuffer
|
||||||
|
|
||||||
// MultiBuffer implements software multi buffering, that allows to reuse
|
// MultiBuffer implements software multi buffering, that allows to reuse
|
||||||
// existing buffers without creating new ones, increasing performance.
|
// existing buffers without creating new ones, improving performance.
|
||||||
type MultiBuffer struct {
|
type MultiBuffer struct {
|
||||||
count int
|
count uint64
|
||||||
buffers [][]byte
|
buffers [][]byte
|
||||||
cur int
|
cur uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// New allocates a MultiBuffer.
|
// New allocates a MultiBuffer.
|
||||||
func New(count int, size int) *MultiBuffer {
|
func New(count uint64, size uint64) *MultiBuffer {
|
||||||
buffers := make([][]byte, count)
|
buffers := make([][]byte, count)
|
||||||
for i := 0; i < count; i++ {
|
for i := uint64(0); i < count; i++ {
|
||||||
buffers[i] = make([]byte, size)
|
buffers[i] = make([]byte, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,10 +24,7 @@ func New(count int, size int) *MultiBuffer {
|
|||||||
|
|
||||||
// Next gets the current buffer and sets the next buffer as the current one.
|
// Next gets the current buffer and sets the next buffer as the current one.
|
||||||
func (mb *MultiBuffer) Next() []byte {
|
func (mb *MultiBuffer) Next() []byte {
|
||||||
ret := mb.buffers[mb.cur]
|
ret := mb.buffers[mb.cur%mb.count]
|
||||||
mb.cur++
|
mb.cur++
|
||||||
if mb.cur >= mb.count {
|
|
||||||
mb.cur = 0
|
|
||||||
}
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
67
pkg/ringbuffer/ringbuffer.go
Normal file
67
pkg/ringbuffer/ringbuffer.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package ringbuffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RingBuffer is a ring buffer.
|
||||||
|
type RingBuffer struct {
|
||||||
|
bufferSize uint64
|
||||||
|
readIndex uint64
|
||||||
|
writeIndex uint64
|
||||||
|
closed int64
|
||||||
|
buffer []unsafe.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a RingBuffer.
|
||||||
|
func New(size uint64) *RingBuffer {
|
||||||
|
return &RingBuffer{
|
||||||
|
bufferSize: size,
|
||||||
|
readIndex: 1,
|
||||||
|
writeIndex: 0,
|
||||||
|
buffer: make([]unsafe.Pointer, size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close makes Pull() return false.
|
||||||
|
func (r *RingBuffer) Close() {
|
||||||
|
atomic.StoreInt64(&r.closed, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset restores Pull().
|
||||||
|
func (r *RingBuffer) Reset() {
|
||||||
|
for i := uint64(0); i < r.bufferSize; i++ {
|
||||||
|
atomic.SwapPointer(&r.buffer[i], nil)
|
||||||
|
}
|
||||||
|
atomic.SwapUint64(&r.writeIndex, 0)
|
||||||
|
r.readIndex = 1
|
||||||
|
atomic.StoreInt64(&r.closed, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push pushes some data at the end of the buffer.
|
||||||
|
func (r *RingBuffer) Push(data interface{}) {
|
||||||
|
writeIndex := atomic.AddUint64(&r.writeIndex, 1)
|
||||||
|
i := writeIndex % r.bufferSize
|
||||||
|
atomic.SwapPointer(&r.buffer[i], unsafe.Pointer(&data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull pulls some data from the beginning of the buffer.
|
||||||
|
func (r *RingBuffer) Pull() (interface{}, bool) {
|
||||||
|
for {
|
||||||
|
if atomic.SwapInt64(&r.closed, 0) == 1 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
i := r.readIndex % r.bufferSize
|
||||||
|
res := (*interface{})(atomic.SwapPointer(&r.buffer[i], nil))
|
||||||
|
if res == nil {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.readIndex++
|
||||||
|
return *res, true
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,7 @@ func newServer(conf ServerConf, address string) (*Server, error) {
|
|||||||
conf.WriteTimeout = 10 * time.Second
|
conf.WriteTimeout = 10 * time.Second
|
||||||
}
|
}
|
||||||
if conf.ReadBufferCount == 0 {
|
if conf.ReadBufferCount == 0 {
|
||||||
conf.ReadBufferCount = 1
|
conf.ReadBufferCount = 1024
|
||||||
}
|
}
|
||||||
if conf.Listen == nil {
|
if conf.Listen == nil {
|
||||||
conf.Listen = net.Listen
|
conf.Listen = net.Listen
|
||||||
@@ -36,11 +36,8 @@ func newServer(conf ServerConf, address string) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if conf.UDPRTPListener != nil {
|
if conf.UDPRTPListener != nil {
|
||||||
conf.UDPRTPListener.streamType = StreamTypeRTP
|
conf.UDPRTPListener.initialize(conf, StreamTypeRTP)
|
||||||
conf.UDPRTPListener.writeTimeout = conf.WriteTimeout
|
conf.UDPRTCPListener.initialize(conf, StreamTypeRTCP)
|
||||||
|
|
||||||
conf.UDPRTCPListener.streamType = StreamTypeRTCP
|
|
||||||
conf.UDPRTCPListener.writeTimeout = conf.WriteTimeout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, err := conf.Listen("tcp", address)
|
listener, err := conf.Listen("tcp", address)
|
||||||
|
@@ -39,8 +39,10 @@ type ServerConf struct {
|
|||||||
// Read buffer count.
|
// Read buffer count.
|
||||||
// If greater than 1, allows to pass buffers to routines different than the one
|
// If greater than 1, allows to pass buffers to routines different than the one
|
||||||
// that is reading frames.
|
// that is reading frames.
|
||||||
// It defaults to 1
|
// It also allows to buffer routed frames and mitigate network fluctuations
|
||||||
ReadBufferCount int
|
// that are particularly high when using UDP.
|
||||||
|
// It defaults to 1024
|
||||||
|
ReadBufferCount uint64
|
||||||
|
|
||||||
// Function used to initialize the TCP listener.
|
// Function used to initialize the TCP listener.
|
||||||
// It defaults to net.Listen
|
// It defaults to net.Listen
|
||||||
|
168
serverconn.go
168
serverconn.go
@@ -8,13 +8,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/pkg/base"
|
"github.com/aler9/gortsplib/pkg/base"
|
||||||
"github.com/aler9/gortsplib/pkg/headers"
|
"github.com/aler9/gortsplib/pkg/headers"
|
||||||
"github.com/aler9/gortsplib/pkg/multibuffer"
|
"github.com/aler9/gortsplib/pkg/multibuffer"
|
||||||
|
"github.com/aler9/gortsplib/pkg/ringbuffer"
|
||||||
"github.com/aler9/gortsplib/pkg/rtcpreceiver"
|
"github.com/aler9/gortsplib/pkg/rtcpreceiver"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,11 +23,13 @@ const (
|
|||||||
serverConnWriteBufferSize = 4096
|
serverConnWriteBufferSize = 4096
|
||||||
serverConnCheckStreamInterval = 5 * time.Second
|
serverConnCheckStreamInterval = 5 * time.Second
|
||||||
serverConnReceiverReportInterval = 10 * time.Second
|
serverConnReceiverReportInterval = 10 * time.Second
|
||||||
|
serverConnTCPFrameReadBufferSize = 2048
|
||||||
)
|
)
|
||||||
|
|
||||||
// server errors.
|
// server errors.
|
||||||
var (
|
var (
|
||||||
ErrServerTeardown = errors.New("teardown")
|
ErrServerTeardown = errors.New("teardown")
|
||||||
|
errServerCSeqMissing = errors.New("CSeq is missing")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServerConnState is the state of the connection.
|
// ServerConnState is the state of the connection.
|
||||||
@@ -138,20 +140,24 @@ type ServerConn struct {
|
|||||||
state ServerConnState
|
state ServerConnState
|
||||||
tracks map[int]ServerConnTrack
|
tracks map[int]ServerConnTrack
|
||||||
tracksProtocol *StreamProtocol
|
tracksProtocol *StreamProtocol
|
||||||
rtcpReceivers []*rtcpreceiver.RTCPReceiver
|
|
||||||
udpLastFrameTimes []*int64
|
|
||||||
writeMutex sync.Mutex
|
|
||||||
readHandlers ServerConnReadHandlers
|
readHandlers ServerConnReadHandlers
|
||||||
nextFramesEnabled bool
|
rtcpReceivers []*rtcpreceiver.RTCPReceiver
|
||||||
|
doEnableFrames bool
|
||||||
framesEnabled bool
|
framesEnabled bool
|
||||||
readTimeoutEnabled bool
|
readTimeoutEnabled bool
|
||||||
udpTimeout *int32
|
|
||||||
|
// writer
|
||||||
|
frameRingBuffer *ringbuffer.RingBuffer
|
||||||
|
backgroundWriteDone chan struct{}
|
||||||
|
|
||||||
|
// background record
|
||||||
|
backgroundRecordTerminate chan struct{}
|
||||||
|
backgroundRecordDone chan struct{}
|
||||||
|
udpTimeout int32
|
||||||
|
udpLastFrameTimes []*int64
|
||||||
|
|
||||||
// in
|
// in
|
||||||
terminate chan struct{}
|
terminate chan struct{}
|
||||||
|
|
||||||
backgroundRecordTerminate chan struct{}
|
|
||||||
backgroundRecordDone chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServerConn(conf ServerConf, nconn net.Conn) *ServerConn {
|
func newServerConn(conf ServerConf, nconn net.Conn) *ServerConn {
|
||||||
@@ -163,13 +169,14 @@ func newServerConn(conf ServerConf, nconn net.Conn) *ServerConn {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
return &ServerConn{
|
return &ServerConn{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
nconn: nconn,
|
nconn: nconn,
|
||||||
br: bufio.NewReaderSize(conn, serverConnReadBufferSize),
|
br: bufio.NewReaderSize(conn, serverConnReadBufferSize),
|
||||||
bw: bufio.NewWriterSize(conn, serverConnWriteBufferSize),
|
bw: bufio.NewWriterSize(conn, serverConnWriteBufferSize),
|
||||||
tracks: make(map[int]ServerConnTrack),
|
tracks: make(map[int]ServerConnTrack),
|
||||||
udpTimeout: new(int32),
|
frameRingBuffer: ringbuffer.New(conf.ReadBufferCount),
|
||||||
terminate: make(chan struct{}),
|
backgroundWriteDone: make(chan struct{}),
|
||||||
|
terminate: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +213,30 @@ func (sc *ServerConn) Tracks() map[int]ServerConnTrack {
|
|||||||
return sc.tracks
|
return sc.tracks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sc *ServerConn) backgroundWrite() {
|
||||||
|
defer close(sc.backgroundWriteDone)
|
||||||
|
|
||||||
|
for {
|
||||||
|
what, ok := sc.frameRingBuffer.Pull()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch w := what.(type) {
|
||||||
|
case *base.InterleavedFrame:
|
||||||
|
sc.nconn.SetWriteDeadline(time.Now().Add(sc.conf.WriteTimeout))
|
||||||
|
w.Write(sc.bw)
|
||||||
|
|
||||||
|
case *base.Response:
|
||||||
|
sc.nconn.SetWriteDeadline(time.Now().Add(sc.conf.WriteTimeout))
|
||||||
|
w.Write(sc.bw)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unsupported type: %T", what))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (sc *ServerConn) checkState(allowed map[ServerConnState]struct{}) error {
|
func (sc *ServerConn) checkState(allowed map[ServerConnState]struct{}) error {
|
||||||
if _, ok := allowed[sc.state]; ok {
|
if _, ok := allowed[sc.state]; ok {
|
||||||
return nil
|
return nil
|
||||||
@@ -236,12 +267,12 @@ func (sc *ServerConn) frameModeEnable() {
|
|||||||
switch sc.state {
|
switch sc.state {
|
||||||
case ServerConnStatePlay:
|
case ServerConnStatePlay:
|
||||||
if *sc.tracksProtocol == StreamProtocolTCP {
|
if *sc.tracksProtocol == StreamProtocolTCP {
|
||||||
sc.nextFramesEnabled = true
|
sc.doEnableFrames = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case ServerConnStateRecord:
|
case ServerConnStateRecord:
|
||||||
if *sc.tracksProtocol == StreamProtocolTCP {
|
if *sc.tracksProtocol == StreamProtocolTCP {
|
||||||
sc.nextFramesEnabled = true
|
sc.doEnableFrames = true
|
||||||
sc.readTimeoutEnabled = true
|
sc.readTimeoutEnabled = true
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -266,16 +297,25 @@ func (sc *ServerConn) frameModeEnable() {
|
|||||||
func (sc *ServerConn) frameModeDisable() {
|
func (sc *ServerConn) frameModeDisable() {
|
||||||
switch sc.state {
|
switch sc.state {
|
||||||
case ServerConnStatePlay:
|
case ServerConnStatePlay:
|
||||||
sc.nextFramesEnabled = false
|
if *sc.tracksProtocol == StreamProtocolTCP {
|
||||||
|
sc.framesEnabled = false
|
||||||
|
sc.frameRingBuffer.Close()
|
||||||
|
<-sc.backgroundWriteDone
|
||||||
|
}
|
||||||
|
|
||||||
case ServerConnStateRecord:
|
case ServerConnStateRecord:
|
||||||
close(sc.backgroundRecordTerminate)
|
close(sc.backgroundRecordTerminate)
|
||||||
<-sc.backgroundRecordDone
|
<-sc.backgroundRecordDone
|
||||||
|
|
||||||
sc.nextFramesEnabled = false
|
if *sc.tracksProtocol == StreamProtocolTCP {
|
||||||
sc.readTimeoutEnabled = false
|
sc.readTimeoutEnabled = false
|
||||||
|
sc.nconn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
if *sc.tracksProtocol == StreamProtocolUDP {
|
sc.framesEnabled = false
|
||||||
|
sc.frameRingBuffer.Close()
|
||||||
|
<-sc.backgroundWriteDone
|
||||||
|
|
||||||
|
} else {
|
||||||
for _, track := range sc.tracks {
|
for _, track := range sc.tracks {
|
||||||
sc.conf.UDPRTPListener.removePublisher(sc.ip(), track.rtpPort)
|
sc.conf.UDPRTPListener.removePublisher(sc.ip(), track.rtpPort)
|
||||||
sc.conf.UDPRTCPListener.removePublisher(sc.ip(), track.rtcpPort)
|
sc.conf.UDPRTCPListener.removePublisher(sc.ip(), track.rtcpPort)
|
||||||
@@ -285,6 +325,13 @@ func (sc *ServerConn) frameModeDisable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sc *ServerConn) handleRequest(req *base.Request) (*base.Response, error) {
|
func (sc *ServerConn) handleRequest(req *base.Request) (*base.Response, error) {
|
||||||
|
if cseq, ok := req.Header["CSeq"]; !ok || len(cseq) != 1 {
|
||||||
|
return &base.Response{
|
||||||
|
StatusCode: base.StatusBadRequest,
|
||||||
|
Header: base.Header{},
|
||||||
|
}, errServerCSeqMissing
|
||||||
|
}
|
||||||
|
|
||||||
if sc.readHandlers.OnRequest != nil {
|
if sc.readHandlers.OnRequest != nil {
|
||||||
sc.readHandlers.OnRequest(req)
|
sc.readHandlers.OnRequest(req)
|
||||||
}
|
}
|
||||||
@@ -676,19 +723,6 @@ func (sc *ServerConn) handleRequest(req *base.Request) (*base.Response, error) {
|
|||||||
|
|
||||||
func (sc *ServerConn) backgroundRead() error {
|
func (sc *ServerConn) backgroundRead() error {
|
||||||
handleRequestOuter := func(req *base.Request) error {
|
handleRequestOuter := func(req *base.Request) error {
|
||||||
// check cseq
|
|
||||||
cseq, ok := req.Header["CSeq"]
|
|
||||||
if !ok || len(cseq) != 1 {
|
|
||||||
sc.writeMutex.Lock()
|
|
||||||
sc.nconn.SetWriteDeadline(time.Now().Add(sc.conf.WriteTimeout))
|
|
||||||
base.Response{
|
|
||||||
StatusCode: base.StatusBadRequest,
|
|
||||||
Header: base.Header{},
|
|
||||||
}.Write(sc.bw)
|
|
||||||
sc.writeMutex.Unlock()
|
|
||||||
return errors.New("CSeq is missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := sc.handleRequest(req)
|
res, err := sc.handleRequest(req)
|
||||||
|
|
||||||
if res.Header == nil {
|
if res.Header == nil {
|
||||||
@@ -696,7 +730,9 @@ func (sc *ServerConn) backgroundRead() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add cseq
|
// add cseq
|
||||||
res.Header["CSeq"] = cseq
|
if err != errServerCSeqMissing {
|
||||||
|
res.Header["CSeq"] = req.Header["CSeq"]
|
||||||
|
}
|
||||||
|
|
||||||
// add server
|
// add server
|
||||||
res.Header["Server"] = base.HeaderValue{"gortsplib"}
|
res.Header["Server"] = base.HeaderValue{"gortsplib"}
|
||||||
@@ -705,33 +741,42 @@ func (sc *ServerConn) backgroundRead() error {
|
|||||||
sc.readHandlers.OnResponse(res)
|
sc.readHandlers.OnResponse(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
sc.writeMutex.Lock()
|
// start background write
|
||||||
|
if sc.doEnableFrames {
|
||||||
|
sc.doEnableFrames = false
|
||||||
|
sc.framesEnabled = true
|
||||||
|
|
||||||
sc.nconn.SetWriteDeadline(time.Now().Add(sc.conf.WriteTimeout))
|
// write response before frames
|
||||||
res.Write(sc.bw)
|
sc.nconn.SetWriteDeadline(time.Now().Add(sc.conf.WriteTimeout))
|
||||||
|
res.Write(sc.bw)
|
||||||
|
|
||||||
// set framesEnabled after sending the response
|
// start background write
|
||||||
// in order to start sending frames after the response, never before
|
sc.frameRingBuffer.Reset()
|
||||||
if sc.framesEnabled != sc.nextFramesEnabled {
|
sc.backgroundWriteDone = make(chan struct{})
|
||||||
sc.framesEnabled = sc.nextFramesEnabled
|
go sc.backgroundWrite()
|
||||||
|
|
||||||
|
// write to background write
|
||||||
|
} else if sc.framesEnabled {
|
||||||
|
sc.frameRingBuffer.Push(res)
|
||||||
|
|
||||||
|
// write directly
|
||||||
|
} else {
|
||||||
|
sc.nconn.SetWriteDeadline(time.Now().Add(sc.conf.WriteTimeout))
|
||||||
|
res.Write(sc.bw)
|
||||||
}
|
}
|
||||||
|
|
||||||
sc.writeMutex.Unlock()
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var req base.Request
|
var req base.Request
|
||||||
var frame base.InterleavedFrame
|
var frame base.InterleavedFrame
|
||||||
tcpFrameBuffer := multibuffer.New(sc.conf.ReadBufferCount, clientConnTCPFrameReadBufferSize)
|
tcpFrameBuffer := multibuffer.New(sc.conf.ReadBufferCount, serverConnTCPFrameReadBufferSize)
|
||||||
var errRet error
|
var errRet error
|
||||||
|
|
||||||
outer:
|
outer:
|
||||||
for {
|
for {
|
||||||
if sc.readTimeoutEnabled {
|
if sc.readTimeoutEnabled {
|
||||||
sc.nconn.SetReadDeadline(time.Now().Add(sc.conf.ReadTimeout))
|
sc.nconn.SetReadDeadline(time.Now().Add(sc.conf.ReadTimeout))
|
||||||
} else {
|
|
||||||
sc.nconn.SetReadDeadline(time.Time{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc.framesEnabled {
|
if sc.framesEnabled {
|
||||||
@@ -764,7 +809,7 @@ outer:
|
|||||||
} else {
|
} else {
|
||||||
err := req.Read(sc.br)
|
err := req.Read(sc.br)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if atomic.LoadInt32(sc.udpTimeout) == 1 {
|
if atomic.LoadInt32(&sc.udpTimeout) == 1 {
|
||||||
errRet = fmt.Errorf("no UDP packets received recently (maybe there's a firewall/NAT in between)")
|
errRet = fmt.Errorf("no UDP packets received recently (maybe there's a firewall/NAT in between)")
|
||||||
} else {
|
} else {
|
||||||
errRet = err
|
errRet = err
|
||||||
@@ -801,41 +846,34 @@ func (sc *ServerConn) Read(readHandlers ServerConnReadHandlers) chan error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteFrame writes a frame.
|
// WriteFrame writes a frame.
|
||||||
func (sc *ServerConn) WriteFrame(trackID int, streamType StreamType, payload []byte) error {
|
func (sc *ServerConn) WriteFrame(trackID int, streamType StreamType, payload []byte) {
|
||||||
sc.writeMutex.Lock()
|
|
||||||
defer sc.writeMutex.Unlock()
|
|
||||||
|
|
||||||
if *sc.tracksProtocol == StreamProtocolUDP {
|
if *sc.tracksProtocol == StreamProtocolUDP {
|
||||||
track := sc.tracks[trackID]
|
track := sc.tracks[trackID]
|
||||||
|
|
||||||
if streamType == StreamTypeRTP {
|
if streamType == StreamTypeRTP {
|
||||||
return sc.conf.UDPRTPListener.write(payload, &net.UDPAddr{
|
sc.conf.UDPRTPListener.write(payload, &net.UDPAddr{
|
||||||
IP: sc.ip(),
|
IP: sc.ip(),
|
||||||
Zone: sc.zone(),
|
Zone: sc.zone(),
|
||||||
Port: track.rtpPort,
|
Port: track.rtpPort,
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return sc.conf.UDPRTCPListener.write(payload, &net.UDPAddr{
|
sc.conf.UDPRTCPListener.write(payload, &net.UDPAddr{
|
||||||
IP: sc.ip(),
|
IP: sc.ip(),
|
||||||
Zone: sc.zone(),
|
Zone: sc.zone(),
|
||||||
Port: track.rtcpPort,
|
Port: track.rtcpPort,
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamProtocolTCP
|
// StreamProtocolTCP
|
||||||
|
|
||||||
if !sc.framesEnabled {
|
sc.frameRingBuffer.Push(&base.InterleavedFrame{
|
||||||
return errors.New("frames are disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
sc.nconn.SetWriteDeadline(time.Now().Add(sc.conf.WriteTimeout))
|
|
||||||
frame := base.InterleavedFrame{
|
|
||||||
TrackID: trackID,
|
TrackID: trackID,
|
||||||
StreamType: streamType,
|
StreamType: streamType,
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
}
|
})
|
||||||
return frame.Write(sc.bw)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *ServerConn) backgroundRecord() {
|
func (sc *ServerConn) backgroundRecord() {
|
||||||
@@ -859,7 +897,7 @@ func (sc *ServerConn) backgroundRecord() {
|
|||||||
last := time.Unix(atomic.LoadInt64(lastUnix), 0)
|
last := time.Unix(atomic.LoadInt64(lastUnix), 0)
|
||||||
|
|
||||||
if now.Sub(last) >= sc.conf.ReadTimeout {
|
if now.Sub(last) >= sc.conf.ReadTimeout {
|
||||||
atomic.StoreInt32(sc.udpTimeout, 1)
|
atomic.StoreInt32(&sc.udpTimeout, 1)
|
||||||
sc.nconn.Close()
|
sc.nconn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
120
serverudpl.go
120
serverudpl.go
@@ -7,15 +7,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/pkg/multibuffer"
|
"github.com/aler9/gortsplib/pkg/multibuffer"
|
||||||
|
"github.com/aler9/gortsplib/pkg/ringbuffer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// use the same buffer size as gstreamer's rtspsrc
|
serverConnUDPListenerKernelReadBufferSize = 0x80000 // same as gstreamer's rtspsrc
|
||||||
kernelReadBufferSize = 0x80000
|
serverConnUDPListenerReadBufferSize = 2048
|
||||||
|
|
||||||
readBufferSize = 2048
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type bufAddrPair struct {
|
||||||
|
buf []byte
|
||||||
|
addr *net.UDPAddr
|
||||||
|
}
|
||||||
|
|
||||||
type publisherData struct {
|
type publisherData struct {
|
||||||
publisher *ServerConn
|
publisher *ServerConn
|
||||||
trackID int
|
trackID int
|
||||||
@@ -39,14 +43,14 @@ func (p *publisherAddr) fill(ip net.IP, port int) {
|
|||||||
|
|
||||||
// ServerUDPListener is a UDP server that can be used to send and receive RTP and RTCP packets.
|
// ServerUDPListener is a UDP server that can be used to send and receive RTP and RTCP packets.
|
||||||
type ServerUDPListener struct {
|
type ServerUDPListener struct {
|
||||||
streamType StreamType
|
|
||||||
writeTimeout time.Duration
|
|
||||||
|
|
||||||
pc *net.UDPConn
|
pc *net.UDPConn
|
||||||
|
initialized bool
|
||||||
|
streamType StreamType
|
||||||
|
writeTimeout time.Duration
|
||||||
readBuf *multibuffer.MultiBuffer
|
readBuf *multibuffer.MultiBuffer
|
||||||
publishersMutex sync.RWMutex
|
publishersMutex sync.RWMutex
|
||||||
publishers map[publisherAddr]*publisherData
|
publishers map[publisherAddr]*publisherData
|
||||||
writeMutex sync.Mutex
|
ringBuffer *ringbuffer.RingBuffer
|
||||||
|
|
||||||
// out
|
// out
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
@@ -60,70 +64,102 @@ func NewServerUDPListener(address string) (*ServerUDPListener, error) {
|
|||||||
}
|
}
|
||||||
pc := tmp.(*net.UDPConn)
|
pc := tmp.(*net.UDPConn)
|
||||||
|
|
||||||
err = pc.SetReadBuffer(kernelReadBufferSize)
|
err = pc.SetReadBuffer(serverConnUDPListenerKernelReadBufferSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &ServerUDPListener{
|
return &ServerUDPListener{
|
||||||
pc: pc,
|
pc: pc,
|
||||||
readBuf: multibuffer.New(1, readBufferSize),
|
|
||||||
publishers: make(map[publisherAddr]*publisherData),
|
publishers: make(map[publisherAddr]*publisherData),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
go s.run()
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the listener.
|
// Close closes the listener.
|
||||||
func (s *ServerUDPListener) Close() {
|
func (s *ServerUDPListener) Close() {
|
||||||
s.pc.Close()
|
s.pc.Close()
|
||||||
<-s.done
|
|
||||||
|
if s.initialized {
|
||||||
|
s.ringBuffer.Close()
|
||||||
|
<-s.done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerUDPListener) initialize(conf ServerConf, streamType StreamType) {
|
||||||
|
if s.initialized {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.initialized = true
|
||||||
|
s.streamType = streamType
|
||||||
|
s.writeTimeout = conf.WriteTimeout
|
||||||
|
s.readBuf = multibuffer.New(conf.ReadBufferCount, serverConnUDPListenerReadBufferSize)
|
||||||
|
s.ringBuffer = ringbuffer.New(conf.ReadBufferCount)
|
||||||
|
go s.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerUDPListener) run() {
|
func (s *ServerUDPListener) run() {
|
||||||
defer close(s.done)
|
defer close(s.done)
|
||||||
|
|
||||||
for {
|
var wg sync.WaitGroup
|
||||||
buf := s.readBuf.Next()
|
|
||||||
n, addr, err := s.pc.ReadFromUDP(buf)
|
wg.Add(1)
|
||||||
if err != nil {
|
go func() {
|
||||||
break
|
defer wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf := s.readBuf.Next()
|
||||||
|
n, addr, err := s.pc.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
s.publishersMutex.RLock()
|
||||||
|
defer s.publishersMutex.RUnlock()
|
||||||
|
|
||||||
|
// find publisher data
|
||||||
|
var pubAddr publisherAddr
|
||||||
|
pubAddr.fill(addr.IP, addr.Port)
|
||||||
|
pubData, ok := s.publishers[pubAddr]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
atomic.StoreInt64(pubData.publisher.udpLastFrameTimes[pubData.trackID], now.Unix())
|
||||||
|
pubData.publisher.rtcpReceivers[pubData.trackID].ProcessFrame(now, s.streamType, buf[:n])
|
||||||
|
pubData.publisher.readHandlers.OnFrame(pubData.trackID, s.streamType, buf[:n])
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
func() {
|
wg.Add(1)
|
||||||
s.publishersMutex.RLock()
|
go func() {
|
||||||
defer s.publishersMutex.RUnlock()
|
defer wg.Done()
|
||||||
|
|
||||||
// find publisher data
|
for {
|
||||||
var pubAddr publisherAddr
|
tmp, ok := s.ringBuffer.Pull()
|
||||||
pubAddr.fill(addr.IP, addr.Port)
|
|
||||||
pubData, ok := s.publishers[pubAddr]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
pair := tmp.(bufAddrPair)
|
||||||
|
|
||||||
now := time.Now()
|
s.pc.SetWriteDeadline(time.Now().Add(s.writeTimeout))
|
||||||
atomic.StoreInt64(pubData.publisher.udpLastFrameTimes[pubData.trackID], now.Unix())
|
s.pc.WriteTo(pair.buf, pair.addr)
|
||||||
pubData.publisher.rtcpReceivers[pubData.trackID].ProcessFrame(now, s.streamType, buf[:n])
|
}
|
||||||
pubData.publisher.readHandlers.OnFrame(pubData.trackID, s.streamType, buf[:n])
|
}()
|
||||||
}()
|
|
||||||
}
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerUDPListener) port() int {
|
func (s *ServerUDPListener) port() int {
|
||||||
return s.pc.LocalAddr().(*net.UDPAddr).Port
|
return s.pc.LocalAddr().(*net.UDPAddr).Port
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerUDPListener) write(buf []byte, addr *net.UDPAddr) error {
|
func (s *ServerUDPListener) write(buf []byte, addr *net.UDPAddr) {
|
||||||
s.writeMutex.Lock()
|
s.ringBuffer.Push(bufAddrPair{buf, addr})
|
||||||
defer s.writeMutex.Unlock()
|
|
||||||
|
|
||||||
s.pc.SetWriteDeadline(time.Now().Add(s.writeTimeout))
|
|
||||||
_, err := s.pc.WriteTo(buf, addr)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerUDPListener) addPublisher(ip net.IP, port int, trackID int, sc *ServerConn) {
|
func (s *ServerUDPListener) addPublisher(ip net.IP, port int, trackID int, sc *ServerConn) {
|
||||||
|
Reference in New Issue
Block a user