Skip to content

Commit

Permalink
Merge pull request #23 from vladimirvivien/device-controls
Browse files Browse the repository at this point in the history
Adds support for user device controls
  • Loading branch information
vladimirvivien committed Sep 3, 2022
2 parents a16f71b + 0bd8cf2 commit ab7db70
Show file tree
Hide file tree
Showing 19 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
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 ab7db70

Please sign in to comment.