update pipelines config settings extractor.

This commit is contained in:
Miroslav Šedivý 2021-03-30 00:36:13 +02:00
parent 051ab9f426
commit d84297fbec
3 changed files with 102 additions and 46 deletions

View File

@ -2,6 +2,7 @@ package capture
import (
"fmt"
"strings"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@ -56,19 +57,20 @@ 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")
}
for key, cnf := range config.VideoPipelines {
pipelineConf := cnf
createPipeline := func() string {
screen := desktop.GetScreenSize()
if pipelineConf.GstPipeline != "" {
return strings.Replace(pipelineConf.GstPipeline, "{display}", config.Display, 1)
}
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")
logger.Panic().Err(err).
Str("video_id", key).
Msg("unable to get video pipeline")
}
return fmt.Sprintf(
@ -78,11 +80,14 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
}
// trigger function to catch evaluation errors at startup
_ = createPipeline()
pipeline := createPipeline()
logger.Info().
Str("video_id", key).
Str("pipeline", pipeline).
Msg("syntax check for video stream pipeline passed")
// append to videos
videos[key] = streamNew(codec, createPipeline)
videoIDs = append(videoIDs, key)
videos[key] = streamNew(config.VideoCodec, createPipeline)
}
return &CaptureManagerCtx{
@ -106,7 +111,7 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
)
}),
videos: videos,
videoIDs: videoIDs,
videoIDs: config.VideoIDs,
}
}

View File

@ -15,7 +15,9 @@ import (
type Capture struct {
Display string
Video map[string] types.VideoConfig
VideoCodec codec.RTPCodec
VideoIDs []string
VideoPipelines map[string]types.VideoConfig
AudioDevice string
AudioCodec codec.RTPCodec
@ -49,6 +51,22 @@ func (Capture) Init(cmd *cobra.Command) error {
return err
}
// videos
cmd.PersistentFlags().String("capture.video.codec", "vp8", "video codec to be used")
if err := viper.BindPFlag("capture.video.codec", cmd.PersistentFlags().Lookup("capture.video.codec")); err != nil {
return err
}
cmd.PersistentFlags().StringSlice("capture.video.ids", []string{}, "ordered list of video ids")
if err := viper.BindPFlag("capture.video.ids", cmd.PersistentFlags().Lookup("capture.video.ids")); err != nil {
return err
}
cmd.PersistentFlags().String("capture.video.pipelines", "", "pipelines config in JSON used for video streaming")
if err := viper.BindPFlag("capture.video.pipelines", cmd.PersistentFlags().Lookup("capture.video.pipelines")); err != nil {
return err
}
// broadcast
cmd.PersistentFlags().Int("capture.screencast.audio_bitrate", 128, "broadcast audio bitrate in KB/s")
if err := viper.BindPFlag("capture.screencast.audio_bitrate", cmd.PersistentFlags().Lookup("capture.screencast.audio_bitrate")); err != nil {
@ -99,10 +117,42 @@ func (s *Capture) Set() {
s.Display = os.Getenv("DISPLAY")
// video
if err := viper.UnmarshalKey("capture.video", &s.Video, viper.DecodeHook(
utils.JsonStringAutoDecode(s.Video),
videoCodec := viper.GetString("capture.video.codec")
switch videoCodec {
case "vp8":
s.VideoCodec = codec.VP8()
case "vp9":
s.VideoCodec = codec.VP9()
case "h264":
s.VideoCodec = codec.H264()
default:
log.Warn().Str("codec", videoCodec).Msgf("unknown video codec, using Vp8")
s.VideoCodec = codec.VP8()
}
s.VideoIDs = viper.GetStringSlice("capture.video.ids")
if err := viper.UnmarshalKey("capture.video.pipelines", &s.VideoPipelines, viper.DecodeHook(
utils.JsonStringAutoDecode(s.VideoPipelines),
)); err != nil {
log.Warn().Err(err).Msgf("unable to parse video settings")
log.Warn().Err(err).Msgf("unable to parse video pipelines")
}
// default video
if len(s.VideoPipelines) == 0 {
log.Warn().Msgf("no video pipelines specified, using defaults")
s.VideoCodec = codec.VP8()
s.VideoPipelines = map[string]types.VideoConfig{
"main": types.VideoConfig{
GstPipeline: "ximagesrc display-name={display} show-pointer=false use-damage=false "+
"! video/x-raw "+
"! videoconvert "+
"! queue "+
"! vp8enc end-usage=cbr cpu-used=4 threads=4 deadline=1 keyframe-max-dist=25 "+
"! appsink name=appsink",
},
}
s.VideoIDs = []string{"main"}
}
// audio

View File

@ -3,6 +3,7 @@ package types
import (
"math"
"strings"
"context"
"fmt"
"github.com/pion/webrtc/v3/pkg/media"
@ -50,33 +51,17 @@ type CaptureManager interface {
}
type VideoConfig struct {
Codec string `mapstructure:"codec"`
Width string `mapstructure:"width"` // expression
Height string `mapstructure:"height"` // expression
Fps string `mapstructure:"fps"` // expression
GstPrefix string `mapstructure:"gst_prefix"` // pipeline prefix, starts with !
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")
}
GstSuffix string `mapstructure:"gst_suffix"` // pipeline suffix, starts with !
GstPipeline string `mapstructure:"gst_pipeline"` // whole pipeline as a string
}
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,
@ -92,34 +77,43 @@ func (config *VideoConfig) GetPipeline(screen ScreenSize) (string, error) {
// get fps pipeline
fpsPipeline := "! video/x-raw ! videoconvert ! queue"
if config.Fps != "" {
var err error
val, err := gval.Evaluate(config.Fps, values, language...)
eval, err := gval.Full(language...).NewEvaluable(config.Fps)
if err != nil {
return "", err
}
if val != nil {
// TODO: To fraction.
fpsPipeline = fmt.Sprintf("! video/x-raw,framerate=%v ! videoconvert ! queue", val)
val, err := eval.EvalFloat64(context.Background(), values)
if err != nil {
return "", err
}
fpsPipeline = fmt.Sprintf("! video/x-raw,framerate=%d/100 ! videoconvert ! queue", int(val*100))
}
// get scale pipeline
scalePipeline := ""
if config.Width != "" && config.Height != "" {
w, err := gval.Evaluate(config.Width, values, language...)
eval, err := gval.Full(language...).NewEvaluable(config.Width)
if err != nil {
return "", err
}
h, err := gval.Evaluate(config.Height, values, language...)
w, err := eval.EvalInt(context.Background(), values)
if err != nil {
return "", err
}
if w != nil && h != nil {
scalePipeline = fmt.Sprintf("! videoscale ! video/x-raw,width=%v,height=%v ! queue", w, h)
eval, err = gval.Full(language...).NewEvaluable(config.Height)
if err != nil {
return "", err
}
h, err := eval.EvalInt(context.Background(), values)
if err != nil {
return "", err
}
scalePipeline = fmt.Sprintf("! videoscale ! video/x-raw,width=%d,height=%d ! queue", w, h)
}
// get encoder pipeline
@ -141,5 +135,12 @@ func (config *VideoConfig) GetPipeline(screen ScreenSize) (string, error) {
}
}
return fmt.Sprintf("%s %s %s", fpsPipeline, scalePipeline, encPipeline), nil
// join strings with space
return strings.Join([]string{
fpsPipeline,
scalePipeline,
config.GstPrefix,
encPipeline,
config.GstSuffix,
}[:]," "), nil
}