From 9b07e75bbda05835f06d0f671d6176650d525586 Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Sat, 17 Oct 2020 10:29:00 +0200 Subject: [PATCH 1/6] Added ListDevices() and expose more device capabilities, including device name. --- examples/list_devices/list_devices.go | 26 ++++++++ v4l2.go | 9 +-- webcam.go | 85 ++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 15 deletions(-) create mode 100644 examples/list_devices/list_devices.go diff --git a/examples/list_devices/list_devices.go b/examples/list_devices/list_devices.go new file mode 100644 index 0000000..59c08e7 --- /dev/null +++ b/examples/list_devices/list_devices.go @@ -0,0 +1,26 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/blackjack/webcam" +) + +func main() { + flag.Parse() + + devices, err := webcam.ListDevices() + if err != nil { + panic(err.Error()) + } + if len(devices) == 0 { + fmt.Println("No valid video devices found in %q", webcam.VIDEO4LINUX_DIR) + } else { + fmt.Println("Video devices found:") + for devPath, name := range devices { + fmt.Printf(" %q located in %s", name, devPath) + } + } +} + diff --git a/v4l2.go b/v4l2.go index 578c111..f619732 100644 --- a/v4l2.go +++ b/v4l2.go @@ -203,18 +203,15 @@ type v4l2_control struct { value int32 } -func checkCapabilities(fd uintptr) (supportsVideoCapture bool, supportsVideoStreaming bool, err error) { - - caps := &v4l2_capability{} +func checkCapabilities(fd uintptr) (caps *v4l2_capability, err error) { + caps = &v4l2_capability{} err = ioctl.Ioctl(fd, VIDIOC_QUERYCAP, uintptr(unsafe.Pointer(caps))) if err != nil { - return + return nil, err } - supportsVideoCapture = (caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) != 0 - supportsVideoStreaming = (caps.capabilities & V4L2_CAP_STREAMING) != 0 return } diff --git a/webcam.go b/webcam.go index 19d3559..8be5d8f 100644 --- a/webcam.go +++ b/webcam.go @@ -5,8 +5,13 @@ package webcam import ( "errors" + "fmt" "golang.org/x/sys/unix" + "os" + "path" + "path/filepath" "reflect" + "strings" "unsafe" ) @@ -16,6 +21,7 @@ type Webcam struct { bufcount uint32 buffers [][]byte streaming bool + capabilities *v4l2_capability } type ControlID uint32 @@ -38,26 +44,48 @@ func Open(path string) (*Webcam, error) { return nil, err } - supportsVideoCapture, supportsVideoStreaming, err := checkCapabilities(fd) - + caps, err := checkCapabilities(fd) if err != nil { return nil, err } - if !supportsVideoCapture { - return nil, errors.New("Not a video capture device") + w := &Webcam{ + fd: uintptr(fd), + bufcount: 256, + capabilities: caps, } - if !supportsVideoStreaming { + // Makes sure it supports some form of video capture capability. + if !w.SupportsVideoCapture() { + return nil, errors.New("Not a video capture device") + } + if !w.SupportsVideoStreaming() { return nil, errors.New("Device does not support the streaming I/O method") } - w := new(Webcam) - w.fd = uintptr(fd) - w.bufcount = 256 return w, nil } +func (w *Webcam) SupportsVideoCapture() bool { + return (w.capabilities.capabilities & V4L2_CAP_VIDEO_CAPTURE) != 0 +} + +func (w *Webcam) SupportsVideoStreaming() bool { + return (w.capabilities.capabilities & V4L2_CAP_STREAMING) != 0 +} + +func (w *Webcam) Card() string { + return CToGoString(w.capabilities.card[:]) +} + +func (w *Webcam) Driver() string { + return CToGoString(w.capabilities.driver[:]) +} + +func (w *Webcam) BusInfo() string { + return CToGoString(w.capabilities.bus_info[:]) +} + // Returns image formats supported by the device alongside with // their text description // Not that this function is somewhat experimental. Frames are not ordered in @@ -146,7 +174,7 @@ func (w *Webcam) GetControls() map[ControlID]Control { // Get the value of a control. func (w *Webcam) GetControl(id ControlID) (int32, error) { return getControl(w.fd, uint32(id)) -} + // Set a control. func (w *Webcam) SetControl(id ControlID, value int32) error { @@ -281,6 +309,7 @@ func (w *Webcam) SetAutoWhiteBalance(val bool) error { return setControl(w.fd, V4L2_CID_AUTO_WHITE_BALANCE, v) } + func gobytes(p unsafe.Pointer, n int) []byte { h := reflect.SliceHeader{uintptr(p), n, n} @@ -288,3 +317,41 @@ func gobytes(p unsafe.Pointer, n int) []byte { return s } + +// VIDEO4LINUX_DIR path to kernel known list of videos devices. +var VIDEO4LINUX_DIR string = "/sys/class/video4linux/" + +// ListDevices enumerates video devices present in the system. It returns a map of +// of path names to the "human readable" device name (the "card name"). +func ListDevices() (devices map[string]string, err error) { + devices = make(map[string]string) + err = filepath.Walk(VIDEO4LINUX_DIR, func(_ string, info os.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("failure accessing %q: %v", VIDEO4LINUX_DIR, err) + } + if info.IsDir() { return nil } // Root directory. + if !strings.HasPrefix(info.Name(), "video") && !strings.HasPrefix(info.Name(), "subdev") { + return nil + } + devPath := path.Join("/dev", info.Name()) + w, err := Open(devPath) + if err != nil{ + return fmt.Errorf("Failed to open device %q: %v", devPath, err) + } + defer w.Close() + + // For some reason the kernel creates more than one path per actual physical device, + // one of which has no supported formats and can't be used for streaming. + formats := w.GetSupportedFormats() + if len(formats) == 0 { + return nil + } + devices[devPath] = w.Card() + return nil + }) + if err != nil { + return nil, err + } + return +} + From a7cda99bbf6b3c945916b10932a80077a23fd046 Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Sat, 17 Oct 2020 10:45:07 +0200 Subject: [PATCH 2/6] Fixed a small last minute typo. --- webcam.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webcam.go b/webcam.go index 8be5d8f..1576682 100644 --- a/webcam.go +++ b/webcam.go @@ -174,7 +174,7 @@ func (w *Webcam) GetControls() map[ControlID]Control { // Get the value of a control. func (w *Webcam) GetControl(id ControlID) (int32, error) { return getControl(w.fd, uint32(id)) - +} // Set a control. func (w *Webcam) SetControl(id ControlID, value int32) error { From 0edba52ba0a8545070a9134848478a9f9a64a6ad Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Sun, 18 Oct 2020 21:38:57 +0200 Subject: [PATCH 3/6] Fixed Println -> Printf. --- examples/list_devices/list_devices.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/list_devices/list_devices.go b/examples/list_devices/list_devices.go index 59c08e7..1c173aa 100644 --- a/examples/list_devices/list_devices.go +++ b/examples/list_devices/list_devices.go @@ -15,11 +15,11 @@ func main() { panic(err.Error()) } if len(devices) == 0 { - fmt.Println("No valid video devices found in %q", webcam.VIDEO4LINUX_DIR) + fmt.Printf("No valid video devices found in %q\n", webcam.VIDEO4LINUX_DIR) } else { fmt.Println("Video devices found:") for devPath, name := range devices { - fmt.Printf(" %q located in %s", name, devPath) + fmt.Printf(" %q located in %s\n", name, devPath) } } } From f2edfca23c283e106242eb1cd425f6e0cc0acd4d Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Thu, 29 Oct 2020 09:24:04 +0100 Subject: [PATCH 4/6] Check for directory existance, and just return no devices in that case, as opposed to an error. --- webcam.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webcam.go b/webcam.go index 1576682..cb49e73 100644 --- a/webcam.go +++ b/webcam.go @@ -325,6 +325,10 @@ var VIDEO4LINUX_DIR string = "/sys/class/video4linux/" // of path names to the "human readable" device name (the "card name"). func ListDevices() (devices map[string]string, err error) { devices = make(map[string]string) + if _, err := os.Stat(VIDEO4LINUX_DIR); !os.IsNotExist(err) { + // If no cameras were ever plugged in, directory is not created. + return + } err = filepath.Walk(VIDEO4LINUX_DIR, func(_ string, info os.FileInfo, err error) error { if err != nil { return fmt.Errorf("failure accessing %q: %v", VIDEO4LINUX_DIR, err) From bddca61513dae57d2992a0c4557fdbdf8209ebc0 Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Mon, 2 Nov 2020 06:46:54 +0100 Subject: [PATCH 5/6] fix err shadowed bug. --- webcam.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webcam.go b/webcam.go index cb49e73..fb1c3c1 100644 --- a/webcam.go +++ b/webcam.go @@ -325,7 +325,7 @@ var VIDEO4LINUX_DIR string = "/sys/class/video4linux/" // of path names to the "human readable" device name (the "card name"). func ListDevices() (devices map[string]string, err error) { devices = make(map[string]string) - if _, err := os.Stat(VIDEO4LINUX_DIR); !os.IsNotExist(err) { + if _, err = os.Stat(VIDEO4LINUX_DIR); !os.IsNotExist(err) { // If no cameras were ever plugged in, directory is not created. return } From 1d4040d0ed777ba29c696742f7089b4e48b63550 Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Mon, 2 Nov 2020 09:37:01 +0100 Subject: [PATCH 6/6] Fixed handling of the directory -- I was testing with the wrong version due to a go modules confusion. --- webcam.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/webcam.go b/webcam.go index fb1c3c1..61bf650 100644 --- a/webcam.go +++ b/webcam.go @@ -319,14 +319,17 @@ func gobytes(p unsafe.Pointer, n int) []byte { } // VIDEO4LINUX_DIR path to kernel known list of videos devices. -var VIDEO4LINUX_DIR string = "/sys/class/video4linux/" +var VIDEO4LINUX_DIR string = "/sys/class/video4linux" // ListDevices enumerates video devices present in the system. It returns a map of // of path names to the "human readable" device name (the "card name"). func ListDevices() (devices map[string]string, err error) { devices = make(map[string]string) - if _, err = os.Stat(VIDEO4LINUX_DIR); !os.IsNotExist(err) { - // If no cameras were ever plugged in, directory is not created. + if _, err = os.Stat(VIDEO4LINUX_DIR); err != nil { + if os.IsNotExist(err) { + // No devices present, make error nil and return an empty list. + err = nil + } return } err = filepath.Walk(VIDEO4LINUX_DIR, func(_ string, info os.FileInfo, err error) error {