Skip to content

Commit 21b5031

Browse files
authored
playback: improve /list performance (#3663) (#4102)
Segments are now parsed in parallel.
1 parent ac0ddc9 commit 21b5031

File tree

2 files changed

+98
-54
lines changed

2 files changed

+98
-54
lines changed

internal/playback/on_get.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ func seekAndMux(
6666

6767
segmentStartOffset := start.Sub(segments[0].Start)
6868

69-
segmentMaxElapsed, err := segmentFMP4SeekAndMuxParts(f, segmentStartOffset, duration, firstInit, m)
69+
segmentDuration, err := segmentFMP4SeekAndMuxParts(f, segmentStartOffset, duration, firstInit, m)
7070
if err != nil {
7171
return err
7272
}
7373

74-
segmentEnd = start.Add(segmentMaxElapsed)
74+
segmentEnd = start.Add(segmentDuration)
7575

7676
for _, seg := range segments[1:] {
7777
f, err = os.Open(seg.Fpath)
@@ -92,13 +92,13 @@ func seekAndMux(
9292

9393
segmentStartOffset := seg.Start.Sub(start)
9494

95-
var segmentMaxElapsed time.Duration
96-
segmentMaxElapsed, err = segmentFMP4MuxParts(f, segmentStartOffset, duration, firstInit, m)
95+
var segmentDuration time.Duration
96+
segmentDuration, err = segmentFMP4MuxParts(f, segmentStartOffset, duration, firstInit, m)
9797
if err != nil {
9898
return err
9999
}
100100

101-
segmentEnd = start.Add(segmentMaxElapsed)
101+
segmentEnd = start.Add(segmentDuration)
102102
}
103103

104104
err = m.flush()

internal/playback/on_list.go

+93-49
Original file line numberDiff line numberDiff line change
@@ -22,66 +22,110 @@ func (d listEntryDuration) MarshalJSON() ([]byte, error) {
2222
return json.Marshal(time.Duration(d).Seconds())
2323
}
2424

25+
type parsedSegment struct {
26+
start time.Time
27+
init *fmp4.Init
28+
duration time.Duration
29+
}
30+
31+
func parseSegment(seg *recordstore.Segment) (*parsedSegment, error) {
32+
f, err := os.Open(seg.Fpath)
33+
if err != nil {
34+
return nil, err
35+
}
36+
defer f.Close()
37+
38+
init, duration, err := segmentFMP4ReadHeader(f)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
// if duration is not present in the header, compute it
44+
// by parsing each part
45+
if duration == 0 {
46+
duration, err = segmentFMP4ReadDurationFromParts(f, init)
47+
if err != nil {
48+
return nil, err
49+
}
50+
}
51+
52+
return &parsedSegment{
53+
start: seg.Start,
54+
init: init,
55+
duration: duration,
56+
}, nil
57+
}
58+
59+
func parseSegments(segments []*recordstore.Segment) ([]*parsedSegment, error) {
60+
parsed := make([]*parsedSegment, len(segments))
61+
ch := make(chan error)
62+
63+
// process segments in parallel.
64+
// parallel random access should improve performance in most cases.
65+
// ref: https://pkolaczk.github.io/disk-parallelism/
66+
for i, seg := range segments {
67+
go func(i int, seg *recordstore.Segment) {
68+
var err error
69+
parsed[i], err = parseSegment(seg)
70+
ch <- err
71+
}(i, seg)
72+
}
73+
74+
var err error
75+
76+
for range segments {
77+
err2 := <-ch
78+
if err2 != nil {
79+
err = err2
80+
}
81+
}
82+
83+
return parsed, err
84+
}
85+
2586
type listEntry struct {
2687
Start time.Time `json:"start"`
2788
Duration listEntryDuration `json:"duration"`
2889
URL string `json:"url"`
2990
}
3091

31-
func readDurationAndConcatenate(
92+
func concatenateSegments(parsed []*parsedSegment) []listEntry {
93+
out := []listEntry{}
94+
var prevInit *fmp4.Init
95+
96+
for _, parsed := range parsed {
97+
if len(out) != 0 && segmentFMP4CanBeConcatenated(
98+
prevInit,
99+
out[len(out)-1].Start.Add(time.Duration(out[len(out)-1].Duration)),
100+
parsed.init,
101+
parsed.start) {
102+
prevStart := out[len(out)-1].Start
103+
curEnd := parsed.start.Add(parsed.duration)
104+
out[len(out)-1].Duration = listEntryDuration(curEnd.Sub(prevStart))
105+
} else {
106+
out = append(out, listEntry{
107+
Start: parsed.start,
108+
Duration: listEntryDuration(parsed.duration),
109+
})
110+
}
111+
112+
prevInit = parsed.init
113+
}
114+
115+
return out
116+
}
117+
118+
func parseAndConcatenate(
32119
recordFormat conf.RecordFormat,
33120
segments []*recordstore.Segment,
34121
) ([]listEntry, error) {
35122
if recordFormat == conf.RecordFormatFMP4 {
36-
out := []listEntry{}
37-
var prevInit *fmp4.Init
38-
39-
for _, seg := range segments {
40-
err := func() error {
41-
f, err := os.Open(seg.Fpath)
42-
if err != nil {
43-
return err
44-
}
45-
defer f.Close()
46-
47-
init, duration, err := segmentFMP4ReadHeader(f)
48-
if err != nil {
49-
return err
50-
}
51-
52-
// if duration is not present in the header, compute it
53-
// by parsing each part
54-
if duration == 0 {
55-
duration, err = segmentFMP4ReadDurationFromParts(f, init)
56-
if err != nil {
57-
return err
58-
}
59-
}
60-
61-
if len(out) != 0 && segmentFMP4CanBeConcatenated(
62-
prevInit,
63-
out[len(out)-1].Start.Add(time.Duration(out[len(out)-1].Duration)),
64-
init,
65-
seg.Start) {
66-
prevStart := out[len(out)-1].Start
67-
curEnd := seg.Start.Add(duration)
68-
out[len(out)-1].Duration = listEntryDuration(curEnd.Sub(prevStart))
69-
} else {
70-
out = append(out, listEntry{
71-
Start: seg.Start,
72-
Duration: listEntryDuration(duration),
73-
})
74-
}
75-
76-
prevInit = init
77-
78-
return nil
79-
}()
80-
if err != nil {
81-
return nil, err
82-
}
123+
parsed, err := parseSegments(segments)
124+
if err != nil {
125+
return nil, err
83126
}
84127

128+
out := concatenateSegments(parsed)
85129
return out, nil
86130
}
87131

@@ -135,7 +179,7 @@ func (s *Server) onList(ctx *gin.Context) {
135179
return
136180
}
137181

138-
entries, err := readDurationAndConcatenate(pathConf.RecordFormat, segments)
182+
entries, err := parseAndConcatenate(pathConf.RecordFormat, segments)
139183
if err != nil {
140184
s.writeError(ctx, http.StatusInternalServerError, err)
141185
return

0 commit comments

Comments
 (0)