diff --git a/README.md b/README.md index 6eca78a..c1dfe7d 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,133 @@ # go4vl -A Go library for the Video for Linux user API (V4L2). +A Go library for the `Video for Linux 2` (v4l2) user API. ---- -`go4vl` hides all the complexities of working with V4L2 and -provides idiomatic Go types, like channels, to consume and process captured video frames. +The `go4vl` project is for working with the Video for Linux 2 API for real-time video. +It hides all the complexities of working with V4L2 and provides idiomatic Go types, like channels, to consume and process captured video frames. + +> This project is designed to work with Linux and the Linux Video API. +> It is *NOT* meant to be a portable/cross-platform capable package for real-time video processing. ## Features * Capture and control video data from your Go programs * Idiomatic Go API for device access and video capture +* Use cgo-generated types for correct data representation in Go * Use familiar types such as channels to stream video data * Exposes device enumeration and information * Provides device capture control * Access to video format information -* Streaming support using memory map (other methods coming soon) +* Streaming support using memory map (other methods coming later) + +### Not working/supported yet +* Inherent support for video output +* Only support MMap memory stream (user pointers, DMA not working) +* Device control not implemented yet + +## Prerequisites +* Go compiler/tools +* Linux OS (32- or 64-bit) +* Kernel minimum v5.10.x +* A locally configured C compiler (i.e. gcc) +* Header files for V4L2 (i.e. /usr/include/linux/videodev2.h) + +All examples have been tested using a Rasperry PI 3, running 32-bit Raspberry PI OS. +The package should work with no problem on your 64-bit Linux OS. ## Getting started +To avoid issues with old header files on your machine, upgrade your system to pull down the latest OS packages +with something similar to the following (follow directions for your system to properly upgrade): + +```shell +sudo apt update +sudo apt full-upgrade +``` + To include `go4vl` in your own code, pull the package ```bash go get github.com/vladimirvivien/go4vl/v4l2 ``` -## Example +## Examples +The following is a simple example that captures video data from an attached camera device to +and saves them as JPEG files. The example assumes the attached device supports JPEG (MJPEG) output format inherently. + +```go +package main + +import ( + ... + "github.com/vladimirvivien/go4vl/v4l2" +) + +func main() { + // open device + device, err := v4l2.Open("/dev/video0") + if err != nil { + log.Fatalf("failed to open device: %s", err) + } + defer device.Close() + + // configure device with preferred fmt + if err := device.SetPixFormat(v4l2.PixFormat{ + Width: 640, + Height: 480, + PixelFormat: v4l2.PixelFmtMJPEG, + Field: v4l2.FieldNone, + }); err != nil { + log.Fatalf("failed to set format: %s", err) + } + + // start a device stream with 3 video buffers + if err := device.StartStream(3); err != nil { + log.Fatalf("failed to start stream: %s", err) + } + + ctx, cancel := context.WithCancel(context.TODO()) + // capture video data at 15 fps + frameChan, err := device.Capture(ctx, 15) + if err != nil { + log.Fatal(err) + } + + // grab 10 frames from frame channel and save them as files + totalFrames := 10 + count := 0 + for frame := range frameChan { + fileName := fmt.Sprintf("capture_%d.jpg", count) + file, err := os.Create(fileName) + if err != nil { + log.Printf("failed to create file %s: %s", fileName, err) + continue + } + if _, err := file.Write(frame); err != nil { + log.Printf("failed to write file %s: %s", fileName, err) + continue + } + if err := file.Close(); err != nil { + log.Printf("failed to close file %s: %s", fileName, err) + } + count++ + if count >= totalFrames { + break + } + } + + cancel() // stop capture + if err := device.StopStream(); err != nil { + log.Fatal(err) + } + fmt.Println("Done.") +} +``` + +### Other examples +The [./examples](./examples) directory contains additional examples including: + +* [device_info](./examples/device_info) - queries and prints devince information +* [webcam](./examples/webcam) - uses the v4l2 package to create a simple webcam that streams images from an attached camera accessible via a web page. + +## Roadmap +There is no defined roadmap. The main goal is to port as much functionlities as possible so that +adopters can use Go to create cool video-based tools on platforms such as the Raspberry Pi. \ No newline at end of file diff --git a/TODO.md b/TODO.md index 25c743e..93613bd 100644 --- a/TODO.md +++ b/TODO.md @@ -7,3 +7,4 @@ A general list (of no implied order) of high level tasks for the project. * [ ] Support for YUYV conversion * [ ] Set and query device controls * [ ] Support for User pointer and other stream mode +* [x] Use cgo-generated Go types to avoid alignment bugs \ No newline at end of file diff --git a/examples/capture/capture.go b/examples/capture/capture.go index 949b6ff..4d60515 100644 --- a/examples/capture/capture.go +++ b/examples/capture/capture.go @@ -23,9 +23,9 @@ func main() { defer device.Close() // helper function to search for format descriptions - findPreferredFmt := func(fmts []v4l2.FormatDescription, pixEncoding v4l2.FourCCEncoding) *v4l2.FormatDescription { + findPreferredFmt := func(fmts []v4l2.FormatDescription, pixEncoding v4l2.FourCCType) *v4l2.FormatDescription { for _, desc := range fmts { - if desc.GetPixelFormat() == pixEncoding{ + if desc.PixelFormat == pixEncoding{ return &desc } } @@ -39,7 +39,7 @@ func main() { } // search for preferred formats - preferredFmts := []v4l2.FourCCEncoding{v4l2.PixelFmtMPEG, v4l2.PixelFmtMJPEG, v4l2.PixelFmtJPEG, v4l2.PixelFmtYUYV} + preferredFmts := []v4l2.FourCCType{v4l2.PixelFmtMPEG, v4l2.PixelFmtMJPEG, v4l2.PixelFmtJPEG, v4l2.PixelFmtYUYV} var fmtDesc *v4l2.FormatDescription for _, preferredFmt := range preferredFmts{ fmtDesc = findPreferredFmt(fmtDescs, preferredFmt) @@ -52,24 +52,45 @@ func main() { if fmtDesc == nil { log.Fatalf("device does not support any of %#v", preferredFmts) } - - frameSize, err := fmtDesc.GetFrameSize() + log.Printf("Found preferred fmt: %s", fmtDesc) + frameSizes, err := v4l2.GetFormatFrameSizes(device.GetFileDescriptor(), fmtDesc.PixelFormat) if err!=nil{ log.Fatalf("failed to get framesize info: %s", err) } + // select size 640x480 for format + var frmSize v4l2.FrameSize + for _, size := range frameSizes { + if size.Width == 640 && size.Height == 480 { + frmSize = size + break + } + } + + if frmSize.Width == 0 { + log.Fatalf("Size 640x480 not supported for fmt: %s", fmtDesc) + } + // configure device with preferred fmt + if err := device.SetPixFormat(v4l2.PixFormat{ - Width: frameSize.Width, - Height: frameSize.Height, - PixelFormat: fmtDesc.GetPixelFormat(), + Width: frmSize.Width, + Height: frmSize.Height, + PixelFormat: fmtDesc.PixelFormat, Field: v4l2.FieldNone, }); err != nil { log.Fatalf("failed to set format: %s", err) } + pixFmt, err := device.GetPixFormat() + if err != nil { + log.Fatalf("failed to get format: %s", err) + } + log.Printf("Pixel format set to [%s]", pixFmt) + // start stream - if err := device.StartStream(10); err != nil { + log.Println("Start capturing...") + if err := device.StartStream(3); err != nil { log.Fatalf("failed to start stream: %s", err) } @@ -82,6 +103,7 @@ func main() { // process frames from capture channel totalFrames := 10 count := 0 + log.Println("Streaming frames from device...") for frame := range frameChan { fileName := fmt.Sprintf("capture_%d.jpg", count) file, err := os.Create(fileName) @@ -93,6 +115,7 @@ func main() { log.Printf("failed to write file %s: %s", fileName, err) continue } + log.Printf("Saved file: %s", fileName) if err := file.Close(); err != nil { log.Printf("failed to close file %s: %s", fileName, err) } diff --git a/examples/cgo_types/README.md b/examples/cgo_types/README.md new file mode 100644 index 0000000..bc5f416 --- /dev/null +++ b/examples/cgo_types/README.md @@ -0,0 +1,17 @@ +# Example: capture with cgo-generated types +:warning: + +This example illustrates how to use ioctl directly +to communicate to device using cgo-generated types. + +## Do not use it ## + +Use package `v4l2` to do realtime image capture, as shown in example +[examples/capture](../capture). + +:warning: + +The example in this directory shows most of the moving pieces that make +the V4L2 API works using Go. It illustrates the steps, in detail, that +are required to communicate with a device driver to configure, initiate, +and capture images without using the Go v4l2 device type provided. \ No newline at end of file diff --git a/examples/native_c_types/capture.go b/examples/cgo_types/cgo_capture.go similarity index 87% rename from examples/native_c_types/capture.go rename to examples/cgo_types/cgo_capture.go index c4df9f6..63be537 100644 --- a/examples/native_c_types/capture.go +++ b/examples/cgo_types/cgo_capture.go @@ -16,51 +16,6 @@ import ( 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 { @@ -115,9 +70,6 @@ func setFormat(fd uintptr, pixFmt PixFormat) error { 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 diff --git a/examples/device_info/devinfo.go b/examples/device_info/devinfo.go index f46f65b..1a9e3fe 100644 --- a/examples/device_info/devinfo.go +++ b/examples/device_info/devinfo.go @@ -50,20 +50,18 @@ func printDeviceDriverInfo(dev *v4l2.Device) error { // print driver info fmt.Println("Device Info:") - fmt.Printf(template, "Driver name", caps.DriverName()) - fmt.Printf(template, "Card name", caps.CardName()) - fmt.Printf(template, "Bus info", caps.BusInfo()) + fmt.Printf(template, "Driver name", caps.Driver) + fmt.Printf(template, "Card name", caps.Card) + fmt.Printf(template, "Bus info", caps.BusInfo) - verVal := caps.GetVersion() - version := fmt.Sprintf("%d.%d.%d", verVal>>16, (verVal>>8)&0xff, verVal&0xff) - fmt.Printf(template, "Driver version", version) + fmt.Printf(template, "Driver version", caps.GetVersionInfo()) - fmt.Printf("\t%-16s : %0x\n", "Driver capabilities", caps.GetCapabilities()) + fmt.Printf("\t%-16s : %0x\n", "Driver capabilities", caps.Capabilities) for _, desc := range caps.GetDriverCapDescriptions() { fmt.Printf("\t\t%s\n", desc.Desc) } - fmt.Printf("\t%-16s : %0x\n", "Device capabilities", caps.GetCapabilities()) + fmt.Printf("\t%-16s : %0x\n", "Device capabilities", caps.Capabilities) for _, desc := range caps.GetDeviceCapDescriptions() { fmt.Printf("\t\t%s\n", desc.Desc) } @@ -141,7 +139,7 @@ func printFormatDesc(dev *v4l2.Device) error { } fmt.Println("Supported formats:") for i, desc := range descs{ - frmSizes, err := desc.GetFrameSizes() + frmSizes, err := v4l2.GetFormatFrameSizes(dev.GetFileDescriptor(), desc.PixelFormat) if err != nil { return fmt.Errorf("format desc: %w", err) } @@ -150,8 +148,7 @@ func printFormatDesc(dev *v4l2.Device) error { for _, size := range frmSizes{ sizeStr.WriteString(fmt.Sprintf("[%dx%d] ", size.Width, size.Height)) } - fmt.Printf(template, fmt.Sprintf("[%0d] %s", i, desc.GetDescription()), sizeStr.String()) - + fmt.Printf(template, fmt.Sprintf("[%0d] %s", i, desc.Description), sizeStr.String()) } return nil } diff --git a/examples/v4l2_direct/README.md b/examples/manual_types/README.md similarity index 60% rename from examples/v4l2_direct/README.md rename to examples/manual_types/README.md index 00cf018..2ed22d8 100644 --- a/examples/v4l2_direct/README.md +++ b/examples/manual_types/README.md @@ -1,7 +1,10 @@ -# Example: capture with V4L2 directly +# Example: using handcrafted types (Deprecated) :warning: -This example is here to illustrate the complexity of v4l2. +This example is here to illustrate the complexity of v4l2 when using hand-crafted +Go types to communicate with the driver. This approach was abandoned in favor of +cgo-generated types (see v4l2 package) for stability. + Do not use it. If you want to play around with image capture, use the diff --git a/examples/v4l2_direct/v4l2_capture.go b/examples/manual_types/v4l2_capture.go similarity index 100% rename from examples/v4l2_direct/v4l2_capture.go rename to examples/manual_types/v4l2_capture.go diff --git a/examples/webcam/webcam.go b/examples/webcam/webcam.go index 2c86636..1534b41 100644 --- a/examples/webcam/webcam.go +++ b/examples/webcam/webcam.go @@ -18,7 +18,7 @@ import ( var ( frames <-chan []byte fps uint32 = 30 - pixfmt v4l2.FourCCEncoding + pixfmt v4l2.FourCCType ) // servePage reads templated HTML diff --git a/v4l2/capability.go b/v4l2/capability.go index 1af56ce..f246d86 100644 --- a/v4l2/capability.go +++ b/v4l2/capability.go @@ -1,5 +1,8 @@ package v4l2 +// #include +import "C" + import ( "fmt" "unsafe" @@ -9,42 +12,41 @@ import ( // see https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L451 const ( - CapVideoCapture = 0x00000001 // V4L2_CAP_VIDEO_CAPTURE - CapVideoOutput = 0x00000002 // V4L2_CAP_VIDEO_OUTPUT - CapVideoOverlay = 0x00000004 // V4L2_CAP_VIDEO_OVERLAY - CapVBICapture = 0x00000010 // V4L2_CAP_VBI_CAPTURE - CapVBIOutput = 0x00000020 // V4L2_CAP_VBI_OUTPUT - CapSlicedVBICapture = 0x00000040 // V4L2_CAP_SLICED_VBI_CAPTURE - CapSlicedVBIOutput = 0x00000080 // V4L2_CAP_SLICED_VBI_OUTPUT - CapRDSCapture = 0x00000100 // V4L2_CAP_RDS_CAPTURE - CapVideoOutputOverlay = 0x00000200 // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - CapHWFrequencySeek = 0x00000400 // V4L2_CAP_HW_FREQ_SEEK - CapRDSOutput = 0x00000800 // V4L2_CAP_RDS_OUTPUT - - CapVideoCaptureMPlane = 0x00001000 // V4L2_CAP_VIDEO_CAPTURE_MPLANE - CapVideoOutputMPlane = 0x00002000 // V4L2_CAP_VIDEO_OUTPUT_MPLANE - CapVideoMem2MemMPlane = 0x00004000 // V4L2_CAP_VIDEO_M2M_MPLANE - CapVideoMem2Mem = 0x00008000 // V4L2_CAP_VIDEO_M2M - - CapTuner = 0x00010000 // V4L2_CAP_TUNER - CapAudio = 0x00020000 // V4L2_CAP_AUDIO - CapRadio = 0x00040000 // V4L2_CAP_RADIO - CapModulator = 0x00080000 // V4L2_CAP_MODULATOR - - CapSDRCapture = 0x00100000 // V4L2_CAP_SDR_CAPTURE - CapExtendedPixFormat = 0x00200000 // V4L2_CAP_EXT_PIX_FORMAT - CapSDROutput = 0x00400000 // V4L2_CAP_SDR_OUTPUT - CapMetadataCapture = 0x00800000 // V4L2_CAP_META_CAPTURE - - CapReadWrite = 0x01000000 // V4L2_CAP_READWRITE - CapAsyncIO = 0x02000000 // V4L2_CAP_ASYNCIO - CapStreaming = 0x04000000 // V4L2_CAP_STREAMING - CapMetadataOutput = 0x08000000 // V4L2_CAP_META_OUTPUT - - CapTouch = 0x10000000 // V4L2_CAP_TOUCH - CapIOMediaController = 0x20000000 // V4L2_CAP_IO_MC - - CapDeviceCapabilities = 0x80000000 // V4L2_CAP_DEVICE_CAPS + CapVideoCapture uint32 = C.V4L2_CAP_VIDEO_CAPTURE + CapVideoOutput uint32 = C.V4L2_CAP_VIDEO_OUTPUT + CapVideoOverlay uint32 = C.V4L2_CAP_VIDEO_OVERLAY + CapVBICapture uint32 = C.V4L2_CAP_VBI_CAPTURE + CapVBIOutput uint32 = C.V4L2_CAP_VBI_OUTPUT + CapSlicedVBICapture uint32 = C.V4L2_CAP_SLICED_VBI_CAPTURE + CapSlicedVBIOutput uint32 = C.V4L2_CAP_SLICED_VBI_OUTPUT + CapRDSCapture uint32 = C.V4L2_CAP_RDS_CAPTURE + CapVideoOutputOverlay uint32 = C.V4L2_CAP_VIDEO_OUTPUT_OVERLAY + CapHWFrequencySeek uint32 = C.V4L2_CAP_HW_FREQ_SEEK + CapRDSOutput uint32 = C.V4L2_CAP_RDS_OUTPUT + + CapVideoCaptureMPlane uint32 = C.V4L2_CAP_VIDEO_CAPTURE_MPLANE + CapVideoOutputMPlane uint32 = C.V4L2_CAP_VIDEO_OUTPUT_MPLANE + CapVideoMem2MemMPlane uint32 = C.V4L2_CAP_VIDEO_M2M_MPLANE + CapVideoMem2Mem uint32 = C.V4L2_CAP_VIDEO_M2M + + CapTuner uint32 = C.V4L2_CAP_TUNER + CapAudio uint32 = C.V4L2_CAP_AUDIO + CapRadio uint32 = C.V4L2_CAP_RADIO + CapModulator uint32 = C.V4L2_CAP_MODULATOR + + CapSDRCapture uint32 = C.V4L2_CAP_SDR_CAPTURE + CapExtendedPixFormat uint32 = C.V4L2_CAP_EXT_PIX_FORMAT + CapSDROutput uint32 = C.V4L2_CAP_SDR_OUTPUT + CapMetadataCapture uint32 = C.V4L2_CAP_META_CAPTURE + + CapReadWrite uint32 = C.V4L2_CAP_READWRITE + CapAsyncIO uint32 = C.V4L2_CAP_ASYNCIO + CapStreaming uint32 = C.V4L2_CAP_STREAMING + + CapMetadataOutput uint32 = C.V4L2_CAP_META_OUTPUT + CapTouch uint32 = C.V4L2_CAP_TOUCH + CapIOMediaController uint32 = C.V4L2_CAP_IO_MC + CapDeviceCapabilities uint32 = C.V4L2_CAP_DEVICE_CAPS ) type CapabilityDesc struct { @@ -93,87 +95,84 @@ var ( } ) -// v4l2Capability type for device (see v4l2_capability) -// This type stores the capability information returned by the device. -// https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.v4l2_capability -// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L440 -type v4l2Capability struct { - driver [16]uint8 - card [32]uint8 - busInfo [32]uint8 - version uint32 - capabilities uint32 - deviceCaps uint32 - reserved [3]uint32 -} - -// Capability represents capabilities retrieved for the device. +// Capability represents capabilities retrieved for the device (see v4l2_capability). // Use attached methods on this type to access capabilities. -// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.v4l2_capability +// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L440 +// https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-querycap.html#c.V4L.v4l2_capability type Capability struct { - v4l2Cap v4l2Capability + // Driver name of the driver module + Driver string + + // Card name of the device card + Card string + + // BusInfo is the name of the device bus + BusInfo string + + // Version is the kernel version + Version uint32 + + // Capabilities returns all exported capabilities for the physical device (opened or not) + Capabilities uint32 + + // DeviceCapabilities is the capability for this particular (opened) device or node + DeviceCapabilities uint32 } // GetCapability retrieves capability info for device func GetCapability(fd uintptr) (Capability, error) { - v4l2Cap := v4l2Capability{} - if err := Send(fd, VidiocQueryCap, uintptr(unsafe.Pointer(&v4l2Cap))); err != nil { + var v4l2Cap C.struct_v4l2_capability + if err := send(fd, C.VIDIOC_QUERYCAP, uintptr(unsafe.Pointer(&v4l2Cap))); err != nil { return Capability{}, fmt.Errorf("capability: %w", err) } - return Capability{v4l2Cap: v4l2Cap}, nil -} - -// GetCapabilities returns the capability mask as a union of -// all exported capabilities for the physical device (opened or not). -// Use this method to access capabilities. -func (c Capability) GetCapabilities() uint32 { - return c.v4l2Cap.capabilities -} - -// GetDeviceCaps returns the capability mask for the open device. -// This is a subset of capabilities returned by GetCapabilities. -func (c Capability) GetDeviceCaps() uint32 { - return c.v4l2Cap.deviceCaps + return Capability{ + Driver: C.GoString((*C.char)(&v4l2Cap.driver[0])), + Card: C.GoString((*C.char)(&v4l2Cap.card[0])), + BusInfo: C.GoString((*C.char)(&v4l2Cap.bus_info[0])), + Version: uint32(v4l2Cap.version), + Capabilities: uint32(v4l2Cap.capabilities), + DeviceCapabilities: uint32(v4l2Cap.device_caps), + }, nil } // IsVideoCaptureSupported returns caps & CapVideoCapture func (c Capability) IsVideoCaptureSupported() bool { - return (c.v4l2Cap.capabilities & CapVideoCapture) != 0 + return (uint32(c.Capabilities) & CapVideoCapture) != 0 } // IsVideoOutputSupported returns caps & CapVideoOutput func (c Capability) IsVideoOutputSupported() bool { - return (c.v4l2Cap.capabilities & CapVideoOutput) != 0 + return (uint32(c.Capabilities) & CapVideoOutput) != 0 } // IsVideoOverlaySupported returns caps & CapVideoOverlay func (c Capability) IsVideoOverlaySupported() bool { - return (c.v4l2Cap.capabilities & CapVideoOverlay) != 0 + return (uint32(c.Capabilities) & CapVideoOverlay) != 0 } // IsVideoOutputOverlaySupported returns caps & CapVideoOutputOverlay func (c Capability) IsVideoOutputOverlaySupported() bool { - return (c.v4l2Cap.capabilities & CapVideoOutputOverlay) != 0 + return (uint32(c.Capabilities) & CapVideoOutputOverlay) != 0 } // IsVideoCaptureMultiplanarSupported returns caps & CapVideoCaptureMPlane func (c Capability) IsVideoCaptureMultiplanarSupported() bool { - return (c.v4l2Cap.capabilities & CapVideoCaptureMPlane) != 0 + return (uint32(c.Capabilities) & CapVideoCaptureMPlane) != 0 } // IsVideoOutputMultiplanerSupported returns caps & CapVideoOutputMPlane func (c Capability) IsVideoOutputMultiplanerSupported() bool { - return (c.v4l2Cap.capabilities & CapVideoOutputMPlane) != 0 + return (uint32(c.Capabilities) & CapVideoOutputMPlane) != 0 } // IsReadWriteSupported returns caps & CapReadWrite func (c Capability) IsReadWriteSupported() bool { - return (c.v4l2Cap.capabilities & CapReadWrite) != 0 + return (uint32(c.Capabilities) & CapReadWrite) != 0 } // IsStreamingSupported returns caps & CapStreaming func (c Capability) IsStreamingSupported() bool { - return (c.v4l2Cap.capabilities & CapStreaming) != 0 + return (uint32(c.Capabilities) & CapStreaming) != 0 } // IsDeviceCapabilitiesProvided returns true if the device returns @@ -181,14 +180,14 @@ func (c Capability) IsStreamingSupported() bool { // See notes on VL42_CAP_DEVICE_CAPS: // https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-querycap.html?highlight=v4l2_cap_device_caps func (c Capability) IsDeviceCapabilitiesProvided() bool { - return (c.v4l2Cap.capabilities & CapDeviceCapabilities) != 0 + return (uint32(c.Capabilities) & CapDeviceCapabilities) != 0 } // GetDriverCapDescriptions return textual descriptions of driver capabilities func (c Capability) GetDriverCapDescriptions() []CapabilityDesc { var result []CapabilityDesc for _, cap := range Capabilities { - if c.GetCapabilities() & cap.Cap == cap.Cap { + if c.Capabilities&cap.Cap == cap.Cap { result = append(result, cap) } } @@ -199,34 +198,18 @@ func (c Capability) GetDriverCapDescriptions() []CapabilityDesc { func (c Capability) GetDeviceCapDescriptions() []CapabilityDesc { var result []CapabilityDesc for _, cap := range Capabilities { - if c.GetDeviceCaps() & cap.Cap == cap.Cap { + if c.DeviceCapabilities&cap.Cap == cap.Cap { result = append(result, cap) } } return result } -// DriverName returns a string value for the driver name -func (c Capability) DriverName() string { - return toGoString(c.v4l2Cap.driver[:]) -} - -// CardName returns a string value for device's card -func (c Capability) CardName() string { - return toGoString(c.v4l2Cap.card[:]) -} - -// BusInfo returns the device's bus info -func (c Capability) BusInfo() string { - return toGoString(c.v4l2Cap.busInfo[:]) -} - -// GetVersion returns the device's version -func (c Capability) GetVersion() uint32 { - return c.v4l2Cap.version +func (c Capability) GetVersionInfo() VersionInfo { + return VersionInfo{value: c.Version} } // String returns a string value representing driver information func (c Capability) String() string { - return fmt.Sprintf("driver: %s; card: %s; bus info: %s", c.DriverName(), c.CardName(), c.BusInfo()) + return fmt.Sprintf("driver: %s; card: %s; bus info: %s", c.Driver, c.Card, c.BusInfo) } diff --git a/v4l2/crop.go b/v4l2/crop.go index 0d1d6c6..f750629 100644 --- a/v4l2/crop.go +++ b/v4l2/crop.go @@ -1,5 +1,8 @@ package v4l2 +// #include +import "C" + import ( "fmt" "unsafe" @@ -24,39 +27,37 @@ type Fract struct { } // CropCapability (v4l2_cropcap) -// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/vidioc-cropcap.html#c.v4l2_cropcap +// https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-cropcap.html#c.v4l2_cropcap // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1221 type CropCapability struct { StreamType uint32 Bounds Rect DefaultRect Rect PixelAspect Fract -} - -// Crop (v4l2_crop) -// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/vidioc-g-crop.html#c.v4l2_crop -// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1228 -type Crop struct { - StreamType uint32 - Rect Rect + _ [4]uint32 } // GetCropCapability retrieves cropping info for specified device // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-cropcap.html#ioctl-vidioc-cropcap func GetCropCapability(fd uintptr) (CropCapability, error) { - cropCap := CropCapability{} - cropCap.StreamType = BufTypeVideoCapture - if err := Send(fd, VidiocCropCap, uintptr(unsafe.Pointer(&cropCap))); err != nil { + var cap C.struct_v4l2_cropcap + cap._type = C.uint(BufTypeVideoCapture) + + if err := send(fd, C.VIDIOC_CROPCAP, uintptr(unsafe.Pointer(&cap))); err != nil { return CropCapability{}, fmt.Errorf("crop capability: %w", err) } - return cropCap, nil + + return *(*CropCapability)(unsafe.Pointer(&cap)), nil } // SetCropRect sets the cropping dimension for specified device // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-crop.html#ioctl-vidioc-g-crop-vidioc-s-crop func SetCropRect(fd uintptr, r Rect) error { - crop := Crop{Rect: r, StreamType: BufTypeVideoCapture} - if err := Send(fd, VidiocSetCrop, uintptr(unsafe.Pointer(&crop))); err != nil { + var crop C.struct_v4l2_crop + crop._type = C.uint(BufTypeVideoCapture) + crop.c = *(*C.struct_v4l2_rect)(unsafe.Pointer(&r)) + + if err := send(fd, C.VIDIOC_S_CROP, uintptr(unsafe.Pointer(&crop))); err != nil { return fmt.Errorf("set crop: %w", err) } return nil diff --git a/v4l2/device.go b/v4l2/device.go index e071b21..2b55a29 100644 --- a/v4l2/device.go +++ b/v4l2/device.go @@ -165,7 +165,7 @@ func (d *Device) StartStream(buffSize uint32) error { } // allocate device buffers - bufReq, err := AllocateBuffers(d.fd, buffSize) + bufReq, err := InitBuffers(d.fd, buffSize) if err != nil { return fmt.Errorf("device: start stream: %w", err) } @@ -175,16 +175,16 @@ func (d *Device) StartStream(buffSize uint32) error { bufCount := int(d.requestedBuf.Count) d.buffers = make([][]byte, d.requestedBuf.Count) for i := 0; i < bufCount; i++ { - bufInfo, err := GetBufferInfo(d.fd, uint32(i)) + buffer, err := GetBuffer(d.fd, uint32(i)) if err != nil { - return fmt.Errorf("device: start stream: %w", err) + return fmt.Errorf("device start stream: %w", err) } - offset := bufInfo.GetService().Offset - length := bufInfo.Length + offset := buffer.Info.Offset + length := buffer.Length mappedBuf, err := MapMemoryBuffer(d.fd, int64(offset), int(length)) if err != nil { - return fmt.Errorf("device: start stream: %w", err) + return fmt.Errorf("device start stream: %w", err) } d.buffers[i] = mappedBuf } @@ -193,13 +193,13 @@ func (d *Device) StartStream(buffSize uint32) error { for i := 0; i < bufCount; i++ { _, err := QueueBuffer(d.fd, uint32(i)) if err != nil { - return fmt.Errorf("device: start stream: %w", err) + return fmt.Errorf("device start stream: %w", err) } } // turn on device stream if err := StreamOn(d.fd); err != nil { - return fmt.Errorf("device: start stream: %w", err) + return fmt.Errorf("device start stream: %w", err) } d.streaming = true diff --git a/v4l2/format.go b/v4l2/format.go index f58c385..dab36bf 100644 --- a/v4l2/format.go +++ b/v4l2/format.go @@ -1,41 +1,36 @@ package v4l2 +// #include +import "C" + import ( "fmt" "unsafe" ) -// FourCCEncoding represents the four character encoding value -type FourCCEncoding = uint32 +// FourCCType represents the four character encoding value +type FourCCType = uint32 // Some Predefined pixel format definitions // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/pixfmt.html // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L518 var ( - PixFmtRGB24 = fourcc('R', 'G', 'B', '3') // V4L2_PIX_FMT_RGB24 - PixFmtGrey = fourcc('G', 'R', 'E', 'Y') // V4L2_PIX_FMT_GREY - PixelFmtYUYV = fourcc('Y', 'U', 'Y', 'V') // V4L2_PIX_FMT_YUYV - PixelFmtYYUV = fourcc('Y', 'Y', 'U', 'V') // V4L2_PIX_FMT_YYUV - PixelFmtYVYU = fourcc('Y', 'V', 'Y', 'U') // V4L2_PIX_FMT_YVYU - PixelFmtUYVY = fourcc('U', 'Y', 'V', 'Y') // V4L2_PIX_FMT_UYVY - PixelFmtVYUY = fourcc('V', 'Y', 'U', 'Y') // V4L2_PIX_FMT_VYUY - PixelFmtMJPEG = fourcc('M', 'J', 'P', 'G') // V4L2_PIX_FMT_MJPEG - PixelFmtJPEG = fourcc('J', 'P', 'E', 'G') // V4L2_PIX_FMT_JPEG - PixelFmtMPEG = fourcc('M', 'P', 'E', 'G') // V4L2_PIX_FMT_MPEG - PixelFmtH264 = fourcc('H', '2', '6', '4') // V4L2_PIX_FMT_H264 - PixelFmtMPEG4 = fourcc('M', 'P', 'G', '4') // V4L2_PIX_FMT_MPEG4 + PixFmtRGB24 FourCCType = C.V4L2_PIX_FMT_RGB24 + PixFmtGrey FourCCType = C.V4L2_PIX_FMT_GREY + PixelFmtYUYV FourCCType = C.V4L2_PIX_FMT_YUYV + PixelFmtYYUV FourCCType = C.V4L2_PIX_FMT_YYUV + PixelFmtYVYU FourCCType = C.V4L2_PIX_FMT_YVYU + PixelFmtUYVY FourCCType = C.V4L2_PIX_FMT_UYVY + PixelFmtVYUY FourCCType = C.V4L2_PIX_FMT_VYUY + PixelFmtMJPEG FourCCType = C.V4L2_PIX_FMT_MJPEG + PixelFmtJPEG FourCCType = C.V4L2_PIX_FMT_JPEG + PixelFmtMPEG FourCCType = C.V4L2_PIX_FMT_MPEG + PixelFmtH264 FourCCType = C.V4L2_PIX_FMT_H264 + PixelFmtMPEG4 FourCCType = C.V4L2_PIX_FMT_MPEG4 ) -// fourcc implements the four character code encoding found -// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L81 -// #define v4l2_fourcc(a, b, c, d)\ -// ((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24)) -func fourcc(a, b, c, d uint32) FourCCEncoding { - return (a | b<<8) | c<<16 | d<<24 -} - -// PixelFormats provides a map of FourCC encoding description -var PixelFormats = map[FourCCEncoding]string{ +// PixelFormats provides a map of FourCCType encoding description +var PixelFormats = map[FourCCType]string{ PixFmtRGB24: "24-bit RGB 8-8-8", PixFmtGrey: "8-bit Greyscale", PixelFmtYUYV: "YUYV 4:2:2", @@ -47,7 +42,7 @@ var PixelFormats = map[FourCCEncoding]string{ } // IsPixYUVEncoded returns true if the pixel format is a chrome+luminance YUV format -func IsPixYUVEncoded(pixFmt FourCCEncoding) bool { +func IsPixYUVEncoded(pixFmt FourCCType) bool { switch pixFmt { case PixelFmtYUYV, @@ -66,24 +61,24 @@ func IsPixYUVEncoded(pixFmt FourCCEncoding) bool { type ColorspaceType = uint32 const ( - ColorspaceTypeDefault ColorspaceType = iota //V4L2_COLORSPACE_DEFAULT - ColorspaceSMPTE170M //V4L2_COLORSPACE_SMPTE170M - ColorspaceSMPTE240M // V4L2_COLORSPACE_SMPTE240M - ColorspaceREC709 // V4L2_COLORSPACE_REC709 - ColorspaceBT878 // V4L2_COLORSPACE_BT878 (absolete) - Colorspace470SystemM // V4L2_COLORSPACE_470_SYSTEM_M (absolete) - Colorspace470SystemBG // V4L2_COLORSPACE_470_SYSTEM_BG - ColorspaceJPEG // V4L2_COLORSPACE_JPEG - ColorspaceSRGB // V4L2_COLORSPACE_SRGB - ColorspaceOPRGB // V4L2_COLORSPACE_OPRGB - ColorspaceBT2020 // V4L2_COLORSPACE_BT2020 - ColorspaceRaw // V4L2_COLORSPACE_RAW - ColorspaceDCIP3 // V4L2_COLORSPACE_DCI_P3 + ColorspaceDefault ColorspaceType = C.V4L2_COLORSPACE_DEFAULT + ColorspaceSMPTE170M ColorspaceType = C.V4L2_COLORSPACE_SMPTE170M + ColorspaceSMPTE240M ColorspaceType = C.V4L2_COLORSPACE_SMPTE240M + ColorspaceREC709 ColorspaceType = C.V4L2_COLORSPACE_REC709 + ColorspaceBT878 ColorspaceType = C.V4L2_COLORSPACE_BT878 //(absolete) + Colorspace470SystemM ColorspaceType = C.V4L2_COLORSPACE_470_SYSTEM_M //(absolete) + Colorspace470SystemBG ColorspaceType = C.V4L2_COLORSPACE_470_SYSTEM_BG + ColorspaceJPEG ColorspaceType = C.V4L2_COLORSPACE_JPEG + ColorspaceSRGB ColorspaceType = C.V4L2_COLORSPACE_SRGB + ColorspaceOPRGB ColorspaceType = C.V4L2_COLORSPACE_OPRGB + ColorspaceBT2020 ColorspaceType = C.V4L2_COLORSPACE_BT2020 + ColorspaceRaw ColorspaceType = C.V4L2_COLORSPACE_RAW + ColorspaceDCIP3 ColorspaceType = C.V4L2_COLORSPACE_DCI_P3 ) // Colorspaces is a map of colorspace to its respective description var Colorspaces = map[ColorspaceType]string{ - ColorspaceTypeDefault: "Default", + ColorspaceDefault: "Default", ColorspaceREC709: "Rec. 709", Colorspace470SystemBG: "470 System BG", ColorspaceJPEG: "JPEG", @@ -100,14 +95,14 @@ var Colorspaces = map[ColorspaceType]string{ type YCbCrEncodingType = uint32 const ( - YCbCrEncodingDefault YCbCrEncodingType = iota // V4L2_YCBCR_ENC_DEFAULT - YCbCrEncoding601 // V4L2_YCBCR_ENC_601 - YCbCrEncoding709 // V4L2_YCBCR_ENC_709 - YCbCrEncodingXV601 // V4L2_YCBCR_ENC_XV601 - YCbCrEncodingXV709 // V4L2_YCBCR_ENC_XV709 - _ // V4L2_YCBCR_ENC_SYCC (absolete) - YCbCrEncodingBT2020 // V4L2_YCBCR_ENC_BT2020 - YCbCrEncodingBT2020ConstLum // V4L2_YCBCR_ENC_BT2020_CONST_LUM + YCbCrEncodingDefault YCbCrEncodingType = C.V4L2_YCBCR_ENC_DEFAULT + YCbCrEncoding601 YCbCrEncodingType = C.V4L2_YCBCR_ENC_601 + YCbCrEncoding709 YCbCrEncodingType = C.V4L2_YCBCR_ENC_709 + YCbCrEncodingXV601 YCbCrEncodingType = C.V4L2_YCBCR_ENC_XV601 + YCbCrEncodingXV709 YCbCrEncodingType = C.V4L2_YCBCR_ENC_XV709 + _ YCbCrEncodingType = C.V4L2_YCBCR_ENC_SYCC //(absolete) + YCbCrEncodingBT2020 YCbCrEncodingType = C.V4L2_YCBCR_ENC_BT2020 + YCbCrEncodingBT2020ConstLum YCbCrEncodingType = C.V4L2_YCBCR_ENC_BT2020_CONST_LUM ) var YCbCrEncodings = map[YCbCrEncodingType]string{ @@ -139,8 +134,8 @@ func ColorspaceToYCbCrEnc(cs ColorspaceType) YCbCrEncodingType { type HSVEncodingType = YCbCrEncodingType const ( - HSVEncoding180 = HSVEncodingType(128) // V4L2_HSV_ENC_180 - HSVEncoding256 = HSVEncodingType(129) // V4L2_HSV_ENC_256 + HSVEncoding180 HSVEncodingType = C.V4L2_HSV_ENC_180 + HSVEncoding256 HSVEncodingType = C.V4L2_HSV_ENC_256 ) // QuantizationType (v4l2_quantization) @@ -149,9 +144,9 @@ const ( type QuantizationType = uint32 const ( - QuantizationDefault QuantizationType = iota // V4L2_QUANTIZATION_DEFAULT - QuantizationFullRange // V4L2_QUANTIZATION_FULL_RANGE - QuantizationLimitedRange // V4L2_QUANTIZATION_LIM_RANGE + QuantizationDefault QuantizationType = C.V4L2_QUANTIZATION_DEFAULT + QuantizationFullRange QuantizationType = C.V4L2_QUANTIZATION_FULL_RANGE + QuantizationLimitedRange QuantizationType = C.V4L2_QUANTIZATION_LIM_RANGE ) var Quantizations = map[QuantizationType]string{ @@ -176,14 +171,14 @@ func ColorspaceToQuantization(cs ColorspaceType) QuantizationType { type XferFunctionType = uint32 const ( - XferFuncDefault XferFunctionType = iota // V4L2_XFER_FUNC_DEFAULT = 0 - XferFunc709 // V4L2_XFER_FUNC_709 = 1, - XferFuncSRGB // V4L2_XFER_FUNC_SRGB = 2, - XferFuncOpRGB // V4L2_XFER_FUNC_OPRGB = 3, - XferFuncSMPTE240M // V4L2_XFER_FUNC_SMPTE240M = 4, - XferFuncNone // V4L2_XFER_FUNC_NONE = 5, - XferFuncDCIP3 // V4L2_XFER_FUNC_DCI_P3 = 6, - XferFuncSMPTE2084 // V4L2_XFER_FUNC_SMPTE2084 = 7, + XferFuncDefault XferFunctionType = C.V4L2_XFER_FUNC_DEFAULT + XferFunc709 XferFunctionType = C.V4L2_XFER_FUNC_709 + XferFuncSRGB XferFunctionType = C.V4L2_XFER_FUNC_SRGB + XferFuncOpRGB XferFunctionType = C.V4L2_XFER_FUNC_OPRGB + XferFuncSMPTE240M XferFunctionType = C.V4L2_XFER_FUNC_SMPTE240M + XferFuncNone XferFunctionType = C.V4L2_XFER_FUNC_NONE + XferFuncDCIP3 XferFunctionType = C.V4L2_XFER_FUNC_DCI_P3 + XferFuncSMPTE2084 XferFunctionType = C.V4L2_XFER_FUNC_SMPTE2084 ) var XferFunctions = map[XferFunctionType]string{ @@ -223,16 +218,16 @@ func ColorspaceToXferFunc(cs ColorspaceType) XferFunctionType { type FieldType = uint32 const ( - FieldAny FieldType = iota // V4L2_FIELD_ANY - FieldNone // V4L2_FIELD_NONE - FieldTop // V4L2_FIELD_TOP - FieldBottom // V4L2_FIELD_BOTTOM - FieldInterlaced // V4L2_FIELD_INTERLACED - FieldSequentialTopBottom // V4L2_FIELD_SEQ_TB - FieldSequentialBottomTop // V4L2_FIELD_SEQ_BT - FieldAlternate // V4L2_FIELD_ALTERNATE - FieldInterlacedTopBottom // V4L2_FIELD_INTERLACED_TB - FieldInterlacedBottomTop // V4L2_FIELD_INTERLACED_BT + FieldAny FieldType = C.V4L2_FIELD_ANY + FieldNone FieldType = C.V4L2_FIELD_NONE + FieldTop FieldType = C.V4L2_FIELD_TOP + FieldBottom FieldType = C.V4L2_FIELD_BOTTOM + FieldInterlaced FieldType = C.V4L2_FIELD_INTERLACED + FieldSequentialTopBottom FieldType = C.V4L2_FIELD_SEQ_TB + FieldSequentialBottomTop FieldType = C.V4L2_FIELD_SEQ_BT + FieldAlternate FieldType = C.V4L2_FIELD_ALTERNATE + FieldInterlacedTopBottom FieldType = C.V4L2_FIELD_INTERLACED_TB + FieldInterlacedBottomTop FieldType = C.V4L2_FIELD_INTERLACED_BT ) // Fields is a map of FieldType description @@ -249,13 +244,13 @@ var Fields = map[FieldType]string{ FieldInterlacedBottomTop: "interlaced bottom-top", } -// PixFormat (v4l2_pix_format) +// PixFormat contains video image format from v4l2_pix_format. // https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/pixfmt-002.html?highlight=v4l2_pix_format // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L496 type PixFormat struct { Width uint32 Height uint32 - PixelFormat FourCCEncoding + PixelFormat FourCCType Field FieldType BytesPerLine uint32 SizeImage uint32 @@ -263,6 +258,7 @@ type PixFormat struct { Priv uint32 Flags uint32 YcbcrEnc YCbCrEncodingType + HSVEnc HSVEncodingType Quantization QuantizationType XferFunc XferFunctionType } @@ -282,58 +278,43 @@ func (f PixFormat) String() string { ) } -// 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#L2331 -// -// field fmt is a union, thus it's constructed as an appropriately sized array: -// -// struct v4l2_format { -// __u32 type; -// union { -// struct v4l2_pix_format pix; -// struct v4l2_pix_format_mplane pix_mp; -// struct v4l2_window win; -// struct v4l2_vbi_format vbi; -// struct v4l2_sliced_vbi_format sliced; -// struct v4l2_sdr_format sdr; -// struct v4l2_meta_format meta; -// __u8 raw_data[200]; /* user-defined */ -// } fmt; -// }; -type v4l2Format struct { - StreamType uint32 - fmt [200]byte -} - -// getPixFormat returns the PixFormat by casting the pointer to the union type -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) { - 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 +// GetPixFormat retrieves pixel information for the specified driver (via v4l2_format and v4l2_pix_format) +// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2331 +// and 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 { + var v4l2Format C.struct_v4l2_format + v4l2Format._type = C.uint(BufTypeVideoCapture) + + if err := send(fd, C.VIDIOC_G_FMT, uintptr(unsafe.Pointer(&v4l2Format))); err != nil { return PixFormat{}, fmt.Errorf("pix format failed: %w", err) } - return format.getPixFormat(), nil + v4l2PixFmt := *(*C.struct_v4l2_pix_format)(unsafe.Pointer(&v4l2Format.fmt[0])) + return PixFormat{ + Width: uint32(v4l2PixFmt.width), + Height: uint32(v4l2PixFmt.height), + PixelFormat: uint32(v4l2PixFmt.pixelformat), + Field: uint32(v4l2PixFmt.field), + BytesPerLine: uint32(v4l2PixFmt.bytesperline), + SizeImage: uint32(v4l2PixFmt.sizeimage), + Colorspace: uint32(v4l2PixFmt.colorspace), + Priv: uint32(v4l2PixFmt.priv), + Flags: uint32(v4l2PixFmt.flags), + YcbcrEnc: *(*uint32)(unsafe.Pointer(&v4l2PixFmt.anon0[0])), + HSVEnc: *(*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&v4l2PixFmt.anon0[0])) + unsafe.Sizeof(&v4l2PixFmt.anon0[0]))), + Quantization: uint32(v4l2PixFmt.quantization), + XferFunc: uint32(v4l2PixFmt.xfer_func), + }, nil } // 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.setPixFormat(pixFmt) - if err := Send(fd, VidiocSetFormat, uintptr(unsafe.Pointer(format))); err != nil { + var v4l2Format C.struct_v4l2_format + v4l2Format._type = C.uint(BufTypeVideoCapture) + *(*C.struct_v4l2_pix_format)(unsafe.Pointer(&v4l2Format.fmt[0])) = *(*C.struct_v4l2_pix_format)(unsafe.Pointer(&pixFmt)) + + if err := send(fd, C.VIDIOC_S_FMT, uintptr(unsafe.Pointer(&v4l2Format))); err != nil { return fmt.Errorf("pix format failed: %w", err) } return nil diff --git a/v4l2/format_desc.go b/v4l2/format_desc.go index 4ad20f7..01245c8 100644 --- a/v4l2/format_desc.go +++ b/v4l2/format_desc.go @@ -1,5 +1,10 @@ package v4l2 +/* +#include +*/ +import "C" + import ( "errors" "fmt" @@ -12,16 +17,16 @@ import ( type FmtDescFlag = uint32 const ( - FmtDescFlagCompressed FmtDescFlag = 0x0001 // V4L2_FMT_FLAG_COMPRESSED - FmtDescFlagEmulated FmtDescFlag = 0x0002 // V4L2_FMT_FLAG_EMULATED - FmtDescFlagContinuousBytestream FmtDescFlag = 0x0004 // V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM - FmtDescFlagDynResolution FmtDescFlag = 0x0008 // V4L2_FMT_FLAG_DYN_RESOLUTION - FmtDescFlagEncodedCaptureFrameInterval FmtDescFlag = 0x0010 // V4L2_FMT_FLAG_ENC_CAP_FRAME_INTERVAL - FmtDescFlagConfigColorspace FmtDescFlag = 0x0020 // V4L2_FMT_FLAG_CSC_COLORSPACE - FmtDescFlagConfigXferFunc FmtDescFlag = 0x0040 // V4L2_FMT_FLAG_CSC_XFER_FUNC - FmtDescFlagConfigYcbcrEnc FmtDescFlag = 0x0080 // V4L2_FMT_FLAG_CSC_YCBCR_ENC - FmtDescFlagConfigHsvEnc FmtDescFlag = FmtDescFlagConfigYcbcrEnc // V4L2_FMT_FLAG_CSC_HSV_ENC - FmtDescFlagConfigQuantization FmtDescFlag = 0x0100 // V4L2_FMT_FLAG_CSC_QUANTIZATION + FmtDescFlagCompressed FmtDescFlag = C.V4L2_FMT_FLAG_COMPRESSED + FmtDescFlagEmulated FmtDescFlag = C.V4L2_FMT_FLAG_EMULATED + FmtDescFlagContinuousBytestream FmtDescFlag = C.V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM + FmtDescFlagDynResolution FmtDescFlag = C.V4L2_FMT_FLAG_DYN_RESOLUTION + FmtDescFlagEncodedCaptureFrameInterval FmtDescFlag = C.V4L2_FMT_FLAG_ENC_CAP_FRAME_INTERVAL + FmtDescFlagConfigColorspace FmtDescFlag = C.V4L2_FMT_FLAG_CSC_COLORSPACE + FmtDescFlagConfigXferFunc FmtDescFlag = C.V4L2_FMT_FLAG_CSC_XFER_FUNC + FmtDescFlagConfigYcbcrEnc FmtDescFlag = C.V4L2_FMT_FLAG_CSC_YCBCR_ENC + FmtDescFlagConfigHsvEnc FmtDescFlag = C.V4L2_FMT_FLAG_CSC_HSV_ENC + FmtDescFlagConfigQuantization FmtDescFlag = C.V4L2_FMT_FLAG_CSC_QUANTIZATION ) var FormatDescriptionFlags = map[FmtDescFlag]string{ @@ -36,72 +41,55 @@ var FormatDescriptionFlags = map[FmtDescFlag]string{ FmtDescFlagConfigQuantization: "Quantization update supported", } -// v4l2FormatDesc (v4l2_fmtdesc) +// FormatDescription (v4l2_fmtdesc) provides access to the device format description // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L784 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-fmt.html -type v4l2FormatDesc struct { - index uint32 // format number - bufType BufType // stream type BufType - flags FmtDescFlag - description [32]uint8 // string description - pixelFormat FourCCEncoding // Format fourcc value - mbusCode uint32 // media bus code - reserved [3]uint32 -} - -// FormatDescription provides access to the device format description -// See v4l2FormatDesc type FormatDescription struct { - fd uintptr - v4l2FormatDesc -} - -// GetIndex returns the format number -func (d FormatDescription) GetIndex() uint32 { - return d.index -} - -// GetBufType returns the type for the buffer (see v4l2_buf_type) -func (d FormatDescription) GetBufType() BufType { - return d.bufType -} - -// GetFlags returns image description flags (see FmtDescFlag) -func (d FormatDescription) GetFlags() FmtDescFlag { - return d.flags + // Index returns the format number + Index uint32 + // StreamType type for the buffer (see v4l2_buf_type) + StreamType BufType + // Flags is the image description flags (see FmtDescFlag) + Flags FmtDescFlag + // Description is a string value for the format description + Description string + // PixelFormat stores the four character encoding for the format + PixelFormat FourCCType + // MBusCode is the media bus code for drivers that advertise v4l2_cap_io_mc + MBusCode uint32 } -// GetDescription returns a string value for the format description -func (d FormatDescription) GetDescription() string { - return toGoString(d.description[:]) +func (d FormatDescription) String() string { + return fmt.Sprintf( + "Format: %s [index: %d, flags: %s, format:%s]", + d.Description, + d.Index, + FormatDescriptionFlags[d.Flags], + PixelFormats[d.PixelFormat], + ) } - -// GetPixelFormat returns the four character encoding for the format -func (d FormatDescription) GetPixelFormat() FourCCEncoding { - return d.pixelFormat -} - -// GetBusCode returns the media bus code for drivers that advertise v4l2_cap_io_mc -func (d FormatDescription) GetBusCode() uint32 { - return d.mbusCode -} - -// GetFrameSizes return all supported frame sizes for the format description. -func (d FormatDescription) GetFrameSizes() ([]FrameSize, error) { - if d.fd == 0 { - return nil, fmt.Errorf("invalid file descriptor") +func makeFormatDescription(fmtDesc C.struct_v4l2_fmtdesc) FormatDescription { + return FormatDescription{ + Index: uint32(fmtDesc.index), + StreamType: uint32(fmtDesc._type), + Flags: uint32(fmtDesc.flags), + Description: C.GoString((*C.char)(&fmtDesc.description[0])), + PixelFormat: uint32(fmtDesc.pixelformat), + MBusCode: uint32(fmtDesc.mbus_code), } - return GetFormatFrameSizes(d.fd, d.pixelFormat) } // GetFormatDescription returns a device format description at index func GetFormatDescription(fd uintptr, index uint32) (FormatDescription, error) { - desc := v4l2FormatDesc{index: index, bufType: BufTypeVideoCapture} - if err := Send(fd, VidiocEnumFmt, uintptr(unsafe.Pointer(&desc))); err != nil { + var fmtDesc C.struct_v4l2_fmtdesc + fmtDesc.index = C.uint(index) + fmtDesc._type = C.uint(BufTypeVideoCapture) + + if err := send(fd, C.VIDIOC_ENUM_FMT, uintptr(unsafe.Pointer(&fmtDesc))); err != nil { return FormatDescription{}, fmt.Errorf("format desc: index %d: %w", index, err) } - return FormatDescription{fd: fd, v4l2FormatDesc: desc}, nil + return makeFormatDescription(fmtDesc), nil } // GetAllFormatDescriptions attempts to retrieve all device format descriptions by @@ -111,30 +99,33 @@ func GetFormatDescription(fd uintptr, index uint32) (FormatDescription, error) { func GetAllFormatDescriptions(fd uintptr) (result []FormatDescription, err error) { index := uint32(0) for { - desc := v4l2FormatDesc{index: index, bufType: BufTypeVideoCapture} - if err = Send(fd, VidiocEnumFmt, uintptr(unsafe.Pointer(&desc))); err != nil { + var fmtDesc C.struct_v4l2_fmtdesc + fmtDesc.index = C.uint(index) + fmtDesc._type = C.uint(BufTypeVideoCapture) + + if err = send(fd, C.VIDIOC_ENUM_FMT, uintptr(unsafe.Pointer(&fmtDesc))); err != nil { if errors.Is(err, ErrorBadArgument) && len(result) > 0 { break } return result, fmt.Errorf("format desc: all: %w", err) } - result = append(result, FormatDescription{fd: fd, v4l2FormatDesc: desc}) + result = append(result, makeFormatDescription(fmtDesc)) index++ } return result, nil } // GetFormatDescriptionByEncoding returns a FormatDescription that matches the specified encoded pixel format -func GetFormatDescriptionByEncoding(fd uintptr, enc FourCCEncoding)(FormatDescription, error) { +func GetFormatDescriptionByEncoding(fd uintptr, enc FourCCType) (FormatDescription, error) { descs, err := GetAllFormatDescriptions(fd) if err != nil { return FormatDescription{}, fmt.Errorf("format desc: encoding %s: %s", PixelFormats[enc], err) } for _, desc := range descs { - if desc.GetPixelFormat() == enc{ + if desc.PixelFormat == enc { return desc, nil } } return FormatDescription{}, fmt.Errorf("format desc: driver does not support encoding %d", enc) -} \ No newline at end of file +} diff --git a/v4l2/format_framesizes.go b/v4l2/format_framesizes.go index f10af98..45124ee 100644 --- a/v4l2/format_framesizes.go +++ b/v4l2/format_framesizes.go @@ -1,59 +1,31 @@ package v4l2 +//#include +import "C" + import ( "errors" "fmt" "unsafe" ) -//enum v4l2_frmsizetypes { -//V4L2_FRMSIZE_TYPE_DISCRETE = 1, -//V4L2_FRMSIZE_TYPE_CONTINUOUS = 2, -//V4L2_FRMSIZE_TYPE_STEPWISE = 3, -//}; -// -//struct v4l2_frmsize_discrete { -//__u32 width; /* Frame width [pixel] */ -//__u32 height; /* Frame height [pixel] */ -//}; -// -//struct v4l2_frmsize_stepwise { -//__u32 min_width; /* Minimum frame width [pixel] */ -//__u32 max_width; /* Maximum frame width [pixel] */ -//__u32 step_width; /* Frame width step size [pixel] */ -//__u32 min_height; /* Minimum frame height [pixel] */ -//__u32 max_height; /* Maximum frame height [pixel] */ -//__u32 step_height; /* Frame height step size [pixel] */ -//}; -// -//struct v4l2_frmsizeenum { -//__u32 index; /* Frame size number */ -//__u32 pixel_format; /* Pixel format */ -//__u32 type; /* Frame size type the device supports. */ -// -//union { /* Frame size */ -//struct v4l2_frmsize_discrete discrete; -//struct v4l2_frmsize_stepwise stepwise; -//}; -// -//__u32 reserved[2]; /* Reserved space for future use */ -//}; - type FrameSizeType = uint32 const ( - FrameSizeTypeDiscrete FrameSizeType = iota + 1 // V4L2_FRMSIZE_TYPE_DISCRETE = 1 - FrameSizeTypeContinuous // V4L2_FRMSIZE_TYPE_CONTINUOUS = 2 - FrameSizeTypeStepwise // V4L2_FRMSIZE_TYPE_STEPWISE = 3 + FrameSizeTypeDiscrete FrameSizeType = C.V4L2_FRMSIZE_TYPE_DISCRETE + FrameSizeTypeContinuous FrameSizeType = C.V4L2_FRMSIZE_TYPE_CONTINUOUS + FrameSizeTypeStepwise FrameSizeType = C.V4L2_FRMSIZE_TYPE_STEPWISE ) -// FrameSize is the frame size supported by the driver for the pixel format. +// FrameSize uses v4l2_frmsizeenum to get supporeted frame size for the driver based for the pixel format. // Use FrameSizeType to determine which sizes the driver support. +// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L829 +// https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-framesizes.html type FrameSize struct { FrameSizeType FrameSizeDiscrete FrameSizeStepwise - PixelFormat FourCCEncoding + PixelFormat FourCCType } // FrameSizeDiscrete (v4l2_frmsize_discrete) @@ -74,30 +46,19 @@ type FrameSizeStepwise struct { StepHeight uint32 // Frame height step size [pixel] } -// v4l2FrameSizes (v4l2_frmsizeenum) -// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L829 -// https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-framesizes.html -type v4l2FrameSizeEnum struct { - index uint32 - pixelFormat FourCCEncoding - frameSizeType FrameSizeType - frameSize [24]byte // union sized for largest struct: stepwise - reserved [2]uint32 -} - -// getFrameSize retrieves the supported frame size -func (fs v4l2FrameSizeEnum) getFrameSize() FrameSize { - frameSize := FrameSize{FrameSizeType: fs.frameSizeType, PixelFormat: fs.pixelFormat} - switch fs.frameSizeType { +// getFrameSize retrieves the supported frame size based on the type +func getFrameSize(frmSizeEnum C.struct_v4l2_frmsizeenum) FrameSize { + frameSize := FrameSize{FrameSizeType: FrameSizeType(frmSizeEnum._type), PixelFormat: FourCCType(frmSizeEnum.pixel_format)} + switch frameSize.FrameSizeType { case FrameSizeTypeDiscrete: - fsDiscrete := (*FrameSizeDiscrete)(unsafe.Pointer(&fs.frameSize[0])) + fsDiscrete := (*FrameSizeDiscrete)(unsafe.Pointer(&frmSizeEnum.anon0[0])) frameSize.FrameSizeDiscrete = *fsDiscrete frameSize.FrameSizeStepwise.MinWidth = frameSize.FrameSizeDiscrete.Width frameSize.FrameSizeStepwise.MinHeight = frameSize.FrameSizeDiscrete.Height frameSize.FrameSizeStepwise.MaxWidth = frameSize.FrameSizeDiscrete.Width frameSize.FrameSizeStepwise.MaxHeight = frameSize.FrameSizeDiscrete.Height case FrameSizeTypeStepwise, FrameSizeTypeContinuous: - fsStepwise := (*FrameSizeStepwise)(unsafe.Pointer(&fs.frameSize[0])) + fsStepwise := (*FrameSizeStepwise)(unsafe.Pointer(&frmSizeEnum.anon0[0])) frameSize.FrameSizeStepwise = *fsStepwise frameSize.FrameSizeDiscrete.Width = frameSize.FrameSizeStepwise.MaxWidth frameSize.FrameSizeDiscrete.Height = frameSize.FrameSizeStepwise.MaxHeight @@ -106,20 +67,26 @@ func (fs v4l2FrameSizeEnum) getFrameSize() FrameSize { } // GetFormatFrameSize returns a supported device frame size for a specified encoding at index -func GetFormatFrameSize(fd uintptr, index uint32, encoding FourCCEncoding) (FrameSize, error) { - fsEnum := v4l2FrameSizeEnum{index: index, pixelFormat: encoding} - if err := Send(fd, VidiocEnumFrameSizes, uintptr(unsafe.Pointer(&fsEnum))); err != nil { +func GetFormatFrameSize(fd uintptr, index uint32, encoding FourCCType) (FrameSize, error) { + var frmSizeEnum C.struct_v4l2_frmsizeenum + frmSizeEnum.index = C.uint(index) + frmSizeEnum.pixel_format = C.uint(encoding) + + if err := send(fd, C.VIDIOC_ENUM_FRAMESIZES, uintptr(unsafe.Pointer(&frmSizeEnum))); err != nil { return FrameSize{}, fmt.Errorf("frame size: index %d: %w", index, err) } - return fsEnum.getFrameSize(), nil + return getFrameSize(frmSizeEnum), nil } // GetFormatFrameSizes returns all supported device frame sizes for a specified encoding -func GetFormatFrameSizes(fd uintptr, encoding FourCCEncoding) (result []FrameSize, err error) { +func GetFormatFrameSizes(fd uintptr, encoding FourCCType) (result []FrameSize, err error) { index := uint32(0) for { - fsEnum := v4l2FrameSizeEnum{index: index, pixelFormat: encoding} - if err = Send(fd, VidiocEnumFrameSizes, uintptr(unsafe.Pointer(&fsEnum))); err != nil { + var frmSizeEnum C.struct_v4l2_frmsizeenum + frmSizeEnum.index = C.uint(index) + frmSizeEnum.pixel_format = C.uint(encoding) + + if err = send(fd, C.VIDIOC_ENUM_FRAMESIZES, uintptr(unsafe.Pointer(&frmSizeEnum))); err != nil { if errors.Is(err, ErrorBadArgument) && len(result) > 0 { break } @@ -128,8 +95,8 @@ func GetFormatFrameSizes(fd uintptr, encoding FourCCEncoding) (result []FrameSiz // At index 0, check the frame type, if not discrete exit loop. // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-framesizes.html - result = append(result, fsEnum.getFrameSize()) - if index == 0 && fsEnum.frameSizeType != FrameSizeTypeDiscrete { + result = append(result, getFrameSize(frmSizeEnum)) + if index == 0 && uint32(frmSizeEnum._type) != FrameSizeTypeDiscrete { break } index++ @@ -150,8 +117,11 @@ func GetAllFormatFrameSizes(fd uintptr) (result []FrameSize, err error) { for _, format := range formats { index := uint32(0) for { - fsEnum := v4l2FrameSizeEnum{index: index, pixelFormat: format.GetPixelFormat()} - if err = Send(fd, VidiocEnumFrameSizes, uintptr(unsafe.Pointer(&fsEnum))); err != nil { + var frmSizeEnum C.struct_v4l2_frmsizeenum + frmSizeEnum.index = C.uint(index) + frmSizeEnum.pixel_format = C.uint(format.PixelFormat) + + if err = send(fd, C.VIDIOC_ENUM_FRAMESIZES, uintptr(unsafe.Pointer(&frmSizeEnum))); err != nil { if errors.Is(err, ErrorBadArgument) && len(result) > 0 { break } @@ -160,8 +130,8 @@ func GetAllFormatFrameSizes(fd uintptr) (result []FrameSize, err error) { // At index 0, check the frame type, if not discrete exit loop. // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-framesizes.html - result = append(result, fsEnum.getFrameSize()) - if index == 0 && fsEnum.frameSizeType != FrameSizeTypeDiscrete { + result = append(result, getFrameSize(frmSizeEnum)) + if index == 0 && uint32(frmSizeEnum._type) != FrameSizeTypeDiscrete { break } index++ diff --git a/v4l2/ioctl.go b/v4l2/ioctl.go index 6b44d30..d49527c 100644 --- a/v4l2/ioctl.go +++ b/v4l2/ioctl.go @@ -1,66 +1,9 @@ package v4l2 import ( - "unsafe" - sys "golang.org/x/sys/unix" ) -// ioctl uses a 32-bit value to encode commands sent to the kernel for device control. -// Requests sent via ioctl uses the following layout: -// - lower 16 bits: ioctl command -// - Upper 14 bits: size of the parameter structure -// - MSB 2 bits: are reserved for indicating the ``access mode''. -// https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h - -const ( - // ioctl command bit sizes - // See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L23 - iocNumberBits = 8 - iocTypeBits = 8 - iocSizeBits = 14 - iocDirBits = 2 - - // ioctl bit layout positions - // see https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L44 - numShift = 0 - typeShift = numShift + iocNumberBits - sizeShift = typeShift + iocTypeBits - dirShift = sizeShift + iocSizeBits - - // ioctl direction bits - // These values are from the ioctl.h in linux. - // See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L57 - iocNone = 0 // no op - iocWrite = 1 // userland app is writing, kernel reading - iocRead = 2 // userland app is reading, kernel writing - -) - -// iocEnc encodes V42L API command as value. -// See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L69 -func iocEnc(dir, iocType, number, size uintptr) uintptr { - return (dir << dirShift) | (iocType << typeShift) | (number << numShift) | (size << sizeShift) -} - -// iocEncR encodes ioctl command where program reads result from kernel. -// See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L86 -func iocEncR(iocType, number, size uintptr) uintptr { - return iocEnc(iocRead, iocType, number, size) -} - -// iocEncW encodes ioctl command where program writes values read by the kernel. -// See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L87 -func iocEncW(iocType, number, size uintptr) uintptr { - return iocEnc(iocWrite, iocType, number, size) -} - -// iocEncRW encodes ioctl command for program reads and program writes. -// See https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ioctl.h#L88 -func iocEncRW(iocType, number, size uintptr) uintptr { - return iocEnc(iocRead|iocWrite, iocType, number, size) -} - // ioctl is a wrapper for Syscall(SYS_IOCTL) func ioctl(fd, req, arg uintptr) (err sys.Errno) { if _, _, errno := sys.Syscall(sys.SYS_IOCTL, fd, req, arg); errno != 0 { @@ -72,31 +15,8 @@ func ioctl(fd, req, arg uintptr) (err sys.Errno) { return 0 } -// V4L2 command request values for ioctl -// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2510 -// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/user-func.html - -var ( - VidiocQueryCap = iocEncR('V', 0, unsafe.Sizeof(v4l2Capability{})) // Represents command VIDIOC_QUERYCAP - VidiocEnumFmt = iocEncRW('V', 2, unsafe.Sizeof(v4l2FormatDesc{})) // Represents command VIDIOC_ENUM_FMT - VidiocGetFormat = iocEncRW('V', 4, unsafe.Sizeof(v4l2Format{})) // Represents command VIDIOC_G_FMT - VidiocSetFormat = iocEncRW('V', 5, unsafe.Sizeof(v4l2Format{})) // Represents command VIDIOC_S_FMT - VidiocReqBufs = iocEncRW('V', 8, unsafe.Sizeof(RequestBuffers{})) // Represents command VIDIOC_REQBUFS - VidiocQueryBuf = iocEncRW('V', 9, unsafe.Sizeof(BufferInfo{})) // Represents command VIDIOC_QUERYBUF - VidiocQueueBuf = iocEncRW('V', 15, unsafe.Sizeof(BufferInfo{})) // Represents command VIDIOC_QBUF - VidiocDequeueBuf = iocEncRW('V', 17, unsafe.Sizeof(BufferInfo{})) // Represents command VIDIOC_DQBUF - VidiocStreamOn = iocEncW('V', 18, unsafe.Sizeof(int32(0))) // Represents command VIDIOC_STREAMON - VidiocStreamOff = iocEncW('V', 19, unsafe.Sizeof(int32(0))) // Represents command VIDIOC_STREAMOFF - VidiocGetParam = iocEncRW('V', 21, unsafe.Sizeof(v4l2StreamParam{})) // Represents command VIDIOC_G_PARM - VidiocEnumInput = iocEncRW('V', 26, unsafe.Sizeof(v4l2InputInfo{})) // Represents command VIDIOC_ENUMINPUT - VidiocGetVideoInput = iocEncR('V', 38, unsafe.Sizeof(int32(0))) // Represents command VIDIOC_G_INPUT - VidiocCropCap = iocEncRW('V', 58, unsafe.Sizeof(CropCapability{})) // Represents command VIDIOC_CROPCAP - VidiocSetCrop = iocEncW('V', 60, unsafe.Sizeof(Crop{})) // Represents command VIDIOC_S_CROP - VidiocEnumFrameSizes = iocEncRW('V', 74, unsafe.Sizeof(v4l2FrameSizeEnum{})) // Represents command VIDIOC_ENUM_FRAMESIZES -) - -// Send sends a request to the kernel (via ioctl syscall) -func Send(fd, req, arg uintptr) error { +// send sends a request to the kernel (via ioctl syscall) +func send(fd, req, arg uintptr) error { errno := ioctl(fd, req, arg) if errno == 0 { return nil diff --git a/v4l2/stream_param.go b/v4l2/stream_param.go index 4f8573a..1dd24ce 100644 --- a/v4l2/stream_param.go +++ b/v4l2/stream_param.go @@ -1,5 +1,8 @@ package v4l2 +// #include +import "C" + import ( "fmt" "unsafe" @@ -11,8 +14,8 @@ import ( type StreamParamFlag = uint32 const ( - StreamParamModeHighQuality StreamParamFlag = 0x0001 // V4L2_MODE_HIGHQUALITY - StreamParamTimePerFrame StreamParamFlag = 0x1000 // V4L2_CAP_TIMEPERFRAME + StreamParamModeHighQuality StreamParamFlag = C.V4L2_MODE_HIGHQUALITY + StreamParamTimePerFrame StreamParamFlag = C.V4L2_CAP_TIMEPERFRAME ) // CaptureParam (v4l2_captureparam) @@ -24,44 +27,19 @@ type CaptureParam struct { TimePerFrame Fract ExtendedMode uint32 ReadBuffers uint32 - reserved [4]uint32 + _ [4]uint32 } -// v4l2StreamParam (v4l2_streamparam) +// GetStreamCaptureParam returns streaming capture parameter for the driver (v4l2_streamparm). // https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html // See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2347 -// -// Field param points to an embedded union, therefore, it points to an array sized -// as the largest knwon member of the embedded struct. See below: -// -// struct v4l2_streamparm { -// __u32 type; -// union { -// struct v4l2_captureparm capture; -// struct v4l2_outputparm output; -// __u8 raw_data[200]; -// } parm; -//}; -type v4l2StreamParam struct { - streamType StreamMemoryType - param [200]byte // embedded union -} -// getCaptureParam returns CaptureParam value from v4l2StreamParam embedded union -// if p.streamType = BufTypeVideoCapture. -func (p v4l2StreamParam) getCaptureParam() CaptureParam { - var param CaptureParam - if p.streamType == BufTypeVideoCapture { - param = *(*CaptureParam)(unsafe.Pointer(&p.param[0])) - } - return param -} +func GetStreamCaptureParam(fd uintptr) (CaptureParam, error) { + var param C.struct_v4l2_streamparm + param._type = C.uint(BufTypeVideoCapture) -// GetStreamCaptureParam returns streaming capture parameter for the driver. -func GetStreamCaptureParam (fd uintptr)(CaptureParam, error){ - param := v4l2StreamParam{streamType: BufTypeVideoCapture} - if err := Send(fd, VidiocGetParam, uintptr(unsafe.Pointer(¶m))); err != nil { + if err := send(fd, C.VIDIOC_G_PARM, uintptr(unsafe.Pointer(¶m))); err != nil { return CaptureParam{}, fmt.Errorf("stream param: %w", err) } - return param.getCaptureParam(), nil -} \ No newline at end of file + return *(*CaptureParam)(unsafe.Pointer(¶m.parm[0])), nil +} diff --git a/v4l2/streaming.go b/v4l2/streaming.go index 56165a3..fc7dfe5 100644 --- a/v4l2/streaming.go +++ b/v4l2/streaming.go @@ -1,5 +1,8 @@ package v4l2 +// #include +import "C" + import ( "errors" "fmt" @@ -18,28 +21,27 @@ import ( type BufType = uint32 const ( - BufTypeVideoCapture BufType = iota + 1 // V4L2_BUF_TYPE_VIDEO_CAPTURE = 1 - BufTypeVideoOutput // V4L2_BUF_TYPE_VIDEO_OUTPUT = 2 - BufTypeOverlay // V4L2_BUF_TYPE_VIDEO_OVERLAY = 3 + BufTypeVideoCapture BufType = C.V4L2_BUF_TYPE_VIDEO_CAPTURE + BufTypeVideoOutput BufType = C.V4L2_BUF_TYPE_VIDEO_OUTPUT + BufTypeOverlay BufType = C.V4L2_BUF_TYPE_VIDEO_OVERLAY ) -// StreamMemoryType (v4l2_memory) +// StreamType (v4l2_memory) // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/mmap.html?highlight=v4l2_memory_mmap // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L188 -type StreamMemoryType = uint32 +type StreamType = uint32 const ( - StreamMemoryTypeMMAP StreamMemoryType = iota + 1 // V4L2_MEMORY_MMAP = 1, - StreamMemoryTypeUserPtr // V4L2_MEMORY_USERPTR = 2, - StreamMemoryTypeOverlay // V4L2_MEMORY_OVERLAY = 3, - StreamMemoryTypeDMABuf // V4L2_MEMORY_DMABUF = 4, + StreamTypeMMAP StreamType = C.V4L2_MEMORY_MMAP + StreamTypeUserPtr StreamType = C.V4L2_MEMORY_USERPTR + StreamTypeOverlay StreamType = C.V4L2_MEMORY_OVERLAY + StreamTypeDMABuf StreamType = C.V4L2_MEMORY_DMABUF ) // TODO implement vl42_create_buffers -// RequestBuffers (v4l2_requestbuffers) -// This type is used to allocate buffer/io resources when initializing streaming io for -// memory mapped, user pointer, or DMA buffer access. +// RequestBuffers (v4l2_requestbuffers) is used to request buffer allocation initializing +// streaming for memory mapped, user pointer, or DMA buffer access. // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L949 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-reqbufs.html?highlight=v4l2_requestbuffers#c.V4L.v4l2_requestbuffers type RequestBuffers struct { @@ -47,43 +49,15 @@ type RequestBuffers struct { StreamType uint32 Memory uint32 Capabilities uint32 - Reserved [1]uint32 + _ [1]uint32 } -// BufferInfo (v4l2_buffer) -// This type is used to send buffers management info between application and driver +// Buffer (v4l2_buffer) is used to send buffers info between application and driver // after streaming IO has been initialized. // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/buffer.html#c.V4L.v4l2_buffer // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1037 // -// BufferInfo represents type v4l2_buffer which contains unions as shown below. -// Remember, the union is represented as an arry of bytes sized as the largest -// member in bytes. -// -// struct v4l2_buffer { -// __u32 index; -// __u32 type; -// __u32 bytesused; -// __u32 flags; -// __u32 field; -// struct timeval timestamp; -// struct v4l2_timecode timecode; -// __u32 sequence; -// __u32 memory; -// union { -// __u32 offset; -// unsigned long userptr; -// struct v4l2_plane *planes; -// __s32 fd; -// } m; -// __u32 length; -// __u32 reserved2; -// union { -// __s32 request_fd; -// __u32 reserved; -// }; -// }; -type BufferInfo struct { +type Buffer struct { Index uint32 StreamType uint32 BytesUsed uint32 @@ -93,61 +67,56 @@ type BufferInfo struct { Timecode Timecode Sequence uint32 Memory uint32 - m [unsafe.Sizeof(&BufferService{})]byte // union m, cast to BufferService + Info BufferInfo // union m Length uint32 Reserved2 uint32 RequestFD int32 } -func (b BufferInfo) GetService() BufferService { - m := (*BufferService)(unsafe.Pointer(&b.m[0])) - return *m +// makeBuffer makes a Buffer value from C.struct_v4l2_buffer +func makeBuffer(v4l2Buf C.struct_v4l2_buffer) Buffer { + return Buffer{ + Index: uint32(v4l2Buf.index), + StreamType: uint32(v4l2Buf._type), + BytesUsed: uint32(v4l2Buf.bytesused), + Flags: uint32(v4l2Buf.flags), + Field: uint32(v4l2Buf.field), + Timestamp: *(*sys.Timeval)(unsafe.Pointer(&v4l2Buf.timestamp)), + Timecode: *(*Timecode)(unsafe.Pointer(&v4l2Buf.timecode)), + Sequence: uint32(v4l2Buf.sequence), + Memory: uint32(v4l2Buf.memory), + Info: *(*BufferInfo)(unsafe.Pointer(&v4l2Buf.m[0])), + Length: uint32(v4l2Buf.length), + Reserved2: uint32(v4l2Buf.reserved2), + RequestFD: *(*int32)(unsafe.Pointer(&v4l2Buf.anon0[0])), + } } -// BufferService represents Union of several values in type Buffer +// BufferInfo represents Union of several values in type Buffer // that are used to service the stream depending on the type of streaming // selected (MMap, User pointer, planar, file descriptor for DMA) -type BufferService struct { +type BufferInfo struct { Offset uint32 UserPtr uintptr - Planes *PlaneInfo + Planes *Plane FD int32 } -// PlaneInfo (v4l2_plane) -// Represents a plane in a multi-planar buffers +// Plane (see struct v4l2_plane) represents a plane in a multi-planar buffers // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/buffer.html#c.V4L.v4l2_plane // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L990 // -// PlaneInfo includes a uinion of types as shown below: -// struct v4l2_plane { -// __u32 bytesused; -// __u32 length; -// union { -// __u32 mem_offset; -// unsigned long userptr; -// __s32 fd; -// } m; -// __u32 data_offset; -// __u32 reserved[11]; -// }; -type PlaneInfo struct { +type Plane struct { BytesUsed uint32 Length uint32 - m [unsafe.Sizeof(uintptr(0))]byte // union m, cast to BufferPlaneService + Info PlaneInfo // union m DataOffset uint32 - Reserved [11]uint32 -} - -func (p PlaneInfo) GetService() PlaneService { - m := (*PlaneService)(unsafe.Pointer(&p.m[0])) - return *m } -// PlaneService representes the combination of type +// PlaneInfo representes the combination of type // of type of memory stream that can be serviced for the // associated plane. -type PlaneService struct { +type PlaneInfo struct { MemOffset uint32 UserPtr uintptr FD int32 @@ -158,7 +127,7 @@ type PlaneService struct { // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-streamon.html func StreamOn(fd uintptr) error { bufType := BufTypeVideoCapture - if err := Send(fd, VidiocStreamOn, uintptr(unsafe.Pointer(&bufType))); err != nil { + if err := send(fd, C.VIDIOC_STREAMON, uintptr(unsafe.Pointer(&bufType))); err != nil { return fmt.Errorf("stream on: %w", err) } return nil @@ -169,91 +138,95 @@ func StreamOn(fd uintptr) error { // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-streamon.html func StreamOff(fd uintptr) error { bufType := BufTypeVideoCapture - if err := Send(fd, VidiocStreamOff, uintptr(unsafe.Pointer(&bufType))); err != nil { + if err := send(fd, C.VIDIOC_STREAMOFF, uintptr(unsafe.Pointer(&bufType))); err != nil { return fmt.Errorf("stream off: %w", err) } return nil } -// AllocateBuffers sends buffer allocation request to underlying driver +// InitBuffers sends buffer allocation request to initialize buffer IO // for video capture when using either mem map, user pointer, or DMA buffers. -func AllocateBuffers(fd uintptr, buffSize uint32) (RequestBuffers, error) { - req := RequestBuffers{ - Count: buffSize, - StreamType: BufTypeVideoCapture, - Memory: StreamMemoryTypeMMAP, - } - - if err := Send(fd, VidiocReqBufs, uintptr(unsafe.Pointer(&req))); err != nil { +// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-reqbufs.html#vidioc-reqbufs +func InitBuffers(fd uintptr, buffSize uint32) (RequestBuffers, error) { + var req C.struct_v4l2_requestbuffers + req.count = C.uint(buffSize) + req._type = C.uint(BufTypeVideoCapture) + req.memory = C.uint(StreamTypeMMAP) + + if err := send(fd, C.VIDIOC_REQBUFS, uintptr(unsafe.Pointer(&req))); err != nil { return RequestBuffers{}, fmt.Errorf("request buffers: %w", err) } - if req.Count < 2 { + if req.count < 2 { return RequestBuffers{}, errors.New("request buffers: insufficient memory on device") } - return req, nil + return *(*RequestBuffers)(unsafe.Pointer(&req)), nil } -// GetBuffersInfo retrieves information for allocated buffers at provided index. +// GetBuffer retrieves bunffer info for allocated buffers at provided index. // This call should take place after buffers are allocated (for mmap for instance). -func GetBufferInfo(fd uintptr, index uint32) (BufferInfo, error) { - buf := BufferInfo{ - StreamType: BufTypeVideoCapture, - Memory: StreamMemoryTypeMMAP, - Index: index, - } - - if err := Send(fd, VidiocQueryBuf, uintptr(unsafe.Pointer(&buf))); err != nil { - return BufferInfo{}, fmt.Errorf("buffer info: %w", err) +func GetBuffer(fd uintptr, index uint32) (Buffer, error) { + var v4l2Buf C.struct_v4l2_buffer + v4l2Buf._type = C.uint(BufTypeVideoCapture) + v4l2Buf.memory = C.uint(StreamTypeMMAP) + v4l2Buf.index = C.uint(index) + + if err := send(fd, C.VIDIOC_QUERYBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil { + return Buffer{}, fmt.Errorf("query buffer: %w", err) } - return buf, nil + return makeBuffer(v4l2Buf), nil } // MapMemoryBuffer creates a local buffer mapped to the address space of the device specified by fd. func MapMemoryBuffer(fd uintptr, offset int64, len int) ([]byte, error) { - return sys.Mmap(int(fd), offset, len, sys.PROT_READ|sys.PROT_WRITE, sys.MAP_SHARED) + data, err := sys.Mmap(int(fd), offset, len, sys.PROT_READ|sys.PROT_WRITE, sys.MAP_SHARED) + if err != nil { + return nil, fmt.Errorf("map memory buffer: %w", err) + } + return data, nil } // UnmapMemoryBuffer removes the buffer that was previously mapped. func UnmapMemoryBuffer(buf []byte) error { - return sys.Munmap(buf) + if err := sys.Munmap(buf); err != nil { + return fmt.Errorf("unmap memory buffer: %w", err) + } + return nil } -// QueueBuffer enqueues a buffer in the device driver (empty for capturing, filled for video output) -// when using either memory map, user pointer, or DMA buffers. BufferInfo is returned with +// QueueBuffer enqueues a buffer in the device driver (as empty for capturing, or filled for video output) +// when using either memory map, user pointer, or DMA buffers. Buffer is returned with // additional information about the queued buffer. // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-qbuf.html#vidioc-qbuf -func QueueBuffer(fd uintptr, index uint32) (BufferInfo, error) { - buf := BufferInfo{ - StreamType: BufTypeVideoCapture, - Memory: StreamMemoryTypeMMAP, - Index: index, - } - - if err := Send(fd, VidiocQueueBuf, uintptr(unsafe.Pointer(&buf))); err != nil { - return BufferInfo{}, fmt.Errorf("buffer queue: %w", err) +func QueueBuffer(fd uintptr, index uint32) (Buffer, error) { + var v4l2Buf C.struct_v4l2_buffer + v4l2Buf._type = C.uint(BufTypeVideoCapture) + v4l2Buf.memory = C.uint(StreamTypeMMAP) + v4l2Buf.index = C.uint(index) + + if err := send(fd, C.VIDIOC_QBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil { + return Buffer{}, fmt.Errorf("buffer queue: %w", err) } - return buf, nil + return makeBuffer(v4l2Buf), nil } // DequeueBuffer dequeues a buffer in the device driver, marking it as consumed by the application, -// when using either memory map, user pointer, or DMA buffers. BufferInfo is returned with +// when using either memory map, user pointer, or DMA buffers. Buffer is returned with // additional information about the dequeued buffer. // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-qbuf.html#vidioc-qbuf -func DequeueBuffer(fd uintptr) (BufferInfo, error) { - buf := BufferInfo{ - StreamType: BufTypeVideoCapture, - Memory: StreamMemoryTypeMMAP, - } +func DequeueBuffer(fd uintptr) (Buffer, error) { + var v4l2Buf C.struct_v4l2_buffer + v4l2Buf._type = C.uint(BufTypeVideoCapture) + v4l2Buf.memory = C.uint(StreamTypeMMAP) - if err := Send(fd, VidiocDequeueBuf, uintptr(unsafe.Pointer(&buf))); err != nil { - return BufferInfo{}, fmt.Errorf("buffer dequeue: %w", err) + if err := send(fd, C.VIDIOC_DQBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil { + return Buffer{}, fmt.Errorf("buffer dequeue: %w", err) } - return buf, nil + return makeBuffer(v4l2Buf), nil } // WaitForDeviceRead blocks until the specified device is diff --git a/v4l2/timing.go b/v4l2/timing.go index f573408..1d268a5 100644 --- a/v4l2/timing.go +++ b/v4l2/timing.go @@ -1,16 +1,19 @@ package v4l2 +// #include +import "C" + // TimecodeType // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/buffer.html?highlight=v4l2_timecode#timecode-type // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L886 type TimecodeType = uint32 const ( - TimecodeType24FPS TimecodeType = iota + 1 // V4L2_TC_TYPE_24FPS - TimecodeType25FPS // V4L2_TC_TYPE_25FPS - TimecodeType30FPS // V4L2_TC_TYPE_30FPS - TimecodeType50FPS // V4L2_TC_TYPE_50FPS - TimecodeType60FPS // V4L2_TC_TYPE_60FPS + TimecodeType24FPS TimecodeType = C.V4L2_TC_TYPE_24FPS + TimecodeType25FPS TimecodeType = C.V4L2_TC_TYPE_25FPS + TimecodeType30FPS TimecodeType = C.V4L2_TC_TYPE_30FPS + TimecodeType50FPS TimecodeType = C.V4L2_TC_TYPE_50FPS + TimecodeType60FPS TimecodeType = C.V4L2_TC_TYPE_60FPS ) // TimecodeFlag @@ -19,19 +22,19 @@ const ( type TimecodeFlag = uint32 const ( - TimecodeFlagDropFrame TimecodeFlag = 0x0001 // V4L2_TC_FLAG_DROPFRAME 0x0001 - TimecodeFlagColorFrame TimecodeFlag = 0x0002 // V4L2_TC_FLAG_COLORFRAME 0x0002 + TimecodeFlagDropFrame TimecodeFlag = C.V4L2_TC_FLAG_DROPFRAME + TimecodeFlagColorFrame TimecodeFlag = C.V4L2_TC_FLAG_COLORFRAME ) // Timecode (v4l2_timecode) // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/buffer.html?highlight=v4l2_timecode#c.V4L.v4l2_timecode // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L875 type Timecode struct { - Type TimecodeType - Flags TimecodeFlag - Frames uint8 - Seconds uint8 - Minutes uint8 - Hours uint8 - Userbits [4]uint8 + Type TimecodeType + Flags TimecodeFlag + Frames uint8 + Seconds uint8 + Minutes uint8 + Hours uint8 + _ [4]uint8 // userbits } diff --git a/v4l2/types.go b/v4l2/types.go index 01e5423..ac4f7ee 100644 --- a/v4l2/types.go +++ b/v4l2/types.go @@ -1,2 +1,25 @@ package v4l2 +import ( + "fmt" +) + +type VersionInfo struct { + value uint32 +} + +func (v VersionInfo) Major() uint32{ + return v.value >> 16 +} + +func (v VersionInfo) Minor() uint32{ + return (v.value>>8)&0xff +} + +func (v VersionInfo) Patch() uint32{ + return v.value&0xff +} + +func (v VersionInfo) String() string { + return fmt.Sprintf("v%d.%d.%d", v.Major(), v.Minor(), v.Patch()) +} \ No newline at end of file diff --git a/v4l2/video_info.go b/v4l2/video_info.go index de3d955..fa29b93 100644 --- a/v4l2/video_info.go +++ b/v4l2/video_info.go @@ -1,9 +1,13 @@ package v4l2 +// #include +import "C" + import ( "fmt" - sys "golang.org/x/sys/unix" "unsafe" + + sys "golang.org/x/sys/unix" ) // InputStatus @@ -11,9 +15,9 @@ import ( type InputStatus = uint32 var ( - InputStatusNoPower = InputStatus(0x00000001) // V4L2_IN_ST_NO_POWER - InputStatusNoSignal = InputStatus(0x00000002) // V4L2_IN_ST_NO_SIGNAL - InputStatusNoColor = InputStatus(0x00000004) // V4L2_IN_ST_NO_COLOR + InputStatusNoPower InputStatus = C.V4L2_IN_ST_NO_POWER + InputStatusNoSignal InputStatus = C.V4L2_IN_ST_NO_SIGNAL + InputStatusNoColor InputStatus = C.V4L2_IN_ST_NO_COLOR ) var InputStatuses = map[InputStatus]string{ @@ -33,63 +37,50 @@ const ( type StandardId = uint64 -type v4l2InputInfo struct { - index uint32 - name [32]uint8 - inputType InputType - audioset uint32 - tuner uint32 - std StandardId - status InputStatus - capabilities uint32 - reserved [3]uint32 - _ [4]uint8 // go compiler alignment adjustment for 32-bit platforms (Raspberry pi's, etc) -} - // InputInfo (v4l2_input) // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1649 // https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-enuminput.html type InputInfo struct { - v4l2InputInfo + v4l2Input C.struct_v4l2_input } func (i InputInfo) GetIndex() uint32 { - return i.index + return uint32(i.v4l2Input.index) } func (i InputInfo) GetName() string { - return toGoString(i.name[:]) + return C.GoString((*C.char)(&i.v4l2Input.name[0])) } func (i InputInfo) GetInputType() InputType { - return i.inputType + return InputType(i.v4l2Input._type) } func (i InputInfo) GetAudioset() uint32 { - return i.audioset + return uint32(i.v4l2Input.audioset) } func (i InputInfo) GetTuner() uint32 { - return i.tuner + return uint32(i.v4l2Input.tuner) } func (i InputInfo) GetStandardId() StandardId { - return i.std + return StandardId(i.v4l2Input.std) } func (i InputInfo) GetStatus() uint32 { - return i.status + return uint32(i.v4l2Input.status) } func (i InputInfo) GetCapabilities() uint32 { - return i.capabilities + return uint32(i.v4l2Input.capabilities) } // GetCurrentVideoInputIndex returns the currently selected video input index // See https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-input.html func GetCurrentVideoInputIndex(fd uintptr) (int32, error) { var index int32 - if err := Send(fd, VidiocGetVideoInput, uintptr(unsafe.Pointer(&index))); err != nil { + if err := send(fd, C.VIDIOC_G_INPUT, uintptr(unsafe.Pointer(&index))); err != nil { return -1, fmt.Errorf("video input get: %w", err) } return index, nil @@ -98,11 +89,12 @@ func GetCurrentVideoInputIndex(fd uintptr) (int32, error) { // GetVideoInputInfo returns specified input information for video device // See https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-enuminput.html func GetVideoInputInfo(fd uintptr, index uint32) (InputInfo, error) { - input := v4l2InputInfo{index: index} - if err := Send(fd, VidiocEnumInput, uintptr(unsafe.Pointer(&input))); err != nil { + var input C.struct_v4l2_input + input.index = C.uint(index) + if err := send(fd, C.VIDIOC_ENUMINPUT, uintptr(unsafe.Pointer(&input))); err != nil { return InputInfo{}, fmt.Errorf("video input info: index %d: %w", index, err) } - return InputInfo{v4l2InputInfo: input}, nil + return InputInfo{v4l2Input: input}, nil } // GetAllVideoInputInfo returns all input information for device by @@ -110,15 +102,16 @@ func GetVideoInputInfo(fd uintptr, index uint32) (InputInfo, error) { func GetAllVideoInputInfo(fd uintptr) (result []InputInfo, err error) { index := uint32(0) for { - input := v4l2InputInfo{index: index} - if err = Send(fd, VidiocEnumInput, uintptr(unsafe.Pointer(&input))); err != nil { + var input C.struct_v4l2_input + input.index = C.uint(index) + if err = send(fd, C.VIDIOC_ENUMINPUT, uintptr(unsafe.Pointer(&input))); err != nil { errno := err.(sys.Errno) if errno.Is(sys.EINVAL) && len(result) > 0 { break } return result, fmt.Errorf("all video info: %w", err) } - result = append(result, InputInfo{v4l2InputInfo: input}) + result = append(result, InputInfo{v4l2Input: input}) index++ } return result, err