use launch
This commit is contained in:
Vendored
+37
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user