use launch

This commit is contained in:
kaedwen
2024-04-01 18:01:50 +02:00
parent ac2e689131
commit 146e656b8c
6 changed files with 153 additions and 93 deletions
+37
View File
@@ -0,0 +1,37 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"env": {
"HTTP_STATIC": "${workspaceFolder}/static/dist",
"VIDEO_SRC_DEVICE": "/dev/v4l/by-id/usb-Microsoft_Microsoft®_LifeCam_HD-3000-video-index0",
//"AUDIO_SRC_DEVICE": "plughw:4,0,0",
"AUDIO_SRC": "pulsesrc",
"AUDIO_SINK": "pulsesink",
"INPUT_DEVICE": "/dev/input/event5",
"JINGLE_PATH": "${workspaceFolder}/audio",
"SONOS_TARGET": "Living Room",
"VIDEO_SRC_CODEC": "H264",
"VIDEO_SRC_HEIGHT": "600",
"VIDEO_SRC_WIDTH": "800",
"VIDEO_SRC_FPS": "15",
"VIDEO_SRC_BPS": "8000",
}
},
{
"name": "Launch Package Test",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/test/main.go"
}
]
}
+8 -6
View File
@@ -49,12 +49,14 @@ type ConfigHTTP struct {
}
type ConfigVideoSourceStream struct {
Source string `arg:"--video-src,env:VIDEO_SRC" default:"v4l2src"`
Device string `arg:"--video-src-device,env:VIDEO_SRC_DEVICE" default:"/dev/video0"`
Codec StreamCodec `arg:"--video-src-codec,env:VIDEO_SRC_CODEC" default:"vp8"`
Height uint `arg:"--video-src-height,env:VIDEO_SRC_HEIGHT" default:"480"`
Width uint `arg:"--video-src-width,env:VIDEO_SRC_WIDTH" default:"640"`
Queue bool `arg:"--video-src-queue,env:VIDEO_SRC_QUEUE" default:"false"`
Source string `arg:"--video-src,env:VIDEO_SRC" default:"v4l2src"`
Device string `arg:"--video-src-device,env:VIDEO_SRC_DEVICE" default:"/dev/video0"`
Codec StreamCodec `arg:"--video-src-codec,env:VIDEO_SRC_CODEC" default:"vp8"`
Height uint `arg:"--video-src-height,env:VIDEO_SRC_HEIGHT" default:"480"`
Width uint `arg:"--video-src-width,env:VIDEO_SRC_WIDTH" default:"640"`
Framerate uint `arg:"--video-src-fps,env:VIDEO_SRC_FPS" default:"30"`
Bitrate uint `arg:"--video-src-bps,env:VIDEO_SRC_BPS" default:"300"`
Queue bool `arg:"--video-src-queue,env:VIDEO_SRC_QUEUE" default:"false"`
}
type ConfigAudioSourceStream struct {
+16 -3
View File
@@ -1,6 +1,9 @@
package streamer
import "fmt"
import (
"fmt"
"strings"
)
type PipelineBuilder struct {
parts []string
@@ -18,9 +21,19 @@ func (pb *PipelineBuilder) Add(element string) *PipelineBuilder {
func (pb *PipelineBuilder) AddWithProperties(element string, properties map[string]any) *PipelineBuilder {
pl := make([]string, 0, len(properties))
for k, v := range properties {
pl = append(pl, fmt.Sprintf("%s=%v", k, v))
pl = append(pl, fmt.Sprint(k, "=", v))
}
pb.parts = append(pb.parts, fmt.Sprint(element, pl))
pb.parts = append(pb.parts, fmt.Sprint(element, " ", strings.Join(pl, " ")))
return pb
}
func (pb *PipelineBuilder) AddFilter(c *Caps) {
if c != nil {
pb.parts = append(pb.parts, c.Build())
}
}
func (pb *PipelineBuilder) Build() string {
return strings.Join(pb.parts, " ! ")
}
+55 -10
View File
@@ -38,11 +38,56 @@ func setCallback(sink *app.Sink, ch chan media.Sample) {
})
}
func CreateVideoPipelineSinkWithLaunch(_ *zap.Logger, s StreamElement) (*gst.Pipeline, <-chan media.Sample, error) {
func CreateVideoPipelineSinkWithLaunch(lg *zap.Logger, s StreamElement) (*gst.Pipeline, <-chan media.Sample, error) {
pb := NewPipelineBuilder()
pb.AddWithProperties("v4l2src", map[string]any{"device", s.})
pipeline, err := gst.NewPipelineFromString(`v4l2src device=/dev/video4 ! capsfilter caps="video/x-raw,width=(int)320,height=(int)240" ! videoconvert ! queue ! capsfilter caps="video/x-raw,format=(string)I420" ! x264enc speed-preset=ultrafast tune=zerolatency key-int-max=20 ! capsfilter caps="video/x-h264,stream-format=(string)byte-stream" ! appsink name=appsink`)
pb.AddWithProperties("v4l2src", s.Properties)
pb.AddFilter(s.SrcCaps)
pb.Add("videoconvert")
if s.Queue {
pb.Add("queue")
}
switch s.Codec {
case common.VP8:
pb.AddWithProperties("vp8enc", map[string]any{
"bitrate": s.Bitrate,
"error-resilient": "partitions",
"keyframe-max-dist": int(10),
"cpu-used": int(5),
"deadline": int(1),
"auto-alt-ref": true,
})
case common.VP9:
pb.AddWithProperties("vp9enc", map[string]any{
"bitrate": s.Bitrate,
})
case common.H264:
pb.AddFilter(NewCaps("video/x-raw", map[string]any{
"format": "I420",
}))
pb.AddWithProperties("x264enc", map[string]any{
"bitrate": s.Bitrate,
"speed-preset": "ultrafast",
"tune": "zerolatency",
"key-int-max": int(20),
})
pb.AddFilter(NewCaps("video/x-h264", map[string]any{
"stream-format": "byte-stream",
}))
default:
return nil, nil, fmt.Errorf("unsupported video codec given - %s", s.Codec)
}
pb.AddWithProperties("appsink", map[string]any{
"name": "appsink",
})
ps := pb.Build()
lg.Info("launch pipeline", zap.String("definition", ps))
pipeline, err := gst.NewPipelineFromString(ps)
if err != nil {
return nil, nil, err
}
@@ -168,13 +213,13 @@ func CreateAudioPipelineSink(s StreamElement, lg *zap.Logger) (*gst.Pipeline, <-
src.Set(name, value)
}
if s.Caps != nil {
c := s.Caps.Build()
lg.Info("capsfilter", zap.String("caps", c.String()))
elems = append(elems, Element{src, c})
} else {
elems = append(elems, Element{src, nil})
}
// if s.Caps != nil {
// c := s.Caps.Build()
// lg.Info("capsfilter", zap.String("caps", c.String()))
// elems = append(elems, Element{src, c})
// } else {
// elems = append(elems, Element{src, nil})
// }
// just to be on the save side
conv, err := gst.NewElement("audioconvert")
+14 -61
View File
@@ -1,83 +1,36 @@
package streamer
import (
"github.com/go-gst/go-gst/gst"
"fmt"
"strings"
"github.com/kaedwen/webrtc/pkg/common"
)
type StreamElement struct {
Kind string
Codec common.StreamCodec
Bitrate uint
Properties map[string]interface{}
Caps *CapsBuilder
SrcCaps *Caps
EnvCaps *Caps
Queue bool
}
type CapsBuilder struct {
caps Caps
}
type Caps struct {
mime string
values []CapsValue
filter map[string]any
}
type CapsValue struct {
name string
value any
func NewCaps(mime string, filter map[string]any) *Caps {
return &Caps{mime, filter}
}
func NewCapsBuilder(mime string) *CapsBuilder {
return &CapsBuilder{
caps: Caps{mime, nil},
}
}
func (b *CapsBuilder) Height(v int) *CapsBuilder {
b.caps.values = append(b.caps.values, CapsValue{
name: "height",
value: v,
})
return b
}
func (b *CapsBuilder) Width(v int) *CapsBuilder {
b.caps.values = append(b.caps.values, CapsValue{
name: "width",
value: v,
})
return b
}
func (b *CapsBuilder) Channels(v int) *CapsBuilder {
b.caps.values = append(b.caps.values, CapsValue{
name: "channels",
value: v,
})
return b
}
func (b *CapsBuilder) Format(v string) *CapsBuilder {
b.caps.values = append(b.caps.values, CapsValue{
name: "format",
value: v,
})
return b
}
func (b *CapsBuilder) Rate(v int) *CapsBuilder {
b.caps.values = append(b.caps.values, CapsValue{
name: "rate",
value: v,
})
return b
}
func (b *CapsBuilder) Build() *gst.Caps {
caps := gst.NewEmptySimpleCaps(b.caps.mime)
for _, v := range b.caps.values {
caps.SetValue(v.name, v.value)
func (c *Caps) Build() string {
fv := make([]string, 0, len(c.filter))
for k, v := range c.filter {
fv = append(fv, fmt.Sprint(k, "=", v))
}
return caps
return fmt.Sprint(c.mime, ",", strings.Join(fv, ","))
}
+23 -13
View File
@@ -3,6 +3,7 @@ package webrtc
import (
"context"
"errors"
"fmt"
"io"
"sync"
"time"
@@ -120,9 +121,12 @@ func (wh *WebrtcHandler) handleAudioSamples(ctx context.Context, cfg *common.Con
src := streamer.StreamElement{
Kind: cfg.Source,
Properties: properties,
Caps: streamer.NewCapsBuilder("audio/x-raw").Channels(int(cfg.Channels)).Rate(48000),
Queue: cfg.Queue,
Codec: cfg.Codec,
SrcCaps: streamer.NewCaps("audio/x-raw", map[string]any{
"channels": cfg.Channels,
"rate": 48000,
}),
Queue: cfg.Queue,
Codec: cfg.Codec,
}
var err error
@@ -155,19 +159,25 @@ func (wh *WebrtcHandler) handleAudioSamples(ctx context.Context, cfg *common.Con
}
func (wh *WebrtcHandler) handleVideoSamples(ctx context.Context, cfg *common.ConfigVideoSourceStream) error {
// src := streamer.StreamElement{
// Kind: cfg.Source,
// Properties: map[string]interface{}{
// "device": cfg.Device,
// },
// Caps: streamer.NewCapsBuilder("video/x-raw").Format("YUY2").Width(int(cfg.Width)).Height(int(cfg.Height)),
// Queue: cfg.Queue,
// Codec: cfg.Codec,
// }
src := streamer.StreamElement{
Kind: cfg.Source,
Properties: map[string]interface{}{
"device": cfg.Device,
},
SrcCaps: streamer.NewCaps("video/x-raw", map[string]any{
"height": cfg.Height,
"width": cfg.Width,
"framerate": fmt.Sprintf("%d/1", cfg.Framerate),
"format": "YUY2",
}),
Bitrate: cfg.Bitrate,
Queue: cfg.Queue,
Codec: cfg.Codec,
}
var err error
var videoCh <-chan media.Sample
wh.videoPipeline, videoCh, err = streamer.CreateVideoPipelineSinkWithLaunch(wh.lg)
wh.videoPipeline, videoCh, err = streamer.CreateVideoPipelineSinkWithLaunch(wh.lg, src)
if err != nil {
return err
}