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

Controls #21

Merged
merged 21 commits into from
Jan 20, 2019
Merged
Show file tree
Hide file tree
Changes from 10 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
20 changes: 20 additions & 0 deletions examples/getcontrols.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Example program that reads the list of available controls and prints them.
package main

import "github.com/blackjack/webcam"
import "fmt"

func main() {
cam, err := webcam.Open("/dev/video0")
if err != nil {
panic(err.Error())
}
defer cam.Close()

clist := cam.GetControlList()

fmt.Println("Available controls: ")
for _, c := range clist {
fmt.Printf("%32s Min: %4d Max: %5d\n", c.Name, c.Min, c.Max)
}
}
2 changes: 2 additions & 0 deletions ioctl/ioctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const (
typeShift = numberShift + numberBits
sizeShift = typeShift + typeBits
directionShift = sizeShift + sizeBits

ErrEINVAL = unix.EINVAL
)

func ioc(dir, t, nr, size uintptr) uintptr {
Expand Down
87 changes: 75 additions & 12 deletions v4l2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,28 @@ package webcam
import (
"bytes"
"encoding/binary"
"strings"
"unsafe"

"github.com/blackjack/webcam/ioctl"
"golang.org/x/sys/unix"
)

type controlType int

const (
c_int controlType = iota
c_bool
c_menu
)

type control struct {
id uint32
c_type controlType
min int32
max int32
}

const (
V4L2_CAP_VIDEO_CAPTURE uint32 = 0x00000001
V4L2_CAP_STREAMING uint32 = 0x04000000
Expand All @@ -29,6 +45,28 @@ const (
V4L2_CID_PRIVATE_BASE uint32 = 0x08000000
)

const (
V4L2_CTRL_TYPE_INTEGER uint32 = 1
V4L2_CTRL_TYPE_BOOLEAN uint32 = 2
V4L2_CTRL_TYPE_MENU uint32 = 3
V4L2_CTRL_TYPE_BUTTON uint32 = 4
V4L2_CTRL_TYPE_INTEGER64 uint32 = 5
V4L2_CTRL_TYPE_CTRL_CLASS uint32 = 6
V4L2_CTRL_TYPE_STRING uint32 = 7
V4L2_CTRL_TYPE_BITMASK uint32 = 8
V4L2_CTRL_TYPE_INTEGER_MENU uint32 = 9

V4L2_CTRL_COMPOUND_TYPES uint32 = 0x0100
V4L2_CTRL_TYPE_U8 uint32 = 0x0100
V4L2_CTRL_TYPE_U16 uint32 = 0x0101
V4L2_CTRL_TYPE_U32 uint32 = 0x0102
)

const (
V4L2_CTRL_FLAG_DISABLED uint32 = 0x00000001
V4L2_CTRL_FLAG_NEXT_CTRL uint32 = 0x80000000
)

var (
VIDIOC_QUERYCAP = ioctl.IoR(uintptr('V'), 0, unsafe.Sizeof(v4l2_capability{}))
VIDIOC_ENUM_FMT = ioctl.IoRW(uintptr('V'), 2, unsafe.Sizeof(v4l2_fmtdesc{}))
Expand All @@ -37,6 +75,7 @@ var (
VIDIOC_QUERYBUF = ioctl.IoRW(uintptr('V'), 9, unsafe.Sizeof(v4l2_buffer{}))
VIDIOC_QBUF = ioctl.IoRW(uintptr('V'), 15, unsafe.Sizeof(v4l2_buffer{}))
VIDIOC_DQBUF = ioctl.IoRW(uintptr('V'), 17, unsafe.Sizeof(v4l2_buffer{}))
VIDIOC_G_CTRL = ioctl.IoRW(uintptr('V'), 27, unsafe.Sizeof(v4l2_control{}))
VIDIOC_S_CTRL = ioctl.IoRW(uintptr('V'), 28, unsafe.Sizeof(v4l2_control{}))
VIDIOC_QUERYCTRL = ioctl.IoRW(uintptr('V'), 36, unsafe.Sizeof(v4l2_queryctrl{}))
//sizeof int32
Expand Down Expand Up @@ -418,28 +457,52 @@ func waitForFrame(fd uintptr, timeout uint32) (count int, err error) {

}

func getControl(fd uintptr, id uint32) (int32, error) {
ctrl := &v4l2_control{}
ctrl.id = id
err := ioctl.Ioctl(fd, VIDIOC_G_CTRL, uintptr(unsafe.Pointer(ctrl)))
return ctrl.value, err
}

func setControl(fd uintptr, id uint32, val int32) error {
ctrl := &v4l2_control{}
ctrl.id = id
ctrl.value = val
return ioctl.Ioctl(fd, VIDIOC_S_CTRL, uintptr(unsafe.Pointer(ctrl)))
}

func getControls(fd uintptr) map[uint32]string {
query := &v4l2_queryctrl{}
var controls map[uint32]string
func queryControls(fd uintptr) map[string]control {
aamcrae marked this conversation as resolved.
Show resolved Hide resolved
var controls map[string]control = make(map[string]control)
var err error
for query.id = V4L2_CID_BASE; err == nil; query.id++ {
err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query)))
if err == nil {
controls[query.id] = CToGoString(query.name[:])
}
}
err = nil
for query.id = V4L2_CID_PRIVATE_BASE; err == nil; query.id++ {
// Don't use V42L_CID_BASE since it is the same as brightness.
var id uint32
for err != ioctl.ErrEINVAL {
id |= V4L2_CTRL_FLAG_NEXT_CTRL
query := &v4l2_queryctrl{}
query.id = id
err = ioctl.Ioctl(fd, VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(query)))
id = query.id
if err == nil {
controls[query.id] = CToGoString(query.name[:])
if (query.flags & V4L2_CTRL_FLAG_DISABLED) != 0 {
continue
}
var c control
switch query._type {
default:
continue
case V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER64:
c.c_type = c_int
case V4L2_CTRL_TYPE_BOOLEAN:
c.c_type = c_bool
case V4L2_CTRL_TYPE_MENU:
c.c_type = c_menu
}
c.id = id
c.min = query.minimum
c.max = query.maximum
// Normalise name (' ' -> '_', make lower case).
n := strings.Replace(strings.ToLower(CToGoString(query.name[:])), " ", "_", -1)
aamcrae marked this conversation as resolved.
Show resolved Hide resolved
controls[n] = c
}
}
return controls
Expand Down
48 changes: 48 additions & 0 deletions webcam.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ type Webcam struct {
bufcount uint32
buffers [][]byte
streaming bool
controls map[string]control
aamcrae marked this conversation as resolved.
Show resolved Hide resolved
}

type Control struct {
aamcrae marked this conversation as resolved.
Show resolved Hide resolved
Name string
Min int32
Max int32
}

// Open a webcam with a given path
Expand Down Expand Up @@ -126,6 +133,47 @@ func (w *Webcam) SetBufferCount(count uint32) error {
return nil
}

// Get the list of available controls.
func (w *Webcam) GetControlList() []Control {
aamcrae marked this conversation as resolved.
Show resolved Hide resolved
var clist []Control
if len(w.controls) == 0 {
w.controls = queryControls(w.fd)
}
for s, c := range w.controls {
clist = append(clist, Control{s, c.min, c.max})
}
return clist
}

// Get the value of a control.
func (w *Webcam) GetControl(name string) (int32, error) {
aamcrae marked this conversation as resolved.
Show resolved Hide resolved
c, err := w.lookupControl(name)
if err != nil {
return 0, err
}
return getControl(w.fd, c.id)
}

// Set a control.
func (w *Webcam) SetControl(name string, value int32) error {
aamcrae marked this conversation as resolved.
Show resolved Hide resolved
c, err := w.lookupControl(name)
if err != nil {
return err
}
return setControl(w.fd, c.id, value)
}

func (w *Webcam) lookupControl(name string) (control, error) {
if len(w.controls) == 0 {
w.controls = queryControls(w.fd)
}
c, ok := w.controls[name]
if !ok {
return control{}, errors.New("Unknown control: " + name)
}
return c, nil
}

// Start streaming process
func (w *Webcam) StartStreaming() error {
if w.streaming {
Expand Down