forked from shimberger/gohls
-
Notifications
You must be signed in to change notification settings - Fork 1
/
encoder.go
192 lines (161 loc) · 4.5 KB
/
encoder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package hls
import (
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"path/filepath"
log "github.com/sirupsen/logrus"
)
type EncodingRequest struct {
file string
segment int64
res int64
data chan *[]byte
err chan error
}
func NewEncodingRequest(file string, segment int64, res int64) *EncodingRequest {
return &EncodingRequest{file, segment, res, make(chan *[]byte, 1), make(chan error, 1)}
}
func NewWarmupEncodingRequest(file string, segment int64, res int64) *EncodingRequest {
return &EncodingRequest{file, segment, res, nil, nil}
}
func (r *EncodingRequest) sendError(err error) {
if r.err != nil {
r.err <- err
}
}
func (r *EncodingRequest) sendData(data *[]byte) {
if r.data != nil {
r.data <- data
}
}
func (r *EncodingRequest) getCacheKey() string {
h := sha1.New()
h.Write([]byte(r.file))
return fmt.Sprintf("%x.%v.%v", h.Sum(nil), r.res, r.segment)
}
type Encoder struct {
cacheDir string
reqChan chan EncodingRequest
}
func NewEncoder(cacheDir string, workerCount int) *Encoder {
rc := make(chan EncodingRequest, 100)
encoder := &Encoder{cacheDir, rc}
go func() {
for {
r := <-rc
cache, err := encoder.GetFromCache(r)
if err != nil {
r.sendError(err)
continue
}
if cache != nil {
r.sendData(&cache)
continue
}
log.Debugf("Encoding %v:%v", r.file, r.segment)
data, err := execute(FFMPEGPath, EncodingArgs(r.file, r.segment, r.res))
if err != nil {
r.err <- err
continue
}
r.sendData(&data)
tmp := encoder.GetCacheFile(r) + ".tmp"
mkerr := os.MkdirAll(filepath.Join(HomeDir, cacheDirName, encoder.cacheDir), 0777)
if mkerr != nil {
log.Errorf("Could not create cache dir")
continue
}
if err2 := ioutil.WriteFile(tmp, data, 0777); err2 == nil {
os.Rename(tmp, encoder.GetCacheFile(r))
}
}
}()
return encoder
}
func (e *Encoder) GetFromCache(r EncodingRequest) ([]byte, error) {
cachePath := e.GetCacheFile(r)
if _, err := os.Stat(cachePath); err != nil {
// Cache file could not be openened ....
if os.IsNotExist(err) {
// Because segment has not yet been encoded
return nil, nil
}
// Cache file could not be opened because of an underlying error
return nil, fmt.Errorf("Encoder cache file %v could not be opened because: %v", cachePath, err)
}
// The file could be opened, read it's content
dat, err := ioutil.ReadFile(cachePath)
if err != nil {
return nil, fmt.Errorf("Encoder could not read cache file %v because: %v", cachePath, err)
}
// file was read successfully
return dat, nil
}
func (e *Encoder) GetCacheFile(r EncodingRequest) string {
return filepath.Join(HomeDir, cacheDirName, e.cacheDir, r.getCacheKey())
}
func (e *Encoder) Encode(r EncodingRequest) {
go func() {
log.Debugf("Encoding requested %v:%v", r.file, r.segment)
// This needs to run in it's own go routine because channel writes block
data, err := e.GetFromCache(r)
if err != nil {
r.sendError(err)
return
}
if data != nil {
r.sendData(&data)
return
}
e.reqChan <- r
e.reqChan <- *NewWarmupEncodingRequest(r.file, r.segment+1, r.res)
e.reqChan <- *NewWarmupEncodingRequest(r.file, r.segment+2, r.res)
}()
}
func EncodingArgs(videoFile string, segment int64, res int64) []string {
startTime := segment * hlsSegmentLength
// see http://superuser.com/questions/908280/what-is-the-correct-way-to-fix-keyframes-in-ffmpeg-for-dash
return []string{
// Prevent encoding to run longer than 30 seonds
"-timelimit", "45",
// TODO: Some stuff to investigate
// "-probesize", "524288",
// "-fpsprobesize", "10",
// "-analyzeduration", "2147483647",
// "-hwaccel:0", "vda",
// The start time
// important: needs to be before -i to do input seeking
"-ss", fmt.Sprintf("%v.00", startTime),
// The source file
"-i", videoFile,
// Put all streams to output
// "-map", "0",
// The duration
"-t", fmt.Sprintf("%v.00", hlsSegmentLength),
// TODO: Find out what it does
//"-strict", "-2",
// 720p
"-vf", fmt.Sprintf("scale=-2:%v", res),
// x264 video codec
"-vcodec", "libx264",
// x264 preset
"-preset", "veryfast",
// aac audio codec
"-c:a", "aac",
"-b:a", "128k",
"-ac", "2",
//
"-pix_fmt", "yuv420p",
//"-r", "25", // fixed framerate
"-force_key_frames", "expr:gte(t,n_forced*5.000)",
//"-force_key_frames", "00:00:00.00",
//"-x264opts", "keyint=25:min-keyint=25:scenecut=-1",
//"-f", "mpegts",
"-f", "ssegment",
"-segment_time", fmt.Sprintf("%v.00", hlsSegmentLength),
"-initial_offset", fmt.Sprintf("%v.00", startTime),
"pipe:out%03d.ts",
}
}