Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User device control support #23

Merged
merged 1 commit into from
Sep 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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