mirror of
https://github.com/bluenviron/mediamtx.git
synced 2025-12-20 02:00:05 -08:00
Response times of the /list endpoint were slow because the duration of each segment was computed from scratch by summing the duration of each of its parts. This is improved by storing the duration of the overall segment in the header and using that, if available.
175 lines
3.3 KiB
Go
175 lines
3.3 KiB
Go
package recorder
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/abema/go-mp4"
|
|
"github.com/bluenviron/mediacommon/pkg/formats/fmp4"
|
|
"github.com/bluenviron/mediacommon/pkg/formats/fmp4/seekablebuffer"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/logger"
|
|
)
|
|
|
|
func writeInit(f io.Writer, tracks []*formatFMP4Track) error {
|
|
fmp4Tracks := make([]*fmp4.InitTrack, len(tracks))
|
|
for i, track := range tracks {
|
|
fmp4Tracks[i] = track.initTrack
|
|
}
|
|
|
|
init := fmp4.Init{
|
|
Tracks: fmp4Tracks,
|
|
}
|
|
|
|
var buf seekablebuffer.Buffer
|
|
err := init.Marshal(&buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Write(buf.Bytes())
|
|
return err
|
|
}
|
|
|
|
func writeDuration(f io.ReadWriteSeeker, d time.Duration) error {
|
|
_, err := f.Seek(0, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check and skip ftyp header and content
|
|
|
|
buf := make([]byte, 8)
|
|
_, err = io.ReadFull(f, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !bytes.Equal(buf[4:], []byte{'f', 't', 'y', 'p'}) {
|
|
return fmt.Errorf("ftyp box not found")
|
|
}
|
|
|
|
ftypSize := uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3])
|
|
|
|
_, err = f.Seek(int64(ftypSize), io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check and skip moov header
|
|
|
|
_, err = io.ReadFull(f, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !bytes.Equal(buf[4:], []byte{'m', 'o', 'o', 'v'}) {
|
|
return fmt.Errorf("moov box not found")
|
|
}
|
|
|
|
moovSize := uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3])
|
|
|
|
moovPos, err := f.Seek(8, io.SeekCurrent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var mvhd mp4.Mvhd
|
|
_, err = mp4.Unmarshal(f, uint64(moovSize-8), &mvhd, mp4.Context{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mvhd.DurationV0 = uint32(d / time.Millisecond)
|
|
|
|
_, err = f.Seek(moovPos, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = mp4.Marshal(f, &mvhd, mp4.Context{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type formatFMP4Segment struct {
|
|
f *formatFMP4
|
|
startDTS time.Duration
|
|
startNTP time.Time
|
|
|
|
path string
|
|
fi *os.File
|
|
curPart *formatFMP4Part
|
|
lastDTS time.Duration
|
|
}
|
|
|
|
func (s *formatFMP4Segment) initialize() {
|
|
s.lastDTS = s.startDTS
|
|
}
|
|
|
|
func (s *formatFMP4Segment) close() error {
|
|
var err error
|
|
|
|
if s.curPart != nil {
|
|
err = s.curPart.close()
|
|
}
|
|
|
|
if s.fi != nil {
|
|
s.f.ri.Log(logger.Debug, "closing segment %s", s.path)
|
|
|
|
// write overall duration in the header in order to speed up the playback server
|
|
duration := s.lastDTS - s.startDTS
|
|
err2 := writeDuration(s.fi, duration)
|
|
if err == nil {
|
|
err = err2
|
|
}
|
|
|
|
err2 = s.fi.Close()
|
|
if err == nil {
|
|
err = err2
|
|
}
|
|
|
|
if err2 == nil {
|
|
s.f.ri.rec.OnSegmentComplete(s.path, duration)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (s *formatFMP4Segment) write(track *formatFMP4Track, sample *sample, dtsDuration time.Duration) error {
|
|
s.lastDTS = dtsDuration
|
|
|
|
if s.curPart == nil {
|
|
s.curPart = &formatFMP4Part{
|
|
s: s,
|
|
sequenceNumber: s.f.nextSequenceNumber,
|
|
startDTS: dtsDuration,
|
|
}
|
|
s.curPart.initialize()
|
|
s.f.nextSequenceNumber++
|
|
} else if s.curPart.duration() >= s.f.ri.rec.PartDuration {
|
|
err := s.curPart.close()
|
|
s.curPart = nil
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.curPart = &formatFMP4Part{
|
|
s: s,
|
|
sequenceNumber: s.f.nextSequenceNumber,
|
|
startDTS: dtsDuration,
|
|
}
|
|
s.curPart.initialize()
|
|
s.f.nextSequenceNumber++
|
|
}
|
|
|
|
return s.curPart.write(track, sample, dtsDuration)
|
|
}
|