Skip to content

Commit

Permalink
User device control support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Vladimir Vivien authored and vladimirvivien committed Sep 3, 2022
1 parent a16f71b commit 70f2d97
Show file tree
Hide file tree
Showing 20 changed files with 637 additions and 140 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
54 changes: 54 additions & 0 deletions device/device_control.go
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 1 addition & 2 deletions examples/device_info/README.md
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
Binary file added examples/device_info/device_info
Binary file not shown.
39 changes: 39 additions & 0 deletions examples/format/README.md
Original file line number Diff line number Diff line change
@@ -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)
}
}

}
```
8 changes: 4 additions & 4 deletions examples/format/devfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"log"
"strings"

device2 "github.com/vladimirvivien/go4vl/device"
dev "github.com/vladimirvivien/go4vl/device"
"github.com/vladimirvivien/go4vl/v4l2"
)

Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions examples/user_ctrl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Device user control
100 changes: 100 additions & 0 deletions examples/user_ctrl/ctrl.go
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions multipass/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions multipass/cloud-init.yaml
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions multipass/start.sh
Original file line number Diff line number Diff line change
@@ -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 -
Loading

0 comments on commit 70f2d97

Please sign in to comment.