Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions vidnumerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ const (
(0 << IOCNrShift) |
(unsafe.Sizeof(cap{}) << IOCSizeShift)

// V4L2CapVideoCapture is the device capability flag indicating
// the device supports video capture (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS).
V4L2CapVideoCapture uint32 = 69206017
V4L2CapVideoCapture uint32 = 0x00000001
V4L2CapStreaming uint32 = 0x04000000
V4L2CapDeviceCaps uint32 = 0x80000000
)

type cap struct {
Expand Down Expand Up @@ -60,22 +60,37 @@ func (r *cap) QueryFd(fileDescriptor int) error {
return nil
}

// this function checks the ioctl for VIDIOC_QUERYCAP to see if the device is a video capture device
func (r cap) videoCaptureCaps() uint32 {
if r.capabilities&V4L2CapDeviceCaps != 0 {
return r.deviceCaps
}
return r.capabilities
}

func (r cap) isVideoCapture() bool {
caps := r.videoCaptureCaps()
return caps&V4L2CapVideoCapture != 0 && caps&V4L2CapStreaming != 0
}

// IsVideoCapture checks the ioctl for VIDIOC_QUERYCAP to see if the device is a video capture device.
func IsVideoCapture(path string) (bool, error) {
f, err := os.OpenFile(path, os.O_RDONLY, 0o755)
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return false, err
}
fd := f.Fd()
defer func() {
_ = f.Close()
}()

ic := cap{}
err = ic.QueryFd(int(fd))
err = ic.QueryFd(int(f.Fd()))
if err != nil {
return false, err
}
return ic.deviceCaps == V4L2CapVideoCapture, nil
return ic.isVideoCapture(), nil
}

// this function checks the ioctl for VIDIOC_QUERYCAP to see if the device is a video capture device
// EnumeratedVideoDevices lists all /dev/video* nodes that support video capture.
func EnumeratedVideoDevices() ([]string, error) {
// list all files in the /dev directory
d, err := os.ReadDir("/dev")
Expand Down
68 changes: 61 additions & 7 deletions vidnumerator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,66 @@ func TestEnumeratedVideoDevices(t *testing.T) {
}
}

func TestV4L2CapVideoCaptureConstant(t *testing.T) {
// Verify the constant matches the expected V4L2 capability flags.
// 69206017 = 0x04200001 = V4L2_CAP_VIDEO_CAPTURE (0x1) | V4L2_CAP_STREAMING (0x04000000) | V4L2_CAP_DEVICE_CAPS (0x80000000)
// Note: 69206017 = 0x41F8001 — let's verify the actual hex.
expected := uint32(69206017)
if V4L2CapVideoCapture != expected {
t.Fatalf("V4L2CapVideoCapture = %d, expected %d", V4L2CapVideoCapture, expected)
func TestCapVideoCaptureCapsUsesDeviceCapsWhenPresent(t *testing.T) {
ic := cap{
capabilities: V4L2CapDeviceCaps,
deviceCaps: V4L2CapVideoCapture | V4L2CapStreaming,
}

if got := ic.videoCaptureCaps(); got != ic.deviceCaps {
t.Fatalf("videoCaptureCaps() = %#x, want %#x", got, ic.deviceCaps)
}
}

func TestCapVideoCaptureCapsFallsBackToCapabilities(t *testing.T) {
ic := cap{
capabilities: V4L2CapVideoCapture | V4L2CapStreaming,
}

if got := ic.videoCaptureCaps(); got != ic.capabilities {
t.Fatalf("videoCaptureCaps() = %#x, want %#x", got, ic.capabilities)
}
}

func TestCapIsVideoCaptureAcceptsAdditionalFlags(t *testing.T) {
ic := cap{
capabilities: V4L2CapDeviceCaps,
deviceCaps: V4L2CapVideoCapture |
V4L2CapStreaming |
0x00000002,
}

if !ic.isVideoCapture() {
t.Fatal("expected device with capture and streaming flags to be detected")
}
}

func TestCapIsVideoCaptureRequiresCaptureAndStreaming(t *testing.T) {
tests := []struct {
name string
ic cap
}{
{
name: "missing capture",
ic: cap{
capabilities: V4L2CapDeviceCaps,
deviceCaps: V4L2CapStreaming,
},
},
{
name: "missing streaming",
ic: cap{
capabilities: V4L2CapDeviceCaps,
deviceCaps: V4L2CapVideoCapture,
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.ic.isVideoCapture() {
t.Fatal("expected non-capture device")
}
})
}
}
Loading