initial commit
This commit is contained in:
commit
c67729332c
26 changed files with 1612 additions and 0 deletions
442
webcam.go
Normal file
442
webcam.go
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
package webcam
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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_dummy struct {
|
||||
t uint32
|
||||
fmt struct {
|
||||
raw_data [201]byte
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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{}))
|
||||
)
|
||||
|
||||
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 (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) 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 (slice FrameSizes) Len() int { return len(slice) }
|
||||
func (slice FrameSizes) Less(i, j int) bool {
|
||||
ls := slice[i].MaxWidth * slice[i].MaxHeight
|
||||
rs := slice[j].MaxWidth * slice[j].MaxHeight
|
||||
return ls < rs
|
||||
}
|
||||
func (slice FrameSizes) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[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) Close() error {
|
||||
if err := w.mmapReleaseBuffers(); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.file.Close()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue