diff --git a/internal/ffmpeg/device/device_darwin.go b/internal/ffmpeg/device/device_darwin.go index a22f7e13..7526b885 100644 --- a/internal/ffmpeg/device/device_darwin.go +++ b/internal/ffmpeg/device/device_darwin.go @@ -1,43 +1,43 @@ package device import ( - "bytes" + "github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/pkg/core" "os/exec" + "regexp" "strings" ) // https://trac.ffmpeg.org/wiki/Capture/Webcam const deviceInputPrefix = "-f avfoundation" -func deviceInputSuffix(videoIdx, audioIdx int) string { - video := findMedia(core.KindVideo, videoIdx) - audio := findMedia(core.KindAudio, audioIdx) +func deviceInputSuffix(video, audio string) string { switch { - case video != nil && audio != nil: - return `"` + video.ID + `:` + audio.ID + `"` - case video != nil: - return `"` + video.ID + `"` - case audio != nil: - return `"` + audio.ID + `"` + case video != "" && audio != "": + return `"` + video + `:` + audio + `"` + case video != "": + return `"` + video + `"` + case audio != "": + return `":` + audio + `"` } return "" } -func loadMedias() { +func initDevices() { + // [AVFoundation indev @ 0x147f04510] AVFoundation video devices: + // [AVFoundation indev @ 0x147f04510] [0] FaceTime HD Camera + // [AVFoundation indev @ 0x147f04510] [1] Capture screen 0 + // [AVFoundation indev @ 0x147f04510] AVFoundation audio devices: + // [AVFoundation indev @ 0x147f04510] [0] MacBook Pro Microphone cmd := exec.Command( - Bin, "-hide_banner", "-list_devices", "true", "-f", "avfoundation", "-i", "dummy", + Bin, "-hide_banner", "-list_devices", "true", "-f", "avfoundation", "-i", "", ) + b, _ := cmd.CombinedOutput() - var buf bytes.Buffer - cmd.Stderr = &buf - _ = cmd.Run() + re := regexp.MustCompile(`\[\d+] (.+)`) var kind string - - lines := strings.Split(buf.String(), "\n") -process: - for _, line := range lines { + for _, line := range strings.Split(string(b), "\n") { switch { case strings.HasSuffix(line, "video devices:"): kind = core.KindVideo @@ -45,17 +45,24 @@ process: case strings.HasSuffix(line, "audio devices:"): kind = core.KindAudio continue - case strings.HasPrefix(line, "dummy"): - break process } - // [AVFoundation indev @ 0x7fad54604380] [0] FaceTime HD Camera - name := line[42:] - media := loadMedia(kind, name) - medias = append(medias, media) + m := re.FindStringSubmatch(line) + if m == nil { + continue + } + + name := m[1] + + switch kind { + case core.KindVideo: + videos = append(videos, name) + case core.KindAudio: + audios = append(audios, name) + } + + streams = append(streams, api.Stream{ + Name: name, URL: "ffmpeg:device?" + kind + "=" + name, + }) } } - -func loadMedia(kind, name string) *core.Media { - return &core.Media{Kind: kind, ID: name} -} diff --git a/internal/ffmpeg/device/device_linux.go b/internal/ffmpeg/device/device_linux.go index b9aa6b64..1b35472c 100644 --- a/internal/ffmpeg/device/device_linux.go +++ b/internal/ffmpeg/device/device_linux.go @@ -1,50 +1,60 @@ package device import ( - "bytes" + "github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/pkg/core" - "io/ioutil" + "os" "os/exec" + "regexp" "strings" ) // https://trac.ffmpeg.org/wiki/Capture/Webcam const deviceInputPrefix = "-f v4l2" -func deviceInputSuffix(videoIdx, audioIdx int) string { - if video := findMedia(core.KindVideo, videoIdx); video != nil { - return video.ID +func deviceInputSuffix(video, audio string) string { + if video != "" { + return video } return "" } -func loadMedias() { - files, err := ioutil.ReadDir("/dev") +func initDevices() { + files, err := os.ReadDir("/dev") if err != nil { return } + for _, file := range files { - log.Trace().Msg("[ffmpeg] " + file.Name()) - if strings.HasPrefix(file.Name(), core.KindVideo) { - media := loadMedia(core.KindVideo, "/dev/"+file.Name()) - if media != nil { - medias = append(medias, media) + if !strings.HasPrefix(file.Name(), core.KindVideo) { + continue + } + + name := "/dev/" + file.Name() + + cmd := exec.Command( + Bin, "-hide_banner", "-f", "v4l2", "-list_formats", "all", "-i", name, + ) + b, _ := cmd.CombinedOutput() + + // [video4linux2,v4l2 @ 0x204e1c0] Compressed: mjpeg : Motion-JPEG : 640x360 1280x720 1920x1080 + // [video4linux2,v4l2 @ 0x204e1c0] Raw : yuyv422 : YUYV 4:2:2 : 640x360 1280x720 1920x1080 + // [video4linux2,v4l2 @ 0x204e1c0] Compressed: h264 : H.264 : 640x360 1280x720 1920x1080 + re := regexp.MustCompile("(Raw *|Compressed): +(.+?) : +(.+?) : (.+)") + m := re.FindAllStringSubmatch(string(b), -1) + for _, i := range m { + size, _, _ := strings.Cut(i[4], " ") + stream := api.Stream{ + Name: i[3] + " | " + i[4], + URL: "ffmpeg:device?video=" + name + "&input_format=" + i[2] + "&video_size=" + size, } + + if i[1] != "Compressed" { + stream.URL += "#video=h264#hardware" + } + + videos = append(videos, name) + streams = append(streams, stream) } } } - -func loadMedia(kind, name string) *core.Media { - cmd := exec.Command( - Bin, "-hide_banner", "-f", "v4l2", "-list_formats", "all", "-i", name, - ) - var buf bytes.Buffer - cmd.Stderr = &buf - _ = cmd.Run() - - if !bytes.Contains(buf.Bytes(), []byte("Raw")) { - return nil - } - - return &core.Media{Kind: kind, ID: name} -} diff --git a/internal/ffmpeg/device/device_windows.go b/internal/ffmpeg/device/device_windows.go index f465386a..1f614891 100644 --- a/internal/ffmpeg/device/device_windows.go +++ b/internal/ffmpeg/device/device_windows.go @@ -1,57 +1,50 @@ package device import ( - "bytes" + "github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/pkg/core" "os/exec" - "strings" + "regexp" ) // https://trac.ffmpeg.org/wiki/DirectShow const deviceInputPrefix = "-f dshow" -func deviceInputSuffix(videoIdx, audioIdx int) string { - video := findMedia(core.KindVideo, videoIdx) - audio := findMedia(core.KindAudio, audioIdx) +func deviceInputSuffix(video, audio string) string { switch { - case video != nil && audio != nil: - return `video="` + video.ID + `":audio=` + audio.ID + `"` - case video != nil: - return `video="` + video.ID + `"` - case audio != nil: - return `audio="` + audio.ID + `"` + case video != "" && audio != "": + return `video="` + video + `":audio=` + audio + `"` + case video != "": + return `video="` + video + `"` + case audio != "": + return `audio="` + audio + `"` } return "" } -func loadMedias() { +func initDevices() { cmd := exec.Command( Bin, "-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "", ) + b, _ := cmd.CombinedOutput() - var buf bytes.Buffer - cmd.Stderr = &buf - _ = cmd.Run() + re := regexp.MustCompile(`"([^"]+)" \((video|audio)\)`) + for _, m := range re.FindAllStringSubmatch(string(b), -1) { + name := m[1] + kind := m[2] - lines := strings.Split(buf.String(), "\r\n") - for _, line := range lines { - var kind string - if strings.HasSuffix(line, "(video)") { - kind = core.KindVideo - } else if strings.HasSuffix(line, "(audio)") { - kind = core.KindAudio - } else { - continue + stream := api.Stream{ + Name: name, URL: "ffmpeg:device?" + kind + "=" + name, } - // hope we have constant prefix and suffix sizes - // [dshow @ 00000181e8d028c0] "VMware Virtual USB Video Device" (video) - name := line[28 : len(line)-9] - media := loadMedia(kind, name) - medias = append(medias, media) + switch kind { + case core.KindVideo: + videos = append(videos, name) + stream.URL += "#video=h264#hardware" + case core.KindAudio: + audios = append(audios, name) + } + + streams = append(streams, stream) } } - -func loadMedia(kind, name string) *core.Media { - return &core.Media{Kind: kind, ID: name} -} diff --git a/internal/ffmpeg/device/devices.go b/internal/ffmpeg/device/devices.go index e6e43b5a..e951aa76 100644 --- a/internal/ffmpeg/device/devices.go +++ b/internal/ffmpeg/device/devices.go @@ -2,29 +2,24 @@ package device import ( "github.com/AlexxIT/go2rtc/internal/api" - "github.com/AlexxIT/go2rtc/internal/app" - "github.com/AlexxIT/go2rtc/pkg/core" - "github.com/rs/zerolog" "net/http" "net/url" "strconv" "strings" + "sync" ) func Init() { - log = app.GetLogger("exec") - - api.HandleFunc("api/devices", handle) + api.HandleFunc("api/ffmpeg/devices", apiDevices) } func GetInput(src string) (string, error) { - if medias == nil { - loadMedias() - } + runonce.Do(initDevices) input := deviceInputPrefix - var videoIdx, audioIdx int + var video, audio string + if i := strings.IndexByte(src, '?'); i > 0 { query, err := url.ParseQuery(src[i+1:]) if err != nil { @@ -33,9 +28,9 @@ func GetInput(src string) (string, error) { for key, value := range query { switch key { case "video": - videoIdx, _ = strconv.Atoi(value[0]) + video = value[0] case "audio": - audioIdx, _ = strconv.Atoi(value[0]) + audio = value[0] case "framerate": input += " -framerate " + value[0] case "resolution": @@ -44,48 +39,30 @@ func GetInput(src string) (string, error) { } } - input += " -i " + deviceInputSuffix(videoIdx, audioIdx) + 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 } var Bin string -var log zerolog.Logger -var medias []*core.Media -func findMedia(kind string, index int) *core.Media { - for _, media := range medias { - if media.Kind != kind { - continue - } - if index == 0 { - return media - } - index-- - } - return nil -} - -func handle(w http.ResponseWriter, r *http.Request) { - if medias == nil { - loadMedias() - } - - var items []api.Stream - var iv, ia int - - for _, media := range medias { - var source string - switch media.Kind { - case core.KindVideo: - source = "ffmpeg:device?video=" + strconv.Itoa(iv) - iv++ - case core.KindAudio: - source = "ffmpeg:device?audio=" + strconv.Itoa(ia) - ia++ - } - items = append(items, api.Stream{Name: media.ID, URL: source}) - } - - api.ResponseStreams(w, items) +var videos, audios []string +var streams []api.Stream +var runonce sync.Once + +func apiDevices(w http.ResponseWriter, r *http.Request) { + runonce.Do(initDevices) + + api.ResponseStreams(w, streams) } diff --git a/www/add.html b/www/add.html index fc8a7c46..7e3ce3b6 100644 --- a/www/add.html +++ b/www/add.html @@ -171,6 +171,19 @@ + +
+ +
+
+ + +
@@ -232,19 +245,6 @@ - -
- -
-
- - -