Vladimir Vivien 4 роки тому
батько
коміт
09285a2e7f
8 змінених файлів з 162 додано та 147 видалено
  1. 20 6
      v4l2/capability.go
  2. 0 20
      v4l2/commands.go
  3. 2 2
      v4l2/crop.go
  4. 0 68
      v4l2/encoding.go
  5. 21 13
      v4l2/format.go
  6. 78 10
      v4l2/ioctl.go
  7. 29 28
      v4l2/streaming.go
  8. 12 0
      v4l2/support.go

+ 20 - 6
v4l2/capability.go

@@ -18,7 +18,8 @@ const (
 	CapStreaming          = 0x04000000 // V4L2_CAP_STREAMING
 	CapStreaming          = 0x04000000 // V4L2_CAP_STREAMING
 )
 )
 
 
-// v4l2Capabiolity type for device (see v4l2_capability
+// v4l2Capability type for device (see v4l2_capability)
+// This type stores the capability information returned by the device.
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.v4l2_capability
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.v4l2_capability
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L440
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L440
 type v4l2Capability struct {
 type v4l2Capability struct {
@@ -32,7 +33,7 @@ type v4l2Capability struct {
 }
 }
 
 
 // Capability represents capabilities retrieved for the device.
 // Capability represents capabilities retrieved for the device.
-// Use methods on this type to access capabilities.
+// Use attached methods on this type to access capabilities.
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.v4l2_capability
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.v4l2_capability
 type Capability struct {
 type Capability struct {
 	v4l2Cap v4l2Capability
 	v4l2Cap v4l2Capability
@@ -41,7 +42,7 @@ type Capability struct {
 // GetCapability retrieves capability info for device
 // GetCapability retrieves capability info for device
 func GetCapability(fd uintptr) (Capability, error) {
 func GetCapability(fd uintptr) (Capability, error) {
 	v4l2Cap := v4l2Capability{}
 	v4l2Cap := v4l2Capability{}
-	if err := Send(fd, vidiocQueryCap, uintptr(unsafe.Pointer(&v4l2Cap))); err != nil {
+	if err := Send(fd, VidiocQueryCap, uintptr(unsafe.Pointer(&v4l2Cap))); err != nil {
 		return Capability{}, fmt.Errorf("capability: %w", err)
 		return Capability{}, fmt.Errorf("capability: %w", err)
 	}
 	}
 	return Capability{v4l2Cap: v4l2Cap}, nil
 	return Capability{v4l2Cap: v4l2Cap}, nil
@@ -60,38 +61,51 @@ func (c Capability) GetDeviceCaps() uint32 {
 	return c.v4l2Cap.deviceCaps
 	return c.v4l2Cap.deviceCaps
 }
 }
 
 
+// IsVideoCaptureSupported returns true if the device supports video capture.
+// See V4L2 API's V4L2_CAP_VIDEO_CAPTURE
 func (c Capability) IsVideoCaptureSupported() bool {
 func (c Capability) IsVideoCaptureSupported() bool {
 	return (c.v4l2Cap.capabilities & CapVideoCapture) != 0
 	return (c.v4l2Cap.capabilities & CapVideoCapture) != 0
 }
 }
 
 
+// IsVideoOutputSupported returns true if device supports video output
+// See V4L2 API's V4L2_CAP_VIDEO_OUTPUT
 func (c Capability) IsVideoOutputSupported() bool {
 func (c Capability) IsVideoOutputSupported() bool {
 	return (c.v4l2Cap.capabilities & CapVideoOutput) != 0
 	return (c.v4l2Cap.capabilities & CapVideoOutput) != 0
 }
 }
 
 
+// IsReadWriteSupported returns true if device supports direct read-write operations
+// See V4L2 API's V4L2_CAP_READWRITE
 func (c Capability) IsReadWriteSupported() bool {
 func (c Capability) IsReadWriteSupported() bool {
 	return (c.v4l2Cap.capabilities & CapReadWrite) != 0
 	return (c.v4l2Cap.capabilities & CapReadWrite) != 0
 }
 }
 
 
+// IsStreamingSupported returns true if the device supports streaming.
+// See V4L2 API's V4L2_CAP_STREAMING
 func (c Capability) IsStreamingSupported() bool {
 func (c Capability) IsStreamingSupported() bool {
 	return (c.v4l2Cap.capabilities & CapStreaming) != 0
 	return (c.v4l2Cap.capabilities & CapStreaming) != 0
 }
 }
 
 
+// DriverName returns a string value for the driver name
 func (c Capability) DriverName() string {
 func (c Capability) DriverName() string {
-	return GoString(c.v4l2Cap.driver[:])
+	return toGoString(c.v4l2Cap.driver[:])
 }
 }
 
 
+// CardName returns a string value for device's card
 func (c Capability) CardName() string {
 func (c Capability) CardName() string {
-	return GoString(c.v4l2Cap.card[:])
+	return toGoString(c.v4l2Cap.card[:])
 }
 }
 
 
+// BusInfo returns the device's bus info
 func (c Capability) BusInfo() string {
 func (c Capability) BusInfo() string {
-	return GoString(c.v4l2Cap.busInfo[:])
+	return toGoString(c.v4l2Cap.busInfo[:])
 }
 }
 
 
+// GetVersion returns the device's version
 func (c Capability) GetVersion() uint32 {
 func (c Capability) GetVersion() uint32 {
 	return c.v4l2Cap.version
 	return c.v4l2Cap.version
 }
 }
 
 
+// String returns a string value representing driver information
 func (c Capability) String() string {
 func (c Capability) String() string {
 	return fmt.Sprintf("driver: %s; card: %s; bus info: %s", c.DriverName(), c.CardName(), c.BusInfo())
 	return fmt.Sprintf("driver: %s; card: %s; bus info: %s", c.DriverName(), c.CardName(), c.BusInfo())
 }
 }

+ 0 - 20
v4l2/commands.go

@@ -1,20 +0,0 @@
-package v4l2
-
-import "unsafe"
-
-// v4l2 ioctl commands
-// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2510
-// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/user-func.html
-var (
-	vidiocQueryCap   = encodeRead('V', 0, uintptr(unsafe.Sizeof(v4l2Capability{})))       // VIDIOC_QUERYCAP
-	vidiocGetFormat  = encodeReadWrite('V', 4, uintptr(unsafe.Sizeof(Format{})))          // VIDIOC_G_FMT
-	vidiocSetFormat  = encodeReadWrite('V', 5, uintptr(unsafe.Sizeof(Format{})))          // VIDIOC_S_FMT
-	vidiocReqBufs    = encodeReadWrite('V', 8, uintptr(unsafe.Sizeof(RequestBuffers{})))  // VIDIOC_REQBUFS
-	vidiocQueryBuf   = encodeReadWrite('V', 9, uintptr(unsafe.Sizeof(BufferInfo{})))      // VIDIOC_QUERYBUF
-	vidiocQueueBuf   = encodeReadWrite('V', 15, uintptr(unsafe.Sizeof(BufferInfo{})))     // VIDIOC_QBUF
-	vidiocDequeueBuf = encodeReadWrite('V', 17, uintptr(unsafe.Sizeof(BufferInfo{})))     // VIDIOC_DQBUF
-	vidiocStreamOn   = encodeWrite('V', 18, uintptr(unsafe.Sizeof(int32(0))))             // VIDIOC_STREAMON
-	vidiocStreamOff  = encodeWrite('V', 19, uintptr(unsafe.Sizeof(int32(0))))             // VIDIOC_STREAMOFF
-	vidiocCropCap    = encodeReadWrite('V', 58, uintptr(unsafe.Sizeof(CropCapability{}))) // VIDIOC_CROPCAP
-	vidiocSetCrop    = encodeWrite('V', 60, uintptr(unsafe.Sizeof(Crop{})))               // VIDIOC_S_CROP
-)

+ 2 - 2
v4l2/crop.go

@@ -47,7 +47,7 @@ type Crop struct {
 func GetCropCapability(fd uintptr) (CropCapability, error) {
 func GetCropCapability(fd uintptr) (CropCapability, error) {
 	cropCap := CropCapability{}
 	cropCap := CropCapability{}
 	cropCap.StreamType = BufTypeVideoCapture
 	cropCap.StreamType = BufTypeVideoCapture
-	if err := Send(fd, vidiocCropCap, uintptr(unsafe.Pointer(&cropCap))); err != nil {
+	if err := Send(fd, VidiocCropCap, uintptr(unsafe.Pointer(&cropCap))); err != nil {
 		return CropCapability{}, fmt.Errorf("crop capability: %w", err)
 		return CropCapability{}, fmt.Errorf("crop capability: %w", err)
 	}
 	}
 	return cropCap, nil
 	return cropCap, nil
@@ -57,7 +57,7 @@ func GetCropCapability(fd uintptr) (CropCapability, error) {
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-crop.html#ioctl-vidioc-g-crop-vidioc-s-crop
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-crop.html#ioctl-vidioc-g-crop-vidioc-s-crop
 func SetCropRect(fd uintptr, r Rect) error {
 func SetCropRect(fd uintptr, r Rect) error {
 	crop := Crop{Rect: r, StreamType: BufTypeVideoCapture}
 	crop := Crop{Rect: r, StreamType: BufTypeVideoCapture}
-	if err := Send(fd, vidiocSetCrop, uintptr(unsafe.Pointer(&crop))); err != nil {
+	if err := Send(fd, VidiocSetCrop, uintptr(unsafe.Pointer(&crop))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return fmt.Errorf("setcrop: unsupported: %w", err)
 			return fmt.Errorf("setcrop: unsupported: %w", err)

+ 0 - 68
v4l2/encoding.go

@@ -1,68 +0,0 @@
-package v4l2
-
-import "bytes"
-
-// ioctl command API encoding:
-// ioctl command encoding uses 32 bits total:
-// - command in lower 16 bits
-// - size of the parameter structure in the lower 14 bits of the upper 16 bits.
-// - The highest 2 bits are reserved for indicating the ``access mode''.
-// https://elixir.bootlin.com/linux/v5.13-rc6/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
-	iocNumberBits = 8
-	iocSizeBits   = 14
-	iocOpBits     = 2
-
-	// ioctl bit layout positions
-	numberPos = 0
-	typePos   = numberPos + iocNumberBits
-	sizePos   = typePos + iocTypeBits
-	opPos     = sizePos + iocSizeBits
-)
-
-// encodes V42L API command
-func encode(iocMode, iocType, number, size uintptr) uintptr {
-	return (iocMode << opPos) | (iocType << typePos) | (number << numberPos) | (size << sizePos)
-}
-
-// encodeRead encodes ioctl read command
-func encodeRead(iocType, number, size uintptr) uintptr {
-	return encode(iocOpRead, iocType, number, size)
-}
-
-// encodeWrite encodes ioctl write command
-func encodeWrite(iocType, number, size uintptr) uintptr {
-	return encode(iocOpWrite, iocType, number, size)
-}
-
-// encodeReadWrite encodes ioctl command for read or write
-func encodeReadWrite(iocType, number, size uintptr) uintptr {
-	return encode(iocOpRead|iocOpWrite, iocType, number, size)
-}
-
-// fourcc implements the four character code encoding found
-// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L81
-// #define v4l2_fourcc(a, b, c, d)\
-// 	 ((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24))
-func fourcc(a, b, c, d uint32) uint32 {
-	return (a | b<<8) | c<<16 | d<<24
-}
-
-// GoString encodes C null-terminated string to Go string
-func GoString(s []byte) string {
-	null := bytes.Index(s, []byte{0})
-	if null < 0 {
-		return ""
-	}
-	return string(s[:null])
-}

+ 21 - 13
v4l2/format.go

@@ -24,6 +24,14 @@ var (
 	PixelFmtMPEG4 = fourcc('M', 'P', 'G', '4') // V4L2_PIX_FMT_MPEG4
 	PixelFmtMPEG4 = fourcc('M', 'P', 'G', '4') // V4L2_PIX_FMT_MPEG4
 )
 )
 
 
+// fourcc implements the four character code encoding found
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L81
+// #define v4l2_fourcc(a, b, c, d)\
+// 	 ((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24))
+func fourcc(a, b, c, d uint32) uint32 {
+	return (a | b<<8) | c<<16 | d<<24
+}
+
 // YcbcrEncoding (v4l2_ycbcr_encoding)
 // YcbcrEncoding (v4l2_ycbcr_encoding)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=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
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L300
@@ -54,7 +62,7 @@ const (
 type XferFunction = uint32
 type XferFunction = uint32
 
 
 const (
 const (
-	ferFuncDefault    XferFunction = iota // V4L2_XFER_FUNC_DEFAULT = 0
+	XferFuncDefault   XferFunction = iota // V4L2_XFER_FUNC_DEFAULT     = 0
 	XferFunc709                           // V4L2_XFER_FUNC_709         = 1,
 	XferFunc709                           // V4L2_XFER_FUNC_709         = 1,
 	ferFuncSRGB                           // V4L2_XFER_FUNC_SRGB        = 2,
 	ferFuncSRGB                           // V4L2_XFER_FUNC_SRGB        = 2,
 	XferFuncOpRGB                         // V4L2_XFER_FUNC_OPRGB       = 3,
 	XferFuncOpRGB                         // V4L2_XFER_FUNC_OPRGB       = 3,
@@ -100,7 +108,7 @@ type PixFormat struct {
 	XferFunc     XferFunction
 	XferFunc     XferFunction
 }
 }
 
 
-// Format (v4l2_format)
+// v4l2Format (v4l2_format)
 // https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-g-fmt.html?highlight=v4l2_format
 // https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-g-fmt.html?highlight=v4l2_format
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2303
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2303
 //
 //
@@ -119,25 +127,25 @@ type PixFormat struct {
 // 		__u8	raw_data[200];   /* user-defined */
 // 		__u8	raw_data[200];   /* user-defined */
 // 	} fmt;
 // 	} fmt;
 // };
 // };
-type Format struct {
+type v4l2Format struct {
 	StreamType uint32
 	StreamType uint32
 	fmt        [200]byte
 	fmt        [200]byte
 }
 }
 
 
-func (f Format) GetPixFormat() PixFormat {
+func (f v4l2Format) getPixFormat() PixFormat {
 	pixfmt := (*PixFormat)(unsafe.Pointer(&f.fmt[0]))
 	pixfmt := (*PixFormat)(unsafe.Pointer(&f.fmt[0]))
 	return *pixfmt
 	return *pixfmt
 }
 }
 
 
-func (f Format) SetPixFormat(newPix PixFormat) {
+func (f v4l2Format) setPixFormat(newPix PixFormat) {
 	*(*PixFormat)(unsafe.Pointer(&f.fmt[0])) = newPix
 	*(*PixFormat)(unsafe.Pointer(&f.fmt[0])) = newPix
 }
 }
 
 
 // GetPixFormat retrieves pixel information for the specified driver
 // GetPixFormat retrieves pixel information for the specified driver
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-fmt.html#ioctl-vidioc-g-fmt-vidioc-s-fmt-vidioc-try-fmt
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-fmt.html#ioctl-vidioc-g-fmt-vidioc-s-fmt-vidioc-try-fmt
-func GetPixFormat(fd uintptr) (PixFormat, error){
-	format := Format{StreamType: BufTypeVideoCapture}
-	if err := Send(fd, vidiocGetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
+func GetPixFormat(fd uintptr) (PixFormat, error) {
+	format := v4l2Format{StreamType: BufTypeVideoCapture}
+	if err := Send(fd, VidiocGetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return PixFormat{}, fmt.Errorf("pix format: unsupported: %w", err)
 			return PixFormat{}, fmt.Errorf("pix format: unsupported: %w", err)
@@ -146,16 +154,16 @@ func GetPixFormat(fd uintptr) (PixFormat, error){
 		}
 		}
 	}
 	}
 
 
-	return format.GetPixFormat(), nil
+	return format.getPixFormat(), nil
 }
 }
 
 
 // SetPixFormat sets the pixel format information for the specified driver
 // SetPixFormat sets the pixel format information for the specified driver
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-fmt.html#ioctl-vidioc-g-fmt-vidioc-s-fmt-vidioc-try-fmt
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-fmt.html#ioctl-vidioc-g-fmt-vidioc-s-fmt-vidioc-try-fmt
 func SetPixFormat(fd uintptr, pixFmt PixFormat) error {
 func SetPixFormat(fd uintptr, pixFmt PixFormat) error {
-	format := Format{StreamType: BufTypeVideoCapture}
-	format.SetPixFormat(pixFmt)
+	format := v4l2Format{StreamType: BufTypeVideoCapture}
+	format.setPixFormat(pixFmt)
 
 
-	if err := Send(fd, vidiocSetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
+	if err := Send(fd, VidiocSetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return fmt.Errorf("pix format: unsupported operation: %w", err)
 			return fmt.Errorf("pix format: unsupported operation: %w", err)
@@ -164,4 +172,4 @@ func SetPixFormat(fd uintptr, pixFmt PixFormat) error {
 		}
 		}
 	}
 	}
 	return nil
 	return nil
-}
+}

+ 78 - 10
v4l2/ioctl.go

@@ -1,23 +1,91 @@
 package v4l2
 package v4l2
 
 
 import (
 import (
-	sys "syscall"
+	"unsafe"
+
+	sys "golang.org/x/sys/unix"
 )
 )
 
 
-// Send sends raw command to driver (via ioctl syscall)
-func Send(fd, req, arg uintptr) error {
-	return ioctl(fd, req, arg)
+// 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:
+// - 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
+
+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
+	iocNumberBits = 8
+	iocSizeBits   = 14
+	iocOpBits     = 2
+
+	// ioctl bit layout positions
+	numberPos = 0
+	typePos   = numberPos + iocNumberBits
+	sizePos   = typePos + iocTypeBits
+	opPos     = sizePos + iocSizeBits
+)
+
+// 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)
 }
 }
 
 
+// iocEncRead 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)
+}
+
+// iocEncWrite 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)
+}
+
+// iocEncReadWrite 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)
+}
+
+// ioctl is a wrapper for Syscall(SYS_IOCTL)
 func ioctl(fd, req, arg uintptr) (err error) {
 func ioctl(fd, req, arg uintptr) (err error) {
 	if _, _, errno := sys.Syscall(sys.SYS_IOCTL, fd, req, arg); errno != 0 {
 	if _, _, errno := sys.Syscall(sys.SYS_IOCTL, fd, req, arg); errno != 0 {
-		switch errno {
-		case sys.EINVAL:
-			err = ErrorUnsupported
-		default:
-			err = errno
-		}
+		err = errno
 		return
 		return
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+// V4L2 command request values for ioctl
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2510
+// 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
+	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
+)
+
+// Send sends a raw ioctl request to the kernel (via ioctl syscall)
+func Send(fd, req, arg uintptr) error {
+	return ioctl(fd, req, arg)
+}

+ 29 - 28
v4l2/streaming.go

@@ -59,28 +59,29 @@ type RequestBuffers struct {
 // BufferInfo represents type v4l2_buffer which contains unions as shown below.
 // BufferInfo represents type v4l2_buffer which contains unions as shown below.
 // Remember, the union is represented as an arry of bytes sized as the largest
 // Remember, the union is represented as an arry of bytes sized as the largest
 // member in bytes.
 // member in bytes.
-// struct v4l2_buffer {
-// 	__u32			index;
-// 	__u32			type;
-// 	__u32			bytesused;
-// 	__u32			flags;
-// 	__u32			field;
-// 	struct timeval		timestamp;
-// 	struct v4l2_timecode	timecode;
-// 	__u32			sequence;
-// 	__u32			memory;
-// 	union {
-// 		__u32           offset;
-// 		unsigned long   userptr;
-// 		struct v4l2_plane *planes;
-// 		__s32		fd;
-// 	} m;
-// 	__u32			length;
-// 	__u32			reserved2;
-// 	union {
-// 		__s32		request_fd;
-// 		__u32		reserved;
-// 	};
+//
+//   struct v4l2_buffer {
+// 	   __u32			index;
+// 	   __u32			type;
+// 	   __u32			bytesused;
+// 	   __u32			flags;
+// 	   __u32			field;
+// 	   struct timeval		timestamp;
+// 	   struct v4l2_timecode	timecode;
+// 	   __u32			sequence;
+// 	   __u32			memory;
+// 	   union {
+// 		  __u32             offset;
+// 		  unsigned long     userptr;
+// 		  struct v4l2_plane *planes;
+// 		  __s32		fd;
+// 	   } m;
+// 	   __u32	length;
+// 	   __u32	reserved2;
+// 	   union {
+// 		  __s32		request_fd;
+// 		  __u32		reserved;
+// 	   };
 // };
 // };
 type BufferInfo struct {
 type BufferInfo struct {
 	Index      uint32
 	Index      uint32
@@ -157,7 +158,7 @@ type PlaneService struct {
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-streamon.html
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-streamon.html
 func StreamOn(fd uintptr) error {
 func StreamOn(fd uintptr) error {
 	bufType := BufTypeVideoCapture
 	bufType := BufTypeVideoCapture
-	if err := Send(fd, vidiocStreamOn, uintptr(unsafe.Pointer(&bufType))); err != nil {
+	if err := Send(fd, VidiocStreamOn, uintptr(unsafe.Pointer(&bufType))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return fmt.Errorf("stream on: unsupported: %w", err)
 			return fmt.Errorf("stream on: unsupported: %w", err)
@@ -173,7 +174,7 @@ func StreamOn(fd uintptr) error {
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-streamon.html
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-streamon.html
 func StreamOff(fd uintptr) error {
 func StreamOff(fd uintptr) error {
 	bufType := BufTypeVideoCapture
 	bufType := BufTypeVideoCapture
-	if err := Send(fd, vidiocStreamOff, uintptr(unsafe.Pointer(&bufType))); err != nil {
+	if err := Send(fd, VidiocStreamOff, uintptr(unsafe.Pointer(&bufType))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return fmt.Errorf("stream off: unsupported: %w", err)
 			return fmt.Errorf("stream off: unsupported: %w", err)
@@ -193,7 +194,7 @@ func AllocateBuffers(fd uintptr, buffSize uint32) (RequestBuffers, error) {
 		Memory:     StreamMemoryTypeMMAP,
 		Memory:     StreamMemoryTypeMMAP,
 	}
 	}
 
 
-	if err := Send(fd, vidiocReqBufs, uintptr(unsafe.Pointer(&req))); err != nil {
+	if err := Send(fd, VidiocReqBufs, uintptr(unsafe.Pointer(&req))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return RequestBuffers{}, fmt.Errorf("request buffers: unsupported: %w", err)
 			return RequestBuffers{}, fmt.Errorf("request buffers: unsupported: %w", err)
@@ -217,7 +218,7 @@ func GetBufferInfo(fd uintptr, index uint32) (BufferInfo, error) {
 		Index:      index,
 		Index:      index,
 	}
 	}
 
 
-	if err := Send(fd, vidiocQueryBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
+	if err := Send(fd, VidiocQueryBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return BufferInfo{}, fmt.Errorf("buffer info: unsupported: %w", err)
 			return BufferInfo{}, fmt.Errorf("buffer info: unsupported: %w", err)
@@ -250,7 +251,7 @@ func QueueBuffer(fd uintptr, index uint32) (BufferInfo, error) {
 		Index:      index,
 		Index:      index,
 	}
 	}
 
 
-	if err := Send(fd, vidiocQueueBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
+	if err := Send(fd, VidiocQueueBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return BufferInfo{}, fmt.Errorf("buffer: unsupported: %w", err)
 			return BufferInfo{}, fmt.Errorf("buffer: unsupported: %w", err)
@@ -272,7 +273,7 @@ func DequeueBuffer(fd uintptr) (BufferInfo, error) {
 		Memory:     StreamMemoryTypeMMAP,
 		Memory:     StreamMemoryTypeMMAP,
 	}
 	}
 
 
-	if err := Send(fd, vidiocDequeueBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
+	if err := Send(fd, VidiocDequeueBuf, uintptr(unsafe.Pointer(&buf))); err != nil {
 		switch {
 		switch {
 		case errors.Is(err, ErrorUnsupported):
 		case errors.Is(err, ErrorUnsupported):
 			return BufferInfo{}, fmt.Errorf("buffer: unsupported: %w", err)
 			return BufferInfo{}, fmt.Errorf("buffer: unsupported: %w", err)

+ 12 - 0
v4l2/support.go

@@ -0,0 +1,12 @@
+package v4l2
+
+import "bytes"
+
+// toGoString encodes C null-terminated string as a Go string
+func toGoString(s []byte) string {
+	null := bytes.Index(s, []byte{0})
+	if null < 0 {
+		return ""
+	}
+	return string(s[:null])
+}