add streamsrc to capture.

This commit is contained in:
Miroslav Šedivý 2021-12-01 22:36:45 +01:00
parent a6eca45e7f
commit 5643e7abbc
4 changed files with 168 additions and 22 deletions

View File

@ -10,17 +10,23 @@ 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 {
logger zerolog.Logger logger zerolog.Logger
desktop types.DesktopManager desktop types.DesktopManager
// sinks
broadcast *BroacastManagerCtx broadcast *BroacastManagerCtx
screencast *ScreencastManagerCtx screencast *ScreencastManagerCtx
audio *StreamSinkManagerCtx audio *StreamSinkManagerCtx
videos map[string]*StreamSinkManagerCtx videos map[string]*StreamSinkManagerCtx
videoIDs []string videoIDs []string
// sources
webcam *StreamSrcManagerCtx
microphone *StreamSrcManagerCtx
} }
func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCtx { func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCtx {
@ -95,6 +101,7 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
logger: logger, logger: logger,
desktop: desktop, desktop: desktop,
// sinks
broadcast: broadcastNew(broadcastPipeline), broadcast: broadcastNew(broadcastPipeline),
screencast: screencastNew(config.ScreencastEnabled, screencastPipeline), screencast: screencastNew(config.ScreencastEnabled, screencastPipeline),
audio: streamSinkNew(config.AudioCodec, func() string { audio: streamSinkNew(config.AudioCodec, func() string {
@ -113,6 +120,21 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
}, "audio"), }, "audio"),
videos: videos, videos: videos,
videoIDs: config.VideoIDs, videoIDs: config.VideoIDs,
// sources
webcam: streamSrcNew(map[string]string{}, "webcam"), // TODO
microphone: streamSrcNew(map[string]string{
codec.Opus().Name: "appsrc format=time is-live=true do-timestamp=true name=src " +
"! application/x-rtp, payload=111, encoding-name=OPUS " +
"! rtpopusdepay " +
"! decodebin " +
"! pulsesink device=audio_input",
codec.G722().Name: "appsrc format=time is-live=true do-timestamp=true name=src " +
"! application/x-rtp clock-rate=8000 " +
"! rtpg722depay " +
"! decodebin " +
"! pulsesink device=audio_input",
}, "microphone"),
} }
} }
@ -177,6 +199,9 @@ func (manager *CaptureManagerCtx) Shutdown() error {
video.shutdown() video.shutdown()
} }
manager.webcam.shutdown()
manager.microphone.shutdown()
return nil return nil
} }
@ -200,3 +225,11 @@ func (manager *CaptureManagerCtx) Video(videoID string) (types.StreamSinkManager
func (manager *CaptureManagerCtx) VideoIDs() []string { func (manager *CaptureManagerCtx) VideoIDs() []string {
return manager.videoIDs return manager.videoIDs
} }
func (manager *CaptureManagerCtx) Webcam() types.StreamSrcManager {
return manager.webcam
}
func (manager *CaptureManagerCtx) Microphone() types.StreamSrcManager {
return manager.microphone
}

View File

@ -0,0 +1,111 @@
package capture
import (
"errors"
"sync"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"demodesk/neko/internal/capture/gst"
"demodesk/neko/internal/types"
"demodesk/neko/internal/types/codec"
)
type StreamSrcManagerCtx struct {
logger zerolog.Logger
codecPipeline map[string]string // codec -> pipeline
codec codec.RTPCodec
pipeline *gst.Pipeline
pipelineMu sync.Mutex
pipelineStr string
}
func streamSrcNew(codecPipeline map[string]string, video_id string) *StreamSrcManagerCtx {
logger := log.With().
Str("module", "capture").
Str("submodule", "stream-src").
Str("video_id", video_id).Logger()
return &StreamSrcManagerCtx{
logger: logger,
codecPipeline: codecPipeline,
}
}
func (manager *StreamSrcManagerCtx) shutdown() {
manager.logger.Info().Msgf("shutdown")
manager.Stop()
}
func (manager *StreamSrcManagerCtx) Codec() codec.RTPCodec {
return manager.codec
}
func (manager *StreamSrcManagerCtx) Start(codec codec.RTPCodec) error {
manager.pipelineMu.Lock()
defer manager.pipelineMu.Unlock()
if manager.pipeline != nil {
return types.ErrCapturePipelineAlreadyExists
}
found := false
for codecName, pipeline := range manager.codecPipeline {
if codecName == codec.Name {
manager.pipelineStr = pipeline
manager.codec = codec
found = true
break
}
}
if !found {
return errors.New("no pipeline found for a codec")
}
var err error
manager.logger.Info().
Str("codec", manager.codec.Name).
Str("src", manager.pipelineStr).
Msgf("creating pipeline")
manager.pipeline, err = gst.CreatePipeline(manager.pipelineStr)
if err != nil {
return err
}
manager.pipeline.Play()
return nil
}
func (manager *StreamSrcManagerCtx) Stop() {
manager.pipelineMu.Lock()
defer manager.pipelineMu.Unlock()
if manager.pipeline == nil {
return
}
manager.pipeline.Stop()
manager.logger.Info().Msgf("destroying pipeline")
manager.pipeline = nil
}
func (manager *StreamSrcManagerCtx) Push(bytes []byte) {
manager.pipelineMu.Lock()
defer manager.pipelineMu.Unlock()
if manager.pipeline == nil {
return
}
manager.pipeline.Push("src", bytes)
}
func (manager *StreamSrcManagerCtx) Started() bool {
return manager.pipeline != nil
}

View File

@ -43,6 +43,16 @@ type StreamSinkManager interface {
Started() bool Started() bool
} }
type StreamSrcManager interface {
Codec() codec.RTPCodec
Start(codec codec.RTPCodec) error
Stop()
Push(bytes []byte)
Started() bool
}
type CaptureManager interface { type CaptureManager interface {
Start() Start()
Shutdown() error Shutdown() error
@ -52,6 +62,9 @@ type CaptureManager interface {
Audio() StreamSinkManager Audio() StreamSinkManager
Video(videoID string) (StreamSinkManager, bool) Video(videoID string) (StreamSinkManager, bool)
VideoIDs() []string VideoIDs() []string
Webcam() StreamSrcManager
Microphone() StreamSrcManager
} }
type VideoConfig struct { type VideoConfig struct {

View File

@ -10,7 +10,6 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"demodesk/neko/internal/capture/gst"
"demodesk/neko/internal/config" "demodesk/neko/internal/config"
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
"demodesk/neko/internal/types/codec" "demodesk/neko/internal/types/codec"
@ -174,29 +173,17 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
Uint8("payload-type", uint8(track.PayloadType())). Uint8("payload-type", uint8(track.PayloadType())).
Msgf("received new track") Msgf("received new track")
pipelineStr := "appsrc format=time is-live=true do-timestamp=true name=src" // parse codec
codec, ok := codec.ParseRTC(track.Codec())
//add appropriate decoder if !ok {
switch strings.ToLower(codecName) { logger.Warn().Str("mime", track.Codec().RTPCodecCapability.MimeType).Msg("unknown codec")
case "opus":
pipelineStr += fmt.Sprintf(" ! application/x-rtp, payload=%d, encoding-name=OPUS ! rtpopusdepay ! decodebin", track.PayloadType())
case "g722":
pipelineStr += " ! application/x-rtp clock-rate=8000 ! rtpg722depay ! decodebin"
default:
logger.Panic().Msgf("Unhandled codec %s", codecName)
}
pipelineStr += " ! pulsesink device=audio_input"
logger.Info().Str("pipeline", pipelineStr).Msg("create pipeline")
pipeline, err := gst.CreatePipeline(pipelineStr)
if err != nil {
logger.Err(err).Str("pipeline", pipelineStr).Msg("unable to create pipeline")
return return
} }
pipeline.Play() // add microphone
defer pipeline.Stop() microphone := manager.capture.Microphone()
microphone.Start(codec)
defer microphone.Stop() // TODO: Ensure no new publisher took over.
// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
ticker := time.NewTicker(time.Second * 3) ticker := time.NewTicker(time.Second * 3)
@ -219,8 +206,10 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
break break
} }
pipeline.Push("src", buf[:i]) microphone.Push(buf[:i])
} }
logger.Warn().Msg("microphone connection died")
}) })
connection.OnDataChannel(func(dc *webrtc.DataChannel) { connection.OnDataChannel(func(dc *webrtc.DataChannel) {