Pārlūkot izejas kodu

Example using cgo; example device fmt; updating webcam example

Vladimir Vivien 3 gadi atpakaļ
vecāks
revīzija
eab8daf9ba

+ 59 - 0
examples/format/devfmt.go

@@ -0,0 +1,59 @@
+package main
+
+import (
+	"flag"
+	"log"
+	"strings"
+
+	"github.com/vladimirvivien/go4vl/v4l2"
+)
+
+func main() {
+	devName := "/dev/video0"
+	width := 640
+	height := 480
+	format := "yuyv"
+
+	flag.StringVar(&devName, "d", devName, "device name (path)")
+	flag.IntVar(&width, "w", width, "capture width")
+	flag.IntVar(&height, "h", height, "capture height")
+	flag.StringVar(&format, "f", format, "pixel format")
+	flag.Parse()
+
+	device, err := v4l2.Open(devName)
+	if err != nil {
+		log.Fatalf("failed to open device: %s", err)
+	}
+	defer device.Close()
+
+	currFmt, err := device.GetPixFormat()
+	if err != nil {
+		log.Fatalf("unable to get format: %s", err)
+	}
+	log.Printf("Current format: %s", currFmt)
+
+	fmtEnc := v4l2.PixelFmtYUYV
+	switch strings.ToLower(format) {
+	case "mjpeg":
+		fmtEnc = v4l2.PixelFmtMJPEG
+	case "h264", "h.264":
+		fmtEnc = v4l2.PixelFmtH264
+	case "yuyv":
+		fmtEnc = v4l2.PixelFmtYUYV
+	}
+
+	if err := device.SetPixFormat(v4l2.PixFormat{
+		Width: uint32(width),
+		Height: uint32(height),
+		PixelFormat: fmtEnc,
+		Field: v4l2.FieldNone,
+	}); err != nil {
+		log.Fatalf("failed to set format: %s", err)
+	}
+
+	currFmt, err = device.GetPixFormat()
+	if err != nil {
+		log.Fatalf("unable to get format: %s", err)
+	}
+	log.Printf("Updated format: %s", currFmt)
+}

+ 363 - 0
examples/native_c_types/capture.go

