Prechádzať zdrojové kódy

error reporting refactor; video input info; device info example

Vladimir Vivien 4 rokov pred
rodič
commit
1d9f59b2e7

+ 66 - 49
examples/device_info/devinfo.go

@@ -8,76 +8,93 @@ import (
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 
-func deviceCap(device *v4l2.Device) error {
-	caps, err := device.GetCapability()
+var template = "\t%-24s : %s\n"
+
+func main() {
+	var devName string
+	flag.StringVar(&devName, "d", "/dev/video0", "device name (path)")
+	flag.Parse()
+	device, err := v4l2.Open(devName)
 	if err != nil {
-		return err
+		log.Fatal(err)
 	}
+	defer device.Close()
 
-	log.Printf("%#v", caps.String())
-	return nil
-}
+	if err := printDeviceDriverInfo(device); err != nil {
+		log.Fatal(err)
+	}
 
-func setDefaultCrop(device *v4l2.Device) error {
-	cap, err := device.GetCropCapability()
-	if err != nil {
-		return err
+	if err := printVideoInputInfo(device); err != nil {
+		log.Fatal(err)
 	}
-	log.Printf("device crop capability: %s", cap.String())
-	err = device.SetCropRect(cap.DefaultRect)
-	if err != nil {
-		log.Printf("setcrop unsupported: %s", err)
+
+	if err := printFormatInfo(device); err != nil {
+		log.Fatal(err)
 	}
-	return nil
 }
 
-func getPixelFormat(device *v4l2.Device) error {
-	format, err := device.GetPixFormat()
+func printDeviceDriverInfo(dev *v4l2.Device) error {
+	caps, err := dev.GetCapability()
 	if err != nil {
-		return fmt.Errorf("default format: %w", err)
+		return fmt.Errorf("driver info: %w", err)
 	}
-	log.Println("got default format")
-	log.Printf("pixformat %#v", format)
-	return nil
-}
 
-func setPixelFormat(device *v4l2.Device) error {
-	err := device.SetPixFormat(v4l2.PixFormat{
-		Width:       320,
-		Height:      240,
-		PixelFormat: v4l2.PixelFmtYUYV,
-		Field:       v4l2.FieldNone,
-	})
-	if err != nil {
-		return fmt.Errorf("failed to set format: %w", err)
+	// print driver info
+	fmt.Println("Device Info:")
+	fmt.Printf(template, "Driver name", caps.DriverName())
+	fmt.Printf(template, "Card name", caps.CardName())
+	fmt.Printf(template, "Bus info", caps.BusInfo())
+
+	verVal := caps.GetVersion()
+	version := fmt.Sprintf("%d.%d.%d", verVal>>16, (verVal>>8)&0xff, verVal&0xff)
+	fmt.Printf(template, "Driver version", version)
+
+	fmt.Printf("\t%-16s : %0x\n", "Driver capabilities", caps.GetCapabilities())
+	for _, desc := range caps.GetDriverCapDescriptions() {
+		fmt.Printf("\t\t%s\n", desc.Desc)
 	}
-	log.Println("pixel format set")
+
+	fmt.Printf("\t%-16s : %0x\n", "Device capabilities", caps.GetCapabilities())
+	for _, desc := range caps.GetDeviceCapDescriptions() {
+		fmt.Printf("\t\t%s\n", desc.Desc)
+	}
+
 	return nil
 }
 
-func main() {
-	var devName string
-	flag.StringVar(&devName, "d", "/dev/video0", "device name (path)")
-	flag.Parse()
-	device, err := v4l2.Open(devName)
+func printVideoInputInfo(dev *v4l2.Device) error {
+	// first get current input
+	index, err := dev.GetVideoInputIndex()
 	if err != nil {
-		log.Fatal(err)
+		return fmt.Errorf("video input info: %w", err)
 	}
-	defer device.Close()
 
-	if err := deviceCap(device); err != nil {
-		log.Fatal(err)
-	}
+	fmt.Printf("Video input: %d", index)
 
-	if err := setDefaultCrop(device); err != nil {
-		log.Fatal(err)
+	// get the input info
+	info, err := dev.GetVideoInputInfo(uint32(index))
+	if err != nil {
+		return fmt.Errorf("video input info: %w", err)
 	}
 
-	if err := getPixelFormat(device); err != nil {
-		log.Fatal(err)
-	}
+	// print info
+	fmt.Printf(" (%s : %s)\n", info.GetName(), v4l2.InputStatuses[info.GetStatus()])
 
-	if err := setPixelFormat(device); err != nil {
-		log.Fatal(err)
+	return nil
+}
+
+func printFormatInfo(dev *v4l2.Device) error {
+	pixFmt, err := dev.GetPixFormat()
+	if err != nil {
+		return fmt.Errorf("video capture format: %w", err)
 	}
+	fmt.Println("Format video capture:")
+	fmt.Printf(template, "WidthxHeight", fmt.Sprintf("%dx%d", pixFmt.Width, pixFmt.Height))
+	fmt.Printf(template, "Pixel format", v4l2.PixelFormats[pixFmt.PixelFormat])
+	fmt.Printf(template, "Field", v4l2.Fields[pixFmt.Field])
+	fmt.Printf(template, "Bytes per line", fmt.Sprintf("%d",pixFmt.BytesPerLine))
+	fmt.Printf(template, "Size image", fmt.Sprintf("%d", pixFmt.SizeImage))
+	fmt.Printf(template, "Colorspace", v4l2.Colorspaces[pixFmt.Colorspace])
+
+	return nil
 }

+ 6 - 6
examples/v4l2_direct/v4l2_capture.go

@@ -16,9 +16,9 @@ import (
 
 const (
 	//ioctl command layout
-	iocOpNone  = 0
-	iocOpWrite = 1
-	iocOpRead  = 2
+	iocNone  = 0 // no op
+	iocWrite = 1 // userland app is writing, kernel reading
+	iocRead  = 2 // userland app is reading, kernel writing
 
 	iocTypeBits   = 8
 	iocNumberBits = 8
@@ -40,15 +40,15 @@ func ioEnc(iocMode, iocType, number, size uintptr) uintptr {
 }
 
 func ioEncR(iocType, number, size uintptr) uintptr {
-	return ioEnc(iocOpRead, iocType, number, size)
+	return ioEnc(iocRead, iocType, number, size)
 }
 
 func ioEncW(iocType, number, size uintptr) uintptr {
-	return ioEnc(iocOpWrite, iocType, number, size)
+	return ioEnc(iocWrite, iocType, number, size)
 }
 
 func ioEncRW(iocType, number, size uintptr) uintptr {
-	return ioEnc(iocOpRead|iocOpWrite, iocType, number, size)
+	return ioEnc(iocRead|iocWrite, iocType, number, size)
 }
 
 // four character pixel format encoding

+ 132 - 11
v4l2/capability.go

@@ -12,10 +12,85 @@ const (
 	CapVideoCapture       = 0x00000001 // V4L2_CAP_VIDEO_CAPTURE
 	CapVideoOutput        = 0x00000002 // V4L2_CAP_VIDEO_OUTPUT
 	CapVideoOverlay       = 0x00000004 // V4L2_CAP_VIDEO_OVERLAY
+	CapVBICapture         = 0x00000010 // V4L2_CAP_VBI_CAPTURE
+	CapVBIOutput          = 0x00000020 // V4L2_CAP_VBI_OUTPUT
+	CapSlicedVBICapture   = 0x00000040 // V4L2_CAP_SLICED_VBI_CAPTURE
+	CapSlicedVBIOutput    = 0x00000080 // V4L2_CAP_SLICED_VBI_OUTPUT
+	CapRDSCapture         = 0x00000100 // V4L2_CAP_RDS_CAPTURE
 	CapVideoOutputOverlay = 0x00000200 // V4L2_CAP_VIDEO_OUTPUT_OVERLAY
-	CapReadWrite          = 0x01000000 // V4L2_CAP_READWRITE
-	CapAsyncIO            = 0x02000000 // V4L2_CAP_ASYNCIO
-	CapStreaming          = 0x04000000 // V4L2_CAP_STREAMING
+	CapHWFrequencySeek    = 0x00000400 // V4L2_CAP_HW_FREQ_SEEK
+	CapRDSOutput          = 0x00000800 // V4L2_CAP_RDS_OUTPUT
+
+	CapVideoCaptureMPlane = 0x00001000 // V4L2_CAP_VIDEO_CAPTURE_MPLANE
+	CapVideoOutputMPlane  = 0x00002000 // V4L2_CAP_VIDEO_OUTPUT_MPLANE
+	CapVideoMem2MemMPlane = 0x00004000 // V4L2_CAP_VIDEO_M2M_MPLANE
+	CapVideoMem2Mem       = 0x00008000 // V4L2_CAP_VIDEO_M2M
+
+	CapTuner     = 0x00010000 // V4L2_CAP_TUNER
+	CapAudio     = 0x00020000 // V4L2_CAP_AUDIO
+	CapRadio     = 0x00040000 // V4L2_CAP_RADIO
+	CapModulator = 0x00080000 // V4L2_CAP_MODULATOR
+
+	CapSDRCapture        = 0x00100000 // V4L2_CAP_SDR_CAPTURE
+	CapExtendedPixFormat = 0x00200000 // V4L2_CAP_EXT_PIX_FORMAT
+	CapSDROutput         = 0x00400000 // V4L2_CAP_SDR_OUTPUT
+	CapMetadataCapture   = 0x00800000 // V4L2_CAP_META_CAPTURE
+
+	CapReadWrite      = 0x01000000 // V4L2_CAP_READWRITE
+	CapAsyncIO        = 0x02000000 // V4L2_CAP_ASYNCIO
+	CapStreaming      = 0x04000000 // V4L2_CAP_STREAMING
+	CapMetadataOutput = 0x08000000 // V4L2_CAP_META_OUTPUT
+
+	CapTouch             = 0x10000000 // V4L2_CAP_TOUCH
+	CapIOMediaController = 0x20000000 // V4L2_CAP_IO_MC
+
+	CapDeviceCapabilities = 0x80000000 // V4L2_CAP_DEVICE_CAPS
+)
+
+type CapabilityDesc struct {
+	Cap  uint32
+	Desc string
+}
+
+var (
+	Capabilities = []CapabilityDesc{
+		{Cap: CapVideoCapture, Desc: "video capture (single-planar)"},
+		{Cap: CapVideoOutput, Desc: "video output (single-planar)"},
+		{Cap: CapVideoOverlay, Desc: "video overlay"},
+		{Cap: CapVBICapture, Desc: "raw VBI capture"},
+		{Cap: CapVBIOutput, Desc: "raw VBI output"},
+		{Cap: CapSlicedVBICapture, Desc: "sliced VBI capture"},
+		{Cap: CapSlicedVBIOutput, Desc: "sliced VBI output"},
+		{Cap: CapRDSCapture, Desc: "RDS capture"},
+		{Cap: CapVideoOutputOverlay, Desc: "video output overlay"},
+		{Cap: CapHWFrequencySeek, Desc: "hardware frequency seeking"},
+		{Cap: CapRDSOutput, Desc: "RDS output"},
+
+		{Cap: CapVideoCaptureMPlane, Desc: "video capture (multi-planar)"},
+		{Cap: CapVideoOutputMPlane, Desc: "video output (multi-planar)"},
+		{Cap: CapVideoMem2MemMPlane, Desc: "memory-to-memory video (multi-planar)"},
+		{Cap: CapVideoMem2Mem, Desc: "memory-to-memory video (single-planar)"},
+
+		{Cap: CapTuner, Desc: "video tuner"},
+		{Cap: CapAudio, Desc: "audio inputs or outputs"},
+		{Cap: CapRadio, Desc: "radio receiver"},
+		{Cap: CapModulator, Desc: "radio frequency modulator"},
+
+		{Cap: CapSDRCapture, Desc: "SDR capture"},
+		{Cap: CapExtendedPixFormat, Desc: "extended pixel format"},
+		{Cap: CapSDROutput, Desc: "SDR output"},
+		{Cap: CapMetadataCapture, Desc: "metadata capture"},
+
+		{Cap: CapReadWrite, Desc: "read/write IO"},
+		{Cap: CapAsyncIO, Desc: "asynchronous IO"},
+		{Cap: CapStreaming, Desc: "streaming IO"},
+		{Cap: CapMetadataOutput, Desc: "metadata output"},
+
+		{Cap: CapTouch, Desc: "touch capability"},
+		{Cap: CapIOMediaController, Desc: "IO media controller"},
+
+		{Cap: CapDeviceCapabilities, Desc: "device capabilities"},
+	}
 )
 
 // v4l2Capability type for device (see v4l2_capability)
@@ -61,30 +136,76 @@ func (c Capability) GetDeviceCaps() uint32 {
 	return c.v4l2Cap.deviceCaps
 }
 
-// IsVideoCaptureSupported returns true if the device supports video capture.
-// See V4L2 API's V4L2_CAP_VIDEO_CAPTURE
+// IsVideoCaptureSupported returns caps & CapVideoCapture
 func (c Capability) IsVideoCaptureSupported() bool {
 	return (c.v4l2Cap.capabilities & CapVideoCapture) != 0
 }
 
-// IsVideoOutputSupported returns true if device supports video output
-// See V4L2 API's V4L2_CAP_VIDEO_OUTPUT
+// IsVideoOutputSupported returns caps & CapVideoOutput
 func (c Capability) IsVideoOutputSupported() bool {
 	return (c.v4l2Cap.capabilities & CapVideoOutput) != 0
 }
 
-// IsReadWriteSupported returns true if device supports direct read-write operations
-// See V4L2 API's V4L2_CAP_READWRITE
+// IsVideoOverlaySupported returns caps & CapVideoOverlay
+func (c Capability) IsVideoOverlaySupported() bool {
+	return (c.v4l2Cap.capabilities & CapVideoOverlay) != 0
+}
+
+// IsVideoOutputOverlaySupported returns caps & CapVideoOutputOverlay
+func (c Capability) IsVideoOutputOverlaySupported() bool {
+	return (c.v4l2Cap.capabilities & CapVideoOutputOverlay) != 0
+}
+
+// IsVideoCaptureMultiplanarSupported returns caps & CapVideoCaptureMPlane
+func (c Capability) IsVideoCaptureMultiplanarSupported() bool {
+	return (c.v4l2Cap.capabilities & CapVideoCaptureMPlane) != 0
+}
+
+// IsVideoOutputMultiplanerSupported returns caps & CapVideoOutputMPlane
+func (c Capability) IsVideoOutputMultiplanerSupported() bool {
+	return (c.v4l2Cap.capabilities & CapVideoOutputMPlane) != 0
+}
+
+// IsReadWriteSupported returns caps & CapReadWrite
 func (c Capability) IsReadWriteSupported() bool {
 	return (c.v4l2Cap.capabilities & CapReadWrite) != 0
 }
 
-// IsStreamingSupported returns true if the device supports streaming.
-// See V4L2 API's V4L2_CAP_STREAMING
+// IsStreamingSupported returns caps & CapStreaming
 func (c Capability) IsStreamingSupported() bool {
 	return (c.v4l2Cap.capabilities & CapStreaming) != 0
 }
 
+// IsDeviceCapabilitiesProvided returns true if the device returns
+// device-specific capabilities (via CapDeviceCapabilities)
+// See notes on VL42_CAP_DEVICE_CAPS:
+// https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-querycap.html?highlight=v4l2_cap_device_caps
+func (c Capability) IsDeviceCapabilitiesProvided() bool {
+	return (c.v4l2Cap.capabilities & CapDeviceCapabilities) != 0
+}
+
+// GetDriverCapDescriptions return textual descriptions of driver capabilities
+func (c Capability) GetDriverCapDescriptions() []CapabilityDesc {
+	var result []CapabilityDesc
+	for _, cap := range Capabilities {
+		if c.GetCapabilities() & cap.Cap == cap.Cap {
+			result = append(result, cap)
+		}
+	}
+	return result
+}
+
+// GetDeviceCapDescriptions return textual descriptions of device capabilities
+func (c Capability) GetDeviceCapDescriptions() []CapabilityDesc {
+	var result []CapabilityDesc
+	for _, cap := range Capabilities {
+		if c.GetDeviceCaps() & cap.Cap == cap.Cap {
+			result = append(result, cap)
+		}
+	}
+	return result
+}
+
 // DriverName returns a string value for the driver name
 func (c Capability) DriverName() string {
 	return toGoString(c.v4l2Cap.driver[:])

+ 1 - 7
v4l2/crop.go

@@ -1,7 +1,6 @@
 package v4l2
 
 import (
-	"errors"
 	"fmt"
 	"unsafe"
 )
@@ -58,12 +57,7 @@ func GetCropCapability(fd uintptr) (CropCapability, error) {
 func SetCropRect(fd uintptr, r Rect) error {
 	crop := Crop{Rect: r, StreamType: BufTypeVideoCapture}
 	if err := Send(fd, VidiocSetCrop, uintptr(unsafe.Pointer(&crop))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return fmt.Errorf("setcrop: unsupported: %w", err)
-		default:
-			return fmt.Errorf("setcrop failed: %w", err)
-		}
+		return fmt.Errorf("set crop: %w", err)
 	}
 	return nil
 }

+ 14 - 0
v4l2/device.go

@@ -116,6 +116,20 @@ func (d *Device) GetFormatDescriptions() ([]FormatDescription, error) {
 	return GetAllFormatDescriptions(d.fd)
 }
 
+func (d *Device) GetVideoInputIndex()(int32, error) {
+	if err := d.assertVideoCaptureSupport(); err != nil {
+		return 0, fmt.Errorf("device: %w", err)
+	}
+	return GetCurrentVideoInputIndex(d.fd)
+}
+
+func (d *Device) GetVideoInputInfo(index uint32) (InputInfo, error) {
+	if err := d.assertVideoCaptureSupport(); err != nil {
+		return InputInfo{}, fmt.Errorf("device: %w", err)
+	}
+	return GetVideoInputInfo(d.fd, index)
+}
+
 func (d *Device) StartStream(buffSize uint32) error {
 	if d.streaming {
 		return nil

+ 33 - 0
v4l2/errors.go

@@ -0,0 +1,33 @@
+package v4l2
+
+import (
+	"errors"
+	sys "syscall"
+)
+
+var (
+	ErrorSystem = errors.New("system error")
+	ErrorBadArgument = errors.New("bad argument error")
+	ErrorTemporary = errors.New("temporary error")
+	ErrorTimeout = errors.New("timeout error")
+	ErrorUnsupported = errors.New("unsupported error")
+)
+
+func parseErrorType(errno sys.Errno) error {
+	switch errno {
+	case sys.EBADF, sys.ENOMEM, sys.ENODEV, sys.EIO, sys.ENXIO: // structural, terminal
+	return ErrorSystem
+	case sys.EFAULT, sys.EINVAL: // bad argument, terminal
+	return ErrorBadArgument
+	case sys.ENOTTY: // unsupported
+	return ErrorUnsupported
+	default:
+		if errno.Timeout() {
+			return ErrorTimeout
+		}
+		if errno.Temporary() {
+			return ErrorTemporary
+		}
+		return errno
+	}
+}

+ 97 - 38
v4l2/format.go

@@ -1,7 +1,6 @@
 package v4l2
 
 import (
-	"errors"
 	"fmt"
 	"unsafe"
 )
@@ -35,6 +34,18 @@ func fourcc(a, b, c, d uint32) FourCCEncoding {
 	return (a | b<<8) | c<<16 | d<<24
 }
 
+// PixelFormats provides a map of FourCC encoding description
+var PixelFormats = map[FourCCEncoding]string{
+	PixFmtRGB24:   "24-bit RGB 8-8-8",
+	PixFmtGrey:    "8-bit Greyscale",
+	PixelFmtYUYV:  "YUYV 4:2:2",
+	PixelFmtMJPEG: "Motion-JPEG",
+	PixelFmtJPEG:  "JFIF JPEG",
+	PixelFmtMPEG:  "MPEG-1/2/4",
+	PixelFmtH264:  "H.264",
+	PixelFmtMPEG4: "MPEG-4 Part 2 ES",
+}
+
 // YcbcrEncoding (v4l2_ycbcr_encoding)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_ycbcr_encoding
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L300
@@ -59,40 +70,98 @@ const (
 	QuantizationLimRange                      // V4L2_QUANTIZATION_LIM_RANGE
 )
 
-// XferFunction (v4l2_xfer_func)
+// XferFunctionType (v4l2_xfer_func)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_xfer_func#c.V4L.v4l2_xfer_func
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L259
-type XferFunction = uint32
+type XferFunctionType = uint32
 
 const (
-	XferFuncDefault   XferFunction = iota // V4L2_XFER_FUNC_DEFAULT     = 0
-	XferFunc709                           // V4L2_XFER_FUNC_709         = 1,
-	ferFuncSRGB                           // V4L2_XFER_FUNC_SRGB        = 2,
-	XferFuncOpRGB                         // V4L2_XFER_FUNC_OPRGB       = 3,
-	XferFuncSmpte240M                     // V4L2_XFER_FUNC_SMPTE240M   = 4,
-	XferFuncNone                          // V4L2_XFER_FUNC_NONE        = 5,
-	XferFuncDciP3                         // V4L2_XFER_FUNC_DCI_P3      = 6,
-	XferFuncSmpte2084                     // V4L2_XFER_FUNC_SMPTE2084   = 7,
+	XferFuncDefault   XferFunctionType = iota // V4L2_XFER_FUNC_DEFAULT     = 0
+	XferFunc709                               // V4L2_XFER_FUNC_709         = 1,
+	XferFuncSRGB                              // V4L2_XFER_FUNC_SRGB        = 2,
+	XferFuncOpRGB                             // V4L2_XFER_FUNC_OPRGB       = 3,
+	XferFuncSMPTE240M                         // V4L2_XFER_FUNC_SMPTE240M   = 4,
+	XferFuncNone                              // V4L2_XFER_FUNC_NONE        = 5,
+	XferFuncDCIP3                             // V4L2_XFER_FUNC_DCI_P3      = 6,
+	XferFuncSMPTE2084                         // V4L2_XFER_FUNC_SMPTE2084   = 7,
 )
 
-// Field (v4l2_field)
+var XferFunctions = map[XferFunctionType]string{
+	XferFuncDefault:   "Default",
+	XferFunc709:       "Rec. 709",
+	XferFuncSRGB:      "sRGB",
+	XferFuncOpRGB:     "opRGB",
+	XferFuncSMPTE240M: "SMPTE 240M",
+	XferFuncNone:      "None",
+	XferFuncDCIP3:     "DCI-P3",
+	XferFuncSMPTE2084: "SMPTE 2084",
+}
+
+// FieldType (v4l2_field)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/field-order.html?highlight=v4l2_field#c.v4l2_field
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L88
-type Field = uint32
+type FieldType = uint32
+
+const (
+	FieldAny                 FieldType = iota // V4L2_FIELD_ANY
+	FieldNone                                 // V4L2_FIELD_NONE
+	FieldTop                                  // V4L2_FIELD_TOP
+	FieldBottom                               // V4L2_FIELD_BOTTOM
+	FieldInterlaced                           // V4L2_FIELD_INTERLACED
+	FieldSequentialTopBottom                  // V4L2_FIELD_SEQ_TB
+	FieldSequentialBottomTop                  // V4L2_FIELD_SEQ_BT
+	FieldAlternate                            // V4L2_FIELD_ALTERNATE
+	FieldInterlacedTopBottom                  // V4L2_FIELD_INTERLACED_TB
+	FieldInterlacedBottomTop                  // V4L2_FIELD_INTERLACED_BT
+)
+
+// Fields is a map of FieldType description
+var Fields = map[FieldType]string{
+	FieldAny:                 "any",
+	FieldNone:                "none",
+	FieldTop:                 "top",
+	FieldBottom:              "bottom",
+	FieldInterlaced:          "interlaced",
+	FieldSequentialTopBottom: "sequential top-bottom",
+	FieldSequentialBottomTop: "Sequential botton-top",
+	FieldAlternate:           "alternating",
+	FieldInterlacedTopBottom: "interlaced top-bottom",
+	FieldInterlacedBottomTop: "interlaced bottom-top",
+}
+
+// ColorspaceType
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L195
+type ColorspaceType = uint32
 
 const (
-	FieldAny          Field = iota // V4L2_FIELD_ANY
-	FieldNone                      // V4L2_FIELD_NONE
-	FieldTop                       // V4L2_FIELD_TOP
-	FieldBottom                    // V4L2_FIELD_BOTTOM
-	FieldInterlaced                // V4L2_FIELD_INTERLACED
-	FieldSeqTb                     // V4L2_FIELD_SEQ_TB
-	FieldSeqBt                     // V4L2_FIELD_SEQ_BT
-	FieldAlternate                 // V4L2_FIELD_ALTERNATE
-	FieldInterlacedTb              // V4L2_FIELD_INTERLACED_TB
-	FieldInterlacedBt              // V4L2_FIELD_INTERLACED_BT
+	ColorspaceTypeDefault ColorspaceType = iota //V4L2_COLORSPACE_DEFAULT
+	ColorspaceSMPTE170M                         //V4L2_COLORSPACE_SMPTE170M
+	ColorspaceSMPTE240M                         // V4L2_COLORSPACE_SMPTE240M
+	ColorspaceREC709                            // V4L2_COLORSPACE_REC709
+	ColorspaceBT878                             // V4L2_COLORSPACE_BT878 (absolete)
+	Colorspace470SystemM                        // V4L2_COLORSPACE_470_SYSTEM_M (absolete)
+	Colorspace470SystemBG                       // V4L2_COLORSPACE_470_SYSTEM_BG
+	ColorspaceJPEG                              // V4L2_COLORSPACE_JPEG
+	ColorspaceSRGB                              // V4L2_COLORSPACE_SRGB
+	ColorspaceOPRGB                             // V4L2_COLORSPACE_OPRGB
+	ColorspaceBT2020                            // V4L2_COLORSPACE_BT2020
+	ColorspaceRaw                               // V4L2_COLORSPACE_RAW
+	ColorspaceDCIP3                             // V4L2_COLORSPACE_DCI_P3
 )
 
+// Colorspaces is a map of colorspace to its respective description
+var Colorspaces = map[ColorspaceType]string{
+	ColorspaceTypeDefault: "Default",
+	ColorspaceREC709:      "Rec. 709",
+	Colorspace470SystemBG: "470 System BG",
+	ColorspaceJPEG:        "JPEG",
+	ColorspaceSRGB:        "sRGB",
+	ColorspaceOPRGB:       "opRGB",
+	ColorspaceBT2020:      "BT.2020",
+	ColorspaceRaw:         "Raw",
+	ColorspaceDCIP3:       "DCI-P3",
+}
+
 // PixFormat (v4l2_pix_format)
 // https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/pixfmt-002.html?highlight=v4l2_pix_format
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L496
@@ -100,15 +169,15 @@ type PixFormat struct {
 	Width        uint32
 	Height       uint32
 	PixelFormat  FourCCEncoding
-	Field        Field
+	Field        FieldType
 	BytesPerLine uint32
 	SizeImage    uint32
-	Colorspace   uint32
+	Colorspace   ColorspaceType
 	Priv         uint32
 	Flags        uint32
 	YcbcrEnc     YcbcrEncoding
 	Quantization Quantization
-	XferFunc     XferFunction
+	XferFunc     XferFunctionType
 }
 
 // v4l2Format (v4l2_format)
@@ -151,12 +220,7 @@ func (f v4l2Format) setPixFormat(newPix PixFormat) {
 func GetPixFormat(fd uintptr) (PixFormat, error) {
 	format := v4l2Format{StreamType: BufTypeVideoCapture}
 	if err := Send(fd, VidiocGetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return PixFormat{}, fmt.Errorf("pix format: unsupported: %w", err)
-		default:
-			return PixFormat{}, fmt.Errorf("pix format failed: %w", err)
-		}
+		return PixFormat{}, fmt.Errorf("pix format failed: %w", err)
 	}
 
 	return format.getPixFormat(), nil
@@ -169,12 +233,7 @@ func SetPixFormat(fd uintptr, pixFmt PixFormat) error {
 	format.setPixFormat(pixFmt)
 
 	if err := Send(fd, VidiocSetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return fmt.Errorf("pix format: unsupported operation: %w", err)
-		default:
-			return fmt.Errorf("pix format failed: %w", err)
-		}
+		return fmt.Errorf("pix format failed: %w", err)
 	}
 	return nil
 }

+ 14 - 14
v4l2/format_desc.go

@@ -1,9 +1,10 @@
 package v4l2
 
 import (
-	"errors"
 	"fmt"
 	"unsafe"
+
+	sys "golang.org/x/sys/unix"
 )
 
 // FmtDescFlag image format description flags
@@ -78,7 +79,7 @@ func (d FormatDescription) GetBusCode() uint32 {
 // NOTE: This method must be used on a FormatDescription value that was created
 // with a call to GetFormatDescription or GetAllFormatDescriptions.
 func (d FormatDescription) GetFrameSize() (FrameSize, error) {
-	if d.fd == 0{
+	if d.fd == 0 {
 		return FrameSize{}, fmt.Errorf("invalid file descriptor")
 	}
 	return GetFormatFrameSize(d.fd, d.index, d.pixelFormat)
@@ -88,29 +89,28 @@ func (d FormatDescription) GetFrameSize() (FrameSize, error) {
 func GetFormatDescription(fd uintptr, index uint32) (FormatDescription, error) {
 	desc := v4l2FormatDesc{index: index, bufType: BufTypeVideoCapture}
 	if err := Send(fd, VidiocEnumFmt, uintptr(unsafe.Pointer(&desc))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return FormatDescription{}, fmt.Errorf("format desc: index %d: not found %w", index, err)
-		default:
-			return FormatDescription{}, fmt.Errorf("format desc failed: %w", err)
-		}
+		return FormatDescription{}, fmt.Errorf("format desc: index %d: %w", index, err)
+
 	}
-	return FormatDescription{fd:fd, v4l2FormatDesc:desc}, nil
+	return FormatDescription{fd: fd, v4l2FormatDesc: desc}, nil
 }
 
 // GetAllFormatDescriptions attempts to retrieve all device format descriptions by
-// iterating from 0 upto an index that returns an error. At that point, the function
+// iterating from 0 up to an index that returns an error. At that point, the function
 // will return the collected descriptions and the error.
 // So if len(result) > 0, then error could be ignored.
-func GetAllFormatDescriptions(fd uintptr) (result []FormatDescription, err error){
+func GetAllFormatDescriptions(fd uintptr) (result []FormatDescription, err error) {
 	index := uint32(0)
 	for {
 		desc := v4l2FormatDesc{index: index, bufType: BufTypeVideoCapture}
 		if err = Send(fd, VidiocEnumFmt, uintptr(unsafe.Pointer(&desc))); err != nil {
-			break
+			errno := err.(sys.Errno)
+			if errno.Is(sys.EINVAL) && len(result) > 0 {
+				break
+			}
 		}
-		result = append(result, FormatDescription{fd: fd, v4l2FormatDesc:desc})
+		result = append(result, FormatDescription{fd: fd, v4l2FormatDesc: desc})
 		index++
 	}
 	return result, err
-}
+}

+ 4 - 10
v4l2/format_framesizes.go

@@ -1,7 +1,6 @@
 package v4l2
 
 import (
-	"errors"
 	"fmt"
 	"unsafe"
 )
@@ -87,7 +86,7 @@ type v4l2FrameSizeEnum struct {
 
 // getFrameSize retrieves the supported frame size
 func (fs v4l2FrameSizeEnum) getFrameSize() FrameSize {
-	frameSize := FrameSize{FrameSizeType:fs.frameSizeType, PixelFormat: fs.pixelFormat}
+	frameSize := FrameSize{FrameSizeType: fs.frameSizeType, PixelFormat: fs.pixelFormat}
 	switch fs.frameSizeType {
 	case FrameSizeTypeDiscrete:
 		fsDiscrete := (*FrameSizeDiscrete)(unsafe.Pointer(&fs.frameSize[0]))
@@ -103,12 +102,7 @@ func (fs v4l2FrameSizeEnum) getFrameSize() FrameSize {
 func GetFormatFrameSize(fd uintptr, index uint32, encoding FourCCEncoding) (FrameSize, error) {
 	fsEnum := v4l2FrameSizeEnum{index: index, pixelFormat: encoding}
 	if err := Send(fd, VidiocEnumFrameSizes, uintptr(unsafe.Pointer(&fsEnum))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return FrameSize{}, fmt.Errorf("frame size: index %d: not found %w", index, err)
-		default:
-			return FrameSize{}, fmt.Errorf("frame size: %w", err)
-		}
+		return FrameSize{}, fmt.Errorf("frame size: index %d: %w", index, err)
 	}
 	return fsEnum.getFrameSize(), nil
 }
@@ -134,7 +128,7 @@ func GetAllFormatFrameSizes(fd uintptr) (result []FrameSize, err error) {
 			// At index 0, check the frame type, if not discrete exit loop.
 			// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-framesizes.html
 			result = append(result, fsEnum.getFrameSize())
-			if index == 0 && fsEnum.frameSizeType != FrameSizeTypeDiscrete{
+			if index == 0 && fsEnum.frameSizeType != FrameSizeTypeDiscrete {
 				break
 			}
 
@@ -142,4 +136,4 @@ func GetAllFormatFrameSizes(fd uintptr) (result []FrameSize, err error) {
 		}
 	}
 	return result, err
-}
+}

+ 65 - 45
v4l2/ioctl.go

@@ -7,64 +7,69 @@ import (
 )
 
 // ioctl uses a 32-bit value to encode commands sent to the kernel for device control.
-// Requests sent via ioctl uses a 32-bit value with the following layout:
+// Requests sent via ioctl uses the following layout:
 // - lower 16 bits: ioctl command
 // - Upper 14 bits: size of the parameter structure
 // - MSB 2 bits: are reserved for indicating the ``access mode''.
-// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/asm-generic/ioctl.h
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h
 
 const (
-	// ioctl op direction:
-	// Write: userland is writing and kernel is reading.
-	// Read:  userland is reading and kernel is writing.
-	iocOpNone  = 0
-	iocOpWrite = 1
-	iocOpRead  = 2
-
 	// ioctl command bit sizes
-	iocTypeBits   = 8
+	// See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L23
 	iocNumberBits = 8
+	iocTypeBits   = 8
 	iocSizeBits   = 14
-	iocOpBits     = 2
+	iocDirBits    = 2
 
 	// ioctl bit layout positions
-	numberPos = 0
-	typePos   = numberPos + iocNumberBits
-	sizePos   = typePos + iocTypeBits
-	opPos     = sizePos + iocSizeBits
+	// see https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L44
+	numShift  = 0
+	typeShift = numShift + iocNumberBits
+	sizeShift = typeShift + iocTypeBits
+	dirShift  = sizeShift + iocSizeBits
+
+	// ioctl direction bits
+	// These values are from the ioctl.h in linux.
+	// See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L57
+	iocNone  = 0 // no op
+	iocWrite = 1 // userland app is writing, kernel reading
+	iocRead  = 2 // userland app is reading, kernel writing
+
 )
 
 // iocEnc encodes V42L API command as value.
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L69
-func iocEnc(iocMode, iocType, number, size uintptr) uintptr {
-	return (iocMode << opPos) | (iocType << typePos) | (number << numberPos) | (size << sizePos)
+func iocEnc(dir, iocType, number, size uintptr) uintptr {
+	return (dir << dirShift) | (iocType << typeShift) | (number << numShift) | (size << sizeShift)
 }
 
-// iocEncRead encodes ioctl command where program reads result from kernel.
+// iocEncR encodes ioctl command where program reads result from kernel.
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L86
-func iocEncRead(iocType, number, size uintptr) uintptr {
-	return iocEnc(iocOpRead, iocType, number, size)
+func iocEncR(iocType, number, size uintptr) uintptr {
+	return iocEnc(iocRead, iocType, number, size)
 }
 
-// iocEncWrite encodes ioctl command where program writes values read by the kernel.
+// iocEncW encodes ioctl command where program writes values read by the kernel.
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L87
-func iocEncWrite(iocType, number, size uintptr) uintptr {
-	return iocEnc(iocOpWrite, iocType, number, size)
+func iocEncW(iocType, number, size uintptr) uintptr {
+	return iocEnc(iocWrite, iocType, number, size)
 }
 
-// iocEncReadWrite encodes ioctl command for program reads and program writes.
+// iocEncRW encodes ioctl command for program reads and program writes.
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L88
-func iocEncReadWrite(iocType, number, size uintptr) uintptr {
-	return iocEnc(iocOpRead|iocOpWrite, iocType, number, size)
+func iocEncRW(iocType, number, size uintptr) uintptr {
+	return iocEnc(iocRead|iocWrite, iocType, number, size)
 }
 
 // ioctl is a wrapper for Syscall(SYS_IOCTL)
-func ioctl(fd, req, arg uintptr) (err error) {
+func ioctl(fd, req, arg uintptr) (err sys.Errno) {
 	if _, _, errno := sys.Syscall(sys.SYS_IOCTL, fd, req, arg); errno != 0 {
-		err = errno
-		return
+		if errno != 0 {
+			err = errno
+			return
+		}
 	}
-	return nil
+	return 0
 }
 
 // V4L2 command request values for ioctl
@@ -72,22 +77,37 @@ func ioctl(fd, req, arg uintptr) (err error) {
 // https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/user-func.html
 
 var (
-	VidiocQueryCap       = iocEncRead('V', 0, uintptr(unsafe.Sizeof(v4l2Capability{})))          // Represents command VIDIOC_QUERYCAP
-	VidiocEnumFmt        = iocEncReadWrite('V', 2, uintptr(unsafe.Sizeof(v4l2FormatDesc{})))     // Represents command VIDIOC_ENUM_FMT
-	VidiocGetFormat      = iocEncReadWrite('V', 4, uintptr(unsafe.Sizeof(v4l2Format{})))         // Represents command VIDIOC_G_FMT
-	VidiocSetFormat      = iocEncReadWrite('V', 5, uintptr(unsafe.Sizeof(v4l2Format{})))         // Represents command VIDIOC_S_FMT
-	VidiocReqBufs        = iocEncReadWrite('V', 8, uintptr(unsafe.Sizeof(RequestBuffers{})))     // Represents command VIDIOC_REQBUFS
-	VidiocQueryBuf       = iocEncReadWrite('V', 9, uintptr(unsafe.Sizeof(BufferInfo{})))         // Represents command VIDIOC_QUERYBUF
-	VidiocQueueBuf       = iocEncReadWrite('V', 15, uintptr(unsafe.Sizeof(BufferInfo{})))        // Represents command VIDIOC_QBUF
-	VidiocDequeueBuf     = iocEncReadWrite('V', 17, uintptr(unsafe.Sizeof(BufferInfo{})))        // Represents command VIDIOC_DQBUF
-	VidiocStreamOn       = iocEncWrite('V', 18, uintptr(unsafe.Sizeof(int32(0))))                // Represents command VIDIOC_STREAMON
-	VidiocStreamOff      = iocEncWrite('V', 19, uintptr(unsafe.Sizeof(int32(0))))                // Represents command VIDIOC_STREAMOFF
-	VidiocCropCap        = iocEncReadWrite('V', 58, uintptr(unsafe.Sizeof(CropCapability{})))    // Represents command VIDIOC_CROPCAP
-	VidiocSetCrop        = iocEncWrite('V', 60, uintptr(unsafe.Sizeof(Crop{})))                  // Represents command VIDIOC_S_CROP
-	VidiocEnumFrameSizes = iocEncReadWrite('V', 74, uintptr(unsafe.Sizeof(v4l2FrameSizeEnum{}))) // Represents command VIDIOC_ENUM_FRAMESIZES
+	VidiocQueryCap       = iocEncR('V', 0, unsafe.Sizeof(v4l2Capability{}))      // Represents command VIDIOC_QUERYCAP
+	VidiocEnumFmt        = iocEncRW('V', 2, unsafe.Sizeof(v4l2FormatDesc{}))     // Represents command VIDIOC_ENUM_FMT
+	VidiocGetFormat      = iocEncRW('V', 4, unsafe.Sizeof(v4l2Format{}))         // Represents command VIDIOC_G_FMT
+	VidiocSetFormat      = iocEncRW('V', 5, unsafe.Sizeof(v4l2Format{}))         // Represents command VIDIOC_S_FMT
+	VidiocReqBufs        = iocEncRW('V', 8, unsafe.Sizeof(RequestBuffers{}))     // Represents command VIDIOC_REQBUFS
+	VidiocQueryBuf       = iocEncRW('V', 9, unsafe.Sizeof(BufferInfo{}))         // Represents command VIDIOC_QUERYBUF
+	VidiocQueueBuf       = iocEncRW('V', 15, unsafe.Sizeof(BufferInfo{}))        // Represents command VIDIOC_QBUF
+	VidiocDequeueBuf     = iocEncRW('V', 17, unsafe.Sizeof(BufferInfo{}))        // Represents command VIDIOC_DQBUF
+	VidiocStreamOn       = iocEncW('V', 18, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_STREAMON
+	VidiocStreamOff      = iocEncW('V', 19, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_STREAMOFF
+	VidiocEnumInput      = iocEncRW('V', 26, unsafe.Sizeof(v4l2InputInfo{}))     // Represents command VIDIOC_ENUMINPUT
+	VidiocGetVideoInput  = iocEncR('V', 38, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_G_INPUT
+	VidiocCropCap        = iocEncRW('V', 58, unsafe.Sizeof(CropCapability{}))    // Represents command VIDIOC_CROPCAP
+	VidiocSetCrop        = iocEncW('V', 60, unsafe.Sizeof(Crop{}))               // Represents command VIDIOC_S_CROP
+	VidiocEnumFrameSizes = iocEncRW('V', 74, unsafe.Sizeof(v4l2FrameSizeEnum{})) // Represents command VIDIOC_ENUM_FRAMESIZES
 )
 
-// Send sends a raw ioctl request to the kernel (via ioctl syscall)
+// Send sends a request to the kernel (via ioctl syscall)
 func Send(fd, req, arg uintptr) error {
-	return ioctl(fd, req, arg)
+	errno := ioctl(fd, req, arg)
+	if errno == 0 {
+		return nil
+	}
+	parsedErr := parseErrorType(errno)
+	switch parsedErr {
+	case ErrorUnsupported, ErrorSystem, ErrorBadArgument:
+		return parsedErr
+	case ErrorTimeout, ErrorTemporary:
+		// TODO add code for automatic retry/recovery
+		return errno
+	default:
+		return errno
+	}
 }

+ 7 - 36
v4l2/streaming.go

@@ -159,12 +159,7 @@ type PlaneService struct {
 func StreamOn(fd uintptr) error {
 	bufType := BufTypeVideoCapture
 	if err := Send(fd, VidiocStreamOn, uintptr(unsafe.Pointer(&bufType))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return fmt.Errorf("stream on: unsupported: %w", err)
-		default:
-			return fmt.Errorf("stream on: %w", err)
-		}
+		return fmt.Errorf("stream on: %w", err)
 	}
 	return nil
 }
@@ -175,12 +170,7 @@ func StreamOn(fd uintptr) error {
 func StreamOff(fd uintptr) error {
 	bufType := BufTypeVideoCapture
 	if err := Send(fd, VidiocStreamOff, uintptr(unsafe.Pointer(&bufType))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return fmt.Errorf("stream off: unsupported: %w", err)
-		default:
-			return fmt.Errorf("stream off: %w", err)
-		}
+		return fmt.Errorf("stream off: %w", err)
 	}
 	return nil
 }
@@ -195,12 +185,7 @@ func AllocateBuffers(fd uintptr, buffSize uint32) (RequestBuffers, error) {
 	}
 
 	if err := Send(fd, VidiocReqBufs, uintptr(unsafe.Pointer(&req))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return RequestBuffers{}, fmt.Errorf("request buffers: unsupported: %w", err)
-		default:
-			return RequestBuffers{}, fmt.Errorf("request buffers: %w", err)
-		}
+		return RequestBuffers{}, fmt.Errorf("request buffers: %w", err)
 	}
 	if req.Count < 2 {
 		return RequestBuffers{}, errors.New("request buffers: insufficient memory on device")
@@ -219,12 +204,7 @@ func GetBufferInfo(fd uintptr, index uint32) (BufferInfo, error) {
 	}
 
 	if err := Send(fd, VidiocQueryBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return BufferInfo{}, fmt.Errorf("buffer info: unsupported: %w", err)
-		default:
-			return BufferInfo{}, fmt.Errorf("buffer info: %w", err)
-		}
+		return BufferInfo{}, fmt.Errorf("buffer info: %w", err)
 	}
 
 	return buf, nil
@@ -252,12 +232,7 @@ func QueueBuffer(fd uintptr, index uint32) (BufferInfo, error) {
 	}
 
 	if err := Send(fd, VidiocQueueBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return BufferInfo{}, fmt.Errorf("buffer: unsupported: %w", err)
-		default:
-			return BufferInfo{}, fmt.Errorf("buffer: %w", err)
-		}
+		return BufferInfo{}, fmt.Errorf("buffer queue: %w", err)
 	}
 
 	return buf, nil
@@ -274,12 +249,8 @@ func DequeueBuffer(fd uintptr) (BufferInfo, error) {
 	}
 
 	if err := Send(fd, VidiocDequeueBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
-		switch {
-		case errors.Is(err, ErrorUnsupported):
-			return BufferInfo{}, fmt.Errorf("buffer: unsupported: %w", err)
-		default:
-			return BufferInfo{}, fmt.Errorf("buffer: %w", err)
-		}
+		return BufferInfo{}, fmt.Errorf("buffer dequeue: %w", err)
+
 	}
 
 	return buf, nil

+ 0 - 5
v4l2/types.go

@@ -1,7 +1,2 @@
 package v4l2
 
-import "errors"
-
-var (
-	ErrorUnsupported = errors.New("unsupported feature")
-)

+ 125 - 0
v4l2/video_info.go

@@ -0,0 +1,125 @@
+package v4l2
+
+import (
+	"fmt"
+	sys "golang.org/x/sys/unix"
+	"unsafe"
+)
+
+// InputStatus
+// See https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-enuminput.html?highlight=v4l2_input#input-status
+type InputStatus = uint32
+
+var (
+	InputStatusNoPower  = InputStatus(0x00000001) // V4L2_IN_ST_NO_POWER
+	InputStatusNoSignal = InputStatus(0x00000002) // V4L2_IN_ST_NO_SIGNAL
+	InputStatusNoColor  = InputStatus(0x00000004) // V4L2_IN_ST_NO_COLOR
+)
+
+var InputStatuses = map[InputStatus]string{
+	0:                   "ok",
+	InputStatusNoPower:  "no power",
+	InputStatusNoSignal: "no signal",
+	InputStatusNoColor:  "no color",
+}
+
+type InputType = uint32
+
+const (
+	InputTypeTuner InputType = iota + 1
+	InputTypeCamera
+	InputTypeTouch
+)
+
+type StandardId = uint64
+
+type v4l2InputInfo struct {
+	index        uint32
+	name         [32]uint8
+	inputType    InputType
+	audioset     uint32
+	tuner        uint32
+	std          StandardId
+	status       InputStatus
+	capabilities uint32
+	reserved     [3]uint32
+	_            [4]uint8 // go compiler alignment adjustment for 32-bit platforms (Raspberry pi's, etc)
+}
+
+// InputInfo (v4l2_input)
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1649
+// https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-enuminput.html
+type InputInfo struct {
+	v4l2InputInfo
+}
+
+func (i InputInfo) GetIndex() uint32 {
+	return i.index
+}
+
+func (i InputInfo) GetName() string {
+	return toGoString(i.name[:])
+}
+
+func (i InputInfo) GetInputType() InputType {
+	return i.inputType
+}
+
+func (i InputInfo) GetAudioset() uint32 {
+	return i.audioset
+}
+
+func (i InputInfo) GetTuner() uint32 {
+	return i.tuner
+}
+
+func (i InputInfo) GetStandardId() StandardId {
+	return i.std
+}
+
+func (i InputInfo) GetStatus() uint32 {
+	return i.status
+}
+
+func (i InputInfo) GetCapabilities() uint32 {
+	return i.capabilities
+}
+
+// GetCurrentVideoInputIndex returns the currently selected video input index
+// See https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-input.html
+func GetCurrentVideoInputIndex(fd uintptr) (int32, error) {
+	var index int32
+	if err := Send(fd, VidiocGetVideoInput, uintptr(unsafe.Pointer(&index))); err != nil {
+		return -1, fmt.Errorf("video input get: %w", err)
+	}
+	return index, nil
+}
+
+// GetVideoInputInfo returns specified input information for video device
+// See https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-enuminput.html
+func GetVideoInputInfo(fd uintptr, index uint32) (InputInfo, error) {
+	input := v4l2InputInfo{index: index}
+	if err := Send(fd, VidiocEnumInput, uintptr(unsafe.Pointer(&input))); err != nil {
+		return InputInfo{}, fmt.Errorf("video input info: index %d: %w", index, err)
+	}
+	return InputInfo{v4l2InputInfo: input}, nil
+}
+
+// GetAllVideoInputInfo returns all input information for device by
+// iterating from input index = 0 until an error (EINVL) is returned.
+func GetAllVideoInputInfo(fd uintptr) (result []InputInfo, err error) {
+	index := uint32(0)
+	for {
+		input := v4l2InputInfo{index: index}
+		if err = Send(fd, VidiocEnumInput, uintptr(unsafe.Pointer(&input))); err != nil {
+			errno := err.(sys.Errno)
+			if errno.Is(sys.EINVAL) && len(result) > 0 {
+				break
+			}
+			return result, fmt.Errorf("all video info: %w", err)
+		}
+		result = append(result, InputInfo{v4l2InputInfo: input})
+		index++
+	}
+	return result, err
+}