use launch
This commit is contained in:
1 parent
ac2e689131
commit
146e656b8c
6 files changed
+153
-93
No files matched your search
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 {
|
type ConfigVideoSourceStream struct {
|
||||||
Source string `arg:"--video-src,env:VIDEO_SRC" default:"v4l2src"`
|
Source string `arg:"--video-src,env:VIDEO_SRC" default:"v4l2src"`
|
||||||
Device string `arg:"--video-src-device,env:VIDEO_SRC_DEVICE" default:"/dev/video0"`
|
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"`
|
Codec StreamCodec `arg:"--video-src-codec,env:VIDEO_SRC_CODEC" default:"vp8"`
|
||||||
Height uint `arg:"--video-src-height,env:VIDEO_SRC_HEIGHT" default:"480"`
|
Height uint `arg:"--video-src-height,env:VIDEO_SRC_HEIGHT" default:"480"`
|
||||||
Width uint `arg:"--video-src-width,env:VIDEO_SRC_WIDTH" default:"640"`
|
Width uint `arg:"--video-src-width,env:VIDEO_SRC_WIDTH" default:"640"`
|
||||||
Queue bool `arg:"--video-src-queue,env:VIDEO_SRC_QUEUE" default:"false"`
|
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 {
|
type ConfigAudioSourceStream struct {
|
||||||
|
|||||||
+16
-3
@@ -1,6 +1,9 @@
|
|||||||
package streamer
|
package streamer
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type PipelineBuilder struct {
|
type PipelineBuilder struct {
|
||||||
parts []string
|
parts []string
|
||||||
@@ -18,9 +21,19 @@ func (pb *PipelineBuilder) Add(element string) *PipelineBuilder {
|
|||||||
func (pb *PipelineBuilder) AddWithProperties(element string, properties map[string]any) *PipelineBuilder {
|
func (pb *PipelineBuilder) AddWithProperties(element string, properties map[string]any) *PipelineBuilder {
|
||||||
pl := make([]string, 0, len(properties))
|
pl := make([]string, 0, len(properties))
|
||||||
for k, v := range 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
|
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 := NewPipelineBuilder()
|
||||||
|
|
||||||
pb.AddWithProperties("v4l2src", map[string]any{"device", s.})
|
pb.AddWithProperties("v4l2src", s.Properties)
|
||||||
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.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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -168,13 +213,13 @@ func CreateAudioPipelineSink(s StreamElement, lg *zap.Logger) (*gst.Pipeline, <-
|
|||||||
src.Set(name, value)
|
src.Set(name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Caps != nil {
|
// if s.Caps != nil {
|
||||||
c := s.Caps.Build()
|
// c := s.Caps.Build()
|
||||||
lg.Info("capsfilter", zap.String("caps", c.String()))
|
// lg.Info("capsfilter", zap.String("caps", c.String()))
|
||||||
elems = append(elems, Element{src, c})
|
// elems = append(elems, Element{src, c})
|
||||||
} else {
|
// } else {
|
||||||
elems = append(elems, Element{src, nil})
|
// elems = append(elems, Element{src, nil})
|
||||||
}
|
// }
|
||||||
|
|
||||||
// just to be on the save side
|
// just to be on the save side
|
||||||
conv, err := gst.NewElement("audioconvert")
|
conv, err := gst.NewElement("audioconvert")
|
||||||
|
|||||||
+14
-61
@@ -1,83 +1,36 @@
|
|||||||
package streamer
|
package streamer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-gst/go-gst/gst"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kaedwen/webrtc/pkg/common"
|
"github.com/kaedwen/webrtc/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StreamElement struct {
|
type StreamElement struct {
|
||||||
Kind string
|
Kind string
|
||||||
Codec common.StreamCodec
|
Codec common.StreamCodec
|
||||||
|
Bitrate uint
|
||||||
Properties map[string]interface{}
|
Properties map[string]interface{}
|
||||||
Caps *CapsBuilder
|
SrcCaps *Caps
|
||||||
|
EnvCaps *Caps
|
||||||
Queue bool
|
Queue bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CapsBuilder struct {
|
|
||||||
caps Caps
|
|
||||||
}
|
|
||||||
|
|
||||||
type Caps struct {
|
type Caps struct {
|
||||||
mime string
|
mime string
|
||||||
values []CapsValue
|
filter map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
type CapsValue struct {
|
func NewCaps(mime string, filter map[string]any) *Caps {
|
||||||
name string
|
return &Caps{mime, filter}
|
||||||
value any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCapsBuilder(mime string) *CapsBuilder {
|
func (c *Caps) Build() string {
|
||||||
return &CapsBuilder{
|
fv := make([]string, 0, len(c.filter))
|
||||||
caps: Caps{mime, nil},
|
for k, v := range c.filter {
|
||||||
}
|
fv = append(fv, fmt.Sprint(k, "=", v))
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return caps
|
return fmt.Sprint(c.mime, ",", strings.Join(fv, ","))
|
||||||
}
|
}
|
||||||
+23
-13
@@ -3,6 +3,7 @@ package webrtc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -120,9 +121,12 @@ func (wh *WebrtcHandler) handleAudioSamples(ctx context.Context, cfg *common.Con
|
|||||||
src := streamer.StreamElement{
|
src := streamer.StreamElement{
|
||||||
Kind: cfg.Source,
|
Kind: cfg.Source,
|
||||||
Properties: properties,
|
Properties: properties,
|
||||||
Caps: streamer.NewCapsBuilder("audio/x-raw").Channels(int(cfg.Channels)).Rate(48000),
|
SrcCaps: streamer.NewCaps("audio/x-raw", map[string]any{
|
||||||
Queue: cfg.Queue,
|
"channels": cfg.Channels,
|
||||||
Codec: cfg.Codec,
|
"rate": 48000,
|
||||||
|
}),
|
||||||
|
Queue: cfg.Queue,
|
||||||
|
Codec: cfg.Codec,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
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 {
|
func (wh *WebrtcHandler) handleVideoSamples(ctx context.Context, cfg *common.ConfigVideoSourceStream) error {
|
||||||
// src := streamer.StreamElement{
|
src := streamer.StreamElement{
|
||||||
// Kind: cfg.Source,
|
Kind: cfg.Source,
|
||||||
// Properties: map[string]interface{}{
|
Properties: map[string]interface{}{
|
||||||
// "device": cfg.Device,
|
"device": cfg.Device,
|
||||||
// },
|
},
|
||||||
// Caps: streamer.NewCapsBuilder("video/x-raw").Format("YUY2").Width(int(cfg.Width)).Height(int(cfg.Height)),
|
SrcCaps: streamer.NewCaps("video/x-raw", map[string]any{
|
||||||
// Queue: cfg.Queue,
|
"height": cfg.Height,
|
||||||
// Codec: cfg.Codec,
|
"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 err error
|
||||||
var videoCh <-chan media.Sample
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user