@@ -0,0 +1,363 @@
+package main
+
+/*
+#include <linux/videodev2.h>
+*/
+import "C"
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"os"
+	"time"
+	"unsafe"
+
+	sys "golang.org/x/sys/unix"
+)
+
+// ========================= V4L2 command encoding =====================
+// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/asm-generic/ioctl.h
+
+const (
+	//ioctl command layout
+	iocNone  = 0 // no op
+	iocWrite = 1 // userland app is writing, kernel reading
+	iocRead  = 2 // userland app is reading, kernel writing
+
+	iocTypeBits   = 8
+	iocNumberBits = 8
+	iocSizeBits   = 14
+	iocOpBits     = 2
+
+	numberPos = 0
+	typePos   = numberPos + iocNumberBits
+	sizePos   = typePos + iocTypeBits
+	opPos     = sizePos + iocSizeBits
+)
+
+// ioctl command encoding funcs
+func ioEnc(iocMode, iocType, number, size uintptr) uintptr {
+	return (iocMode << opPos) |
+		(iocType << typePos) |
+		(number << numberPos) |
+		(size << sizePos)
+}
+
+func ioEncR(iocType, number, size uintptr) uintptr {
+	return ioEnc(iocRead, iocType, number, size)
+}
+
+func ioEncW(iocType, number, size uintptr) uintptr {
+	return ioEnc(iocWrite, iocType, number, size)
+}
+
+func ioEncRW(iocType, number, size uintptr) uintptr {
+	return ioEnc(iocRead|iocWrite, iocType, number, size)
+}
+
+// four character pixel format encoding
+func fourcc(a, b, c, d uint32) uint32 {
+	return (a | b<<8) | c<<16 | d<<24
+}
+
+// wrapper for ioctl system call
+func ioctl(fd, req, arg uintptr) (err error) {
+	if _, _, errno := sys.Syscall(sys.SYS_IOCTL, fd, req, arg); errno != 0 {
+		err = errno
+		return
+	}
+	return nil
+}
+
+// ========================= Pixel Format =========================
+// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/linux/videodev2.h#L682
+
+var (
+	PixelFmtMJPEG uint32 = C.V4L2_PIX_FMT_MJPEG
+)
+
+// Pix format field types
+// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/linux/videodev2.h#L89
+const (
+	FieldAny  uint32 = C.V4L2_FIELD_ANY
+	FieldNone uint32 = C.V4L2_FIELD_NONE
+)
+
+// buff stream types
+// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/linux/videodev2.h#L142
+const (
+	BufTypeVideoCapture uint32 = C.V4L2_BUF_TYPE_VIDEO_CAPTURE
+	BufTypeVideoOutput  uint32 = C.V4L2_BUF_TYPE_VIDEO_OUTPUT
+	BufTypeOverlay      uint32 = C.V4L2_BUF_TYPE_VIDEO_OVERLAY
+)
+
+// PixFormat represents v4l2_pix_format
+// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/linux/videodev2.h#L496
+type PixFormat struct {
+	Width        uint32
+	Height       uint32
+	PixelFormat  uint32
+	Field        uint32
+	BytesPerLine uint32
+	SizeImage    uint32
+	Colorspace   uint32
+	Priv         uint32
+	Flags        uint32
+	YcbcrEnc     uint32
+	Quantization uint32
+	XferFunc     uint32
+}
+
+// setsFormat sets pixel format of device
+func setFormat(fd uintptr, pixFmt PixFormat) error {
+	var v4l2Fmt C.struct_v4l2_format
+	v4l2Fmt._type = C.uint(BufTypeVideoCapture)
+	*(*C.struct_v4l2_pix_format)(unsafe.Pointer(&v4l2Fmt.fmt[0])) = *(*C.struct_v4l2_pix_format)(unsafe.Pointer(&pixFmt))
+
+	// encode command to send
+	// vidiocSetFormat := ioEncRW('V', 5, uintptr(unsafe.Sizeof(v4l2Fmt)))
+
+	// send command
+	if err := ioctl(fd, C.VIDIOC_S_FMT, uintptr(unsafe.Pointer(&v4l2Fmt))); err != nil {
+		return err
+	}
+	log.Printf("setting format to: %dx%d\n", pixFmt.Width, pixFmt.Height)
+	return nil
+}
+
+func getFormat(fd uintptr) (PixFormat, error){
+	var v4l2Fmt C.struct_v4l2_format
+	v4l2Fmt._type = C.uint(BufTypeVideoCapture)
+
+	// send command
+	if err := ioctl(fd, C.VIDIOC_G_FMT, uintptr(unsafe.Pointer(&v4l2Fmt))); err != nil {
+		return PixFormat{}, err
+	}
+
+	var pixFmt PixFormat
+	*(*C.struct_v4l2_pix_format)(unsafe.Pointer(&pixFmt))= *(*C.struct_v4l2_pix_format)(unsafe.Pointer(&v4l2Fmt.fmt[0]))
+
+	return pixFmt, nil
+
+}
+
+// =========================== Buffers and Streaming ========================== //
+
+// Memory buffer types
+// https://elixir.bootlin.com/linux/v5.13-rc6/source/include/uapi/linux/videodev2.h#L188
+const (
+	StreamMemoryTypeMMAP    uint32 = C.V4L2_MEMORY_MMAP
+)
+
+// reqBuffers requests that the device allocates a `count`
+// number of internal buffers before they can be mapped into
+// the application's address space. The driver will return
+// the actual number of buffers allocated in the RequestBuffers
+// struct.
+func reqBuffers(fd uintptr, count uint32) error {
+	var reqbuf C.struct_v4l2_requestbuffers
+	reqbuf.count = C.uint(count)
+	reqbuf._type = C.uint(BufTypeVideoCapture)
+	reqbuf.memory = C.uint(StreamMemoryTypeMMAP)
+
+	if err := ioctl(fd, C.VIDIOC_REQBUFS, uintptr(unsafe.Pointer(&reqbuf))); err != nil {
+		return err
+	}
+	log.Printf("Request %d buffers OK\n", count)
+	return nil
+}
+
+// ================================ Map device Memory ===============================
+
+// buffer service is embedded uion m
+// in v4l2_buffer C type.
+type BufferService struct {
+	Offset  uint32
+	UserPtr uintptr
+	Planes  uintptr
+	FD      int32
+}
+
+// mamapBuffer first queries the status of the device buffer at idx
+// by retrieving BufferInfo which returns the length of the buffer and
+// the current offset of the allocated buffers.  That information is
+// used to map the device's buffer unto the application's address space.
+func mmapBuffer(fd uintptr, idx uint32) ([]byte, error) {
+	var v4l2Buf C.struct_v4l2_buffer
+	v4l2Buf._type = C.uint(BufTypeVideoCapture)
+	v4l2Buf.memory = C.uint(StreamMemoryTypeMMAP)
+	v4l2Buf.index = C.uint(idx)
+
+	// send ioctl command
+	if err := ioctl(fd, C.VIDIOC_QUERYBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
+		return nil, err
+	}
+
+	// grab m union and place it in type BufferService
+	bufSvc := *(*BufferService)(unsafe.Pointer(&v4l2Buf.m[0]))
+
+	// map the memory and get []byte to access it
+	mbuf, err := sys.Mmap(int(fd), int64(bufSvc.Offset), int(v4l2Buf.length), sys.PROT_READ|sys.PROT_WRITE, sys.MAP_SHARED)
+	if err != nil {
+		return nil, err
+	}
+
+	return mbuf, nil
+}
+
+// =========================== Start device streaming =========================
+
+// startStreaming requests the device to start the capture process and start
+// filling device buffers.
+func startStreaming(fd uintptr) error {
+	bufType := C.uint(BufTypeVideoCapture)
+	if err := ioctl(fd, C.VIDIOC_STREAMON, uintptr(unsafe.Pointer(&bufType))); err != nil {
+		return err
+	}
+	return nil
+}
+
+// ======================== Queue/Dequeue device buffer =======================
+
+// queueBuffer requests that an emptty buffer is enqueued into the device's
+// incoming queue at the specified index (so that it can be filled later).
+func queueBuffer(fd uintptr, idx uint32) error {
+	var v4l2Buf C.struct_v4l2_buffer
+	v4l2Buf._type = C.uint(BufTypeVideoCapture)
+	v4l2Buf.memory = C.uint(StreamMemoryTypeMMAP)
+	v4l2Buf.index = C.uint(idx)
+
+	if err := ioctl(fd, C.VIDIOC_QBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
+		return err
+	}
+	return nil
+}
+
+// dequeueBuffer is called to dequeue a filled buffer from the devices buffer queue.
+// Once a device buffer is dequeued, it is mapped and is ready to be read by the application.
+func dequeueBuffer(fd uintptr) (uint32, error) {
+	var v4l2Buf C.struct_v4l2_buffer
+	v4l2Buf._type = C.uint(BufTypeVideoCapture)
+	v4l2Buf.memory = C.uint(StreamMemoryTypeMMAP)
+
+	if err := ioctl(fd, C.VIDIOC_DQBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
+		return 0, err
+	}
+	return uint32(v4l2Buf.bytesused), nil
+}
+
+// =========================== Start device streaming =========================
+
+// stopStreaming requests the device to stop the streaming process and release
+// buffer resources.
+func stopStreaming(fd uintptr) error {
+	bufType := C.uint(BufTypeVideoCapture)
+
+	if err := ioctl(fd, C.VIDIOC_STREAMOFF, uintptr(unsafe.Pointer(&bufType))); err != nil {
+		return err
+	}
+	return nil
+}
+
+// use sys.Select to wait for the device to become read-ready.
+func waitForDeviceReady(fd uintptr) error {
+	timeval := sys.NsecToTimeval((2 * time.Second).Nanoseconds())
+	var fdsRead sys.FdSet
+	fdsRead.Set(int(fd))
+	for {
+		n, err := sys.Select(int(fd+1), &fdsRead, nil, nil, &timeval)
+		switch n {
+		case -1:
+			if err == sys.EINTR {
+				continue
+			}
+			return err
+		case 0:
+			return fmt.Errorf("wait for device ready: timeout")
+		default:
+			return nil
+		}
+	}
+}
+
+func main() {
+	var devName string
+	flag.StringVar(&devName, "d", "/dev/video0", "device name (path)")
+	flag.Parse()
+
+	// open device
+	devFile, err := os.OpenFile(devName, sys.O_RDWR|sys.O_NONBLOCK, 0)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer devFile.Close()
+	fd := devFile.Fd()
+
+	// Set the format
+	if err := setFormat(fd, PixFormat{
+		Width:       640,
+		Height:      480,
+		PixelFormat: PixelFmtMJPEG,
+		Field:       FieldNone,
+	}); err != nil {
+		log.Fatal(err)
+	}
+
+	pixFmt, err := getFormat(fd)
+	if err != nil {
+		log.Fatal(err)
+	}
+	log.Printf("Format set: %dx%d pixel format %d [FmtMJPG]", pixFmt.Width, pixFmt.Height, pixFmt.PixelFormat)
+
+	// request device to setup 3 buffers
+	if err := reqBuffers(fd, 3); err != nil {
+		log.Fatal(err)
+	}
+
+	// map a device buffer to a local byte slice
+	// here we use the latest buffer
+	data, err := mmapBuffer(fd, 2)
+	if err != nil {
+		log.Fatalf("unable to map device buffer: %s", err)
+	}
+
+	// now, queue an initial device buffer at the selected index
+	//  to be filled with data prior to starting the device stream
+	if err := queueBuffer(fd, 2); err != nil {
+		log.Fatalf("failed to queue initial buffer: %s", err)
+	}
+
+	// now, ask the device to start the stream
+	if err := startStreaming(fd); err != nil {
+		log.Fatalf("failed to start streaming: %s", err)
+	}
+
+	// now wait for the device to be ready for read operation,
+	// this means the mapped buffer is ready to be consumed
+	if err := waitForDeviceReady(fd); err != nil {
+		log.Fatalf("failed during device read-wait: %s", err)
+	}
+
+	// deqeue the device buffer so that the local mapped byte slice
+	// is filled.
+	bufSize, err := dequeueBuffer(fd)
+	if err != nil {
+		log.Fatalf("failed during device read-wait: %s", err)
+	}
+
+	// save mapped buffer bytes to file
+	jpgFile, err := os.Create("capture.jpg")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer jpgFile.Close()
+	if _, err := jpgFile.Write(data[:bufSize]); err != nil {
+		log.Fatalf("failed to save file: %s", err)
+	}
+
+	// release streaming resources
+	if err := stopStreaming(fd); err != nil {
+		log.Fatalf("failed to stop stream: %s", err)
+	}
+}

