mirror of
https://github.com/aler9/rtsp-simple-server
synced 2026-04-22 23:17:11 +08:00
improve playback precision of alwaysAvailable offline segment (#5530)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Always-available streams
|
||||
|
||||
When the publisher or source of a stream is offline, the server can be configured to fill gaps in the stream with an offline segment that is played on repeat until a publisher comes back online. This allows readers to stay connected regardless of the state of the stream. The offline segment and any future online stream are concatenated without decoding or re-encoding packets, using the original codec.
|
||||
When the publisher or source of a stream is offline, the server can be configured to fill gaps in the stream with an offline segment that is played on repeat until a publisher comes back online. This allows readers to stay connected regardless of the state of the stream. The offline segment and online stream are concatenated without re-encoding any frame, using the original codec.
|
||||
|
||||
This feature can be enabled by toggling the `alwaysAvailable` flag and filling `alwaysAvailableTracks`:
|
||||
|
||||
|
||||
@@ -50,9 +50,6 @@ func (t *offlineSubStreamTrack) initialize() {
|
||||
func (t *offlineSubStreamTrack) run() {
|
||||
defer t.wg.Done()
|
||||
|
||||
var pts int64
|
||||
systemTime := time.Now()
|
||||
|
||||
if t.file != "" {
|
||||
f, err := os.Open(t.file)
|
||||
if err != nil {
|
||||
@@ -60,7 +57,7 @@ func (t *offlineSubStreamTrack) run() {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = t.runFile(pts, systemTime, f, t.pos)
|
||||
err = t.runFile(f, t.pos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -68,12 +65,13 @@ func (t *offlineSubStreamTrack) run() {
|
||||
}
|
||||
|
||||
const audioWritesPerSecond = 10
|
||||
var pts int64
|
||||
startSystemTime := time.Now()
|
||||
|
||||
switch forma := t.format.(type) {
|
||||
case *format.Opus:
|
||||
unitsPerWrite := (forma.ClockRate() / 960) / audioWritesPerSecond
|
||||
writeDuration := 960 * int64(unitsPerWrite)
|
||||
writeDurationGo := multiplyAndDivide2(time.Duration(writeDuration), time.Second, 48000)
|
||||
|
||||
for {
|
||||
payload := make(unit.PayloadOpus, unitsPerWrite)
|
||||
@@ -88,7 +86,9 @@ func (t *offlineSubStreamTrack) run() {
|
||||
})
|
||||
|
||||
pts += writeDuration
|
||||
systemTime = systemTime.Add(writeDurationGo)
|
||||
|
||||
ptsGo := multiplyAndDivide2(time.Duration(pts), time.Second, 48000)
|
||||
systemTime := startSystemTime.Add(ptsGo)
|
||||
|
||||
if !t.sleep(systemTime) {
|
||||
return
|
||||
@@ -98,7 +98,6 @@ func (t *offlineSubStreamTrack) run() {
|
||||
case *format.MPEG4Audio:
|
||||
unitsPerWrite := (forma.ClockRate() / mpeg4audio.SamplesPerAccessUnit) / audioWritesPerSecond
|
||||
writeDuration := mpeg4audio.SamplesPerAccessUnit * int64(unitsPerWrite)
|
||||
writeDurationGo := multiplyAndDivide2(time.Duration(writeDuration), time.Second, time.Duration(forma.ClockRate()))
|
||||
|
||||
for {
|
||||
var frame []byte
|
||||
@@ -122,7 +121,9 @@ func (t *offlineSubStreamTrack) run() {
|
||||
})
|
||||
|
||||
pts += writeDuration
|
||||
systemTime = systemTime.Add(writeDurationGo)
|
||||
|
||||
ptsGo := multiplyAndDivide2(time.Duration(pts), time.Second, time.Duration(forma.ClockRate()))
|
||||
systemTime := startSystemTime.Add(ptsGo)
|
||||
|
||||
if !t.sleep(systemTime) {
|
||||
return
|
||||
@@ -132,7 +133,6 @@ func (t *offlineSubStreamTrack) run() {
|
||||
case *format.G711:
|
||||
samplesPerWrite := forma.ClockRate() / audioWritesPerSecond
|
||||
writeDuration := samplesPerWrite
|
||||
writeDurationGo := multiplyAndDivide2(time.Duration(writeDuration), time.Second, time.Duration(forma.ClockRate()))
|
||||
|
||||
for {
|
||||
var sample byte
|
||||
@@ -154,7 +154,9 @@ func (t *offlineSubStreamTrack) run() {
|
||||
})
|
||||
|
||||
pts += int64(writeDuration)
|
||||
systemTime = systemTime.Add(writeDurationGo)
|
||||
|
||||
ptsGo := multiplyAndDivide2(time.Duration(pts), time.Second, time.Duration(forma.ClockRate()))
|
||||
systemTime := startSystemTime.Add(ptsGo)
|
||||
|
||||
if !t.sleep(systemTime) {
|
||||
return
|
||||
@@ -164,7 +166,6 @@ func (t *offlineSubStreamTrack) run() {
|
||||
case *format.LPCM:
|
||||
samplesPerWrite := forma.ClockRate() / audioWritesPerSecond
|
||||
writeDuration := samplesPerWrite
|
||||
writeDurationGo := multiplyAndDivide2(time.Duration(writeDuration), time.Second, time.Duration(forma.ClockRate()))
|
||||
|
||||
for {
|
||||
payload := make(unit.PayloadLPCM, samplesPerWrite*forma.ChannelCount*(forma.BitDepth/8))
|
||||
@@ -176,7 +177,9 @@ func (t *offlineSubStreamTrack) run() {
|
||||
})
|
||||
|
||||
pts += int64(writeDuration)
|
||||
systemTime = systemTime.Add(writeDurationGo)
|
||||
|
||||
ptsGo := multiplyAndDivide2(time.Duration(pts), time.Second, time.Duration(forma.ClockRate()))
|
||||
systemTime := startSystemTime.Add(ptsGo)
|
||||
|
||||
if !t.sleep(systemTime) {
|
||||
return
|
||||
@@ -205,14 +208,14 @@ func (t *offlineSubStreamTrack) run() {
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
|
||||
err := t.runFile(pts, systemTime, r, 0)
|
||||
err := t.runFile(r, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *offlineSubStreamTrack) runFile(pts int64, systemTime time.Time, r io.ReadSeeker, pos int) error {
|
||||
func (t *offlineSubStreamTrack) runFile(r io.ReadSeeker, pos int) error {
|
||||
var presentation pmp4.Presentation
|
||||
err := presentation.Unmarshal(r)
|
||||
if err != nil {
|
||||
@@ -220,6 +223,8 @@ func (t *offlineSubStreamTrack) runFile(pts int64, systemTime time.Time, r io.Re
|
||||
}
|
||||
|
||||
track := presentation.Tracks[pos]
|
||||
var pts int64
|
||||
startSystemTime := time.Now()
|
||||
|
||||
for {
|
||||
// in case of the embedded video, codec parameters are not in the description
|
||||
@@ -303,9 +308,9 @@ func (t *offlineSubStreamTrack) runFile(pts int64, systemTime time.Time, r io.Re
|
||||
|
||||
pts += multiplyAndDivide(int64(sample.Duration)+int64(sample.PTSOffset),
|
||||
int64(t.format.ClockRate()), int64(track.TimeScale))
|
||||
durationGo := multiplyAndDivide2(time.Duration(int64(sample.Duration)+int64(sample.PTSOffset)),
|
||||
time.Second, time.Duration(track.TimeScale))
|
||||
systemTime = systemTime.Add(durationGo)
|
||||
|
||||
ptsGo := multiplyAndDivide2(time.Duration(pts), time.Second, time.Duration(t.format.ClockRate()))
|
||||
systemTime := startSystemTime.Add(ptsGo)
|
||||
|
||||
if !t.sleep(systemTime) {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user