package config import ( "fmt" "os" "gopkg.in/yaml.v3" ) type Config struct { LogLevel string `yaml:"log_level"` Auth bool `yaml:"auth"` AuthSecret string `yaml:"auth_secret"` Port int `yaml:"port"` UStreamer UStreamer `yaml:"ustreamer"` Video FFmpeg `yaml:"video"` WebRtc WebRtc `yaml:"webrtc"` Stream Stream `yaml:"stream"` ISOPath string `yaml:"iso_path"` NanoKVMUISupport bool `yaml:"nano_kvm_ui_support"` } type WebRtc struct { Host string `yaml:"host"` VideoPort int `yaml:"video_port"` AudioPort int `yaml:"audio_port"` } type ExtProcess struct { Path string `yaml:"path"` Args string `yaml:"args"` } type Stream struct { Source string `yaml:"source"` // mjpeg or h264 } const ( StreamSourceMjpeg = "mjpeg" StreamSourceH264 = "h264" StreamSourceHevc = "hevc" ) type StreamInput string const ( StreamInputVideo = "v" StreamInputVideoAudio = "va" ) type FFmpeg struct { Commands map[StreamInput]string `yaml:"commands"` FPS int `yaml:"fps"` Bitrate int `yaml:"bitrate"` Height int `yaml:"height"` GOP int `yaml:"gop"` Codec string `yaml:"codec"` } func (f FFmpeg) FormatCmd(cmd string) string { return fmt.Sprintf(cmd, f.Height, f.Codec, f.Bitrate*1000, f.FPS, f.GOP) } type UStreamer struct { Host string `yaml:"host"` Port int `yaml:"port"` ExtProcess } var c Config func Get() Config { return c } func Init() { c = Config{ LogLevel: "debug", Auth: false, Port: 8080, UStreamer: UStreamer{ Host: "0.0.0.0", Port: 8888, ExtProcess: ExtProcess{ Path: "/app/ustreamer", Args: "--host 0.0.0.0 --port 8888 -m BGR24 -f 60", }, }, Video: FFmpeg{ Commands: map[StreamInput]string{ StreamInputVideoAudio: "/usr/bin/arecord -D hw:0,0 -f dat -r 48000 -c 2 --buffer-size=150 | /app/ffmpeg -re -init_hw_device rkmpp=hw -filter_hw_device hw" + " -f wav -i pipe:0 -map 0:a -c:a libopus -b:a 48000 -sample_fmt s16 -ssrc 1 -payload_type 111 -f rtp -max_delay 0 -application lowdelay" + " -f rtp rtp://127.0.0.1:5006?pkt_size=1200" + " -i /dev/video0 -map 1:v -vf hwupload,scale_rkrga=h=%d:force_original_aspect_ratio=1 -c:v %s_rkmpp -flags +low_delay -b:v %d -framerate %d -g %d" + " -f rtp rtp://127.0.0.1:5004?pkt_size=1200", StreamInputVideo: "/app/ffmpeg -re -hide_banner -loglevel error -init_hw_device rkmpp=hw -filter_hw_device hw" + " -i /dev/video0 -vf hwupload,scale_rkrga=h=%d:force_original_aspect_ratio=1 -c:v %s_rkmpp -flags +low_delay -b:v %d -framerate %d -g %d" + " -f rtp rtp://127.0.0.1:5004?pkt_size=1200", }, FPS: 60, Bitrate: 6000, Height: 720, GOP: 5, Codec: StreamSourceH264, }, WebRtc: WebRtc{ Host: "127.0.0.1", VideoPort: 5004, AudioPort: 5006, }, Stream: Stream{ Source: StreamSourceH264, }, ISOPath: "/data", NanoKVMUISupport: true, } } func Load(fname string) error { yamlData, err := os.ReadFile(fname) if err != nil { return fmt.Errorf("config: couldn't read config file: %w", err) } var cfg Config err = yaml.Unmarshal(yamlData, &cfg) if err != nil { return fmt.Errorf("config: couldn't parse config file: %w", err) } c = cfg return nil } func Save(fname string) error { data, err := yaml.Marshal(c) if err != nil { return fmt.Errorf("config: failed to marshal config: %w", err) } err = os.WriteFile(fname, data, 0o660) if err != nil { return fmt.Errorf("config: failed to write config: %w", err) } return nil }