Switch to config file
parent
35b4b3a8b2
commit
f41680455c
25
config.go
25
config.go
|
@ -1,24 +1,31 @@
|
|||
package main
|
||||
|
||||
type Config struct {
|
||||
// Bind address
|
||||
Bind string `json:"bind"`
|
||||
|
||||
// FFmpeg binary
|
||||
ffmpeg string
|
||||
FFmpeg string `json:"ffmpeg"`
|
||||
// FFprobe binary
|
||||
ffprobe string
|
||||
FFprobe string `json:"ffprobe"`
|
||||
// Temp files directory
|
||||
tempdir string
|
||||
TempDir string `json:"tempdir"`
|
||||
|
||||
// Size of each chunk in seconds
|
||||
chunkSize int
|
||||
ChunkSize int `json:"chunkSize"`
|
||||
// How many *chunks* to look behind before restarting transcoding
|
||||
lookBehind int
|
||||
LookBehind int `json:"lookBehind"`
|
||||
// Number of chunks in goal to restart encoding
|
||||
goalBufferMin int
|
||||
GoalBufferMin int `json:"goalBufferMin"`
|
||||
// Number of chunks in goal to stop encoding
|
||||
goalBufferMax int
|
||||
GoalBufferMax int `json:"goalBufferMax"`
|
||||
|
||||
// Number of seconds to wait before shutting down encoding
|
||||
streamIdleTime int
|
||||
StreamIdleTime int `json:"streamIdleTime"`
|
||||
// Number of seconds to wait before shutting down a client
|
||||
managerIdleTime int
|
||||
ManagerIdleTime int `json:"managerIdleTime"`
|
||||
|
||||
// Hardware acceleration configuration
|
||||
VAAPI bool `json:"vaapi"`
|
||||
NVENC bool `json:"nvenc"`
|
||||
}
|
||||
|
|
81
main.go
81
main.go
|
@ -1,7 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -24,8 +26,8 @@ func NewHandler(c *Config) *Handler {
|
|||
}
|
||||
|
||||
// Recreate tempdir
|
||||
os.RemoveAll(c.tempdir)
|
||||
os.MkdirAll(c.tempdir, 0755)
|
||||
os.RemoveAll(c.TempDir)
|
||||
os.MkdirAll(c.TempDir, 0755)
|
||||
|
||||
go h.watchClose()
|
||||
return h
|
||||
|
@ -120,51 +122,54 @@ func (h *Handler) Close() {
|
|||
h.close <- ""
|
||||
}
|
||||
|
||||
func loadConfig(path string, c *Config) {
|
||||
// load json config
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("Error when opening file: ", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(content, &c)
|
||||
if err != nil {
|
||||
log.Fatal("Error loading config file", err)
|
||||
}
|
||||
|
||||
// Print loaded config
|
||||
fmt.Printf("%+v\n", c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) >= 2 && os.Args[1] == "test" {
|
||||
fmt.Println("test successful")
|
||||
return
|
||||
}
|
||||
|
||||
c := &Config{
|
||||
Bind: ":47788",
|
||||
ChunkSize: 3,
|
||||
LookBehind: 5,
|
||||
GoalBufferMin: 1,
|
||||
GoalBufferMax: 4,
|
||||
StreamIdleTime: 60,
|
||||
ManagerIdleTime: 60,
|
||||
}
|
||||
|
||||
// Load config file from second argument
|
||||
if len(os.Args) >= 2 {
|
||||
loadConfig(os.Args[1], c)
|
||||
} else {
|
||||
log.Fatal("Missing config file")
|
||||
}
|
||||
|
||||
if c.FFmpeg == "" || c.FFprobe == "" || c.TempDir == "" {
|
||||
log.Fatal("Missing critical param -- check config file")
|
||||
}
|
||||
|
||||
log.Println("Starting VOD server")
|
||||
|
||||
// get executable paths
|
||||
ffmpeg := os.Getenv("FFMPEG")
|
||||
if ffmpeg == "" {
|
||||
ffmpeg = "ffmpeg"
|
||||
}
|
||||
|
||||
ffprobe := os.Getenv("FFPROBE")
|
||||
if ffprobe == "" {
|
||||
ffprobe = "ffprobe"
|
||||
}
|
||||
|
||||
// get tempdir
|
||||
tempdir := os.Getenv("GOVOD_TEMPDIR")
|
||||
if tempdir == "" {
|
||||
tempdir = "/tmp/go-vod"
|
||||
}
|
||||
|
||||
// get port
|
||||
bind := os.Getenv("GOVOD_BIND")
|
||||
if bind == "" {
|
||||
bind = ":47788"
|
||||
}
|
||||
|
||||
h := NewHandler(&Config{
|
||||
ffmpeg: ffmpeg,
|
||||
ffprobe: ffprobe,
|
||||
tempdir: tempdir,
|
||||
chunkSize: 3,
|
||||
lookBehind: 5,
|
||||
goalBufferMin: 1,
|
||||
goalBufferMax: 4,
|
||||
streamIdleTime: 60,
|
||||
managerIdleTime: 60,
|
||||
})
|
||||
|
||||
h := NewHandler(c)
|
||||
http.Handle("/", h)
|
||||
http.ListenAndServe(bind, nil)
|
||||
http.ListenAndServe(c.Bind, nil)
|
||||
|
||||
log.Println("Exiting VOD server")
|
||||
h.Close()
|
||||
|
|
|
@ -49,7 +49,7 @@ func NewManager(c *Config, path string, id string, close chan string) (*Manager,
|
|||
h := fnv.New32a()
|
||||
h.Write([]byte(path))
|
||||
ph := fmt.Sprint(h.Sum32())
|
||||
m.tempDir = fmt.Sprintf("%s/%s-%s", m.c.tempdir, id, ph)
|
||||
m.tempDir = fmt.Sprintf("%s/%s-%s", m.c.TempDir, id, ph)
|
||||
|
||||
// Delete temp dir if exists
|
||||
os.RemoveAll(m.tempDir)
|
||||
|
@ -64,7 +64,7 @@ func NewManager(c *Config, path string, id string, close chan string) (*Manager,
|
|||
m.probe.BitRate *= 2
|
||||
}
|
||||
|
||||
m.numChunks = int(math.Ceil(m.probe.Duration.Seconds() / float64(c.chunkSize)))
|
||||
m.numChunks = int(math.Ceil(m.probe.Duration.Seconds() / float64(c.ChunkSize)))
|
||||
|
||||
// Possible streams
|
||||
m.streams["360p"] = &Stream{c: c, m: m, quality: "360p", height: 360, width: 640, bitrate: 500000}
|
||||
|
@ -134,7 +134,7 @@ func NewManager(c *Config, path string, id string, close chan string) (*Manager,
|
|||
}
|
||||
|
||||
// Nothing done for 5 minutes
|
||||
if m.inactive >= m.c.managerIdleTime/5 {
|
||||
if m.inactive >= m.c.ManagerIdleTime/5 {
|
||||
t.Stop()
|
||||
m.Destroy()
|
||||
m.close <- m.id
|
||||
|
@ -247,7 +247,7 @@ func (m *Manager) ffprobe() error {
|
|||
}
|
||||
|
||||
ctx, _ := context.WithDeadline(context.TODO(), time.Now().Add(5*time.Second))
|
||||
cmd := exec.CommandContext(ctx, m.c.ffprobe, args...)
|
||||
cmd := exec.CommandContext(ctx, m.c.FFprobe, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
|
|
32
stream.go
32
stream.go
|
@ -63,7 +63,7 @@ func (s *Stream) Run() {
|
|||
s.mutex.Lock()
|
||||
// Prune chunks
|
||||
for id := range s.chunks {
|
||||
if id < s.goal-s.c.goalBufferMax {
|
||||
if id < s.goal-s.c.GoalBufferMax {
|
||||
s.pruneChunk(id)
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ func (s *Stream) Run() {
|
|||
s.inactive++
|
||||
|
||||
// Nothing done for 2 minutes
|
||||
if s.inactive >= s.c.streamIdleTime/5 && s.coder != nil {
|
||||
if s.inactive >= s.c.StreamIdleTime/5 && s.coder != nil {
|
||||
t.Stop()
|
||||
s.clear()
|
||||
}
|
||||
|
@ -119,14 +119,14 @@ func (s *Stream) ServeList(w http.ResponseWriter, r *http.Request) error {
|
|||
w.Write([]byte("#EXT-X-VERSION:4\n"))
|
||||
w.Write([]byte("#EXT-X-MEDIA-SEQUENCE:0\n"))
|
||||
w.Write([]byte("#EXT-X-PLAYLIST-TYPE:VOD\n"))
|
||||
w.Write([]byte(fmt.Sprintf("#EXT-X-TARGETDURATION:%d\n", s.c.chunkSize)))
|
||||
w.Write([]byte(fmt.Sprintf("#EXT-X-TARGETDURATION:%d\n", s.c.ChunkSize)))
|
||||
|
||||
query := GetQueryString(r)
|
||||
|
||||
duration := s.m.probe.Duration.Seconds()
|
||||
i := 0
|
||||
for duration > 0 {
|
||||
size := float64(s.c.chunkSize)
|
||||
size := float64(s.c.ChunkSize)
|
||||
if duration < size {
|
||||
size = duration
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ func (s *Stream) ServeList(w http.ResponseWriter, r *http.Request) error {
|
|||
w.Write([]byte(fmt.Sprintf("#EXTINF:%.3f, nodesc\n", size)))
|
||||
w.Write([]byte(fmt.Sprintf("%s-%06d.ts%s\n", s.quality, i, query)))
|
||||
|
||||
duration -= float64(s.c.chunkSize)
|
||||
duration -= float64(s.c.ChunkSize)
|
||||
i++
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ func (s *Stream) ServeChunk(w http.ResponseWriter, id int) error {
|
|||
|
||||
// Will have this soon enough
|
||||
foundBehind := false
|
||||
for i := id - 1; i > id-s.c.lookBehind && i >= 0; i-- {
|
||||
for i := id - 1; i > id-s.c.LookBehind && i >= 0; i-- {
|
||||
if _, ok := s.chunks[i]; ok {
|
||||
foundBehind = true
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ func (s *Stream) ServeFullVideo(w http.ResponseWriter, r *http.Request) error {
|
|||
"-movflags", "frag_keyframe+empty_moov+faststart", "-f", "mov", "pipe:1",
|
||||
}...)
|
||||
|
||||
coder := exec.Command(s.c.ffmpeg, args...)
|
||||
coder := exec.Command(s.c.FFmpeg, args...)
|
||||
log.Printf("%s-%s: %s", s.m.id, s.quality, strings.Join(coder.Args[:], " "))
|
||||
|
||||
cmdStdOut, err := coder.StdoutPipe()
|
||||
|
@ -350,7 +350,7 @@ func (s *Stream) restartAtChunk(w http.ResponseWriter, id int) {
|
|||
chunk := s.createChunk(id) // create first chunk
|
||||
|
||||
// Start the transcoder
|
||||
s.goal = id + s.c.goalBufferMax
|
||||
s.goal = id + s.c.GoalBufferMax
|
||||
s.transcode(id)
|
||||
|
||||
s.waitForChunk(w, chunk) // this is also a request
|
||||
|
@ -372,11 +372,11 @@ func (s *Stream) transcodeArgs(startAt float64) []string {
|
|||
CV := "libx264"
|
||||
|
||||
// Check whether hwaccel should be used
|
||||
if os.Getenv("VAAPI") == "1" {
|
||||
if s.c.VAAPI {
|
||||
CV = "h264_vaapi"
|
||||
extra := "-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi"
|
||||
args = append(args, strings.Split(extra, " ")...)
|
||||
} else if os.Getenv("NVENC") == "1" {
|
||||
} else if s.c.NVENC {
|
||||
CV = "h264_nvenc"
|
||||
extra := "-hwaccel cuda -hwaccel_output_format cuda"
|
||||
args = append(args, strings.Split(extra, " ")...)
|
||||
|
@ -481,7 +481,7 @@ func (s *Stream) transcode(startId int) {
|
|||
// This ensures that the keyframes are aligned
|
||||
startId--
|
||||
}
|
||||
startAt := float64(startId * s.c.chunkSize)
|
||||
startAt := float64(startId * s.c.ChunkSize)
|
||||
|
||||
args := s.transcodeArgs(startAt)
|
||||
|
||||
|
@ -489,15 +489,15 @@ func (s *Stream) transcode(startId int) {
|
|||
args = append(args, []string{
|
||||
"-avoid_negative_ts", "disabled",
|
||||
"-f", "hls",
|
||||
"-hls_time", fmt.Sprintf("%d", s.c.chunkSize),
|
||||
"-force_key_frames", fmt.Sprintf("expr:gte(t,n_forced*%d)", s.c.chunkSize),
|
||||
"-hls_time", fmt.Sprintf("%d", s.c.ChunkSize),
|
||||
"-force_key_frames", fmt.Sprintf("expr:gte(t,n_forced*%d)", s.c.ChunkSize),
|
||||
"-hls_segment_type", "mpegts",
|
||||
"-start_number", fmt.Sprintf("%d", startId),
|
||||
"-hls_segment_filename", s.getTsPath(-1),
|
||||
"-",
|
||||
}...)
|
||||
|
||||
s.coder = exec.Command(s.c.ffmpeg, args...)
|
||||
s.coder = exec.Command(s.c.FFmpeg, args...)
|
||||
log.Printf("%s-%s: %s", s.m.id, s.quality, strings.Join(s.coder.Args[:], " "))
|
||||
|
||||
cmdStdOut, err := s.coder.StdoutPipe()
|
||||
|
@ -520,9 +520,9 @@ func (s *Stream) transcode(startId int) {
|
|||
}
|
||||
|
||||
func (s *Stream) checkGoal(id int) {
|
||||
goal := id + s.c.goalBufferMin
|
||||
goal := id + s.c.GoalBufferMin
|
||||
if goal > s.goal {
|
||||
s.goal = id + s.c.goalBufferMax
|
||||
s.goal = id + s.c.GoalBufferMax
|
||||
|
||||
// resume encoding
|
||||
if s.coder != nil {
|
||||
|
|
Loading…
Reference in New Issue