From 5d726183d5edae1cfc69e1ddd36091cbdc1ac330 Mon Sep 17 00:00:00 2001 From: Tai Groot Date: Tue, 27 Jun 2023 16:06:07 -0700 Subject: [PATCH] initial commit --- .github/FUNDING.yml | 3 ++ LICENSE | 12 ++++++ README.md | 7 ++++ go.mod | 5 +++ go.sum | 2 + vidnumerator.go | 92 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 vidnumerator.go diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..077a514 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: taigrr # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a47db23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (C) 2023 by Tai Groot + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..52a6753 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# VidNumerator + +This is a tiny library that only exports one function: `EnumerateVideoDevices`. +This function uses syscalls to efficiently determine which `/dev/videoN` devices +are webcams and which are the additional metadata control handles. +The list of strings returned are the full filepaths to valid devices. + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..399a380 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/taigrr/vidnumerator + +go 1.20 + +require golang.org/x/sys v0.9.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cb35c1f --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/vidnumerator.go b/vidnumerator.go new file mode 100644 index 0000000..0312b89 --- /dev/null +++ b/vidnumerator.go @@ -0,0 +1,92 @@ +package vidnumerator + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "unsafe" + + "golang.org/x/sys/unix" +) + +const ( + IOCNrBits = 8 + IOCTypeBits = 8 + IOCSizeBits = 14 + IOCDirBits = 2 + IOCNone = 0 + IOCWrite = 1 + IOCRead = 2 + IOCNrShift = 0 + + IOCTypeShift = (IOCNrShift + IOCNrBits) + IOCSizeShift = (IOCTypeShift + IOCTypeBits) + IOCDirShift = (IOCSizeShift + IOCSizeBits) + + VidIOCQueryCap = (IOCRead << IOCDirShift) | + (uintptr('V') << IOCTypeShift) | + (0 << IOCNrShift) | + (unsafe.Sizeof(cap{}) << IOCSizeShift) +) + +type cap struct { + driver [16]uint8 + card [32]uint8 + busInfo [32]uint8 + version uint32 + capabilities uint32 + deviceCaps uint32 + reserved [3]uint32 +} + +func (r *cap) QueryFd(fileDesciptor int) error { + if r == nil { + return fmt.Errorf("nil receiver") + } + _, _, errorNumber := unix.Syscall( + unix.SYS_IOCTL, + uintptr(fileDesciptor), + VidIOCQueryCap, + uintptr(unsafe.Pointer(r)), + ) + if errorNumber != 0 { + return errorNumber + } + return nil +} + +// this function checks the ioctl for VIDIOC_QUERYCAP to see if the device is a video capture device +func EnumeratedVideoDevices() []string { + // list all files in the /dev directory + d, err := os.ReadDir("/dev") + if err != nil { + return []string{} + } + // iterate over the files in the directory + devNames := []string{} + for _, file := range d { + if file.IsDir() { + continue + } + fname := file.Name() + if !strings.HasPrefix(fname, "video") { + continue + } + fname = filepath.Join("/dev/", fname) + f, err := os.OpenFile(fname, os.O_RDONLY, 0o755) + if err != nil { + continue + } + fd := f.Fd() + ic := cap{} + err = ic.QueryFd(int(fd)) + if err != nil { + continue + } + if ic.deviceCaps == 69206017 { + devNames = append(devNames, fname) + } + } + return devNames +}