+ 81 - 10
examples/webcam/webcam.go

@@ -8,14 +8,17 @@ import (
 	"io"
 	"log"
 	"net/http"
+	"strings"
 	"time"
 
+	"github.com/vladimirvivien/go4vl/imgsupport"
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 
 var (
 	frames <-chan []byte
 	fps    uint32 = 30
+	pixfmt v4l2.FourCCEncoding
 )
 
 // servePage reads templated HTML
@@ -56,9 +59,22 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 		io.WriteString(w, fmt.Sprintf("Content-Length: %d\n\n", len(frame)))
 
 		// write frame
-		if _, err := w.Write(frame); err != nil {
-			log.Printf("failed to write image: %s", err)
-			return
+		switch pixfmt {
+		case v4l2.PixelFmtMJPEG:
+			if _, err := w.Write(frame); err != nil {
+				log.Printf("failed to write image: %s", err)
+				return
+			}
+		case v4l2.PixelFmtYUYV:
+			data, err := imgsupport.Yuyv2Jpeg(640, 480, frame)
+			if err != nil {
+				log.Printf("failed to convert yuyv to jpeg: %s", err)
+				continue
+			}
+			if _, err := w.Write(data); err != nil {
+				log.Printf("failed to write image: %s", err)
+				return
+			}
 		}
 		// close boundary
 		if _, err := io.WriteString(w, "\n"); err != nil {
@@ -71,10 +87,43 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 func main() {
 	port := ":9090"
 	devName := "/dev/video0"
+	defaultDev, err := v4l2.Open(devName)
+	skipDefault := false
+	if err != nil {
+		skipDefault = true
+	}
+
+	width := 640
+	height := 480
+	format := "yuyv"
+	if !skipDefault {
+		pix, err := defaultDev.GetPixFormat()
+		if err == nil {
+			width = int(pix.Width)
+			height = int(pix.Height)
+			switch pix.PixelFormat {
+			case v4l2.PixelFmtMJPEG:
+				format = "mjpeg"
+			case v4l2.PixelFmtH264:
+				format = "h264"
+			default:
+				format = "yuyv"
+			}
+		}
+	}
+
 	flag.StringVar(&devName, "d", devName, "device name (path)")
+	flag.IntVar(&width, "w", width, "capture width")
+	flag.IntVar(&height, "h", height, "capture height")
+	flag.StringVar(&format, "f", format, "pixel format")
 	flag.StringVar(&port, "p", port, "webcam service port")
 	flag.Parse()
 
+	// close device used for default info
+	if err := defaultDev.Close(); err != nil {
+		// default device failed to close
+	}
+
 	// open device and setup device
 	device, err := v4l2.Open(devName)
 	if err != nil {
@@ -89,17 +138,23 @@ func main() {
 	log.Printf("device info: %s", caps.String())
 
 	// set device format
-	if err := device.SetPixFormat(v4l2.PixFormat{
-		Width:       640,
-		Height:      480,
-		PixelFormat: v4l2.PixelFmtMJPEG,
-		Field:       v4l2.FieldNone,
-	}); err != nil {
+	currFmt, err := device.GetPixFormat()
+	if err != nil {
+		log.Fatalf("unable to get format: %s", err)
+	}
+	log.Printf("Current format: %s", currFmt)
+	if err := device.SetPixFormat(updateFormat(currFmt, format, width, height)); err != nil {
 		log.Fatalf("failed to set format: %s", err)
 	}
+	currFmt, err = device.GetPixFormat()
+	if err != nil {
+		log.Fatalf("unable to get format: %s", err)
+	}
+	pixfmt = currFmt.PixelFormat
+	log.Printf("Updated format: %s", currFmt)
 
 	// Setup and start stream capture
-	if err := device.StartStream(15); err != nil {
+	if err := device.StartStream(2); err != nil {
 		log.Fatalf("unable to start stream: %s", err)
 	}
 
@@ -127,3 +182,19 @@ func main() {
 		log.Fatal(err)
 	}
 }
+
+func updateFormat(pix v4l2.PixFormat, fmtStr string, w, h int) v4l2.PixFormat {
+	pix.Width = uint32(w)
+	pix.Height = uint32(h)
+
+	switch strings.ToLower(fmtStr) {
+	case "mjpeg", "jpeg":
+		pix.PixelFormat = v4l2.PixelFmtMJPEG
+	case "h264", "h.264":
+		pix.PixelFormat = v4l2.PixelFmtH264
+	case "yuyv":
+		pix.PixelFormat = v4l2.PixelFmtYUYV
+	}
+
+	return pix
+}

+ 17 - 7
imgsupport/converters.go

@@ -6,18 +6,28 @@ import (
 	"image/jpeg"
 )
 
+// Yuyv2Jpeg attempts to convert the YUYV image using Go's built-in
+// YCbCr encoder
 func Yuyv2Jpeg(width, height int, frame []byte) ([]byte, error) {
-	size := len(frame)
+	//size := len(frame)
 	ycbr := image.NewYCbCr(image.Rect(0, 0, width, height), image.YCbCrSubsampleRatio422)
 
-	for i := 0; i < size; i += 4 {
-		y1, u, y2, v := frame[i], frame[i+1], frame[i+2], frame[i+3]
-		ycbr.Y[i]   = y1
-		ycbr.Y[i+1] = y2
-		ycbr.Cb[i]  = u
-		ycbr.Cr[i]  = v
+	for i := range ycbr.Cb {
+		ii := i * 4
+		ycbr.Y[i*2] = frame[ii]
+		ycbr.Y[i*2+1] = frame[ii+2]
+		ycbr.Cb[i] = frame[ii+1]
+		ycbr.Cr[i] = frame[ii+3]
 	}
 
+	//for i := 0; i < size; i += 4 {
+	//	y1, u, y2, v := frame[i], frame[i+1], frame[i+2], frame[i+3]
+	//	ycbr.Y[i]   = y1
+	//	ycbr.Y[i+1] = y2
+	//	ycbr.Cb[i]  = u
+	//	ycbr.Cr[i]  = v
+	//}
+
 	var jpgBuf bytes.Buffer
 	if err := jpeg.Encode(&jpgBuf, ycbr, nil); err != nil {
 		return nil, err

+ 23 - 9
v4l2/format.go

@@ -267,9 +267,24 @@ type PixFormat struct {
 	XferFunc     XferFunctionType
 }
 
+func (f PixFormat) String() string {
+	return fmt.Sprintf(
+		"%s [%dx%d]; field=%s; bytes per line=%d; size image=%d; colorspace=%s; YCbCr=%s; Quant=%s; XferFunc=%s",
+		PixelFormats[f.PixelFormat],
+		f.Width, f.Height,
+		Fields[f.Field],
+		f.BytesPerLine,
+		f.SizeImage,
+		Colorspaces[f.Colorspace],
+		YCbCrEncodings[f.YcbcrEnc],
+		Quantizations[f.Quantization],
+		XferFunctions[f.XferFunc],
+	)
+}
+
 // v4l2Format (v4l2_format)
 // https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-g-fmt.html?highlight=v4l2_format
-// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2303
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2331
 //
 // field fmt is a union, thus it's constructed as an appropriately sized array:
 //
@@ -292,21 +307,21 @@ type v4l2Format struct {
 }
 
 // getPixFormat returns the PixFormat by casting the pointer to the union type
-func (f v4l2Format) getPixFormat() PixFormat {
+func (f *v4l2Format) getPixFormat() PixFormat {
 	pixfmt := (*PixFormat)(unsafe.Pointer(&f.fmt[0]))
 	return *pixfmt
 }
 
 // setPixFormat sets the PixFormat by casting the pointer to the fmt union and set its value
-func (f v4l2Format) setPixFormat(newPix PixFormat) {
-	*(*PixFormat)(unsafe.Pointer(&f.fmt[0])) = newPix
+func (f *v4l2Format) setPixFormat(newPix PixFormat) {
+	f.fmt = *(*[200]byte)(unsafe.Pointer(&newPix))
 }
 
 // GetPixFormat retrieves pixel information for the specified driver
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-fmt.html#ioctl-vidioc-g-fmt-vidioc-s-fmt-vidioc-try-fmt
 func GetPixFormat(fd uintptr) (PixFormat, error) {
-	format := v4l2Format{StreamType: BufTypeVideoCapture}
-	if err := Send(fd, VidiocGetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
+	format := &v4l2Format{StreamType: BufTypeVideoCapture}
+	if err := Send(fd, VidiocGetFormat, uintptr(unsafe.Pointer(format))); err != nil {
 		return PixFormat{}, fmt.Errorf("pix format failed: %w", err)
 	}
 
@@ -316,10 +331,9 @@ func GetPixFormat(fd uintptr) (PixFormat, error) {
 // SetPixFormat sets the pixel format information for the specified driver
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-fmt.html#ioctl-vidioc-g-fmt-vidioc-s-fmt-vidioc-try-fmt
 func SetPixFormat(fd uintptr, pixFmt PixFormat) error {
-	format := v4l2Format{StreamType: BufTypeVideoCapture}
+	format := &v4l2Format{StreamType: BufTypeVideoCapture}
 	format.setPixFormat(pixFmt)
-
-	if err := Send(fd, VidiocSetFormat, uintptr(unsafe.Pointer(&format))); err != nil {
+	if err := Send(fd, VidiocSetFormat, uintptr(unsafe.Pointer(format))); err != nil {
 		return fmt.Errorf("pix format failed: %w", err)
 	}
 	return nil