mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
add video config evaluation.
This commit is contained in:
parent
78b6264494
commit
d8c031af4d
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module demodesk/neko
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/PaesslerAG/gval v1.1.0 // indirect
|
||||||
github.com/go-chi/chi v1.5.4
|
github.com/go-chi/chi v1.5.4
|
||||||
github.com/go-chi/cors v1.1.1
|
github.com/go-chi/cors v1.1.1
|
||||||
github.com/google/uuid v1.2.0 // indirect
|
github.com/google/uuid v1.2.0 // indirect
|
||||||
|
3
go.sum
3
go.sum
@ -14,6 +14,9 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/PaesslerAG/gval v1.1.0 h1:k3RuxeZDO3eejD4cMPSt+74tUSvTnbGvLx0df4mdwFc=
|
||||||
|
github.com/PaesslerAG/gval v1.1.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
|
||||||
|
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"demodesk/neko/internal/config"
|
"demodesk/neko/internal/config"
|
||||||
"demodesk/neko/internal/types"
|
"demodesk/neko/internal/types"
|
||||||
"demodesk/neko/internal/types/codec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CaptureManagerCtx struct {
|
type CaptureManagerCtx struct {
|
||||||
@ -56,6 +55,37 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videos := map[string]*StreamManagerCtx{}
|
||||||
|
videoIDs := []string{}
|
||||||
|
for key, pipelineConf := range config.Video {
|
||||||
|
codec, err := pipelineConf.GetCodec()
|
||||||
|
if err != nil {
|
||||||
|
logger.Panic().Err(err).Str("video_key", key).Msg("unable to get video codec")
|
||||||
|
}
|
||||||
|
|
||||||
|
createPipeline := func() string {
|
||||||
|
screen := desktop.GetScreenSize()
|
||||||
|
|
||||||
|
pipeline, err := pipelineConf.GetPipeline(*screen)
|
||||||
|
if err != nil {
|
||||||
|
logger.Panic().Err(err).Str("video_key", key).Msg("unable to get video pipeline")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"ximagesrc display-name=%s show-pointer=false use-damage=false "+
|
||||||
|
"! %s "+
|
||||||
|
"! appsink name=appsink", config.Display, pipeline,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger function to catch evaluation errors at startup
|
||||||
|
_ = createPipeline()
|
||||||
|
|
||||||
|
// append to videos
|
||||||
|
videos[key] = streamNew(codec, createPipeline)
|
||||||
|
videoIDs = append(videoIDs, key)
|
||||||
|
}
|
||||||
|
|
||||||
return &CaptureManagerCtx{
|
return &CaptureManagerCtx{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
desktop: desktop,
|
desktop: desktop,
|
||||||
@ -76,65 +106,8 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
|||||||
"! appsink name=appsink", config.AudioDevice, config.AudioCodec.Pipeline,
|
"! appsink name=appsink", config.AudioDevice, config.AudioCodec.Pipeline,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
videos: map[string]*StreamManagerCtx{
|
videos: videos,
|
||||||
"hd": streamNew(codec.VP8(), func() string {
|
videoIDs: videoIDs,
|
||||||
screen := desktop.GetScreenSize()
|
|
||||||
bitrate := int((screen.Width * screen.Height * 6) / 4)
|
|
||||||
buffer := bitrate / 1000
|
|
||||||
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"ximagesrc display-name=%s show-pointer=false use-damage=false "+
|
|
||||||
"! video/x-raw,framerate=25/1 "+
|
|
||||||
"! videoconvert "+
|
|
||||||
"! queue "+
|
|
||||||
"! vp8enc end-usage=cbr target-bitrate=%d cpu-used=4 threads=4 deadline=1 undershoot=95 keyframe-max-dist=25 min-quantizer=3 max-quantizer=32 buffer-size=%d buffer-initial-size=%d buffer-optimal-size=%d "+
|
|
||||||
"! appsink name=appsink", config.Display, bitrate, buffer*6, buffer*4, buffer*5,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
"hq": streamNew(codec.VP8(), func() string {
|
|
||||||
screen := desktop.GetScreenSize()
|
|
||||||
bitrate := int((screen.Width * screen.Height * 6) / 4) / 2
|
|
||||||
buffer := bitrate / 1000
|
|
||||||
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"ximagesrc display-name=%s show-pointer=false use-damage=false "+
|
|
||||||
"! video/x-raw,framerate=15/1 "+
|
|
||||||
"! videoconvert "+
|
|
||||||
"! queue "+
|
|
||||||
"! vp8enc end-usage=cbr target-bitrate=%d cpu-used=4 threads=4 deadline=1 undershoot=95 keyframe-max-dist=25 min-quantizer=3 max-quantizer=32 buffer-size=%d buffer-initial-size=%d buffer-optimal-size=%d "+
|
|
||||||
"! appsink name=appsink", config.Display, bitrate, buffer*6, buffer*4, buffer*5,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
"mq": streamNew(codec.VP8(), func() string {
|
|
||||||
screen := desktop.GetScreenSize()
|
|
||||||
bitrate := int((screen.Width * screen.Height * 6) / 4) / 3
|
|
||||||
buffer := bitrate / 1000
|
|
||||||
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"ximagesrc display-name=%s show-pointer=false use-damage=false "+
|
|
||||||
"! video/x-raw,framerate=10/1 "+
|
|
||||||
"! videoconvert "+
|
|
||||||
"! queue "+
|
|
||||||
"! vp8enc end-usage=cbr target-bitrate=%d cpu-used=4 threads=4 deadline=1 undershoot=95 keyframe-max-dist=25 min-quantizer=3 max-quantizer=32 buffer-size=%d buffer-initial-size=%d buffer-optimal-size=%d "+
|
|
||||||
"! appsink name=appsink", config.Display, bitrate, buffer*6, buffer*4, buffer*5,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
"lq": streamNew(codec.VP8(), func() string {
|
|
||||||
screen := desktop.GetScreenSize()
|
|
||||||
bitrate := int((screen.Width * screen.Height * 6) / 4) / 4
|
|
||||||
buffer := bitrate / 1000
|
|
||||||
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"ximagesrc display-name=%s show-pointer=false use-damage=false "+
|
|
||||||
"! video/x-raw,framerate=5/1 "+
|
|
||||||
"! videoconvert "+
|
|
||||||
"! queue "+
|
|
||||||
"! vp8enc end-usage=cbr target-bitrate=%d cpu-used=4 threads=4 deadline=1 undershoot=95 keyframe-max-dist=25 min-quantizer=3 max-quantizer=32 buffer-size=%d buffer-initial-size=%d buffer-optimal-size=%d "+
|
|
||||||
"! appsink name=appsink", config.Display, bitrate, buffer*6, buffer*4, buffer*5,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
videoIDs: []string{"hd", "hq", "mq", "lq"},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,16 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"demodesk/neko/internal/types"
|
||||||
"demodesk/neko/internal/types/codec"
|
"demodesk/neko/internal/types/codec"
|
||||||
|
"demodesk/neko/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Capture struct {
|
type Capture struct {
|
||||||
Display string
|
Display string
|
||||||
|
|
||||||
|
Video map[string] types.VideoConfig
|
||||||
|
|
||||||
AudioDevice string
|
AudioDevice string
|
||||||
AudioCodec codec.RTPCodec
|
AudioCodec codec.RTPCodec
|
||||||
AudioPipeline string
|
AudioPipeline string
|
||||||
@ -94,6 +98,14 @@ func (s *Capture) Set() {
|
|||||||
// Display is provided by env variable
|
// Display is provided by env variable
|
||||||
s.Display = os.Getenv("DISPLAY")
|
s.Display = os.Getenv("DISPLAY")
|
||||||
|
|
||||||
|
// video
|
||||||
|
if err := viper.UnmarshalKey("capture.video", &s.Video, viper.DecodeHook(
|
||||||
|
utils.JsonStringAutoDecode(s.Video),
|
||||||
|
)); err != nil {
|
||||||
|
log.Warn().Err(err).Msgf("unable to parse video settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
// audio
|
||||||
s.AudioDevice = viper.GetString("capture.audio.device")
|
s.AudioDevice = viper.GetString("capture.audio.device")
|
||||||
s.AudioPipeline = viper.GetString("capture.audio.pipeline")
|
s.AudioPipeline = viper.GetString("capture.audio.pipeline")
|
||||||
|
|
||||||
@ -112,11 +124,13 @@ func (s *Capture) Set() {
|
|||||||
s.AudioCodec = codec.Opus()
|
s.AudioCodec = codec.Opus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// broadcast
|
||||||
s.BroadcastAudioBitrate = viper.GetInt("capture.broadcast.audio_bitrate")
|
s.BroadcastAudioBitrate = viper.GetInt("capture.broadcast.audio_bitrate")
|
||||||
s.BroadcastVideoBitrate = viper.GetInt("capture.broadcast.video_bitrate")
|
s.BroadcastVideoBitrate = viper.GetInt("capture.broadcast.video_bitrate")
|
||||||
s.BroadcastPreset = viper.GetString("capture.broadcast.preset")
|
s.BroadcastPreset = viper.GetString("capture.broadcast.preset")
|
||||||
s.BroadcastPipeline = viper.GetString("capture.broadcast.pipeline")
|
s.BroadcastPipeline = viper.GetString("capture.broadcast.pipeline")
|
||||||
|
|
||||||
|
// screencast
|
||||||
s.ScreencastEnabled = viper.GetBool("capture.screencast.enabled")
|
s.ScreencastEnabled = viper.GetBool("capture.screencast.enabled")
|
||||||
s.ScreencastRate = viper.GetString("capture.screencast.rate")
|
s.ScreencastRate = viper.GetString("capture.screencast.rate")
|
||||||
s.ScreencastQuality = viper.GetString("capture.screencast.quality")
|
s.ScreencastQuality = viper.GetString("capture.screencast.quality")
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v3/pkg/media"
|
"github.com/pion/webrtc/v3/pkg/media"
|
||||||
|
"github.com/PaesslerAG/gval"
|
||||||
|
|
||||||
"demodesk/neko/internal/types/codec"
|
"demodesk/neko/internal/types/codec"
|
||||||
)
|
)
|
||||||
@ -43,3 +48,98 @@ type CaptureManager interface {
|
|||||||
Video(videoID string) (StreamManager, bool)
|
Video(videoID string) (StreamManager, bool)
|
||||||
VideoIDs() []string
|
VideoIDs() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VideoConfig struct {
|
||||||
|
Codec string `mapstructure:"codec"`
|
||||||
|
Width string `mapstructure:"width"` // expression
|
||||||
|
Height string `mapstructure:"height"` // expression
|
||||||
|
Fps string `mapstructure:"fps"` // expression
|
||||||
|
GstEncoder string `mapstructure:"gst_encoder"`
|
||||||
|
GstParams map[string]string `mapstructure:"gst_params"` // map of expressions
|
||||||
|
GstPipeline string `mapstructure:"gst_pipeline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *VideoConfig) GetCodec() (codec.RTPCodec, error) {
|
||||||
|
switch strings.ToLower(config.Codec) {
|
||||||
|
case "vp8":
|
||||||
|
return codec.VP8(), nil
|
||||||
|
case "vp9":
|
||||||
|
return codec.VP9(), nil
|
||||||
|
case "h264":
|
||||||
|
return codec.H264(), nil
|
||||||
|
default:
|
||||||
|
return codec.RTPCodec{}, fmt.Errorf("unknown codec")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *VideoConfig) GetPipeline(screen ScreenSize) (string, error) {
|
||||||
|
if config.GstPipeline != "" {
|
||||||
|
return config.GstPipeline, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values := map[string]interface{}{
|
||||||
|
"width": screen.Width,
|
||||||
|
"height": screen.Height,
|
||||||
|
"fps": screen.Rate,
|
||||||
|
}
|
||||||
|
|
||||||
|
language := []gval.Language{
|
||||||
|
gval.Function("round", func(args ...interface{}) (interface{}, error) {
|
||||||
|
return (int)(math.Round(args[0].(float64))), nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// get fps pipeline
|
||||||
|
fpsPipeline := "video/x-raw ! videoconvert ! queue"
|
||||||
|
if config.Fps != "" {
|
||||||
|
var err error
|
||||||
|
val, err := gval.Evaluate(config.Fps, values, language...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != nil {
|
||||||
|
// TODO: To fraction.
|
||||||
|
fpsPipeline = fmt.Sprintf("video/x-raw,framerate=%v ! videoconvert ! queue", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get scale pipeline
|
||||||
|
scalePipeline := ""
|
||||||
|
if config.Width != "" && config.Height != "" {
|
||||||
|
w, err := gval.Evaluate(config.Width, values, language...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := gval.Evaluate(config.Height, values, language...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if w != nil && h != nil {
|
||||||
|
scalePipeline = fmt.Sprintf("! videoscale ! video/x-raw,width=%v,height=%v ! queue", w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get encoder pipeline
|
||||||
|
encPipeline := fmt.Sprintf("! %s", config.GstEncoder)
|
||||||
|
for key, expr := range config.GstParams {
|
||||||
|
if expr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := gval.Evaluate(expr, values, language...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != nil {
|
||||||
|
encPipeline += fmt.Sprintf(" %s=%v", key, val)
|
||||||
|
} else {
|
||||||
|
encPipeline += fmt.Sprintf(" %s=%s", key, expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s %s %s", fpsPipeline, scalePipeline, encPipeline), nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user