mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-09-27 03:56:15 +08:00
this prevents RAM exhaustion.
This commit is contained in:
@@ -352,6 +352,8 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
recordPartDuration:
|
recordPartDuration:
|
||||||
type: string
|
type: string
|
||||||
|
recordMaxPartSize:
|
||||||
|
type: string
|
||||||
recordSegmentDuration:
|
recordSegmentDuration:
|
||||||
type: string
|
type: string
|
||||||
recordDeleteAfter:
|
recordDeleteAfter:
|
||||||
|
@@ -55,6 +55,7 @@ func TestConfFromFile(t *testing.T) {
|
|||||||
RecordPath: "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f",
|
RecordPath: "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f",
|
||||||
RecordFormat: RecordFormatFMP4,
|
RecordFormat: RecordFormatFMP4,
|
||||||
RecordPartDuration: Duration(1 * time.Second),
|
RecordPartDuration: Duration(1 * time.Second),
|
||||||
|
RecordMaxPartSize: 50 * 1024 * 1024,
|
||||||
RecordSegmentDuration: 3600000000000,
|
RecordSegmentDuration: 3600000000000,
|
||||||
RecordDeleteAfter: 86400000000000,
|
RecordDeleteAfter: 86400000000000,
|
||||||
OverridePublisher: true,
|
OverridePublisher: true,
|
||||||
|
@@ -128,6 +128,7 @@ type Path struct {
|
|||||||
RecordPath string `json:"recordPath"`
|
RecordPath string `json:"recordPath"`
|
||||||
RecordFormat RecordFormat `json:"recordFormat"`
|
RecordFormat RecordFormat `json:"recordFormat"`
|
||||||
RecordPartDuration Duration `json:"recordPartDuration"`
|
RecordPartDuration Duration `json:"recordPartDuration"`
|
||||||
|
RecordMaxPartSize StringSize `json:"recordMaxPartSize"`
|
||||||
RecordSegmentDuration Duration `json:"recordSegmentDuration"`
|
RecordSegmentDuration Duration `json:"recordSegmentDuration"`
|
||||||
RecordDeleteAfter Duration `json:"recordDeleteAfter"`
|
RecordDeleteAfter Duration `json:"recordDeleteAfter"`
|
||||||
|
|
||||||
@@ -227,6 +228,7 @@ func (pconf *Path) setDefaults() {
|
|||||||
pconf.RecordPath = "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f"
|
pconf.RecordPath = "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f"
|
||||||
pconf.RecordFormat = RecordFormatFMP4
|
pconf.RecordFormat = RecordFormatFMP4
|
||||||
pconf.RecordPartDuration = Duration(1 * time.Second)
|
pconf.RecordPartDuration = Duration(1 * time.Second)
|
||||||
|
pconf.RecordMaxPartSize = 50 * 1024 * 1024
|
||||||
pconf.RecordSegmentDuration = 3600 * Duration(time.Second)
|
pconf.RecordSegmentDuration = 3600 * Duration(time.Second)
|
||||||
pconf.RecordDeleteAfter = 24 * 3600 * Duration(time.Second)
|
pconf.RecordDeleteAfter = 24 * 3600 * Duration(time.Second)
|
||||||
|
|
||||||
|
@@ -775,6 +775,7 @@ func (pa *path) startRecording() {
|
|||||||
PathFormat: pa.conf.RecordPath,
|
PathFormat: pa.conf.RecordPath,
|
||||||
Format: pa.conf.RecordFormat,
|
Format: pa.conf.RecordFormat,
|
||||||
PartDuration: time.Duration(pa.conf.RecordPartDuration),
|
PartDuration: time.Duration(pa.conf.RecordPartDuration),
|
||||||
|
MaxPartSize: pa.conf.RecordMaxPartSize,
|
||||||
SegmentDuration: time.Duration(pa.conf.RecordSegmentDuration),
|
SegmentDuration: time.Duration(pa.conf.RecordSegmentDuration),
|
||||||
PathName: pa.name,
|
PathName: pa.name,
|
||||||
Stream: pa.stream,
|
Stream: pa.stream,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package recorder
|
package recorder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -46,6 +47,7 @@ type formatFMP4Part struct {
|
|||||||
startDTS time.Duration
|
startDTS time.Duration
|
||||||
|
|
||||||
partTracks map[*formatFMP4Track]*fmp4.PartTrack
|
partTracks map[*formatFMP4Track]*fmp4.PartTrack
|
||||||
|
size uint64
|
||||||
endDTS time.Duration
|
endDTS time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +85,12 @@ func (p *formatFMP4Part) close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *formatFMP4Part) write(track *formatFMP4Track, sample *sample, dts time.Duration) error {
|
func (p *formatFMP4Part) write(track *formatFMP4Track, sample *sample, dts time.Duration) error {
|
||||||
|
size := uint64(len(sample.Payload))
|
||||||
|
if (p.size + size) > uint64(p.s.f.ri.maxPartSize) {
|
||||||
|
return fmt.Errorf("reached maximum part size")
|
||||||
|
}
|
||||||
|
p.size += size
|
||||||
|
|
||||||
partTrack, ok := p.partTracks[track]
|
partTrack, ok := p.partTracks[track]
|
||||||
if !ok {
|
if !ok {
|
||||||
partTrack = &fmp4.PartTrack{
|
partTrack = &fmp4.PartTrack{
|
||||||
|
@@ -20,7 +20,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mpegtsMaxBufferSize = 64 * 1024
|
mpegtsBufferSize = 64 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
func multiplyAndDivide(v, m, d int64) int64 {
|
func multiplyAndDivide(v, m, d int64) int64 {
|
||||||
@@ -419,7 +419,7 @@ func (f *formatMPEGTS) initialize() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.dw = &dynamicWriter{}
|
f.dw = &dynamicWriter{}
|
||||||
f.bw = bufio.NewWriterSize(f.dw, mpegtsMaxBufferSize)
|
f.bw = bufio.NewWriterSize(f.dw, mpegtsBufferSize)
|
||||||
|
|
||||||
f.mw = &mpegts.Writer{W: f.bw, Tracks: tracks}
|
f.mw = &mpegts.Writer{W: f.bw, Tracks: tracks}
|
||||||
err := f.mw.Initialize()
|
err := f.mw.Initialize()
|
||||||
|
@@ -20,6 +20,7 @@ type Recorder struct {
|
|||||||
PathFormat string
|
PathFormat string
|
||||||
Format conf.RecordFormat
|
Format conf.RecordFormat
|
||||||
PartDuration time.Duration
|
PartDuration time.Duration
|
||||||
|
MaxPartSize conf.StringSize
|
||||||
SegmentDuration time.Duration
|
SegmentDuration time.Duration
|
||||||
PathName string
|
PathName string
|
||||||
Stream *stream.Stream
|
Stream *stream.Stream
|
||||||
@@ -56,6 +57,7 @@ func (r *Recorder) Initialize() {
|
|||||||
pathFormat: r.PathFormat,
|
pathFormat: r.PathFormat,
|
||||||
format: r.Format,
|
format: r.Format,
|
||||||
partDuration: r.PartDuration,
|
partDuration: r.PartDuration,
|
||||||
|
maxPartSize: r.MaxPartSize,
|
||||||
segmentDuration: r.SegmentDuration,
|
segmentDuration: r.SegmentDuration,
|
||||||
pathName: r.PathName,
|
pathName: r.PathName,
|
||||||
stream: r.Stream,
|
stream: r.Stream,
|
||||||
@@ -102,6 +104,7 @@ func (r *Recorder) run() {
|
|||||||
pathFormat: r.PathFormat,
|
pathFormat: r.PathFormat,
|
||||||
format: r.Format,
|
format: r.Format,
|
||||||
partDuration: r.PartDuration,
|
partDuration: r.PartDuration,
|
||||||
|
maxPartSize: r.MaxPartSize,
|
||||||
segmentDuration: r.SegmentDuration,
|
segmentDuration: r.SegmentDuration,
|
||||||
pathName: r.PathName,
|
pathName: r.PathName,
|
||||||
stream: r.Stream,
|
stream: r.Stream,
|
||||||
|
@@ -22,6 +22,7 @@ type recorderInstance struct {
|
|||||||
pathFormat string
|
pathFormat string
|
||||||
format conf.RecordFormat
|
format conf.RecordFormat
|
||||||
partDuration time.Duration
|
partDuration time.Duration
|
||||||
|
maxPartSize conf.StringSize
|
||||||
segmentDuration time.Duration
|
segmentDuration time.Duration
|
||||||
pathName string
|
pathName string
|
||||||
stream *stream.Stream
|
stream *stream.Stream
|
||||||
|
@@ -165,6 +165,7 @@ func TestRecorder(t *testing.T) {
|
|||||||
PathFormat: recordPath,
|
PathFormat: recordPath,
|
||||||
Format: f,
|
Format: f,
|
||||||
PartDuration: 100 * time.Millisecond,
|
PartDuration: 100 * time.Millisecond,
|
||||||
|
MaxPartSize: 50 * 1024 * 1024,
|
||||||
SegmentDuration: 1 * time.Second,
|
SegmentDuration: 1 * time.Second,
|
||||||
PathName: "mypath",
|
PathName: "mypath",
|
||||||
Stream: strm,
|
Stream: strm,
|
||||||
@@ -360,6 +361,7 @@ func TestRecorderFMP4NegativeDTS(t *testing.T) {
|
|||||||
PathFormat: recordPath,
|
PathFormat: recordPath,
|
||||||
Format: conf.RecordFormatFMP4,
|
Format: conf.RecordFormatFMP4,
|
||||||
PartDuration: 100 * time.Millisecond,
|
PartDuration: 100 * time.Millisecond,
|
||||||
|
MaxPartSize: 50 * 1024 * 1024,
|
||||||
SegmentDuration: 1 * time.Second,
|
SegmentDuration: 1 * time.Second,
|
||||||
PathName: "mypath",
|
PathName: "mypath",
|
||||||
Stream: strm,
|
Stream: strm,
|
||||||
@@ -465,6 +467,7 @@ func TestRecorderSkipTracksPartial(t *testing.T) {
|
|||||||
PathFormat: recordPath,
|
PathFormat: recordPath,
|
||||||
Format: fo,
|
Format: fo,
|
||||||
PartDuration: 100 * time.Millisecond,
|
PartDuration: 100 * time.Millisecond,
|
||||||
|
MaxPartSize: 50 * 1024 * 1024,
|
||||||
SegmentDuration: 1 * time.Second,
|
SegmentDuration: 1 * time.Second,
|
||||||
PathName: "mypath",
|
PathName: "mypath",
|
||||||
Stream: strm,
|
Stream: strm,
|
||||||
@@ -526,6 +529,7 @@ func TestRecorderSkipTracksFull(t *testing.T) {
|
|||||||
PathFormat: recordPath,
|
PathFormat: recordPath,
|
||||||
Format: fo,
|
Format: fo,
|
||||||
PartDuration: 100 * time.Millisecond,
|
PartDuration: 100 * time.Millisecond,
|
||||||
|
MaxPartSize: 50 * 1024 * 1024,
|
||||||
SegmentDuration: 1 * time.Second,
|
SegmentDuration: 1 * time.Second,
|
||||||
PathName: "mypath",
|
PathName: "mypath",
|
||||||
Stream: strm,
|
Stream: strm,
|
||||||
@@ -572,6 +576,7 @@ func TestRecorderFMP4SegmentSwitch(t *testing.T) {
|
|||||||
PathFormat: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"),
|
PathFormat: filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f"),
|
||||||
Format: conf.RecordFormatFMP4,
|
Format: conf.RecordFormatFMP4,
|
||||||
PartDuration: 100 * time.Millisecond,
|
PartDuration: 100 * time.Millisecond,
|
||||||
|
MaxPartSize: 50 * 1024 * 1024,
|
||||||
SegmentDuration: 1 * time.Second,
|
SegmentDuration: 1 * time.Second,
|
||||||
PathName: "mypath",
|
PathName: "mypath",
|
||||||
Stream: strm,
|
Stream: strm,
|
||||||
|
@@ -83,7 +83,7 @@ func (s *session) initialize() {
|
|||||||
|
|
||||||
s.discardedFrames = &counterdumper.CounterDumper{
|
s.discardedFrames = &counterdumper.CounterDumper{
|
||||||
OnReport: func(val uint64) {
|
OnReport: func(val uint64) {
|
||||||
s.Log(logger.Warn, "connection is too slow, discarding %d %s",
|
s.Log(logger.Warn, "reader is too slow, discarding %d %s",
|
||||||
val,
|
val,
|
||||||
func() string {
|
func() string {
|
||||||
if val == 1 {
|
if val == 1 {
|
||||||
|
@@ -31,7 +31,7 @@ func (w *streamReader) start() {
|
|||||||
|
|
||||||
w.discardedFrames = &counterdumper.CounterDumper{
|
w.discardedFrames = &counterdumper.CounterDumper{
|
||||||
OnReport: func(val uint64) {
|
OnReport: func(val uint64) {
|
||||||
w.parent.Log(logger.Warn, "connection is too slow, discarding %d %s",
|
w.parent.Log(logger.Warn, "reader is too slow, discarding %d %s",
|
||||||
val,
|
val,
|
||||||
func() string {
|
func() string {
|
||||||
if val == 1 {
|
if val == 1 {
|
||||||
|
@@ -492,6 +492,8 @@ pathDefaults:
|
|||||||
# When a system failure occurs, the last part gets lost.
|
# When a system failure occurs, the last part gets lost.
|
||||||
# Therefore, the part duration is equal to the RPO (recovery point objective).
|
# Therefore, the part duration is equal to the RPO (recovery point objective).
|
||||||
recordPartDuration: 1s
|
recordPartDuration: 1s
|
||||||
|
# This prevents RAM exhaustion.
|
||||||
|
recordMaxPartSize: 50M
|
||||||
# Minimum duration of each segment.
|
# Minimum duration of each segment.
|
||||||
recordSegmentDuration: 1h
|
recordSegmentDuration: 1h
|
||||||
# Delete segments after this timespan.
|
# Delete segments after this timespan.
|
||||||
|
Reference in New Issue
Block a user