2
0
Эх сурвалжийг харах

User device control support

This patch adds supports for user device controls API wiht the followings:

* v4l2 control types, values, and enums
* Low level Go functions to query, get, and set controls
* New methods added to the Device type to work with the Control API
* Examples to show how to use the Control API

Additionally:

* Added Canonical Multipass setup file to help with local build/development
* Update to existing documentations
Vladimir Vivien 2 жил өмнө
parent
commit
0bd8cf2b31

+ 3 - 4
README.md

@@ -1,18 +1,17 @@
-[![Go Report Card](https://goreportcard.com/badge/github.com/vladimirvivien/go4vl)](https://goreportcard.com/report/github.com/vladimirvivien/go4vl)
+[![Go Reference](https://pkg.go.dev/badge/github.com/vladimirvivien/go4vl.svg)](https://pkg.go.dev/github.com/vladimirvivien/go4vl) [![Go Report Card](https://goreportcard.com/badge/github.com/vladimirvivien/go4vl)](https://goreportcard.com/report/github.com/vladimirvivien/go4vl)
 
 # go4vl
 
 ![](./docs/go4vl-logo-small.png)
 
-A Go library for the `Video for Linux 2`  (v4l2) user API.
+A Go centric abstraction of the library for  `Video for Linux 2`  (v4l2) user API.
 
 ----
 
 The `go4vl` project is for working with the Video for Linux 2 API for real-time video. 
 It hides all the complexities of working with V4L2 and provides idiomatic Go types, like channels, to consume and process captured video frames.
 
-> This project is designed to work with Linux and the Linux Video API.  
-> It is *NOT* meant to be a portable/cross-platform capable package for real-time video processing.
+> This project is designed to work with Linux and the Linux Video API only.  It is *NOT* meant to be a portable/cross-platform package.
 
 ## Features
 

+ 54 - 0
device/device_control.go

@@ -0,0 +1,54 @@
+package device
+
+import (
+	"fmt"
+
+	"github.com/vladimirvivien/go4vl/v4l2"
+)
+
+// GetControl queries the device for information about the specified control id.
+func (d *Device) GetControl(ctrlID v4l2.CtrlID) (v4l2.Control, error) {
+	ctlr, err := v4l2.GetControl(d.fd, ctrlID)
+	if err != nil {
+		return v4l2.Control{}, fmt.Errorf("device: %s: %w", d.path, err)
+	}
+	return ctlr, nil
+}
+
+// SetControl updates the value of the specified control id.
+func (d *Device) SetControlValue(ctrlID v4l2.CtrlID, val v4l2.CtrlValue) error {
+	err := v4l2.SetControlValue(d.fd, ctrlID, val)
+	if err != nil {
+		return fmt.Errorf("device: %s: %w", d.path, err)
+	}
+	return nil
+}
+
+// QueryAllControls fetches all supported device controls and their current values.
+func (d *Device) QueryAllControls() ([]v4l2.Control, error) {
+	ctrls, err := v4l2.QueryAllControls(d.fd)
+	if err != nil {
+		return nil, fmt.Errorf("device: %s: %w", d.path, err)
+	}
+	return ctrls, nil
+}
+
+// SetControlBrightness is a convenience method for setting value for control v4l2.CtrlBrightness
+func (d *Device) SetControlBrightness(val v4l2.CtrlValue) error {
+	return d.SetControlValue(v4l2.CtrlBrightness, val)
+}
+
+// SetControlContrast is a convenience method for setting value for control v4l2.CtrlContrast
+func (d *Device) SetControlContrast(val v4l2.CtrlValue) error {
+	return d.SetControlValue(v4l2.CtrlContrast, val)
+}
+
+// SetControlSaturation is a convenience method for setting value for control v4l2.CtrlSaturation
+func (d *Device) SetControlSaturation(val v4l2.CtrlValue) error {
+	return d.SetControlValue(v4l2.CtrlSaturation, val)
+}
+
+// SetControlHue is a convenience method for setting value for control v4l2.CtrlHue
+func (d *Device) SetControlHue(val v4l2.CtrlValue) error {
+	return d.SetControlValue(v4l2.CtrlHue, val)
+}

+ 1 - 2
examples/device_info/README.md

@@ -1,7 +1,6 @@
 # Device info example
 
-The example in this directory showcases `go4vl` support for device information. For instance, the following function 
-prints driver information
+The example in this directory showcases `go4vl` support for device information. For instance, the following function prints driver information
 
 ```go
 func main() {

+ 39 - 0
examples/format/README.md

@@ -0,0 +1,39 @@
+# Device Format Example
+
+The examples in this directory highlights the support for V4L2's device format API. It shows how to query format information and set video format for a selected device.
+
+```go
+
+func main() {
+
+    device, err := dev.Open(
+		devName,
+		dev.WithPixFormat(v4l2.PixFormat{Width: uint32(width), Height: uint32(height), PixelFormat: fmtEnc, Field: v4l2.FieldNone}),
+		dev.WithFPS(15),
+	)
+
+    ...
+
+    currFmt, err := device.GetPixFormat()
+	if err != nil {
+		log.Fatalf("unable to get format: %s", err)
+	}
+	log.Printf("Current format: %s", currFmt)
+
+    ...
+
+    // FPS
+	fps, err := device.GetFrameRate()
+	if err != nil {
+		log.Fatalf("failed to get fps: %s", err)
+	}
+	log.Printf("current frame rate: %d fps", fps)
+	// update fps
+	if fps < 30 {
+		if err := device.SetFrameRate(30); err != nil {
+			log.Fatalf("failed to set frame rate: %s", err)
+		}
+	}
+
+}
+```

+ 4 - 4
examples/format/devfmt.go

@@ -5,7 +5,7 @@ import (
 	"log"
 	"strings"
 
-	device2 "github.com/vladimirvivien/go4vl/device"
+	dev "github.com/vladimirvivien/go4vl/device"
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 
@@ -31,10 +31,10 @@ func main() {
 		fmtEnc = v4l2.PixelFmtYUYV
 	}
 
-	device, err := device2.Open(
+	device, err := dev.Open(
 		devName,
-		device2.WithPixFormat(v4l2.PixFormat{Width: uint32(width), Height: uint32(height), PixelFormat: fmtEnc, Field: v4l2.FieldNone}),
-		device2.WithFPS(15),
+		dev.WithPixFormat(v4l2.PixFormat{Width: uint32(width), Height: uint32(height), PixelFormat: fmtEnc, Field: v4l2.FieldNone}),
+		dev.WithFPS(15),
 	)
 	if err != nil {
 		log.Fatalf("failed to open device: %s", err)

+ 1 - 0
examples/user_ctrl/README.md

@@ -0,0 +1 @@
+# Device user control

+ 100 - 0
examples/user_ctrl/ctrl.go

@@ -0,0 +1,100 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"math"
+	"os"
+
+	dev "github.com/vladimirvivien/go4vl/device"
+	"github.com/vladimirvivien/go4vl/v4l2"
+)
+
+var (
+	controls = map[string]v4l2.CtrlID{
+		"brightness": v4l2.CtrlBrightness,
+		"contrast":   v4l2.CtrlContrast,
+	}
+)
+
+func main() {
+	devName := "/dev/video0"
+	flag.StringVar(&devName, "d", devName, "device name (path)")
+	var list bool
+	flag.BoolVar(&list, "list", list, "List current device controls")
+	var ctrlName string
+	flag.StringVar(&ctrlName, "c", ctrlName, fmt.Sprintf("Contrl name to set or get (supported %v)", controls))
+	ctrlVal := math.MinInt32
+	flag.IntVar(&ctrlVal, "v", ctrlVal, fmt.Sprintf("Value for selected control (supported %v)", controls))
+
+	flag.Parse()
+
+	// open device
+	device, err := dev.Open(devName)
+	if err != nil {
+		log.Fatalf("failed to open device: %s", err)
+	}
+	defer device.Close()
+
+	if len(os.Args) < 2 || list {
+		listUserControls(device)
+		os.Exit(0)
+	}
+
+	ctrlID, ok := controls[ctrlName]
+	if !ok {
+		fmt.Printf("Program does not support ctrl [%s]; supported ctrls: %#v\n", ctrlName, controls)
+		os.Exit(1)
+	}
+
+	if ctrlName != "" {
+		if ctrlVal != math.MinInt32 {
+			if err := setUserControlValue(device, ctrlID, ctrlVal); err != nil {
+				fmt.Println(err)
+				os.Exit(1)
+			}
+		}
+		ctrl, err := device.GetControl(ctrlID)
+		if err != nil {
+			fmt.Printf("query controls: %s\n", err)
+			os.Exit(1)
+		}
+		printUserControl(ctrl)
+	}
+}
+
+func setUserControlValue(device *dev.Device, ctrlID v4l2.CtrlID, val int) error {
+	if ctrlID == 0 {
+		return fmt.Errorf("invalid control specified")
+	}
+	return device.SetControlValue(ctrlID, v4l2.CtrlValue(val))
+}
+
+func listUserControls(device *dev.Device) {
+	ctrls, err := device.QueryAllControls()
+	if err != nil {
+		log.Fatalf("query controls: %s", err)
+	}
+
+	for _, ctrl := range ctrls {
+		printUserControl(ctrl)
+	}
+}
+
+func printUserControl(ctrl v4l2.Control) {
+	fmt.Printf("Control id (%d) name: %s\t[min: %d; max: %d; step: %d; default: %d current_val: %d]\n",
+		ctrl.ID, ctrl.Name, ctrl.Minimum, ctrl.Maximum, ctrl.Step, ctrl.Default, ctrl.Value)
+
+	if ctrl.IsMenu() {
+		menus, err := ctrl.GetMenuItems()
+		if err != nil {
+			return
+		}
+
+		for _, m := range menus {
+			fmt.Printf("\tMenu items for %s: %#v", ctrl.Name, m)
+		}
+	}
+
+}

+ 14 - 0
multipass/README.md

@@ -0,0 +1,14 @@
+# Canonical Multipass VM
+
+Use this directory to setup a [Canonical Multipass](https://multipass.run/) Ubuntu VM to build and run project examples. This is useful if you want to build/run project in a non-Linux environment (i.e. Apple's OSX) or do not wish to test against your local environment directly.
+
+## Pre-requisites
+
+* Download [Canonical Multipass](https://multipass.run/)
+* `envsubst` util command (i.e. `brew install gettext` on Mac)
+
+## Run the VM
+
+Use the `start.sh` script to launch the VM. Ensure that your local machine has the required spare CPUs and memory, otherwise, adjust accordingly. 
+
+Once launched, use `multipass shell go4vldev` to log into the ubuntu VM.

+ 16 - 0
multipass/cloud-init.yaml

@@ -0,0 +1,16 @@
+# cloud-config
+
+ssh_authorized_key:
+ - ${SSH_PUBLIC_KEY} 
+
+package_update: true
+package_upgrade: true
+packages:
+ - v4l2loopback-dkms
+
+runcmd:
+  - 'sudo apt -y install linux-modules-extra-$(uname -r)'
+  - 'sudo apt -y install dkms'
+  - 'sudo modprobe v4l2loopback exclusive_caps=1 video_nr=1,2'
+  - 'curl -sL https://go.dev/dl/go1.19.linux-amd64.tar.gz | tar -C /usr/local -xz'
+  - 'export PATH="${PATH}:/usr/local/go/bin'

+ 5 - 0
multipass/start.sh

@@ -0,0 +1,5 @@
+#! /bin/bash
+
+root_dir=$(dirname "${BASH_SOURCE[0]}")/..
+SSH_PUBLIC_KEY=$(cat ~/.ssh/id_rsa.pub)
+envsubst '$SSH_PUBLIC_KEY' < ./cloud-init.yaml | multipass launch jammy -v -n go4vldev --cpus 4 --mem 4g --disk 20g  --mount $root_dir:/home/ubuntu/go4vl  --cloud-init -

+ 149 - 14
v4l2/control.go

@@ -6,42 +6,177 @@ package v4l2
 */
 import "C"
 import (
+	"errors"
 	"fmt"
 	"unsafe"
 )
 
+// ControlValue represents the value for a user control.
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1740
+type CtrlValue = int32
+
 // Control (v4l2_control)
-// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1725
-// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ctrl.html
+//
+// This type is used to query/set/get user-specific controls.
+// For more information about user controls, see https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html.
+//
+// Also, see the followings:
+//
+//   - https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1725
+//   - https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ctrl.html
 type Control struct {
+	fd      uintptr
+	Type    CtrlType
+	ID      CtrlID
+	Value   CtrlValue
+	Name    string
+	Minimum int32
+	Maximum int32
+	Step    int32
+	Default int32
+	flags   uint32
+}
+
+type ControlMenuItem struct {
 	ID    uint32
+	Index uint32
 	Value uint32
+	Name  string
+}
+
+// IsMenu tests whether control Type == CtrlTypeMenu || Type == CtrlIntegerMenu
+func (c Control) IsMenu() bool {
+	return c.Type == CtrlTypeMenu || c.Type == CtrlTypeIntegerMenu
+}
+
+// GetMenuItems returns control menu items if the associated control is a menu.
+func (c Control) GetMenuItems() (result []ControlMenuItem, err error) {
+	if !c.IsMenu() {
+		return result, fmt.Errorf("control is not a menu type")
+	}
+
+	for idx := c.Minimum; idx <= c.Maximum; idx++ {
+		var qryMenu C.struct_v4l2_querymenu
+		qryMenu.id = C.uint(c.ID)
+		qryMenu.index = C.uint(idx)
+		if err = send(c.fd, C.VIDIOC_QUERYMENU, uintptr(unsafe.Pointer(&qryMenu))); err != nil {
+			continue
+		}
+		result = append(result, makeCtrlMenu(qryMenu))
+	}
+
+	return result, nil
 }
 
-// GetControl returns control value for specified ID
-func GetControl(fd uintptr, id uint32) (Control, error) {
+// GetControlValue retrieves the value for a user control with the specified id.
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1740
+func GetControlValue(fd uintptr, id CtrlID) (CtrlValue, error) {
 	var ctrl C.struct_v4l2_control
 	ctrl.id = C.uint(id)
 
 	if err := send(fd, C.VIDIOC_G_CTRL, uintptr(unsafe.Pointer(&ctrl))); err != nil {
-		return Control{}, fmt.Errorf("get control: id %d: %w", id, err)
+		return 0, fmt.Errorf("get control value: VIDIOC_G_CTRL: id %d: %w", id, err)
 	}
 
-	return Control{
-		ID:    uint32(ctrl.id),
-		Value: uint32(ctrl.value),
-	}, nil
+	return CtrlValue(ctrl.value), nil
 }
 
-// SetControl applies control value for specified ID
-func SetControl(fd uintptr, id, value uint32) error {
+// SetControlValue sets the value for a user control with the specified id.
+// This function applies range check based on the values supported by the  control.
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1740
+func SetControlValue(fd uintptr, id CtrlID, val CtrlValue) error {
+	ctrlInfo, err := QueryControlInfo(fd, id)
+	if err != nil {
+		return fmt.Errorf("set control value: id %s: %w", id, err)
+	}
+	if val < ctrlInfo.Minimum || val > ctrlInfo.Maximum {
+		return fmt.Errorf("set control value: out-of-range failure: val %d: expected ctrl.Min %d, ctrl.Max %d", val, ctrlInfo.Minimum, ctrlInfo.Maximum)
+	}
+
 	var ctrl C.struct_v4l2_control
 	ctrl.id = C.uint(id)
-	ctrl.value = C.int(value)
+	ctrl.value = C.int(val)
 
-	if err := send(fd, C.VIDIOC_G_CTRL, uintptr(unsafe.Pointer(&ctrl))); err != nil {
-		return fmt.Errorf("set control: id %d: value: %d: %w", id, value, err)
+	if err := send(fd, C.VIDIOC_S_CTRL, uintptr(unsafe.Pointer(&ctrl))); err != nil {
+		return fmt.Errorf("set control value: id %d: %w", id, err)
 	}
 
 	return nil
 }
+
+// QueryControlInfo queries information about the specified control without the current value.
+func QueryControlInfo(fd uintptr, id CtrlID) (Control, error) {
+	// query control information
+	var qryCtrl C.struct_v4l2_queryctrl
+	qryCtrl.id = C.uint(id)
+
+	if err := send(fd, C.VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(&qryCtrl))); err != nil {
+		return Control{}, fmt.Errorf("query control info: VIDIOC_QUERYCTRL: id %d: %w", id, err)
+	}
+	control := makeControl(qryCtrl)
+	control.fd = fd
+	return control, nil
+}
+
+// GetControl retrieves the value and information for the user control witht he specified id.
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
+func GetControl(fd uintptr, id CtrlID) (Control, error) {
+	control, err := QueryControlInfo(fd, id)
+	if err != nil {
+		return Control{}, fmt.Errorf("get control: %w", err)
+	}
+
+	// retrieve control value
+	ctrlValue, err := GetControlValue(fd, uint32(id))
+	if err != nil {
+		return Control{}, fmt.Errorf("get control: %w", id, err)
+	}
+
+	control.Value = ctrlValue
+	control.fd = fd
+	return control, nil
+}
+
+// QueryAllControls loop through all available user controls and returns information for
+// all enabled controls and their values.
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html
+func QueryAllControls(fd uintptr) (result []Control, err error) {
+	cid := uint32(C.V4L2_CTRL_FLAG_NEXT_CTRL)
+	for {
+		control, err := GetControl(fd, cid)
+		if err != nil {
+			if errors.Is(err, ErrorBadArgument) && len(result) > 0 {
+				break
+			}
+			return result, fmt.Errorf("query all controls: %w", err)
+		}
+		result = append(result, control)
+		// setup next id
+		cid = control.ID | uint32(C.V4L2_CTRL_FLAG_NEXT_CTRL)
+	}
+	return result, nil
+}
+
+func makeControl(qryCtrl C.struct_v4l2_queryctrl) Control {
+	return Control{
+		Type:    CtrlType(qryCtrl._type),
+		ID:      uint32(qryCtrl.id),
+		Name:    C.GoString((*C.char)(unsafe.Pointer(&qryCtrl.name[0]))),
+		Maximum: int32(qryCtrl.maximum),
+		Minimum: int32(qryCtrl.minimum),
+		Step:    int32(qryCtrl.step),
+		Default: int32(qryCtrl.default_value),
+		flags:   uint32(qryCtrl.flags),
+	}
+}
+
+func makeCtrlMenu(qryMenu C.struct_v4l2_querymenu) ControlMenuItem {
+	return ControlMenuItem{
+		ID:    uint32(qryMenu.id),
+		Index: uint32(qryMenu.index),
+		Name:  C.GoString((*C.char)(unsafe.Pointer(&qryMenu.anon0[0]))),
+	}
+}

+ 135 - 0
v4l2/control_values.go

@@ -0,0 +1,135 @@
+package v4l2
+
+/*
+#cgo linux CFLAGS: -I ${SRCDIR}/../include/
+#include <linux/videodev2.h>
+#include <linux/v4l2-controls.h>
+*/
+import "C"
+
+// ControlClass
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L56
+type ControlClass = uint32
+
+const (
+	UserControlClass            ControlClass = C.V4L2_CTRL_CLASS_USER
+	CodecControlClass           ControlClass = C.V4L2_CTRL_CLASS_CODEC
+	CameraControlClass          ControlClass = C.V4L2_CTRL_CLASS_CAMERA
+	CameraFlashControlClass     ControlClass = C.V4L2_CTRL_CLASS_FLASH
+	JPEGControlClass            ControlClass = C.V4L2_CTRL_CLASS_JPEG
+	ImageSourceControlClass     ControlClass = C.V4L2_CTRL_CLASS_IMAGE_SOURCE
+	ImageProcessingControlClass ControlClass = C.V4L2_CTRL_CLASS_IMAGE_PROC
+	DigitalVideoControlClass    ControlClass = C.V4L2_CTRL_CLASS_DV
+	DetectionControlClass       ControlClass = C.V4L2_CTRL_CLASS_DETECT
+	CodecStatelessControlClass  ControlClass = C.V4L2_CTRL_CLASS_CODEC_STATELESS
+	ColorimitryControlClass     ControlClass = C.V4L2_CTRL_CLASS_COLORIMETRY
+)
+
+// CtrlID type for control values
+type CtrlID = uint32
+
+// Powerline frequency control enums
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L100
+const (
+	PowerlineFrequencyDisabled uint32 = iota
+	PowerlineFrequency50Hz
+	PowerlineFrequency60Hz
+	PowerlineFrequencyAuto
+)
+
+// Color FX control enums
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L114
+const (
+	ColorFXNone uint32 = iota
+	ColorFXBlackWhite
+	ColorFXSepia
+	ColorFXNegative
+	ColorFXEmboss
+	ColorFXSketch
+	ColorFXSkyBlue
+	ColorFXGrassGreen
+	ColorFXSkinWhiten
+	ColorFXVivid
+	ColorFXAqua
+	ColorFXArtFreeze
+	ColorFXSilhouette
+	ColorFXSolarization
+	ColorFXAntique
+	ColorFXSetCBCR
+	ColorFXSetRGB
+)
+
+// User Controls
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L74
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/control.html#control-id
+const (
+	CtrlBrightness              CtrlID = C.V4L2_CID_BRIGHTNESS
+	CtrlContrast                CtrlID = C.V4L2_CID_CONTRAST
+	CtrlSaturation              CtrlID = C.V4L2_CID_SATURATION
+	CtrlHue                     CtrlID = C.V4L2_CID_HUE
+	CtrlAutoWhiteBalance        CtrlID = C.V4L2_CID_AUTO_WHITE_BALANCE
+	CtrlDoWhiteBalance          CtrlID = C.V4L2_CID_DO_WHITE_BALANCE
+	CtrlRedBalance              CtrlID = C.V4L2_CID_RED_BALANCE
+	CtrlBlueBalance             CtrlID = C.V4L2_CID_BLUE_BALANCE
+	CtrlGamma                   CtrlID = C.V4L2_CID_GAMMA
+	CtrlExposure                CtrlID = C.V4L2_CID_EXPOSURE
+	CtrlAutogain                CtrlID = C.V4L2_CID_AUTOGAIN
+	CtrlGain                    CtrlID = C.V4L2_CID_GAIN
+	CtrlHFlip                   CtrlID = C.V4L2_CID_HFLIP
+	CtrlVFlip                   CtrlID = C.V4L2_CID_VFLIP
+	CtrlPowerlineFrequency      CtrlID = C.V4L2_CID_POWER_LINE_FREQUENCY
+	CtrlHueAuto                 CtrlID = C.V4L2_CID_HUE_AUTO
+	CtrlWhiteBalanceTemperature CtrlID = C.V4L2_CID_WHITE_BALANCE_TEMPERATURE
+	CtrlSharpness               CtrlID = C.V4L2_CID_SHARPNESS
+	CtrlBacklightCompensation   CtrlID = C.V4L2_CID_BACKLIGHT_COMPENSATION
+	CtrlChromaAutomaticGain     CtrlID = C.V4L2_CID_CHROMA_AGC
+	CtrlColorKiller             CtrlID = C.V4L2_CID_COLOR_KILLER
+	CtrlColorFX                 CtrlID = C.V4L2_CID_COLORFX
+	CtrlColorFXCBCR             CtrlID = C.V4L2_CID_COLORFX_CBCR
+	CtrlColorFXRGB              CtrlID = C.V4L2_CID_COLORFX_RGB
+	CtrlAutoBrightness          CtrlID = C.V4L2_CID_AUTOBRIGHTNESS
+	CtrlRotate                  CtrlID = C.V4L2_CID_ROTATE
+	CtrlBackgroundColor         CtrlID = C.V4L2_CID_BG_COLOR
+	CtrlMinimumCaptureBuffers   CtrlID = C.V4L2_CID_MIN_BUFFERS_FOR_CAPTURE
+	CtrlMinimumOutputBuffers    CtrlID = C.V4L2_CID_MIN_BUFFERS_FOR_OUTPUT
+	CtrlAlphaComponent          CtrlID = C.V4L2_CID_ALPHA_COMPONENT
+)
+
+// ControlType stores values fround in enum v4l2_ctrl_type
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1799
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-queryctrl.html?highlight=v4l2_ctrl_type#c.V4L.v4l2_ctrl_type
+type CtrlType uint32
+
+// CtrlType constants
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1799
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-queryctrl.html?highlight=v4l2_ctrl_type#c.V4L.v4l2_ctrl_type
+const (
+	CtrlTypeInt                 CtrlType = C.V4L2_CTRL_TYPE_INTEGER
+	CtrlTypeBool                CtrlType = C.V4L2_CTRL_TYPE_BOOLEAN
+	CtrlTypeMenu                CtrlType = C.V4L2_CTRL_TYPE_MENU
+	CtrlTypeButton              CtrlType = C.V4L2_CTRL_TYPE_BUTTON
+	CtrlTypeInt64               CtrlType = C.V4L2_CTRL_TYPE_INTEGER64
+	CtrlTypeClass               CtrlType = C.V4L2_CTRL_TYPE_CTRL_CLASS
+	CtrlTypeString              CtrlType = C.V4L2_CTRL_TYPE_STRING
+	CtrlTypeBitMask             CtrlType = C.V4L2_CTRL_TYPE_BITMASK
+	CtrlTypeIntegerMenu         CtrlType = C.V4L2_CTRL_TYPE_INTEGER_MENU
+	CtrlTypeCompoundTypes       CtrlType = C.V4L2_CTRL_COMPOUND_TYPES
+	CtrlTypeU8                  CtrlType = C.V4L2_CTRL_TYPE_U8
+	CtrlTypeU16                 CtrlType = C.V4L2_CTRL_TYPE_U16
+	CtrlTypeU32                 CtrlType = C.V4L2_CTRL_TYPE_U32
+	CtrlTypeArear               CtrlType = C.V4L2_CTRL_TYPE_AREA
+	CtrlTypeHDR10CLLInfo        CtrlType = C.V4L2_CTRL_TYPE_HDR10_CLL_INFO
+	CtrlTypeHDRMasteringDisplay CtrlType = C.V4L2_CTRL_TYPE_HDR10_MASTERING_DISPLAY
+	CtrlTypeH264SPS             CtrlType = C.V4L2_CTRL_TYPE_H264_SPS
+	CtrlTypeH264PPS             CtrlType = C.V4L2_CTRL_TYPE_H264_PPS
+	CtrlTypeH264ScalingMatrix   CtrlType = C.V4L2_CTRL_TYPE_H264_SCALING_MATRIX
+	CtrlTypeH264SliceParams     CtrlType = C.V4L2_CTRL_TYPE_H264_SLICE_PARAMS
+	CtrlTypeH264DecodeParams    CtrlType = C.V4L2_CTRL_TYPE_H264_DECODE_PARAMS
+	CtrlTypeFWHTParams          CtrlType = C.V4L2_CTRL_TYPE_FWHT_PARAMS
+	CtrlTypeVP8Frame            CtrlType = C.V4L2_CTRL_TYPE_VP8_FRAME
+	CtrlTypeMPEG2Quantization   CtrlType = C.V4L2_CTRL_TYPE_MPEG2_QUANTISATION
+	CtrlTypeMPEG2Sequence       CtrlType = C.V4L2_CTRL_TYPE_MPEG2_SEQUENCE
+	CtrlTypeMPEG2Picture        CtrlType = C.V4L2_CTRL_TYPE_MPEG2_PICTURE
+	CtrlTypeVP9CompressedHDR    CtrlType = C.V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR
+	CtrlTypeVP9Frame            CtrlType = C.V4L2_CTRL_TYPE_VP9_FRAME
+)

+ 0 - 100
v4l2/controls.go

@@ -1,100 +0,0 @@
-package v4l2
-
-/*
-#cgo linux CFLAGS: -I ${SRCDIR}/../include/
-#include <linux/videodev2.h>
-*/
-import "C"
-import (
-	"fmt"
-	"unsafe"
-)
-
-// TODO - Implementation of extended controls (v4l2_ext_control) is paused for now,
-// so that efforts can be focused on other parts of the API. This can resumed
-// later when type v4l2_ext_control and v4l2_ext_controls are better understood.
-
-// ExtControl (v4l2_ext_control)
-// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1730
-// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ext-ctrls.html
-type ExtControl struct {
-	ID   uint32
-	Size uint32
-	Ctrl ExtControlUnion
-}
-
-type ExtControlUnion struct {
-	Value              int32
-	Value64            int64
-	String             string
-	PU8                uint8
-	PU16               uint16
-	PU32               uint32
-	PArea              Area
-	PH264SPS           ControlH264SPS
-	PH264PPS           ControlH264PPS
-	PH264ScalingMatrix ControlH264ScalingMatrix
-	H264PredWeights    ControlH264PredictionWeights
-	PH264SliceParams   ControlH264SliceParams
-	PH264DecodeParams  ControlH264DecodeParams
-	PFWHTParams        ControlFWHTParams
-	PVP8Frame          ControlVP8Frame
-	PMPEG2Sequence     ControlMPEG2Sequence
-	PMPEG2Picture      ControlMPEG2Picture
-	PMPEG2Quantization ControlMPEG2Quantization
-	_                  uintptr
-}
-
-// ExtControls (v4l2_ext_controls)
-// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1757
-// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ext-ctrls.html
-type ExtControls struct {
-	Which      uint32
-	Count      uint32
-	ErrorIndex uint32
-	Controls   []ExtControl
-}
-
-// GetExtControls retrieve one or more controls
-func GetExtControls(fd uintptr, controls []ExtControl) (ExtControls, error) {
-	if true {
-		// TODO remove when supported
-		return ExtControls{}, fmt.Errorf("unsupported")
-	}
-
-	var ctrls C.struct_v4l2_ext_controls
-	ctrls.count = C.uint(len(controls))
-
-	// prepare control requests
-	var Cctrls []C.struct_v4l2_ext_control
-	for _, control := range controls {
-		var Cctrl C.struct_v4l2_ext_control
-		Cctrl.id = C.uint(control.ID)
-		Cctrl.size = C.uint(control.Size)
-		*(*ExtControlUnion)(unsafe.Pointer(&Cctrl.anon0[0])) = control.Ctrl
-		Cctrls = append(Cctrls, Cctrl)
-	}
-	ctrls.controls = (*C.struct_v4l2_ext_control)(unsafe.Pointer(&ctrls.controls))
-
-	if err := send(fd, C.VIDIOC_G_EXT_CTRLS, uintptr(unsafe.Pointer(&ctrls))); err != nil {
-		return ExtControls{}, fmt.Errorf("get ext controls: %w", err)
-	}
-
-	// gather returned controls
-	retCtrls := ExtControls{
-		Count:      uint32(ctrls.count),
-		ErrorIndex: uint32(ctrls.error_idx),
-	}
-	// extract controls array
-	Cctrls = *(*[]C.struct_v4l2_ext_control)(unsafe.Pointer(&ctrls.controls))
-	for _, Cctrl := range Cctrls {
-		extCtrl := ExtControl{
-			ID:   uint32(Cctrl.id),
-			Size: uint32(Cctrl.size),
-			Ctrl: *(*ExtControlUnion)(unsafe.Pointer(&Cctrl.anon0[0])),
-		}
-		retCtrls.Controls = append(retCtrls.Controls, extCtrl)
-	}
-
-	return retCtrls, nil
-}

+ 112 - 0
v4l2/ext_controls.go

@@ -0,0 +1,112 @@
+package v4l2
+
+/*
+#cgo linux CFLAGS: -I ${SRCDIR}/../include/
+#include <linux/videodev2.h>
+*/
+import "C"
+import (
+	"fmt"
+	"unsafe"
+)
+
+// TODO - Implementation of extended controls (v4l2_ext_control) is paused for now,
+// so that efforts can be focused on other parts of the API. This can resumed
+// later when type v4l2_ext_control and v4l2_ext_controls are better understood.
+
+// ExtControl (v4l2_ext_control)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1730
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ext-ctrls.html
+type ExtControl struct {
+	ID      uint32
+	Value   int32
+	Value64 int64
+
+	// NOT USED YET
+	Size               uint32
+	String             string
+	PU8                uint
+	PU16               uint16
+	PU32               uint32
+	PArea              Area
+	PH264SPS           ControlH264SPS
+	PH264PPS           ControlH264PPS
+	PH264ScalingMatrix ControlH264ScalingMatrix
+	H264PredWeights    ControlH264PredictionWeights
+	PH264SliceParams   ControlH264SliceParams
+	PH264DecodeParams  ControlH264DecodeParams
+	PFWHTParams        ControlFWHTParams
+	PVP8Frame          ControlVP8Frame
+	PMPEG2Sequence     ControlMPEG2Sequence
+	PMPEG2Picture      ControlMPEG2Picture
+	PMPEG2Quantization ControlMPEG2Quantization
+}
+
+func setExtCtrls(fd uintptr, whichCtrl uint32, ctrls []ExtControl) error {
+	numCtrl := len(ctrls)
+
+	var v4l2CtrlArray []C.struct_v4l2_ext_control
+
+	for _, ctrl := range ctrls {
+		var v4l2Ctrl C.struct_v4l2_ext_control
+		v4l2Ctrl.id = C.uint(ctrl.ID)
+		*(*C.uint)(unsafe.Pointer(&v4l2Ctrl.anon0[0])) = *(*C.uint)(unsafe.Pointer(&ctrl.Value))
+
+		v4l2CtrlArray = append(v4l2CtrlArray, v4l2Ctrl)
+	}
+
+	var v4l2Ctrls C.struct_v4l2_ext_controls
+	*(*uint32)(unsafe.Pointer(&v4l2Ctrls.anon0[0])) = whichCtrl
+	v4l2Ctrls.count = C.uint(numCtrl)
+	v4l2Ctrls.controls = (*C.struct_v4l2_ext_control)(unsafe.Pointer(&v4l2CtrlArray))
+
+	if err := send(fd, C.VIDIOC_S_EXT_CTRLS, uintptr(unsafe.Pointer(&v4l2Ctrls))); err != nil {
+		return fmt.Errorf("set ext controls: %w", err)
+	}
+
+	return nil
+}
+
+// GetExtControls retrieve one or more controls
+// func GetExtControls(fd uintptr, controls []ExtControl) (ExtControls, error) {
+// 	if true {
+// 		// TODO remove when supported
+// 		return ExtControls{}, fmt.Errorf("unsupported")
+// 	}
+
+// 	var ctrls C.struct_v4l2_ext_controls
+// 	ctrls.count = C.uint(len(controls))
+
+// 	// prepare control requests
+// 	var Cctrls []C.struct_v4l2_ext_control
+// 	for _, control := range controls {
+// 		var Cctrl C.struct_v4l2_ext_control
+// 		Cctrl.id = C.uint(control.ID)
+// 		Cctrl.size = C.uint(control.Size)
+// 		*(*ExtControlUnion)(unsafe.Pointer(&Cctrl.anon0[0])) = control.Ctrl
+// 		Cctrls = append(Cctrls, Cctrl)
+// 	}
+// 	ctrls.controls = (*C.struct_v4l2_ext_control)(unsafe.Pointer(&ctrls.controls))
+
+// 	if err := send(fd, C.VIDIOC_G_EXT_CTRLS, uintptr(unsafe.Pointer(&ctrls))); err != nil {
+// 		return ExtControls{}, fmt.Errorf("get ext controls: %w", err)
+// 	}
+
+// 	// gather returned controls
+// 	retCtrls := ExtControls{
+// 		Count:      uint32(ctrls.count),
+// 		ErrorIndex: uint32(ctrls.error_idx),
+// 	}
+// 	// extract controls array
+// 	Cctrls = *(*[]C.struct_v4l2_ext_control)(unsafe.Pointer(&ctrls.controls))
+// 	for _, Cctrl := range Cctrls {
+// 		extCtrl := ExtControl{
+// 			ID:   uint32(Cctrl.id),
+// 			Size: uint32(Cctrl.size),
+// 			Ctrl: *(*ExtControlUnion)(unsafe.Pointer(&Cctrl.anon0[0])),
+// 		}
+// 		retCtrls.Controls = append(retCtrls.Controls, extCtrl)
+// 	}
+
+// 	return retCtrls, nil
+// }

+ 4 - 4
v4l2/controls_fwht.go → v4l2/ext_ctrls_fwht.go

@@ -8,8 +8,8 @@ type ControlFWHTParams struct {
 	Width                uint32
 	Height               uint32
 	Flags                uint32
-	Colorspace           ColorspaceType
-	XFerFunc             XferFunctionType
-	YCbCrEncoding        YCbCrEncodingType
-	Quantization         QuantizationType
+	// Colorspace           ColorspaceType
+	// XFerFunc             XferFunctionType
+	// YCbCrEncoding        YCbCrEncodingType
+	// Quantization         QuantizationType
 }

+ 0 - 0
v4l2/controls_h264.go → v4l2/ext_ctrls_h264.go


+ 0 - 0
v4l2/controls_mpeg2.go → v4l2/ext_ctrls_mpeg2.go


+ 0 - 0
v4l2/controls_vp8.go → v4l2/ext_ctrls_vp8.go


+ 0 - 12
v4l2/types.go

@@ -25,15 +25,3 @@ type StreamingDevice interface {
 	Start(context.Context) error
 	Stop() error
 }
-
-//// CaptureDevice represents a device that captures video from an underlying device
-//type CaptureDevice interface {
-//	StreamingDevice
-//	StartCapture(context.Context) (<-chan []byte, error)
-//}
-//
-//// OutputDevice represents a device that can output video data to an underlying device driver
-//type OutputDevice interface {
-//	StreamingDevice
-//	StartOutput(context.Context, chan<- []byte) error
-//}