Browse Source

Merge pull request #23 from vladimirvivien/device-controls

Adds support for user device controls
Vladimir Vivien 3 years ago
parent
commit
ab7db70eb7

+ 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
 # go4vl
 
 
 ![](./docs/go4vl-logo-small.png)
 ![](./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. 
 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.
 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
 ## 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
 # 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
 ```go
 func main() {
 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"
 	"log"
 	"strings"
 	"strings"
 
 
-	device2 "github.com/vladimirvivien/go4vl/device"
+	dev "github.com/vladimirvivien/go4vl/device"
 	"github.com/vladimirvivien/go4vl/v4l2"
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 )
 
 
@@ -31,10 +31,10 @@ func main() {
 		fmtEnc = v4l2.PixelFmtYUYV
 		fmtEnc = v4l2.PixelFmtYUYV
 	}
 	}
 
 
-	device, err := device2.Open(
+	device, err := dev.Open(
 		devName,
 		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 {
 	if err != nil {
 		log.Fatalf("failed to open device: %s", err)
 		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 "C"
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
 	"unsafe"
 	"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)
 // 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 {
 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
 	ID    uint32
+	Index uint32
 	Value 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
 	var ctrl C.struct_v4l2_control
 	ctrl.id = C.uint(id)
 	ctrl.id = C.uint(id)
 
 
 	if err := send(fd, C.VIDIOC_G_CTRL, uintptr(unsafe.Pointer(&ctrl))); err != nil {
 	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
 	var ctrl C.struct_v4l2_control
 	ctrl.id = C.uint(id)
 	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
 	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
 	Width                uint32
 	Height               uint32
 	Height               uint32
 	Flags                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
 	Start(context.Context) error
 	Stop() 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
-//}