webcam/webcam.go
2021-02-07 14:41:48 +01:00

587 lines
14 KiB
Go

package webcam
import (
"bytes"
"errors"
"fmt"
"image"
"image/jpeg"
"image/png"
"os"
"sort"
"syscall"
"time"
"unsafe"
"github.com/creack/goselect"
)
type Webcam struct {
fd uintptr
file *os.File
path string
timeout time.Duration
buffers [][]byte
}
func Open(path string, timeout time.Duration) (*Webcam, error) {
f, err := os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
if err != nil {
return nil, err
}
w := new(Webcam)
w.fd = f.Fd()
w.file = f
w.path = path
w.timeout = timeout
v, s, err := w.checkCapabilities()
if err != nil {
return nil, err
}
if v {
return nil, errors.New("Not a video capture device")
}
if s {
return nil, errors.New("Device does not support the streaming I/O method")
}
return w, nil
}
type requestbuffers struct {
count uint32
t uint32
memory uint32
reserved [2]uint32
}
type buffer struct {
index uint32
t uint32
bytesused uint32
flags uint32
field uint32
timestamp syscall.Timeval
timecode struct {
t uint32
flags uint32
frames uint8
seconds uint8
minutes uint8
hours uint8
userbits [4]uint8
}
sequence uint32
memory uint32
/*
union {
__u32 offset;
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
*/
offset int64 // m
length uint32
reserved2 uint32
reserved uint32
}
type format struct {
t uint32
pix pix
raw_data [155]byte // placeholder
}
type pix struct {
empty uint32 // placeholder
width uint32
height uint32
pixelformat uint32
field uint32
bytesperline uint32
sizeimage uint32
colorspace uint32
priv uint32
flags uint32
ycbcr_enc uint32
quantization uint32
// commented out for 'empty' placeholder
//xfer_func uint32
}
type capability struct {
driver [16]byte
card [32]byte
bus_info [32]byte
version uint32
capabilities uint32
device_caps uint32
reserved [3]uint32
}
type fmtdesc struct {
index uint32
t uint32
flags uint32
description [32]byte
pixelformat uint32
reserved [4]uint32
}
type frmsizeenum struct {
index uint32
pixel_format uint32
t uint32
/*
struct v4l2_frmsize_stepwise {
__u32 min_width
__u32 max_width
__u32 step_width
__u32 min_height
__u32 max_height
__u32 step_height
};
struct v4l2_frmsize_discrete {
__u32 width
__u32 height
};
*/
frmsize [6]uint32
reserved [2]uint32
}
type queryctrl struct {
id uint32
_type uint32
name [32]uint8
minimum int32
maximum int32
step int32
default_value int32
flags uint32
reserved [2]uint32
}
type querymenu struct {
id uint32
index uint32
name [32]uint8
reserved uint32
}
type control struct {
id uint32
value int32
}
const (
_IOC_READ = 2
_IOC_WRITE = 1
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
_V4L2_BUF_TYPE_VIDEO_CAPTURE = 1
_V4L2_MEMORY_MMAP = 1
_V4L2_FIELD_ANY = 0
_V4L2_CAP_STREAMING = 0x04000000
_V4L2_CAP_VIDEO_CAPTURE = 0x00000001
_V4L2_FRMSIZE_TYPE_DISCRETE = 1
_V4L2_FRMSIZE_TYPE_CONTINUOUS = 2
_V4L2_FRMSIZE_TYPE_STEPWISE = 3
_V4L2_CTRL_FLAG_NEXT_CTRL = 0x80000000
_V4L2_CTRL_FLAG_DISABLED = 0x0001
_V4L2_CTRL_TYPE_MENU = 3
_V4L2_CTRL_CLASS_USER = 0x00980000
_V4L2_CID_BASE = _V4L2_CTRL_CLASS_USER | 0x900
_V4L2_CID_USER_BASE = _V4L2_CID_BASE
_V4L2_CID_USER_CLASS = _V4L2_CTRL_CLASS_USER | 1
// Controls
V4L2_CID_BRIGHTNESS = _V4L2_CID_BASE + 0
V4L2_CID_CONTRAST = _V4L2_CID_BASE + 1
V4L2_CID_SATURATION = _V4L2_CID_BASE + 2
V4L2_CID_HUE = _V4L2_CID_BASE + 3
V4L2_CID_AUDIO_VOLUME = _V4L2_CID_BASE + 5
V4L2_CID_AUDIO_BALANCE = _V4L2_CID_BASE + 6
V4L2_CID_AUDIO_BASS = _V4L2_CID_BASE + 7
V4L2_CID_AUDIO_TREBLE = _V4L2_CID_BASE + 8
V4L2_CID_AUDIO_MUTE = _V4L2_CID_BASE + 9
V4L2_CID_AUDIO_LOUDNESS = _V4L2_CID_BASE + 10
V4L2_CID_AUTO_WHITE_BALANCE = _V4L2_CID_BASE + 12
V4L2_CID_DO_WHITE_BALANCE = _V4L2_CID_BASE + 13
V4L2_CID_RED_BALANCE = _V4L2_CID_BASE + 14
V4L2_CID_BLUE_BALANCE = _V4L2_CID_BASE + 15
V4L2_CID_GAMMA = _V4L2_CID_BASE + 16
V4L2_CID_EXPOSURE = _V4L2_CID_BASE + 17
V4L2_CID_AUTOGAIN = _V4L2_CID_BASE + 18
V4L2_CID_GAIN = _V4L2_CID_BASE + 19
V4L2_CID_HFLIP = _V4L2_CID_BASE + 20
V4L2_CID_VFLIP = _V4L2_CID_BASE + 21
V4L2_CID_POWER_LINE_FREQUENCY = _V4L2_CID_BASE + 24
V4L2_CID_HUE_AUTO = _V4L2_CID_BASE + 25
V4L2_CID_WHITE_BALANCE_TEMPERATURE = _V4L2_CID_BASE + 26
V4L2_CID_SHARPNESS = _V4L2_CID_BASE + 27
V4L2_CID_BACKLIGHT_COMPENSATION = _V4L2_CID_BASE + 28
V4L2_CID_CHROMA_AGC = _V4L2_CID_BASE + 29
V4L2_CID_COLOR_KILLER = _V4L2_CID_BASE + 30
V4L2_CID_COLORFX = _V4L2_CID_BASE + 31
V4L2_CID_AUTOBRIGHTNESS = _V4L2_CID_BASE + 32
V4L2_CID_BAND_STOP_FILTER = _V4L2_CID_BASE + 33
V4L2_CID_ROTATE = _V4L2_CID_BASE + 34
V4L2_CID_BG_COLOR = _V4L2_CID_BASE + 35
V4L2_CID_CHROMA_GAIN = _V4L2_CID_BASE + 36
V4L2_CID_ILLUMINATORS_1 = _V4L2_CID_BASE + 37
V4L2_CID_ILLUMINATORS_2 = _V4L2_CID_BASE + 38
V4L2_CID_MIN_BUFFERS_FOR_CAPTURE = _V4L2_CID_BASE + 39
V4L2_CID_MIN_BUFFERS_FOR_OUTPUT = _V4L2_CID_BASE + 40
V4L2_CID_ALPHA_COMPONENT = _V4L2_CID_BASE + 41
V4L2_CID_COLORFX_CBCR = _V4L2_CID_BASE + 42
)
var (
_VIDIOC_QUERYCAP = _IOR('V', 0, unsafe.Sizeof(capability{}))
_VIDIOC_ENUM_FMT = _IOWR('V', 2, unsafe.Sizeof(fmtdesc{}))
_VIDIOC_S_FMT = _IOWR('V', 5, unsafe.Sizeof(format{}))
_VIDIOC_REQBUFS = _IOWR('V', 8, unsafe.Sizeof(requestbuffers{}))
_VIDIOC_QUERYBUF = _IOWR('V', 9, unsafe.Sizeof(buffer{}))
_VIDIOC_QBUF = _IOWR('V', 15, unsafe.Sizeof(buffer{}))
_VIDIOC_DQBUF = _IOWR('V', 17, unsafe.Sizeof(buffer{}))
_VIDIOC_STREAMON = _IOW('V', 18, unsafe.Sizeof(int32(0)))
_VIDIOC_ENUM_FRAMESIZES = _IOWR('V', 74, unsafe.Sizeof(frmsizeenum{}))
_VIDIOC_QUERYCTRL = _IOWR('V', 36, unsafe.Sizeof(queryctrl{}))
_VIDIOC_QUERYMENU = _IOWR('V', 37, unsafe.Sizeof(querymenu{}))
_VIDIOC_G_CTRL = _IOWR('V', 27, unsafe.Sizeof(control{}))
_VIDIOC_S_CTRL = _IOWR('V', 28, unsafe.Sizeof(control{}))
)
func _IOC(dir, t, nr, size uintptr) uintptr {
return dir<<_IOC_DIRSHIFT |
t<<_IOC_TYPESHIFT |
nr<<_IOC_NRSHIFT |
size<<_IOC_SIZESHIFT
}
func _IOWR(t rune, i, size uintptr) uintptr {
return _IOC(_IOC_READ|_IOC_WRITE, uintptr(t), i, size)
}
func _IOW(t rune, i, size uintptr) uintptr {
return _IOC(_IOC_WRITE, uintptr(t), i, size)
}
func _IOR(t rune, i, size uintptr) uintptr {
return _IOC(_IOC_READ, uintptr(t), i, size)
}
func ioctl(fd, cmd, ptr uintptr) error {
_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
if ep != 0 {
return syscall.Errno(ep)
}
return nil
}
func u2s(a [32]uint8) string {
buf := make([]byte, 32)
for i, u := range a {
if u == 0 {
break
}
buf[i] = byte(u)
}
return string(buf)
}
func (w *Webcam) checkCapabilities() (video, stream bool, err error) {
c := new(capability)
err = ioctl(w.fd, _VIDIOC_QUERYCAP, uintptr(unsafe.Pointer(c)))
video = (c.capabilities & _V4L2_CAP_VIDEO_CAPTURE) == 0
stream = (c.capabilities & _V4L2_CAP_STREAMING) == 0
return
}
func (w *Webcam) enumMenu(id uint32, min, max int32) (items []string) {
var qm querymenu
qm.id = id
for qm.index = uint32(min); qm.index <= uint32(max); qm.index++ {
if ioctl(w.fd, _VIDIOC_QUERYMENU, uintptr(unsafe.Pointer(&qm))) == nil {
items = append(items, u2s(qm.name))
}
}
return
}
func (w *Webcam) GetControls() (m map[string][]string) {
m = make(map[string][]string)
var qc queryctrl
qc.id = _V4L2_CTRL_FLAG_NEXT_CTRL
for ioctl(w.fd, _VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(&qc))) == nil {
if (qc.flags & _V4L2_CTRL_FLAG_DISABLED) == 0 {
items := []string{}
if qc._type == _V4L2_CTRL_TYPE_MENU {
items = w.enumMenu(qc.id, qc.minimum, qc.maximum)
}
m[u2s(qc.name)] = items
}
qc.id |= _V4L2_CTRL_FLAG_NEXT_CTRL
}
return
}
func (w *Webcam) SetControl(ctrl uint32, value int32) error {
var qc queryctrl
qc.id = ctrl
err := ioctl(w.fd, _VIDIOC_QUERYCTRL, uintptr(unsafe.Pointer(&qc)))
if err != nil {
return err
}
if (qc.flags & _V4L2_CTRL_FLAG_DISABLED) != 0 {
return errors.New("unsupported")
}
var c control
c.id = ctrl
c.value = value
return ioctl(w.fd, _VIDIOC_S_CTRL, uintptr(unsafe.Pointer(&c)))
}
func (w *Webcam) GetPixelFormats() (m map[uint32]string) {
m = make(map[uint32]string)
for i := uint32(0); ; i++ {
vfd := fmtdesc{
index: i,
t: _V4L2_BUF_TYPE_VIDEO_CAPTURE,
}
err := ioctl(w.fd, _VIDIOC_ENUM_FMT, uintptr(unsafe.Pointer(&vfd)))
if err != nil {
return
}
var s string
for _, b := range vfd.description {
s = s + string(b)
}
m[vfd.pixelformat] = s
}
return
}
type FrameSize struct {
MinWidth uint32
MaxWidth uint32
StepWidth uint32
MinHeight uint32
MaxHeight uint32
StepHeight uint32
}
func (s FrameSize) String() string {
if s.StepWidth == 0 && s.StepHeight == 0 {
return fmt.Sprintf("%dx%d", s.MaxWidth, s.MaxHeight)
} else {
return fmt.Sprintf("[%d-%d;%d]x[%d-%d;%d]", s.MinWidth, s.MaxWidth, s.StepWidth, s.MinHeight, s.MaxHeight, s.StepHeight)
}
}
type FrameSizes []FrameSize
func (fs FrameSizes) Len() int { return len(fs) }
func (fs FrameSizes) Less(i, j int) bool {
ls := fs[i].MaxWidth * fs[i].MaxHeight
rs := fs[j].MaxWidth * fs[j].MaxHeight
return ls < rs
}
func (fs FrameSizes) Swap(i, j int) { fs[i], fs[j] = fs[j], fs[i] }
func (w *Webcam) GetFrameSizes(code uint32) (fs FrameSizes) {
var s FrameSize
for i := uint32(0); ; i++ {
vfse := frmsizeenum{
index: i,
pixel_format: code,
}
if err := ioctl(w.fd, _VIDIOC_ENUM_FRAMESIZES, uintptr(unsafe.Pointer(&vfse))); err != nil {
break
}
switch vfse.t {
case _V4L2_FRMSIZE_TYPE_DISCRETE:
s.MinWidth = vfse.frmsize[0]
s.MaxWidth = vfse.frmsize[0]
s.StepWidth = 0
s.MinHeight = vfse.frmsize[1]
s.MaxHeight = vfse.frmsize[1]
s.StepHeight = 0
case _V4L2_FRMSIZE_TYPE_CONTINUOUS, _V4L2_FRMSIZE_TYPE_STEPWISE:
s.MinWidth = vfse.frmsize[0]
s.MaxWidth = vfse.frmsize[1]
s.StepWidth = vfse.frmsize[2]
s.MinHeight = vfse.frmsize[3]
s.MaxHeight = vfse.frmsize[4]
s.StepHeight = vfse.frmsize[5]
}
fs = append(fs, s)
}
sort.Sort(fs)
return
}
func (cam *Webcam) SetImageFormat(w, h, formatcode uint32) (uint32, uint32, uint32, error) {
f := format{
t: _V4L2_BUF_TYPE_VIDEO_CAPTURE,
pix: pix{
width: w,
height: h,
pixelformat: formatcode,
field: _V4L2_FIELD_ANY,
},
}
err := ioctl(cam.fd, _VIDIOC_S_FMT, uintptr(unsafe.Pointer(&f)))
return f.pix.pixelformat, f.pix.width, f.pix.height, err
}
func (w *Webcam) mmapRequestBuffers(bufcount uint32) (int, error) {
req := requestbuffers{
count: bufcount,
t: _V4L2_BUF_TYPE_VIDEO_CAPTURE,
memory: _V4L2_MEMORY_MMAP,
}
err := ioctl(w.fd, _VIDIOC_REQBUFS, uintptr(unsafe.Pointer(&req)))
return int(req.count), err
}
func (w *Webcam) mmapQueryBuffer(index uint32) ([]byte, error) {
buf := buffer{
t: _V4L2_BUF_TYPE_VIDEO_CAPTURE,
memory: _V4L2_MEMORY_MMAP,
index: index,
}
err := ioctl(w.fd, _VIDIOC_QUERYBUF, uintptr(unsafe.Pointer(&buf)))
if err != nil {
return nil, err
}
return syscall.Mmap(int(w.fd), buf.offset, int(buf.length), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
}
func (w *Webcam) mmapDequeueBuffer() (uint32, uint32, error) {
buf := buffer{
t: _V4L2_BUF_TYPE_VIDEO_CAPTURE,
memory: _V4L2_MEMORY_MMAP,
}
err := ioctl(w.fd, _VIDIOC_DQBUF, uintptr(unsafe.Pointer(&buf)))
return buf.index, buf.bytesused, err
}
func (w *Webcam) mmapEnqueueBuffer(index uint32) error {
buf := buffer{
t: _V4L2_BUF_TYPE_VIDEO_CAPTURE,
memory: _V4L2_MEMORY_MMAP,
index: index,
}
return ioctl(w.fd, _VIDIOC_QBUF, uintptr(unsafe.Pointer(&buf)))
}
func (w *Webcam) mmapReleaseBuffers() error {
for _, buf := range w.buffers {
if err := syscall.Munmap(buf); err != nil {
return err
}
}
return nil
}
func (w *Webcam) StartStreaming() error {
n, err := w.mmapRequestBuffers(uint32(256))
if err != nil {
return err
}
w.buffers = make([][]byte, n)
for i := range w.buffers {
buf, err := w.mmapQueryBuffer(uint32(i))
if err != nil {
return err
}
w.buffers[i] = buf
err = w.mmapEnqueueBuffer(uint32(i))
if err != nil {
return err
}
}
t := _V4L2_BUF_TYPE_VIDEO_CAPTURE
return ioctl(w.fd, _VIDIOC_STREAMON, uintptr(unsafe.Pointer(&t)))
}
type TimeoutErr string
func (t TimeoutErr) Error() string {
return string(t)
}
func (w *Webcam) WaitForFrame() (err error) {
fds := new(goselect.FDSet)
for {
fds.Zero()
fds.Set(w.fd)
err = goselect.Select(int(w.fd)+1, fds, nil, nil, w.timeout)
if err == nil {
return
}
e, ok := err.(syscall.Errno)
if ok {
if e.Timeout() {
return TimeoutErr("timeout")
}
if e.Temporary() {
continue
}
}
}
}
func (w *Webcam) ReadFrame() ([]byte, error) {
i, l, err := w.mmapDequeueBuffer()
if err != nil {
return nil, err
}
buf := make([]byte, l)
copy(buf, w.buffers[int(i)])
err = w.mmapEnqueueBuffer(i)
if err != nil {
return nil, err
}
return buf, nil
}
func (w *Webcam) ReadJPEG() (image.Image, error) {
buf, err := w.ReadFrame()
if err != nil {
return nil, err
}
return jpeg.Decode(bytes.NewBuffer(buf))
}
func (w *Webcam) ReadPNG() (image.Image, error) {
buf, err := w.ReadFrame()
if err != nil {
return nil, err
}
return png.Decode(bytes.NewBuffer(buf))
}
func (w *Webcam) Close() error {
if err := w.mmapReleaseBuffers(); err != nil {
return err
}
return w.file.Close()
}