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() }