From 209d7b47d940a3bdc9e32e25cf08156ab2cc1798 Mon Sep 17 00:00:00 2001 From: Alexey Khit Date: Tue, 4 Jul 2023 13:02:10 +0300 Subject: [PATCH] Rewrite FFmpeg devices and add support ALSA for Linux --- internal/ffmpeg/device/device_darwin.go | 39 ++++++++++++----- internal/ffmpeg/device/device_linux.go | 38 +++++++++++++--- internal/ffmpeg/device/device_windows.go | 50 ++++++++++++++++++++- internal/ffmpeg/device/devices.go | 56 +++++++++--------------- 4 files changed, 130 insertions(+), 53 deletions(-) diff --git a/internal/ffmpeg/device/device_darwin.go b/internal/ffmpeg/device/device_darwin.go index 7526b885..0900cf0c 100644 --- a/internal/ffmpeg/device/device_darwin.go +++ b/internal/ffmpeg/device/device_darwin.go @@ -3,24 +3,41 @@ package device import ( "github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/pkg/core" + "net/url" "os/exec" "regexp" "strings" ) -// https://trac.ffmpeg.org/wiki/Capture/Webcam -const deviceInputPrefix = "-f avfoundation" +func queryToInput(query url.Values) string { + video := query.Get("video") + audio := query.Get("audio") -func deviceInputSuffix(video, audio string) string { - switch { - case video != "" && audio != "": - return `"` + video + `:` + audio + `"` - case video != "": - return `"` + video + `"` - case audio != "": - return `":` + audio + `"` + if video == "" && audio == "" { + return "" } - return "" + + // https://ffmpeg.org/ffmpeg-devices.html#avfoundation + input := "-f avfoundation" + + if video != "" { + video = indexToItem(videos, video) + + for key, value := range query { + switch key { + case "resolution": + input += " -video_size " + value[0] + case "pixel_format", "framerate", "video_size", "capture_cursor", "capture_mouse_clicks", "capture_raw_data": + input += " -" + key + " " + value[0] + } + } + } + + if audio != "" { + audio = indexToItem(audios, audio) + } + + return input + ` -i "` + video + `:` + audio + `"` } func initDevices() { diff --git a/internal/ffmpeg/device/device_linux.go b/internal/ffmpeg/device/device_linux.go index 1b35472c..1dd8bd74 100644 --- a/internal/ffmpeg/device/device_linux.go +++ b/internal/ffmpeg/device/device_linux.go @@ -3,19 +3,36 @@ package device import ( "github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/pkg/core" + "net/url" "os" "os/exec" "regexp" "strings" ) -// https://trac.ffmpeg.org/wiki/Capture/Webcam -const deviceInputPrefix = "-f v4l2" +func queryToInput(query url.Values) string { + if video := query.Get("video"); video != "" { + // https://ffmpeg.org/ffmpeg-devices.html#video4linux2_002c-v4l2 + input := "-f v4l2" -func deviceInputSuffix(video, audio string) string { - if video != "" { - return video + for key, value := range query { + switch key { + case "resolution": + input += " -video_size " + value[0] + case "video_size", "pixel_format", "input_format", "framerate", "use_libv4l2": + input += " -" + key + " " + value[0] + } + } + + return input + " -i " + indexToItem(videos, video) } + + if audio := query.Get("audio"); audio != "" { + input := "-f alsa" + + return input + " -i " + indexToItem(audios, audio) + } + return "" } @@ -57,4 +74,15 @@ func initDevices() { streams = append(streams, stream) } } + + err = exec.Command(Bin, "-f", "alsa", "-i", "default", "-t", "1", "-f", "null", "-").Run() + if err == nil { + stream := api.Stream{ + Name: "ALSA default", + URL: "ffmpeg:device?audio=default#audio=opus", + } + + audios = append(audios, "default") + streams = append(streams, stream) + } } diff --git a/internal/ffmpeg/device/device_windows.go b/internal/ffmpeg/device/device_windows.go index 1f614891..8bae19c4 100644 --- a/internal/ffmpeg/device/device_windows.go +++ b/internal/ffmpeg/device/device_windows.go @@ -3,12 +3,58 @@ package device import ( "github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/pkg/core" + "net/url" "os/exec" "regexp" ) -// https://trac.ffmpeg.org/wiki/DirectShow -const deviceInputPrefix = "-f dshow" +func queryToInput(query url.Values) string { + video := query.Get("video") + audio := query.Get("audio") + + if video == "" && audio == "" { + return "" + } + + // https://ffmpeg.org/ffmpeg-devices.html#dshow + input := "-f dshow" + + if video != "" { + video = indexToItem(videos, video) + + for key, value := range query { + switch key { + case "resolution": + input += " -video_size " + value[0] + case "video_size", "framerate", "pixel_format": + input += " -" + key + " " + value[0] + } + } + } + + if audio != "" { + audio = indexToItem(audios, audio) + + for key, value := range query { + switch key { + case "sample_rate", "sample_size", "channels", "audio_buffer_size": + input += " -" + key + " " + value[0] + } + } + } + + if video != "" { + input += ` -i video="` + video + `"` + + if audio != "" { + input += `:audio="` + audio + `"` + } + } else { + input += ` -i audio="` + audio + `"` + } + + return input +} func deviceInputSuffix(video, audio string) string { switch { diff --git a/internal/ffmpeg/device/devices.go b/internal/ffmpeg/device/devices.go index 3e657906..4eaa6e87 100644 --- a/internal/ffmpeg/device/devices.go +++ b/internal/ffmpeg/device/devices.go @@ -1,6 +1,7 @@ package device import ( + "errors" "github.com/AlexxIT/go2rtc/internal/api" "net/http" "net/url" @@ -16,45 +17,23 @@ func Init(bin string) { } func GetInput(src string) (string, error) { + i := strings.IndexByte(src, '?') + if i < 0 { + return "", errors.New("empty query: " + src) + } + + query, err := url.ParseQuery(src[i+1:]) + if err != nil { + return "", err + } + runonce.Do(initDevices) - input := deviceInputPrefix - - var video, audio string - - if i := strings.IndexByte(src, '?'); i > 0 { - query, err := url.ParseQuery(src[i+1:]) - if err != nil { - return "", err - } - for key, value := range query { - switch key { - case "video": - video = value[0] - case "audio": - audio = value[0] - case "resolution": - input += " -video_size " + value[0] - default: // "input_format", "framerate", "video_size" - input += " -" + key + " " + value[0] - } - } + if input := queryToInput(query); input != "" { + return input, nil } - if video != "" { - if i, err := strconv.Atoi(video); err == nil && i < len(videos) { - video = videos[i] - } - } - if audio != "" { - if i, err := strconv.Atoi(audio); err == nil && i < len(audios) { - audio = audios[i] - } - } - - input += " -i " + deviceInputSuffix(video, audio) - - return input, nil + return "", errors.New("wrong query: " + src) } var Bin string @@ -68,3 +47,10 @@ func apiDevices(w http.ResponseWriter, r *http.Request) { api.ResponseStreams(w, streams) } + +func indexToItem(items []string, index string) string { + if i, err := strconv.Atoi(index); err == nil && i < len(items) { + return items[i] + } + return index +}