diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fba8c851..f0293f3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,6 +98,20 @@ jobs: uses: actions/upload-artifact@v4 with: { name: go2rtc_mac_arm64, path: go2rtc } + - name: Build go2rtc_freebsd_amd64 + env: { GOOS: freebsd, GOARCH: amd64 } + run: go build -ldflags "-s -w" -trimpath + - name: Upload go2rtc_freebsd_amd64 + uses: actions/upload-artifact@v3 + with: { name: go2rtc_freebsd_amd64, path: go2rtc } + + - name: Build go2rtc_freebsd_arm64 + env: { GOOS: freebsd, GOARCH: arm64 } + run: go build -ldflags "-s -w" -trimpath + - name: Upload go2rtc_freebsd_arm64 + uses: actions/upload-artifact@v3 + with: { name: go2rtc_freebsd_arm64, path: go2rtc } + docker-master: name: Build docker master runs-on: ubuntu-latest diff --git a/internal/ffmpeg/device/device_freebsd.go b/internal/ffmpeg/device/device_freebsd.go new file mode 100644 index 00000000..f3a26a30 --- /dev/null +++ b/internal/ffmpeg/device/device_freebsd.go @@ -0,0 +1,97 @@ +package device + +import ( + "net/url" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/AlexxIT/go2rtc/internal/api" + "github.com/AlexxIT/go2rtc/pkg/core" +) + +func queryToInput(query url.Values) string { + if video := query.Get("video"); video != "" { + // https://ffmpeg.org/ffmpeg-devices.html#video4linux2_002c-v4l2 + input := "-f v4l2" + + 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 oss" + + for key, value := range query { + switch key { + case "channels", "sample_rate": + input += " -" + key + " " + value[0] + } + } + + return input + " -i " + indexToItem(audios, audio) + } + + return "" +} + +func initDevices() { + files, err := os.ReadDir("/dev") + if err != nil { + return + } + + for _, file := range files { + 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 @ 0x860b92280] Raw : yuyv422 : YUYV 4:2:2 : 640x480 160x120 176x144 320x176 320x240 352x288 432x240 544x288 640x360 752x416 800x448 800x600 864x480 960x544 960x720 1024x576 1184x656 1280x720 1280x960 + // [video4linux2,v4l2 @ 0x860b92280] Compressed: mjpeg : Motion-JPEG : 640x480 160x120 176x144 320x176 320x240 352x288 432x240 544x288 640x360 752x416 800x448 800x600 864x480 960x544 960x720 1024x576 1184x656 1280x720 1280x960 + re := regexp.MustCompile("(Raw *|Compressed): +(.+?) : +(.+?) : (.+)") + m := re.FindAllStringSubmatch(string(b), -1) + for _, i := range m { + size, _, _ := strings.Cut(i[4], " ") + stream := &api.Source{ + Name: i[3], + Info: 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) + } + } + + err = exec.Command(Bin, "-f", "oss", "-i", "/dev/dsp", "-t", "1", "-f", "null", "-").Run() + if err == nil { + stream := &api.Source{ + Name: "OSS default", + Info: " ", + URL: "ffmpeg:device?audio=default&channels=1&sample_rate=16000&#audio=opus", + } + + audios = append(audios, "default") + streams = append(streams, stream) + } +} diff --git a/internal/ffmpeg/hardware/hardware_freebsd.go b/internal/ffmpeg/hardware/hardware_freebsd.go new file mode 100644 index 00000000..6ef753ac --- /dev/null +++ b/internal/ffmpeg/hardware/hardware_freebsd.go @@ -0,0 +1,60 @@ +package hardware + +import ( + "runtime" + + "github.com/AlexxIT/go2rtc/internal/api" +) + +const ( + ProbeV4L2M2MH264 = "-f lavfi -i testsrc2 -t 1 -c h264_v4l2m2m -f null -" + ProbeV4L2M2MH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_v4l2m2m -f null -" + ProbeRKMPPH264 = "-f lavfi -i testsrc2 -t 1 -c h264_rkmpp_encoder -f null -" + ProbeRKMPPH265 = "-f lavfi -i testsrc2 -t 1 -c hevc_rkmpp_encoder -f null -" +) + +func ProbeAll(bin string) []*api.Source { + return []*api.Source{ + { + Name: runToString(bin, ProbeV4L2M2MH264), + URL: "ffmpeg:...#video=h264#hardware=" + EngineV4L2M2M, + }, + { + Name: runToString(bin, ProbeV4L2M2MH265), + URL: "ffmpeg:...#video=h265#hardware=" + EngineV4L2M2M, + }, + { + Name: runToString(bin, ProbeRKMPPH264), + URL: "ffmpeg:...#video=h264#hardware=" + EngineRKMPP, + }, + { + Name: runToString(bin, ProbeRKMPPH265), + URL: "ffmpeg:...#video=h265#hardware=" + EngineRKMPP, + }, + } +} + +func ProbeHardware(bin, name string) string { + if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { + switch name { + case "h264": + if run(bin, ProbeV4L2M2MH264) { + return EngineV4L2M2M + } + if run(bin, ProbeRKMPPH264) { + return EngineRKMPP + } + case "h265": + if run(bin, ProbeV4L2M2MH265) { + return EngineV4L2M2M + } + if run(bin, ProbeRKMPPH265) { + return EngineRKMPP + } + } + + return EngineSoftware + } + + return EngineSoftware +} diff --git a/pkg/mdns/syscall_freebsd.go b/pkg/mdns/syscall_freebsd.go new file mode 100644 index 00000000..c1f1225b --- /dev/null +++ b/pkg/mdns/syscall_freebsd.go @@ -0,0 +1,24 @@ +package mdns + +import ( + "syscall" +) + +func SetsockoptInt(fd uintptr, level, opt int, value int) (err error) { + // change SO_REUSEADDR and REUSEPORT flags simultaneously for BSD-like OS + // https://github.com/AlexxIT/go2rtc/issues/626 + // https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ/14388707 + if opt == syscall.SO_REUSEADDR { + if err = syscall.SetsockoptInt(int(fd), level, opt, value); err != nil { + return + } + + opt = syscall.SO_REUSEPORT + } + + return syscall.SetsockoptInt(int(fd), level, opt, value) +} + +func SetsockoptIPMreq(fd uintptr, level, opt int, mreq *syscall.IPMreq) (err error) { + return syscall.SetsockoptIPMreq(int(fd), level, opt, mreq) +}