//go:build linux package device import ( "bytes" "errors" "fmt" "syscall" "unsafe" ) type Device struct { fd int bufs [][]byte pixFmt uint32 } func Open(path string) (*Device, error) { fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CLOEXEC, 0) if err != nil { return nil, err } return &Device{fd: fd}, nil } const buffersCount = 2 type Capability struct { Driver string Card string BusInfo string Version string } func (d *Device) Capability() (*Capability, error) { c := v4l2_capability{} if err := ioctl(d.fd, VIDIOC_QUERYCAP, unsafe.Pointer(&c)); err != nil { return nil, err } return &Capability{ Driver: str(c.driver[:]), Card: str(c.card[:]), BusInfo: str(c.bus_info[:]), Version: fmt.Sprintf("%d.%d.%d", byte(c.version>>16), byte(c.version>>8), byte(c.version)), }, nil } func (d *Device) ListFormats() ([]uint32, error) { var items []uint32 for i := uint32(0); ; i++ { fd := v4l2_fmtdesc{ index: i, typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, } if err := ioctl(d.fd, VIDIOC_ENUM_FMT, unsafe.Pointer(&fd)); err != nil { if !errors.Is(err, syscall.EINVAL) { return nil, err } break } items = append(items, fd.pixelformat) } return items, nil } func (d *Device) ListSizes(pixFmt uint32) ([][2]uint32, error) { var items [][2]uint32 for i := uint32(0); ; i++ { fs := v4l2_frmsizeenum{ index: i, pixel_format: pixFmt, } if err := ioctl(d.fd, VIDIOC_ENUM_FRAMESIZES, unsafe.Pointer(&fs)); err != nil { if !errors.Is(err, syscall.EINVAL) { return nil, err } break } if fs.typ != V4L2_FRMSIZE_TYPE_DISCRETE { continue } items = append(items, [2]uint32{fs.discrete.width, fs.discrete.height}) } return items, nil } func (d *Device) ListFrameRates(pixFmt, width, height uint32) ([]uint32, error) { var items []uint32 for i := uint32(0); ; i++ { fi := v4l2_frmivalenum{ index: i, pixel_format: pixFmt, width: width, height: height, } if err := ioctl(d.fd, VIDIOC_ENUM_FRAMEINTERVALS, unsafe.Pointer(&fi)); err != nil { if !errors.Is(err, syscall.EINVAL) { return nil, err } break } if fi.typ != V4L2_FRMIVAL_TYPE_DISCRETE || fi.discrete.numerator != 1 { continue } items = append(items, fi.discrete.denominator) } return items, nil } func (d *Device) SetFormat(width, height, pixFmt uint32) error { d.pixFmt = pixFmt f := v4l2_format{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, pix: v4l2_pix_format{ width: width, height: height, pixelformat: pixFmt, field: V4L2_FIELD_NONE, colorspace: V4L2_COLORSPACE_DEFAULT, }, } return ioctl(d.fd, VIDIOC_S_FMT, unsafe.Pointer(&f)) } func (d *Device) SetParam(fps uint32) error { p := v4l2_streamparm{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, capture: v4l2_captureparm{ timeperframe: v4l2_fract{numerator: 1, denominator: fps}, }, } return ioctl(d.fd, VIDIOC_S_PARM, unsafe.Pointer(&p)) } func (d *Device) StreamOn() (err error) { rb := v4l2_requestbuffers{ count: buffersCount, typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, memory: V4L2_MEMORY_MMAP, } if err = ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb)); err != nil { return err } d.bufs = make([][]byte, buffersCount) for i := uint32(0); i < buffersCount; i++ { qb := v4l2_buffer{ index: i, typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, memory: V4L2_MEMORY_MMAP, } if err = ioctl(d.fd, VIDIOC_QUERYBUF, unsafe.Pointer(&qb)); err != nil { return err } if d.bufs[i], err = syscall.Mmap( d.fd, int64(qb.offset), int(qb.length), syscall.PROT_READ, syscall.MAP_SHARED, ); nil != err { return err } if err = ioctl(d.fd, VIDIOC_QBUF, unsafe.Pointer(&qb)); err != nil { return err } } typ := uint32(V4L2_BUF_TYPE_VIDEO_CAPTURE) return ioctl(d.fd, VIDIOC_STREAMON, unsafe.Pointer(&typ)) } func (d *Device) StreamOff() (err error) { typ := uint32(V4L2_BUF_TYPE_VIDEO_CAPTURE) if err = ioctl(d.fd, VIDIOC_STREAMOFF, unsafe.Pointer(&typ)); err != nil { return err } for i := range d.bufs { _ = syscall.Munmap(d.bufs[i]) } rb := v4l2_requestbuffers{ count: 0, typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, memory: V4L2_MEMORY_MMAP, } return ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb)) } func (d *Device) Capture() ([]byte, error) { dec := v4l2_buffer{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, memory: V4L2_MEMORY_MMAP, } if err := ioctl(d.fd, VIDIOC_DQBUF, unsafe.Pointer(&dec)); err != nil { return nil, err } src := d.bufs[dec.index][:dec.bytesused] dst := make([]byte, dec.bytesused) switch d.pixFmt { case V4L2_PIX_FMT_YUYV: YUYVtoYUV(dst, src) case V4L2_PIX_FMT_NV12: NV12toYUV(dst, src) default: copy(dst, d.bufs[dec.index][:dec.bytesused]) } enc := v4l2_buffer{ typ: V4L2_BUF_TYPE_VIDEO_CAPTURE, memory: V4L2_MEMORY_MMAP, index: dec.index, } if err := ioctl(d.fd, VIDIOC_QBUF, unsafe.Pointer(&enc)); err != nil { return nil, err } return dst, nil } func (d *Device) Close() error { return syscall.Close(d.fd) } func ioctl(fd int, req uint, arg unsafe.Pointer) error { _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg)) if err != 0 { return err } return nil } func str(b []byte) string { if i := bytes.IndexByte(b, 0); i >= 0 { return string(b[:i]) } return string(b) }