move gst pipelines to codec.

This commit is contained in:
Miroslav Šedivý 2021-02-05 15:10:41 +01:00
parent 36b7eca43c
commit ebd7e7c065
4 changed files with 92 additions and 162 deletions

View File

@ -13,10 +13,8 @@ import (
"unsafe" "unsafe"
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
"demodesk/neko/internal/types/codec"
) )
// Pipeline is a wrapper for a GStreamer Pipeline
type Pipeline struct { type Pipeline struct {
Pipeline *C.GstElement Pipeline *C.GstElement
Sample chan types.Sample Sample chan types.Sample
@ -28,127 +26,11 @@ var pipelines = make(map[int]*Pipeline)
var pipelinesLock sync.Mutex var pipelinesLock sync.Mutex
var registry *C.GstRegistry var registry *C.GstRegistry
const (
videoSrc = "ximagesrc display-name=%s show-pointer=false use-damage=false ! video/x-raw ! videoconvert ! queue ! "
audioSrc = "pulsesrc device=%s ! audio/x-raw,channels=2 ! audioconvert ! "
appSink = " ! appsink name=appsink"
)
func init() { func init() {
C.gstreamer_init() C.gstreamer_init()
registry = C.gst_registry_get() registry = C.gst_registry_get()
} }
func GetRTMPPipeline(audioDevice string, videoDisplay string, pipelineSrc string) string {
video := fmt.Sprintf(videoSrc, videoDisplay)
audio := fmt.Sprintf(audioSrc, audioDevice)
var pipelineStr string
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc, audioDevice, videoDisplay)
} else {
pipelineStr = fmt.Sprintf("flvmux name=mux ! rtmpsink location='{url} live=1' %s audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. %s x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! mux.", audio, video)
}
return pipelineStr
}
func GetJPEGPipeline(videoDisplay string, pipelineSrc string, rate string, quality string) string {
var pipelineStr string
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc, videoDisplay)
} else {
pipelineStr = fmt.Sprintf("ximagesrc display-name=%s show-pointer=true use-damage=false ! videoconvert ! videoscale ! videorate ! video/x-raw,framerate=%s ! jpegenc quality=%s" + appSink, videoDisplay, rate, quality)
}
return pipelineStr
}
func GetAppPipeline(codecRTP codec.RTPCodec, pipelineDevice string, pipelineSrc string) (string, error) {
if pipelineSrc != "" {
return fmt.Sprintf(pipelineSrc + appSink, pipelineDevice), nil
}
var pipelineStr string
switch codecRTP.Name {
case "vp8":
// https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html
// gstreamer1.0-plugins-good
if err := CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc + "vp8enc cpu-used=16 threads=4 deadline=1 error-resilient=partitions keyframe-max-dist=15 static-threshold=20" + appSink, pipelineDevice)
case "vp9":
// https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html
// gstreamer1.0-plugins-good
if err := CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(videoSrc + "vp9enc cpu-used=16 threads=4 deadline=1 keyframe-max-dist=15 static-threshold=20" + appSink, pipelineDevice)
case "h264":
var err error
if err = CheckPlugins([]string{"ximagesrc"}); err != nil {
return "", err
}
// https://gstreamer.freedesktop.org/documentation/x264/index.html
// gstreamer1.0-plugins-ugly
if err = CheckPlugins([]string{"x264"}); err == nil {
pipelineStr = fmt.Sprintf(videoSrc + "video/x-raw,format=I420 ! x264enc threads=4 bitrate=4096 key-int-max=15 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream" + appSink, pipelineDevice)
break
}
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html
// gstreamer1.0-plugins-bad
if err = CheckPlugins([]string{"openh264"}); err == nil {
pipelineStr = fmt.Sprintf(videoSrc + "openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000 ! video/x-h264,stream-format=byte-stream" + appSink, pipelineDevice)
break
}
return "", err
case "opus":
// https://gstreamer.freedesktop.org/documentation/opus/opusenc.html
// gstreamer1.0-plugins-base
if err := CheckPlugins([]string{"pulseaudio", "opus"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc + "opusenc bitrate=128000" + appSink, pipelineDevice)
case "g722":
// https://gstreamer.freedesktop.org/documentation/libav/avenc_g722.html
// gstreamer1.0-libav
if err := CheckPlugins([]string{"pulseaudio", "libav"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc + "avenc_g722" + appSink, pipelineDevice)
case "pcmu":
// https://gstreamer.freedesktop.org/documentation/mulaw/mulawenc.html
// gstreamer1.0-plugins-good
if err := CheckPlugins([]string{"pulseaudio", "mulaw"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc + "audio/x-raw, rate=8000 ! mulawenc" + appSink, pipelineDevice)
case "pcma":
// https://gstreamer.freedesktop.org/documentation/alaw/alawenc.html
// gstreamer1.0-plugins-good
if err := CheckPlugins([]string{"pulseaudio", "alaw"}); err != nil {
return "", err
}
pipelineStr = fmt.Sprintf(audioSrc + "audio/x-raw, rate=8000 ! alawenc" + appSink, pipelineDevice)
default:
return "", fmt.Errorf("unknown codec %s", codecRTP.Name)
}
return pipelineStr, nil
}
// CreatePipeline creates a GStreamer Pipeline
func CreatePipeline(pipelineStr string) (*Pipeline, error) { func CreatePipeline(pipelineStr string) (*Pipeline, error) {
pipelineStrUnsafe := C.CString(pipelineStr) pipelineStrUnsafe := C.CString(pipelineStr)
defer C.free(unsafe.Pointer(pipelineStrUnsafe)) defer C.free(unsafe.Pointer(pipelineStrUnsafe))
@ -177,17 +59,14 @@ func CreatePipeline(pipelineStr string) (*Pipeline, error) {
return p, nil return p, nil
} }
// Start starts the GStreamer Pipeline
func (p *Pipeline) Start() { func (p *Pipeline) Start() {
C.gstreamer_send_start_pipeline(p.Pipeline, C.int(p.id)) C.gstreamer_send_start_pipeline(p.Pipeline, C.int(p.id))
} }
// Play starts the GStreamer Pipeline
func (p *Pipeline) Play() { func (p *Pipeline) Play() {
C.gstreamer_send_play_pipeline(p.Pipeline) C.gstreamer_send_play_pipeline(p.Pipeline)
} }
// Stop stops the GStreamer Pipeline
func (p *Pipeline) Stop() { func (p *Pipeline) Stop() {
C.gstreamer_send_stop_pipeline(p.Pipeline) C.gstreamer_send_stop_pipeline(p.Pipeline)
} }

View File

@ -1,6 +1,7 @@
package capture package capture
import ( import (
"fmt"
"sync" "sync"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -8,7 +9,6 @@ import (
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
"demodesk/neko/internal/config" "demodesk/neko/internal/config"
"demodesk/neko/internal/capture/gst"
) )
type CaptureManagerCtx struct { type CaptureManagerCtx struct {
@ -25,37 +25,59 @@ type CaptureManagerCtx struct {
func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCtx { func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCtx {
logger := log.With().Str("module", "capture").Logger() logger := log.With().Str("module", "capture").Logger()
broadcastPipeline := gst.GetRTMPPipeline( broadcastPipeline := config.BroadcastPipeline
config.Device, if broadcastPipeline == "" {
config.Display, broadcastPipeline = fmt.Sprintf(
config.BroadcastPipeline, "flvmux name=mux ! rtmpsink location='{url} live=1' " +
"pulsesrc device=%s " +
"! audio/x-raw,channels=2 " +
"! audioconvert " +
"! queue " +
"! voaacenc " +
"! mux. " +
"ximagesrc display-name=%s show-pointer=true use-damage=false " +
"! video/x-raw " +
"! videoconvert " +
"! queue " +
"! x264enc threads=4 bitrate=4096 key-int-max=15 byte-stream=true byte-stream=true tune=zerolatency speed-preset=veryfast " +
"! mux.", config.Device, config.Display,
) )
screencastPipeline := gst.GetJPEGPipeline(
config.Display,
config.ScreencastPipeline,
config.ScreencastRate,
config.ScreencastQuality,
)
audioPipeline, err := gst.GetAppPipeline(
config.AudioCodec,
config.Device,
config.AudioParams,
)
if err != nil {
logger.Panic().Err(err).Msg("unable to get pipeline")
} }
videoPipeline, err := gst.GetAppPipeline( screencastPipeline := config.ScreencastPipeline
config.VideoCodec, if screencastPipeline == "" {
config.Display, screencastPipeline = fmt.Sprintf(
config.VideoParams, "ximagesrc display-name=%s show-pointer=true use-damage=false " +
"! video/x-raw,framerate=%s " +
"! videoconvert " +
"! queue " +
"! jpegenc quality=%s " +
"! appsink name=appsink", config.Display, config.ScreencastRate, config.ScreencastQuality,
) )
}
if err != nil { audioPipeline := config.AudioPipeline
logger.Panic().Err(err).Msg("unable to get pipeline") if audioPipeline == "" {
audioPipeline = fmt.Sprintf(
"pulsesrc device=%s " +
"! audio/x-raw,channels=2 " +
"! audioconvert " +
"! queue " +
"! %s " +
"! appsink name=appsink", config.Device, config.AudioCodec.Pipeline,
)
}
videoPipeline := config.VideoPipeline
if videoPipeline == "" {
videoPipeline = fmt.Sprintf(
"ximagesrc display-name=%s show-pointer=false use-damage=false " +
"! video/x-raw " +
"! videoconvert " +
"! queue " +
"! %s " +
"! appsink name=appsink", config.Display, config.VideoCodec.Pipeline,
)
} }
return &CaptureManagerCtx{ return &CaptureManagerCtx{

View File

@ -8,14 +8,16 @@ import (
) )
type Capture struct { type Capture struct {
Display string
Device string Device string
AudioCodec codec.RTPCodec AudioCodec codec.RTPCodec
AudioParams string AudioPipeline string
Display string
VideoCodec codec.RTPCodec VideoCodec codec.RTPCodec
VideoParams string VideoPipeline string
BroadcastPipeline string BroadcastPipeline string
Screencast bool Screencast bool
ScreencastRate string ScreencastRate string
ScreencastQuality string ScreencastQuality string
@ -139,12 +141,14 @@ func (s *Capture) Set() {
s.Device = viper.GetString("device") s.Device = viper.GetString("device")
s.AudioCodec = audioCodec s.AudioCodec = audioCodec
s.AudioParams = viper.GetString("audio") s.AudioPipeline = viper.GetString("audio")
s.Display = viper.GetString("display") s.Display = viper.GetString("display")
s.VideoCodec = videoCodec s.VideoCodec = videoCodec
s.VideoParams = viper.GetString("video") s.VideoPipeline = viper.GetString("video")
s.BroadcastPipeline = viper.GetString("broadcast_pipeline") s.BroadcastPipeline = viper.GetString("broadcast_pipeline")
s.Screencast = viper.GetBool("screencast") s.Screencast = viper.GetBool("screencast")
s.ScreencastRate = viper.GetString("screencast_rate") s.ScreencastRate = viper.GetString("screencast_rate")
s.ScreencastQuality = viper.GetString("screencast_quality") s.ScreencastQuality = viper.GetString("screencast_quality")

View File

@ -7,6 +7,7 @@ type RTPCodec struct {
PayloadType webrtc.PayloadType PayloadType webrtc.PayloadType
Type webrtc.RTPCodecType Type webrtc.RTPCodecType
Capability webrtc.RTPCodecCapability Capability webrtc.RTPCodecCapability
Pipeline string
} }
func (codec *RTPCodec) Register(engine *webrtc.MediaEngine) error { func (codec *RTPCodec) Register(engine *webrtc.MediaEngine) error {
@ -28,6 +29,9 @@ func VP8() RTPCodec {
SDPFmtpLine: "", SDPFmtpLine: "",
RTCPFeedback: nil, RTCPFeedback: nil,
}, },
// https://gstreamer.freedesktop.org/documentation/vpx/vp8enc.html
// gstreamer1.0-plugins-good
Pipeline: "vp8enc cpu-used=16 threads=4 deadline=1 error-resilient=partitions keyframe-max-dist=15 static-threshold=20",
} }
} }
@ -44,6 +48,9 @@ func VP9() RTPCodec {
SDPFmtpLine: "profile-id=0", SDPFmtpLine: "profile-id=0",
RTCPFeedback: nil, RTCPFeedback: nil,
}, },
// https://gstreamer.freedesktop.org/documentation/vpx/vp9enc.html
// gstreamer1.0-plugins-good
Pipeline: "vp9enc cpu-used=16 threads=4 deadline=1 keyframe-max-dist=15 static-threshold=20",
} }
} }
@ -60,6 +67,12 @@ func H264() RTPCodec {
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
RTCPFeedback: nil, RTCPFeedback: nil,
}, },
// https://gstreamer.freedesktop.org/documentation/x264/index.html
// gstreamer1.0-plugins-ugly
Pipeline: "video/x-raw,format=I420 ! x264enc threads=4 bitrate=4096 key-int-max=15 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream",
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html
// gstreamer1.0-plugins-bad
//Pipeline: "openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000 ! video/x-h264,stream-format=byte-stream",
} }
} }
@ -75,6 +88,9 @@ func Opus() RTPCodec {
SDPFmtpLine: "", SDPFmtpLine: "",
RTCPFeedback: nil, RTCPFeedback: nil,
}, },
// https://gstreamer.freedesktop.org/documentation/opus/opusenc.html
// gstreamer1.0-plugins-base
Pipeline: "opusenc bitrate=128000",
} }
} }
@ -90,6 +106,9 @@ func G722() RTPCodec {
SDPFmtpLine: "", SDPFmtpLine: "",
RTCPFeedback: nil, RTCPFeedback: nil,
}, },
// https://gstreamer.freedesktop.org/documentation/libav/avenc_g722.html
// gstreamer1.0-libav
Pipeline: "avenc_g722",
} }
} }
@ -105,6 +124,9 @@ func PCMU() RTPCodec {
SDPFmtpLine: "", SDPFmtpLine: "",
RTCPFeedback: nil, RTCPFeedback: nil,
}, },
// https://gstreamer.freedesktop.org/documentation/mulaw/mulawenc.html
// gstreamer1.0-plugins-good
Pipeline: "audio/x-raw, rate=8000 ! mulawenc",
} }
} }
@ -120,5 +142,8 @@ func PCMA() RTPCodec {
SDPFmtpLine: "", SDPFmtpLine: "",
RTCPFeedback: nil, RTCPFeedback: nil,
}, },
// https://gstreamer.freedesktop.org/documentation/alaw/alawenc.html
// gstreamer1.0-plugins-good
Pipeline: "audio/x-raw, rate=8000 ! alawenc",
} }
} }