Explorar o código

Breaking refactor of device and other types; new control commands

Vladimir Vivien %!s(int64=3) %!d(string=hai) anos
pai
achega
b92e29166f

+ 6 - 6
examples/capture/capture.go

@@ -54,29 +54,29 @@ func main() {
 		log.Fatalf("device does not support any of %#v", preferredFmts)
 	}
 	log.Printf("Found preferred fmt: %s", fmtDesc)
-	frameSizes, err := v4l2.GetFormatFrameSizes(device.GetFileDescriptor(), fmtDesc.PixelFormat)
+	frameSizes, err := v4l2.GetFormatFrameSizes(device.FileDescriptor(), fmtDesc.PixelFormat)
 	if err!=nil{
 		log.Fatalf("failed to get framesize info: %s", err)
 	}
 
 	// select size 640x480 for format
-	var frmSize v4l2.FrameSize
+	var frmSize v4l2.FrameSizeEnum
 	for _, size := range frameSizes {
-		if size.Width == 640 && size.Height == 480 {
+		if size.Size.MinWidth == 640 && size.Size.MinHeight == 480 {
 			frmSize = size
 			break
 		}
 	}
 
-	if frmSize.Width == 0 {
+	if frmSize.Size.MinWidth == 0 {
 		log.Fatalf("Size 640x480 not supported for fmt: %s", fmtDesc)
 	}
 
 	// configure device with preferred fmt
 
 	if err := device.SetPixFormat(v4l2.PixFormat{
-		Width:       frmSize.Width,
-		Height:      frmSize.Height,
+		Width:       frmSize.Size.MinWidth,
+		Height:      frmSize.Size.MinHeight,
 		PixelFormat: fmtDesc.PixelFormat,
 		Field:       v4l2.FieldNone,
 	}); err != nil {

+ 22 - 27
examples/device_info/devinfo.go

@@ -21,7 +21,7 @@ func main() {
 	flag.Parse()
 
 	if devList {
-		if err := listDevices(); err != nil{
+		if err := listDevices(); err != nil {
 			log.Fatal(err)
 		}
 		os.Exit(0)
@@ -67,22 +67,21 @@ func listDevices() error {
 		}
 
 		var busInfo, card string
-		cap, err := dev.GetCapability()
-		if err != nil {
-			// is a media device?
-			if mdi, err := dev.GetMediaInfo(); err == nil {
-				if mdi.BusInfo != "" {
-					busInfo = mdi.BusInfo
-				}else{
-					busInfo = "platform: " + mdi.Driver
-				}
-				if mdi.Model != "" {
-					card = mdi.Model
-				}else{
-					card = mdi.Driver
-				}
+		cap := dev.Capability()
+
+		// is a media device?
+		if mdi, err := dev.GetMediaInfo(); err == nil {
+			if mdi.BusInfo != "" {
+				busInfo = mdi.BusInfo
+			} else {
+				busInfo = "platform: " + mdi.Driver
+			}
+			if mdi.Model != "" {
+				card = mdi.Model
+			} else {
+				card = mdi.Driver
 			}
-		}else{
+		} else {
 			busInfo = cap.BusInfo
 			card = cap.Card
 		}
@@ -95,16 +94,12 @@ func listDevices() error {
 
 		fmt.Printf("Device [%s]: %s: %s\n", path, card, busInfo)
 
-
 	}
 	return nil
 }
 
 func printDeviceDriverInfo(dev *device.Device) error {
-	caps, err := dev.GetCapability()
-	if err != nil {
-		return fmt.Errorf("driver info: %w", err)
-	}
+	caps := dev.Capability()
 
 	// print driver info
 	fmt.Println("Device Info:")
@@ -196,15 +191,15 @@ func printFormatDesc(dev *device.Device) error {
 		return fmt.Errorf("format desc: %w", err)
 	}
 	fmt.Println("Supported formats:")
-	for i, desc := range descs{
-		frmSizes, err := v4l2.GetFormatFrameSizes(dev.GetFileDescriptor(), desc.PixelFormat)
+	for i, desc := range descs {
+		frmSizes, err := v4l2.GetFormatFrameSizes(dev.FileDescriptor(), desc.PixelFormat)
 		if err != nil {
 			return fmt.Errorf("format desc: %w", err)
 		}
 		var sizeStr strings.Builder
 		sizeStr.WriteString("Sizes: ")
-		for _, size := range frmSizes{
-			sizeStr.WriteString(fmt.Sprintf("[%dx%d] ", size.Width, size.Height))
+		for _, size := range frmSizes {
+			sizeStr.WriteString(fmt.Sprintf("[%dx%d] ", size.Size.MinWidth, size.Size.MinHeight))
 		}
 		fmt.Printf(template, fmt.Sprintf("[%0d] %s", i, desc.Description), sizeStr.String())
 	}
@@ -258,6 +253,6 @@ func printCaptureParam(dev *device.Device) error {
 	fmt.Printf(template, "Capture mode", hiqual)
 
 	fmt.Printf(template, "Frames per second", fmt.Sprintf("%d/%d", params.TimePerFrame.Denominator, params.TimePerFrame.Numerator))
-	fmt.Printf(template, "Read buffers", fmt.Sprintf("%d",params.ReadBuffers))
+	fmt.Printf(template, "Read buffers", fmt.Sprintf("%d", params.ReadBuffers))
 	return nil
-}
+}

+ 12 - 8
examples/webcam/webcam.go

@@ -54,6 +54,11 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 	w.WriteHeader(http.StatusOK)
 
 	for frame := range frames {
+		if len(frame) == 0{
+			log.Print("skipping empty frame")
+			continue
+		}
+
 		// start boundary
 		io.WriteString(w, fmt.Sprintf("--%s\n", boundaryName))
 		io.WriteString(w, "Content-Type: image/jpeg\n")
@@ -63,7 +68,7 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 		switch pixfmt {
 		case v4l2.PixelFmtMJPEG:
 			if _, err := w.Write(frame); err != nil {
-				log.Printf("failed to write image: %s", err)
+				log.Printf("failed to write mjpeg image: %s", err)
 				return
 			}
 		case v4l2.PixelFmtYUYV:
@@ -73,7 +78,7 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 				continue
 			}
 			if _, err := w.Write(data); err != nil {
-				log.Printf("failed to write image: %s", err)
+				log.Printf("failed to write yuyv image: %s", err)
 				return
 			}
 		}
@@ -88,6 +93,7 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 func main() {
 	port := ":9090"
 	devName := "/dev/video0"
+	frameRate := int(fps)
 	defaultDev, err := device.Open(devName)
 	skipDefault := false
 	if err != nil {
@@ -118,11 +124,12 @@ func main() {
 	flag.IntVar(&height, "h", height, "capture height")
 	flag.StringVar(&format, "f", format, "pixel format")
 	flag.StringVar(&port, "p", port, "webcam service port")
+	flag.IntVar(&frameRate, "r", frameRate, "frames per second (fps)")
 	flag.Parse()
 
 	// close device used for default info
 	if err := defaultDev.Close(); err != nil {
-		// default device failed to close
+		log.Fatalf("failed to close default device: %s", err)
 	}
 
 	// open device and setup device
@@ -131,10 +138,7 @@ func main() {
 		log.Fatalf("failed to open device: %s", err)
 	}
 	defer device.Close()
-	caps, err := device.GetCapability()
-	if err != nil {
-		log.Println("failed to get device capabilities:", err)
-	}
+	caps := device.Capability()
 	log.Printf("device [%s] opened\n", devName)
 	log.Printf("device info: %s", caps.String())
 
@@ -161,7 +165,7 @@ func main() {
 
 	// start capture
 	ctx, cancel := context.WithCancel(context.TODO())
-	f, err := device.Capture(ctx, fps)
+	f, err := device.Capture(ctx, uint32(frameRate))
 	if err != nil {
 		log.Fatalf("stream capture: %s", err)
 	}

+ 17 - 9
v4l2/capability.go

@@ -135,44 +135,52 @@ func GetCapability(fd uintptr) (Capability, error) {
 	}, nil
 }
 
+// GetCapabilities returns device capabilities if supported
+func (c Capability) GetCapabilities() uint32 {
+	if c.IsDeviceCapabilitiesProvided() {
+		return c.DeviceCapabilities
+	}
+	return c.Capabilities
+}
+
 // IsVideoCaptureSupported returns caps & CapVideoCapture
 func (c Capability) IsVideoCaptureSupported() bool {
-	return (uint32(c.Capabilities) & CapVideoCapture) != 0
+	return c.Capabilities & CapVideoCapture != 0
 }
 
 // IsVideoOutputSupported returns caps & CapVideoOutput
 func (c Capability) IsVideoOutputSupported() bool {
-	return (uint32(c.Capabilities) & CapVideoOutput) != 0
+	return c.Capabilities & CapVideoOutput != 0
 }
 
 // IsVideoOverlaySupported returns caps & CapVideoOverlay
 func (c Capability) IsVideoOverlaySupported() bool {
-	return (uint32(c.Capabilities) & CapVideoOverlay) != 0
+	return c.Capabilities & CapVideoOverlay != 0
 }
 
 // IsVideoOutputOverlaySupported returns caps & CapVideoOutputOverlay
 func (c Capability) IsVideoOutputOverlaySupported() bool {
-	return (uint32(c.Capabilities) & CapVideoOutputOverlay) != 0
+	return c.Capabilities & CapVideoOutputOverlay != 0
 }
 
 // IsVideoCaptureMultiplanarSupported returns caps & CapVideoCaptureMPlane
 func (c Capability) IsVideoCaptureMultiplanarSupported() bool {
-	return (uint32(c.Capabilities) & CapVideoCaptureMPlane) != 0
+	return c.Capabilities & CapVideoCaptureMPlane != 0
 }
 
 // IsVideoOutputMultiplanerSupported returns caps & CapVideoOutputMPlane
 func (c Capability) IsVideoOutputMultiplanerSupported() bool {
-	return (uint32(c.Capabilities) & CapVideoOutputMPlane) != 0
+	return c.Capabilities & CapVideoOutputMPlane != 0
 }
 
 // IsReadWriteSupported returns caps & CapReadWrite
 func (c Capability) IsReadWriteSupported() bool {
-	return (uint32(c.Capabilities) & CapReadWrite) != 0
+	return c.Capabilities & CapReadWrite != 0
 }
 
 // IsStreamingSupported returns caps & CapStreaming
 func (c Capability) IsStreamingSupported() bool {
-	return (uint32(c.Capabilities) & CapStreaming) != 0
+	return c.Capabilities & CapStreaming != 0
 }
 
 // IsDeviceCapabilitiesProvided returns true if the device returns
@@ -180,7 +188,7 @@ 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 (uint32(c.Capabilities) & CapDeviceCapabilities) != 0
+	return c.Capabilities & CapDeviceCapabilities != 0
 }
 
 // GetDriverCapDescriptions return textual descriptions of driver capabilities

+ 42 - 0
v4l2/control.go

@@ -0,0 +1,42 @@
+package v4l2
+
+//#include <linux/videodev2.h>
+import "C"
+import (
+	"fmt"
+	"unsafe"
+)
+
+// Control (v4l2_control)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1725
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ctrl.html
+type Control struct {
+	ID    uint32
+	Value uint32
+}
+
+func GetControl(fd uintptr, id uint32) (Control, error) {
+	var ctrl C.struct_v4l2_control
+	ctrl.id = C.uint(id)
+
+	if err := send(fd, C.VIDIOC_G_CTRL, uintptr(unsafe.Pointer(&ctrl))); err != nil {
+		return Control{}, fmt.Errorf("get control: id %d: %w", id, err)
+	}
+
+	return Control{
+		ID:    uint32(ctrl.id),
+		Value: uint32(ctrl.value),
+	}, nil
+}
+
+func SetControl(fd uintptr, id, value uint32) error {
+	var ctrl C.struct_v4l2_control
+	ctrl.id = C.uint(id)
+	ctrl.value = C.int(value)
+
+	if err := send(fd, C.VIDIOC_G_CTRL, uintptr(unsafe.Pointer(&ctrl))); err != nil {
+		return fmt.Errorf("set control: id %d: value: %d: %w", id, value, err)
+	}
+
+	return nil
+}

+ 97 - 0
v4l2/controls.go

@@ -0,0 +1,97 @@
+package v4l2
+
+//#include <linux/videodev2.h>
+import "C"
+import (
+	"fmt"
+	"unsafe"
+)
+
+// TODO - Implementation of extended controls (v4l2_ext_control) is paused for now,
+// so that efforts can be focused on other parts of the API. This can resumed
+// later when type v4l2_ext_control and v4l2_ext_controls are better understood.
+
+// ExtControl (v4l2_ext_control)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1730
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ext-ctrls.html
+type ExtControl struct {
+	ID   uint32
+	Size uint32
+	Ctrl ExtControlUnion
+}
+
+type ExtControlUnion struct {
+	Value              int32
+	Value64            int64
+	String             string
+	PU8                uint8
+	PU16               uint16
+	PU32               uint32
+	PArea              Area
+	PH264SPS           ControlH264SPS
+	PH264PPS           ControlH264PPS
+	PH264ScalingMatrix ControlH264ScalingMatrix
+	H264PredWeights    ControlH264PredictionWeights
+	PH264SliceParams   ControlH264SliceParams
+	PH264DecodeParams  ControlH264DecodeParams
+	PFWHTParams        ControlFWHTParams
+	PVP8Frame          ControlVP8Frame
+	PMPEG2Sequence     ControlMPEG2Sequence
+	PMPEG2Picture      ControlMPEG2Picture
+	PMPEG2Quantization ControlMPEG2Quantization
+	_                  uintptr
+}
+
+// ExtControls (v4l2_ext_controls)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1757
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-g-ext-ctrls.html
+type ExtControls struct {
+	Which      uint32
+	Count      uint32
+	ErrorIndex uint32
+	Controls   []ExtControl
+}
+
+// GetExtControls retrieve one or more controls
+func GetExtControls(fd uintptr, controls []ExtControl) (ExtControls, error) {
+	if true {
+		// TODO remove when supported
+		return ExtControls{}, fmt.Errorf("unsupported")
+	}
+
+	var ctrls C.struct_v4l2_ext_controls
+	ctrls.count = C.uint(len(controls))
+
+	// prepare control requests
+	var Cctrls []C.struct_v4l2_ext_control
+	for _, control := range controls{
+		var Cctrl C.struct_v4l2_ext_control
+		Cctrl.id = C.uint(control.ID)
+		Cctrl.size = C.uint(control.Size)
+		*(*ExtControlUnion)(unsafe.Pointer(&Cctrl.anon0[0])) = control.Ctrl
+		Cctrls = append(Cctrls, Cctrl)
+	}
+	ctrls.controls = (*C.struct_v4l2_ext_control)(unsafe.Pointer(&ctrls.controls))
+
+	if err := send(fd, C.VIDIOC_G_EXT_CTRLS, uintptr(unsafe.Pointer(&ctrls))); err != nil {
+		return ExtControls{}, fmt.Errorf("get ext controls: %w", err)
+	}
+
+	// gather returned controls
+	retCtrls := ExtControls{
+		Count:      uint32(ctrls.count),
+		ErrorIndex: uint32(ctrls.error_idx),
+	}
+	// extract controls array
+	Cctrls = *(*[]C.struct_v4l2_ext_control)(unsafe.Pointer(&ctrls.controls))
+	for _, Cctrl := range Cctrls {
+		extCtrl := ExtControl{
+			ID: uint32(Cctrl.id),
+			Size: uint32(Cctrl.size),
+			Ctrl: *(*ExtControlUnion)(unsafe.Pointer(&Cctrl.anon0[0])),
+		}
+		retCtrls.Controls = append(retCtrls.Controls, extCtrl)
+	}
+
+	return retCtrls, nil
+}

+ 15 - 0
v4l2/controls_fwht.go

@@ -0,0 +1,15 @@
+package v4l2
+
+// ControlFWHTParams (v4l2_ctrl_fwht_params)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1659
+type ControlFWHTParams struct {
+	BackwardRefTimestamp uint64
+	Version              uint32
+	Width                uint32
+	Height               uint32
+	Flags                uint32
+	Colorspace           ColorspaceType
+	XFerFunc             XferFunctionType
+	YCbCrEncoding        YCbCrEncodingType
+	Quantization         QuantizationType
+}

+ 135 - 0
v4l2/controls_h264.go

@@ -0,0 +1,135 @@
+package v4l2
+
+import "C"
+
+// TODO - Need to figure out how to import the proper header files for H264 support
+const (
+	H264NumDPBEntries uint32 = 16 // C.V4L2_H264_NUM_DPB_ENTRIES
+	H264RefListLength uint32 = 32 // C.V4L2_H264_REF_LIST_LEN
+)
+
+// ControlH264SPS (v4l2_ctrl_h264_sps)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1308
+type ControlH264SPS struct {
+	ProfileIDC                     uint8
+	ConstraintSetFlags             uint8
+	LevelIDC                       uint8
+	SequenceParameterSetID         uint8
+	ChromaFormatIDC                uint8
+	BitDepthLumaMinus8             uint8
+	BitDepthChromaMinus8           uint8
+	Log2MaxFrameNumMinus4          uint8
+	PicOrderCntType                uint8
+	Log2MaxPicOrderCntLsbMinus4    uint8
+	MaxNumRefFrames                uint8
+	NumRefFramesInPicOrderCntCycle uint8
+	OffsetForRefFrame              [255]int32
+	OffsetForNonRefPic             int32
+	OffsetForTopToBottomField      int32
+	PicWidthInMbsMinus1            uint16
+	PicHeightInMapUnitsMinus1      uint16
+	Falgs                          uint32
+}
+
+// ControlH264PPS (v4l2_ctrl_h264_pps)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1364
+type ControlH264PPS struct {
+	PicParameterSetID                uint8
+	SeqParameterSetID                uint8
+	NumSliceGroupsMinus1             uint8
+	NumRefIndexL0DefaultActiveMinus1 uint8
+	NumRefIndexL1DefaultActiveMinus1 uint8
+	WeightedBipredIDC                uint8
+	PicInitQPMinus26                 int8
+	PicInitQSMinus26                 int8
+	ChromaQPIndexOffset              int8
+	SecondChromaQPIndexOffset        int8
+	Flags                            uint16
+}
+
+// ControlH264ScalingMatrix (v4l2_ctrl_h264_scaling_matrix)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1396
+type ControlH264ScalingMatrix struct {
+	ScalingList4x4 [6][16]uint8
+	ScalingList8x8 [6][64]uint8
+}
+
+type H264WeightFators struct {
+	LumaWeight   [32]int16
+	LumaOffset   [32]int16
+	ChromaWeight [32][2]int16
+	ChromaOffset [32][2]int16
+}
+
+// ControlH264PredictionWeights (4l2_ctrl_h264_pred_weights)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1426
+type ControlH264PredictionWeights struct {
+	LumaLog2WeightDenom   uint16
+	ChromaLog2WeightDenom uint16
+	WeightFactors         [2]H264WeightFators
+}
+
+// H264Reference (v4l2_h264_reference)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1452
+type H264Reference struct {
+	Fields uint8
+	Index  uint8
+}
+
+// ControlH264SliceParams (v4l2_ctrl_h264_slice_params)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1499
+type ControlH264SliceParams struct {
+	HeaderBitSize              uint32
+	FirstMBInSlice             uint32
+	SliceType                  uint8
+	ColorPlaneID               uint8
+	RedundantPicCnt            uint8
+	CabacInitIDC               uint8
+	SliceQPDelta               int8
+	SliceQSDelta               int8
+	DisableDeblockingFilterIDC uint8
+	SliceAlphaC0OffsetDiv2     int8
+	SliceBetaOffsetDiv2        int8
+	NumRefIdxL0ActiveMinus1    uint8
+	NumRefIdxL1ActiveMinus1    uint8
+
+	_ uint8 // reserved for padding
+
+	RefPicList0 [H264RefListLength]H264Reference
+	RefPicList1 [H264RefListLength]H264Reference
+
+	Flags uint32
+}
+
+// H264DPBEntry (v4l2_h264_dpb_entry)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1544
+type H264DPBEntry struct {
+	ReferenceTS         uint64
+	PicNum              uint32
+	FrameNum            uint16
+	Fields              uint8
+	_                   [8]uint8 // reserved (padding field)
+	TopFieldOrder       int32
+	BottomFieldOrderCnt int32
+	Flags               uint32
+}
+
+// ControlH264DecodeParams (v4l2_ctrl_h264_decode_params)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1581
+type ControlH264DecodeParams struct {
+	DPB                     [H264NumDPBEntries]H264DPBEntry
+	NalRefIDC               uint16
+	FrameNum                uint16
+	TopFieldOrderCnt        int32
+	BottomFieldOrderCnt     int32
+	IDRPicID                uint16
+	PicOrderCntLSB          uint16
+	DeltaPicOrderCntBottom  int32
+	DeltaPicOrderCnt0       int32
+	DeltaPicOrderCnt1       int32
+	DecRefPicMarkingBitSize uint32
+	PicOrderCntBitSize      uint32
+	SliceGroupChangeCycle   uint32
+	_                       uint32 // reserved (padding)
+	Flags                   uint32
+}

+ 34 - 0
v4l2/controls_mpeg2.go

@@ -0,0 +1,34 @@
+package v4l2
+
+// ControlMPEG2Sequence (v4l2_ctrl_mpeg2_sequence)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1892
+type ControlMPEG2Sequence struct {
+	HorizontalSize            uint16
+	VerticalSize              uint16
+	VBVBufferSize             uint32
+	ProfileAndLevelIndication uint16
+	ChromaFormat              uint8
+	Flags                     uint8
+}
+
+// ControlMPEG2Picture (v4l2_ctrl_mpeg2_picture)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1939
+type ControlMPEG2Picture struct {
+	BackwardRefTimestamp uint64
+	ForwardRefTimestamp  uint64
+	Flags                uint32
+	FCode                [2][2]uint8
+	PictureCodingType    uint8
+	PictureStructure     uint8
+	IntraDCPrecision     uint8
+	_                    [5]uint8 // padding
+}
+
+// ControlMPEG2Quantization (v4l2_ctrl_mpeg2_quantisation)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1972
+type ControlMPEG2Quantization struct {
+	IntraQuantizerMatrix [64]uint8
+	NonIntraQuantizerMatrix [64]uint8
+	ChromaIntraQuantizerMatrix [64]uint8
+	ChromaNonIntraQuantizerMatrix [64]uint8
+}

+ 94 - 0
v4l2/controls_vp8.go

@@ -0,0 +1,94 @@
+package v4l2
+
+//#include <linux/v4l2-controls.h>
+import "C"
+
+const (
+	VP8CoefficientProbabilityCount uint32 = 11 // C.V4L2_VP8_COEFF_PROB_CNT
+	VP8MVProbabilityCount          uint32 = 19 // C.V4L2_VP8_MV_PROB_CNT
+)
+
+// VP8Segment (v4l2_vp8_segment)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1692
+type VP8Segment struct {
+	QuantUpdate          [4]int8
+	LoopFilterUpdate     [4]int8
+	SegmentProbabilities [3]uint8
+	_                    uint8 // padding
+	Flags                uint32
+}
+
+// VP8LoopFilter (v4l2_vp8_loop_filter)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1719
+type VP8LoopFilter struct {
+	ReferenceFrameDelta int8
+	MBModeDelta         int8
+	SharpnessLevel      uint8
+	Level               uint8
+	_                   uint16 // padding
+	Flags               uint32
+}
+
+// VP8Quantization (v4l2_vp8_quantization)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1744
+type VP8Quantization struct {
+	YACQIndex uint8
+	YDCDelta  int8
+	Y2DCDelta int8
+	Y2ACDelta int8
+	UVDCDelta int8
+	UVACDelta int8
+	_         uint16
+}
+
+// VP8Entropy (v4l2_vp8_entropy)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1771
+type VP8Entropy struct {
+	CoefficientProbabilities [4][8][3][VP8CoefficientProbabilityCount]uint8
+	YModeProbabilities       uint8
+	UVModeProbabilities      uint8
+	MVProbabilities          [2][VP8MVProbabilityCount]uint8
+	_                        [3]uint8 // padding
+}
+
+// VP8EntropyCoderState (v4l2_vp8_entropy_coder_state)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1790
+type VP8EntropyCoderState struct {
+	Range    uint8
+	Value    uint8
+	BitCount uint8
+	_        uint8 // padding
+}
+
+// ControlVP8Frame (v4l2_ctrl_vp8_frame)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/v4l2-controls.h#L1836
+type ControlVP8Frame struct {
+	Segment           VP8Segment
+	LoopFilter        VP8LoopFilter
+	Quantization      VP8Quantization
+	Entropy           VP8Entropy
+	EntropyCoderState VP8EntropyCoderState
+
+	Width  uint16
+	Height uint16
+
+	HorizontalScale uint8
+	VerticalScale   uint8
+
+	Version       uint8
+	ProbSkipFalse uint8
+	PropIntra     uint8
+	PropLast      uint8
+	ProbGF        uint8
+	NumDCTParts   uint8
+
+	FirstPartSize   uint32
+	FirstPartHeader uint32
+	DCTPartSize     uint32
+
+	LastFrameTimestamp   uint64
+	GoldenFrameTimestamp uint64
+	AltFrameTimestamp    uint64
+
+	Flags uint64
+}

+ 0 - 18
v4l2/crop.go

@@ -8,24 +8,6 @@ import (
 	"unsafe"
 )
 
-// Rect (v4l2_rect)
-// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/dev-overlay.html?highlight=v4l2_rect#c.v4l2_rect
-// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L412
-type Rect struct {
-	Left   int32
-	Top    int32
-	Width  uint32
-	Height uint32
-}
-
-// Fract (v4l2_fract)
-// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/vidioc-enumstd.html#c.v4l2_fract
-// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L419
-type Fract struct {
-	Numerator   uint32
-	Denominator uint32
-}
-
 // CropCapability (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

+ 84 - 89
v4l2/device/device.go

@@ -11,31 +11,65 @@ import (
 )
 
 type Device struct {
-	path         string
-	file         *os.File
-	fd           uintptr
-	cap          *v4l2.Capability
-	cropCap      *v4l2.CropCapability
-	pixFormat    v4l2.PixFormat
-	buffers      [][]byte
-	requestedBuf v4l2.RequestBuffers
-	streaming    bool
+	path             string
+	file             *os.File
+	fd               uintptr
+	bufType          v4l2.BufType
+	cap              v4l2.Capability
+	cropCap          v4l2.CropCapability
+	selectedFormat   v4l2.PixFormat
+	supportedFormats []v4l2.PixFormat
+	buffers          [][]byte
+	requestedBuf     v4l2.RequestBuffers
+	streaming        bool
 }
 
 // Open creates opens the underlying device at specified path
 // and returns a *Device or an error if unable to open device.
 func Open(path string) (*Device, error) {
-	file, err := os.OpenFile(path, sys.O_RDWR|sys.O_NONBLOCK, 0666)
+	file, err := os.OpenFile(path, sys.O_RDWR|sys.O_NONBLOCK, 0644)
 	if err != nil {
 		return nil, fmt.Errorf("device open: %w", err)
 	}
-	return &Device{path: path, file: file, fd: file.Fd()}, nil
+	dev := &Device{path: path, file: file, fd: file.Fd()}
+
+	cap, err := v4l2.GetCapability(file.Fd())
+	if err != nil {
+		if err := file.Close(); err != nil {
+			return nil, fmt.Errorf("device %s: closing after failure: %s", path, err)
+		}
+		return nil, fmt.Errorf("device %s: capability: %w", path, err)
+	}
+	dev.cap = cap
+
+	switch {
+	case cap.IsVideoCaptureSupported():
+		dev.bufType = v4l2.BufTypeVideoCapture
+	case cap.IsVideoOutputSupported():
+		dev.bufType = v4l2.BufTypeVideoOutput
+	default:
+		if err := file.Close(); err != nil {
+			return nil, fmt.Errorf("device %s: closing after failure: %s", path, err)
+		}
+		return nil, fmt.Errorf("unsupported device type")
+	}
+
+	cropCap, err := v4l2.GetCropCapability(file.Fd())
+	if err != nil {
+		if err := file.Close(); err != nil {
+			return nil, fmt.Errorf("device %s: closing after failure: %s", path, err)
+		}
+		return nil, fmt.Errorf("device %s: crop capability: %w", path, err)
+	}
+	dev.cropCap = cropCap
+
+	return dev, nil
 }
 
 // Close closes the underlying device associated with `d` .
 func (d *Device) Close() error {
-	if d.streaming{
-		if err := d.StopStream(); err != nil{
+	if d.streaming {
+		if err := d.StopStream(); err != nil {
 			return err
 		}
 	}
@@ -43,47 +77,33 @@ func (d *Device) Close() error {
 	return d.file.Close()
 }
 
-// GetFileDescriptor returns the file descriptor value for the device
-func (d *Device) GetFileDescriptor() uintptr {
+// Name returns the device name (or path)
+func (d *Device) Name() uintptr {
 	return d.fd
 }
 
-// GetCapability retrieves device capability info and
-// caches it for future capability check.
-func (d *Device) GetCapability() (*v4l2.Capability, error) {
-	if d.cap != nil {
-		return d.cap, nil
-	}
-	cap, err := v4l2.GetCapability(d.fd)
-	if err != nil {
-		return nil, fmt.Errorf("device: %w", err)
-	}
-	d.cap = &cap
-	return d.cap, nil
+// FileDescriptor returns the file descriptor value for the device
+func (d *Device) FileDescriptor() uintptr {
+	return d.fd
 }
 
-// GetCropCapability returns cropping info for device `d`
-// and caches it for future capability check.
-func (d *Device) GetCropCapability() (v4l2.CropCapability, error) {
-	if d.cropCap != nil {
-		return *d.cropCap, nil
-	}
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return v4l2.CropCapability{}, fmt.Errorf("device: %w", err)
-	}
+// Capability returns device capability info.
+func (d *Device) Capability() v4l2.Capability {
+	return d.cap
+}
 
-	cropCap, err := v4l2.GetCropCapability(d.fd)
-	if err != nil {
-		return v4l2.CropCapability{}, fmt.Errorf("device: %w", err)
+// GetCropCapability returns cropping info for device
+func (d *Device) GetCropCapability() (v4l2.CropCapability, error) {
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.CropCapability{}, v4l2.ErrorUnsupportedFeature
 	}
-	d.cropCap = &cropCap
-	return cropCap, nil
+	return d.cropCap, nil
 }
 
 // SetCropRect crops the video dimension for the device
 func (d *Device) SetCropRect(r v4l2.Rect) error {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return fmt.Errorf("device: %w", err)
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.ErrorUnsupportedFeature
 	}
 	if err := v4l2.SetCropRect(d.fd, r); err != nil {
 		return fmt.Errorf("device: %w", err)
@@ -93,67 +113,70 @@ func (d *Device) SetCropRect(r v4l2.Rect) error {
 
 // GetPixFormat retrieves pixel format info for device
 func (d *Device) GetPixFormat() (v4l2.PixFormat, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return v4l2.PixFormat{}, fmt.Errorf("device: %w", err)
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.PixFormat{}, v4l2.ErrorUnsupportedFeature
 	}
 	pixFmt, err := v4l2.GetPixFormat(d.fd)
 	if err != nil {
 		return v4l2.PixFormat{}, fmt.Errorf("device: %w", err)
 	}
+	d.selectedFormat = pixFmt
 	return pixFmt, nil
 }
 
 // SetPixFormat sets the pixel format for the associated device.
 func (d *Device) SetPixFormat(pixFmt v4l2.PixFormat) error {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return fmt.Errorf("device: %w", err)
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.ErrorUnsupportedFeature
 	}
 
 	if err := v4l2.SetPixFormat(d.fd, pixFmt); err != nil {
 		return fmt.Errorf("device: %w", err)
 	}
+	d.selectedFormat = pixFmt
 	return nil
 }
 
 // GetFormatDescription returns a format description for the device at specified format index
 func (d *Device) GetFormatDescription(idx uint32) (v4l2.FormatDescription, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return v4l2.FormatDescription{}, fmt.Errorf("device: %w", err)
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.FormatDescription{}, v4l2.ErrorUnsupportedFeature
 	}
 
 	return v4l2.GetFormatDescription(d.fd, idx)
 }
 
-
 // GetFormatDescriptions returns all possible format descriptions for device
 func (d *Device) GetFormatDescriptions() ([]v4l2.FormatDescription, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return nil, fmt.Errorf("device: %w", err)
+	if !d.cap.IsVideoCaptureSupported() {
+		return nil, v4l2.ErrorUnsupportedFeature
 	}
 
 	return v4l2.GetAllFormatDescriptions(d.fd)
 }
 
 // GetVideoInputIndex returns current video input index for device
-func (d *Device) GetVideoInputIndex()(int32, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return 0, fmt.Errorf("device: %w", err)
+func (d *Device) GetVideoInputIndex() (int32, error) {
+	if !d.cap.IsVideoCaptureSupported() {
+		return 0, v4l2.ErrorUnsupportedFeature
 	}
+
 	return v4l2.GetCurrentVideoInputIndex(d.fd)
 }
 
 // GetVideoInputInfo returns video input info for device
 func (d *Device) GetVideoInputInfo(index uint32) (v4l2.InputInfo, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return v4l2.InputInfo{}, fmt.Errorf("device: %w", err)
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.InputInfo{}, v4l2.ErrorUnsupportedFeature
 	}
+
 	return v4l2.GetVideoInputInfo(d.fd, index)
 }
 
 // GetCaptureParam returns streaming capture parameter information
 func (d *Device) GetCaptureParam() (v4l2.CaptureParam, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return v4l2.CaptureParam{}, fmt.Errorf("device: %w", err)
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.CaptureParam{}, v4l2.ErrorUnsupportedFeature
 	}
 	return v4l2.GetStreamCaptureParam(d.fd)
 }
@@ -167,9 +190,6 @@ func (d *Device) StartStream(buffSize uint32) error {
 	if d.streaming {
 		return nil
 	}
-	if err := d.assertVideoStreamSupport(); err != nil {
-		return fmt.Errorf("device: %w", err)
-	}
 
 	// allocate device buffers
 	bufReq, err := v4l2.InitBuffers(d.fd, buffSize)
@@ -275,7 +295,7 @@ func (d *Device) Capture(ctx context.Context, fps uint32) (<-chan []byte, error)
 	return dataChan, nil
 }
 
-func (d *Device) StopStream() error{
+func (d *Device) StopStream() error {
 	d.streaming = false
 	for i := 0; i < len(d.buffers); i++ {
 		if err := v4l2.UnmapMemoryBuffer(d.buffers[i]); err != nil {
@@ -286,29 +306,4 @@ func (d *Device) StopStream() error{
 		return fmt.Errorf("device: stop stream: %w", err)
 	}
 	return nil
-}
-
-func (d *Device) assertVideoCaptureSupport() error {
-	cap, err := d.GetCapability()
-	if err != nil {
-		return fmt.Errorf("device capability: %w", err)
-	}
-	if !cap.IsVideoCaptureSupported() {
-		return fmt.Errorf("device capability: video capture not supported")
-	}
-	return nil
-}
-
-func (d *Device) assertVideoStreamSupport() error {
-	cap, err := d.GetCapability()
-	if err != nil {
-		return fmt.Errorf("device capability: %w", err)
-	}
-	if !cap.IsVideoCaptureSupported() {
-		return fmt.Errorf("device capability: video capture not supported")
-	}
-	if !cap.IsStreamingSupported() {
-		return fmt.Errorf("device capability: streaming not supported")
-	}
-	return nil
-}
+}

+ 1 - 1
v4l2/device/list.go

@@ -10,7 +10,7 @@ var (
 	root = "/dev"
 )
 
-// devPattern is device directory name pattern on Linux
+// devPattern is device directory name pattern on Linux (i.e. video0, video10, vbi0, etc)
 var devPattern = regexp.MustCompile(fmt.Sprintf(`%s/(video|radio|vbi|swradio|v4l-subdev|v4l-touch|media)[0-9]+`, root))
 
 // IsDevice tests whether the path matches a V4L device name and is a device file

+ 27 - 0
v4l2/dimension.go

@@ -0,0 +1,27 @@
+package v4l2
+
+// Area (v4l2_area)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L424
+type Area struct {
+	Width uint32
+	Height uint32
+}
+
+// Fract (v4l2_fract)
+// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/vidioc-enumstd.html#c.v4l2_fract
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L419
+type Fract struct {
+	Numerator   uint32
+	Denominator uint32
+}
+
+// Rect (v4l2_rect)
+// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/dev-overlay.html?highlight=v4l2_rect#c.v4l2_rect
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L412
+type Rect struct {
+	Left   int32
+	Top    int32
+	Width  uint32
+	Height uint32
+}
+

+ 9 - 8
v4l2/errors.go

@@ -6,21 +6,22 @@ import (
 )
 
 var (
-	ErrorSystem = errors.New("system error")
-	ErrorBadArgument = errors.New("bad argument error")
-	ErrorTemporary = errors.New("temporary error")
-	ErrorTimeout = errors.New("timeout error")
-	ErrorUnsupported = errors.New("unsupported error")
+	ErrorSystem             = errors.New("system error")
+	ErrorBadArgument        = errors.New("bad argument error")
+	ErrorTemporary          = errors.New("temporary error")
+	ErrorTimeout            = errors.New("timeout error")
+	ErrorUnsupported        = errors.New("unsupported error")
+	ErrorUnsupportedFeature = errors.New("feature unsupported error")
 )
 
 func parseErrorType(errno sys.Errno) error {
 	switch errno {
 	case sys.EBADF, sys.ENOMEM, sys.ENODEV, sys.EIO, sys.ENXIO, sys.EFAULT: // structural, terminal
-	return ErrorSystem
+		return ErrorSystem
 	case sys.EINVAL: // bad argument
-	return ErrorBadArgument
+		return ErrorBadArgument
 	case sys.ENOTTY: // unsupported
-	return ErrorUnsupported
+		return ErrorUnsupported
 	default:
 		if errno.Timeout() {
 			return ErrorTimeout

+ 1 - 1
v4l2/format.go

@@ -301,7 +301,7 @@ func GetPixFormat(fd uintptr) (PixFormat, error) {
 		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]))),
+		HSVEnc:       *(*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&v4l2PixFmt.anon0[0])) + unsafe.Sizeof(C.uint(0)))),
 		Quantization: uint32(v4l2PixFmt.quantization),
 		XferFunc:     uint32(v4l2PixFmt.xfer_func),
 	}, nil

+ 86 - 0
v4l2/format_frameintervals.go

@@ -0,0 +1,86 @@
+package v4l2
+
+//#include <linux/videodev2.h>
+import "C"
+import (
+	"fmt"
+	"unsafe"
+)
+
+// FrameIntervalType (v4l2_frmivaltypes)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L845
+type FrameIntervalType = uint32
+
+const (
+	FrameIntervalTypeDiscrete   FrameIntervalType = C.V4L2_FRMIVAL_TYPE_DISCRETE
+	FrameIntervalTypeContinuous FrameIntervalType = C.V4L2_FRMIVAL_TYPE_CONTINUOUS
+	FrameIntervalTypeStepwise   FrameIntervalType = C.V4L2_FRMIVAL_TYPE_STEPWISE
+)
+
+// FrameIntervalEnum is used to store v4l2_frmivalenum values.
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L857
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-frameintervals.html
+type FrameIntervalEnum struct {
+	Index       uint32
+	PixelFormat FourCCType
+	Width       uint32
+	Height      uint32
+	Type        FrameIntervalType
+	Interval    FrameInterval
+}
+
+// FrameInterval stores all frame interval values regardless of its type. This type maps to v4l2_frmival_stepwise.
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L851
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-frameintervals.html
+type FrameInterval struct {
+	Min  Fract
+	Max  Fract
+	Step Fract
+}
+
+// getFrameInterval retrieves the supported frame interval info from following union based on the type:
+
+// 	union {
+//	    struct v4l2_fract		discrete;
+//	    struct v4l2_frmival_stepwise	stepwise;
+//	}
+
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-frameintervals.html
+func getFrameInterval(interval C.struct_v4l2_frmivalenum) (FrameIntervalEnum,error) {
+	frmInterval := FrameIntervalEnum{
+		Index:       uint32(interval.index),
+		Type:        FrameIntervalType(interval._type),
+		PixelFormat: FourCCType(interval.pixel_format),
+		Width:       uint32(interval.width),
+		Height:      uint32(interval.height),
+	}
+	intervalType := uint32(interval._type)
+	switch intervalType {
+	case FrameIntervalTypeDiscrete:
+		fiDiscrete := *(*Fract)(unsafe.Pointer(&interval.anon0[0]))
+		frmInterval.Interval.Min = fiDiscrete
+		frmInterval.Interval.Max = fiDiscrete
+		frmInterval.Interval.Step.Numerator = 1
+		frmInterval.Interval.Step.Denominator = 1
+	case FrameIntervalTypeStepwise, FrameIntervalTypeContinuous:
+		// Calculate pointer to stepwise member of union
+		frmInterval.Interval = *(*FrameInterval)(unsafe.Pointer(uintptr(unsafe.Pointer(&interval.anon0[0])) + unsafe.Sizeof(Fract{})))
+	default:
+		return FrameIntervalEnum{}, fmt.Errorf("unsupported frame interval type: %d", intervalType)
+	}
+	return frmInterval, nil
+}
+
+// GetFormatFrameInterval returns a supported device frame interval for a specified encoding at index and format
+func GetFormatFrameInterval(fd uintptr, index uint32, encoding FourCCType, width, height uint32) (FrameIntervalEnum, error) {
+	var interval C.struct_v4l2_frmivalenum
+	interval.index = C.uint(index)
+	interval.pixel_format = C.uint(encoding)
+	interval.width = C.uint(width)
+	interval.height = C.uint(height)
+
+	if err := send(fd, C.VIDIOC_ENUM_FRAMEINTERVALS, uintptr(unsafe.Pointer(&interval))); err != nil {
+		return FrameIntervalEnum{}, fmt.Errorf("frame interval: index %d: %w", index, err)
+	}
+	return getFrameInterval(interval)
+}

+ 32 - 26
v4l2/format_framesizes.go

@@ -17,15 +17,15 @@ const (
 	FrameSizeTypeStepwise   FrameSizeType = C.V4L2_FRMSIZE_TYPE_STEPWISE
 )
 
-// FrameSize uses v4l2_frmsizeenum to get supporeted frame size for the driver based for the pixel format.
+// FrameSizeEnum 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
+type FrameSizeEnum struct {
+	Index     uint32
+	Type      FrameSizeType
 	PixelFormat FourCCType
+	Size      FrameSize
 }
 
 // FrameSizeDiscrete (v4l2_frmsize_discrete)
@@ -35,9 +35,10 @@ type FrameSizeDiscrete struct {
 	Height uint32 // height [pixel]
 }
 
-// FrameSizeStepwise (v4l2_frmsize_stepwise)
-// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L820
-type FrameSizeStepwise struct {
+// FrameSize stores all possible frame size information regardless of its type. It is mapped to v4l2_frmsize_stepwise.
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L820
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-framesizes.html
+type FrameSize struct {
 	MinWidth   uint32 // Minimum frame width [pixel]
 	MaxWidth   uint32 // Maximum frame width [pixel]
 	StepWidth  uint32 // Frame width step size [pixel]
@@ -46,40 +47,45 @@ type FrameSizeStepwise struct {
 	StepHeight uint32 // Frame height step size [pixel]
 }
 
-// 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 {
+// getFrameSize retrieves the supported frame size info from following union based on the type:
+
+// union {
+//     struct v4l2_frmsize_discrete	discrete;
+//     struct v4l2_frmsize_stepwise	stepwise;
+// }
+
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L829
+func getFrameSize(frmSizeEnum C.struct_v4l2_frmsizeenum) FrameSizeEnum {
+	frameSize := FrameSizeEnum{Type: FrameSizeType(frmSizeEnum._type), PixelFormat: FourCCType(frmSizeEnum.pixel_format)}
+	switch frameSize.Type {
 	case FrameSizeTypeDiscrete:
-		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
+		fsDiscrete := *(*FrameSizeDiscrete)(unsafe.Pointer(&frmSizeEnum.anon0[0]))
+		frameSize.Size.MinWidth = fsDiscrete.Width
+		frameSize.Size.MinHeight = fsDiscrete.Height
+		frameSize.Size.MaxWidth = fsDiscrete.Width
+		frameSize.Size.MaxHeight = fsDiscrete.Height
 	case FrameSizeTypeStepwise, FrameSizeTypeContinuous:
-		fsStepwise := (*FrameSizeStepwise)(unsafe.Pointer(&frmSizeEnum.anon0[0]))
-		frameSize.FrameSizeStepwise = *fsStepwise
-		frameSize.FrameSizeDiscrete.Width = frameSize.FrameSizeStepwise.MaxWidth
-		frameSize.FrameSizeDiscrete.Height = frameSize.FrameSizeStepwise.MaxHeight
+		// Calculate pointer to access stepwise member
+		frameSize.Size = *(*FrameSize)(unsafe.Pointer(uintptr(unsafe.Pointer(&frmSizeEnum.anon0[0]))+unsafe.Sizeof(FrameSizeDiscrete{})))
+	default:
 	}
 	return frameSize
 }
 
 // GetFormatFrameSize returns a supported device frame size for a specified encoding at index
-func GetFormatFrameSize(fd uintptr, index uint32, encoding FourCCType) (FrameSize, error) {
+func GetFormatFrameSize(fd uintptr, index uint32, encoding FourCCType) (FrameSizeEnum, 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 FrameSizeEnum{}, fmt.Errorf("frame size: index %d: %w", index, err)
 	}
 	return getFrameSize(frmSizeEnum), nil
 }
 
 // GetFormatFrameSizes returns all supported device frame sizes for a specified encoding
-func GetFormatFrameSizes(fd uintptr, encoding FourCCType) (result []FrameSize, err error) {
+func GetFormatFrameSizes(fd uintptr, encoding FourCCType) (result []FrameSizeEnum, err error) {
 	index := uint32(0)
 	for {
 		var frmSizeEnum C.struct_v4l2_frmsizeenum
@@ -107,7 +113,7 @@ func GetFormatFrameSizes(fd uintptr, encoding FourCCType) (result []FrameSize, e
 // GetAllFormatFrameSizes returns all supported frame sizes for all supported formats.
 // It iterates from format at index 0 until it encounters and error and then stops. For
 // each supported format, it retrieves all supported frame sizes.
-func GetAllFormatFrameSizes(fd uintptr) (result []FrameSize, err error) {
+func GetAllFormatFrameSizes(fd uintptr) (result []FrameSizeEnum, err error) {
 	formats, err := GetAllFormatDescriptions(fd)
 	if len(formats) == 0 && err != nil {
 		return nil, fmt.Errorf("frame sizes: %w", err)

+ 0 - 0
v4l2/types.go → v4l2/version.go