Bläddra i källkod

Support for capture stream param; error refactor; fix fmt desc, frame sizes

Vladimir Vivien 4 år sedan
förälder
incheckning
aab6d9c4a2
8 ändrade filer med 423 tillägg och 78 borttagningar
  1. 111 3
      examples/device_info/devinfo.go
  2. 26 0
      v4l2/device.go
  3. 2 2
      v4l2/errors.go
  4. 134 47
      v4l2/format.go
  5. 45 21
      v4l2/format_desc.go
  6. 37 5
      v4l2/format_framesizes.go
  7. 1 0
      v4l2/ioctl.go
  8. 67 0
      v4l2/stream_param.go

+ 111 - 3
examples/device_info/devinfo.go

@@ -4,6 +4,7 @@ import (
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
 	"log"
 	"log"
+	"strings"
 
 
 	"github.com/vladimirvivien/go4vl/v4l2"
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 )
@@ -31,6 +32,14 @@ func main() {
 	if err := printFormatInfo(device); err != nil {
 	if err := printFormatInfo(device); err != nil {
 		log.Fatal(err)
 		log.Fatal(err)
 	}
 	}
+
+	if err := printCropInfo(device); err != nil {
+		log.Fatal(err)
+	}
+
+	if err := printCaptureParam(device); err != nil {
+		log.Fatal(err)
+	}
 }
 }
 
 
 func printDeviceDriverInfo(dev *v4l2.Device) error {
 func printDeviceDriverInfo(dev *v4l2.Device) error {
@@ -88,13 +97,112 @@ func printFormatInfo(dev *v4l2.Device) error {
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("video capture format: %w", err)
 		return fmt.Errorf("video capture format: %w", err)
 	}
 	}
-	fmt.Println("Format video capture:")
-	fmt.Printf(template, "WidthxHeight", fmt.Sprintf("%dx%d", pixFmt.Width, pixFmt.Height))
+	fmt.Println("Video format for capture (default):")
+	fmt.Printf(template, "Width x Height", fmt.Sprintf("%d x %d", pixFmt.Width, pixFmt.Height))
 	fmt.Printf(template, "Pixel format", v4l2.PixelFormats[pixFmt.PixelFormat])
 	fmt.Printf(template, "Pixel format", v4l2.PixelFormats[pixFmt.PixelFormat])
 	fmt.Printf(template, "Field", v4l2.Fields[pixFmt.Field])
 	fmt.Printf(template, "Field", v4l2.Fields[pixFmt.Field])
-	fmt.Printf(template, "Bytes per line", fmt.Sprintf("%d",pixFmt.BytesPerLine))
+	fmt.Printf(template, "Bytes per line", fmt.Sprintf("%d", pixFmt.BytesPerLine))
 	fmt.Printf(template, "Size image", fmt.Sprintf("%d", pixFmt.SizeImage))
 	fmt.Printf(template, "Size image", fmt.Sprintf("%d", pixFmt.SizeImage))
 	fmt.Printf(template, "Colorspace", v4l2.Colorspaces[pixFmt.Colorspace])
 	fmt.Printf(template, "Colorspace", v4l2.Colorspaces[pixFmt.Colorspace])
 
 
+	// xferfunc
+	xfunc := v4l2.XferFunctions[pixFmt.XferFunc]
+	if pixFmt.XferFunc == v4l2.XferFuncDefault {
+		xfunc = fmt.Sprintf("%s (map to %s)", xfunc, v4l2.XferFunctions[v4l2.ColorspaceToXferFunc(pixFmt.XferFunc)])
+	}
+	fmt.Printf(template, "Transfer function", xfunc)
+
+	// ycbcr
+	ycbcr := v4l2.YCbCrEncodings[pixFmt.YcbcrEnc]
+	if pixFmt.YcbcrEnc == v4l2.YCbCrEncodingDefault {
+		ycbcr = fmt.Sprintf("%s (map to %s)", ycbcr, v4l2.YCbCrEncodings[v4l2.ColorspaceToYCbCrEnc(pixFmt.YcbcrEnc)])
+	}
+	fmt.Printf(template, "YCbCr/HSV encoding", ycbcr)
+
+	// quant
+	quant := v4l2.Quantizations[pixFmt.Quantization]
+	if pixFmt.Quantization == v4l2.QuantizationDefault {
+		if v4l2.IsPixYUVEncoded(pixFmt.PixelFormat) {
+			quant = fmt.Sprintf("%s (map to %s)", quant, v4l2.Quantizations[v4l2.QuantizationLimitedRange])
+		} else {
+			quant = fmt.Sprintf("%s (map to %s)", quant, v4l2.Quantizations[v4l2.QuantizationFullRange])
+		}
+	}
+	fmt.Printf(template, "Quantization", quant)
+
+	// format desc
+	return printFormatDesc(dev)
+}
+
+func printFormatDesc(dev *v4l2.Device) error {
+	descs, err := dev.GetFormatDescriptions()
+	if err != nil {
+		return fmt.Errorf("format desc: %w", err)
+	}
+	fmt.Println("Supported formats:")
+	for i, desc := range descs{
+		frmSizes, err := desc.GetFrameSizes()
+		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))
+		}
+		fmt.Printf(template, fmt.Sprintf("[%0d] %s", i, desc.GetDescription()), sizeStr.String())
+
+	}
+	return nil
+}
+
+func printCropInfo(dev *v4l2.Device) error {
+	crop, err := dev.GetCropCapability()
+	if err != nil {
+		return fmt.Errorf("crop capability: %w", err)
+	}
+
+	fmt.Println("Crop capability for video capture:")
+	fmt.Printf(
+		template,
+		"Bounds:",
+		fmt.Sprintf(
+			"left=%d; top=%d; width=%d; heigh=%d",
+			crop.Bounds.Left, crop.Bounds.Top, crop.Bounds.Width, crop.Bounds.Height,
+		),
+	)
+	fmt.Printf(
+		template,
+		"Default:",
+		fmt.Sprintf(
+			"left=%d; top=%d; width=%d; heigh=%d",
+			crop.DefaultRect.Left, crop.DefaultRect.Top, crop.DefaultRect.Width, crop.DefaultRect.Height,
+		),
+	)
+	fmt.Printf(template, "Pixel aspect", fmt.Sprintf("%d/%d", crop.PixelAspect.Numerator, crop.PixelAspect.Denominator))
 	return nil
 	return nil
 }
 }
+
+func printCaptureParam(dev *v4l2.Device) error {
+	params, err := dev.GetCaptureParam()
+	if err != nil {
+		return fmt.Errorf("streaming capture param: %w", err)
+	}
+	fmt.Println("Streaming parameters for video capture:")
+
+	tpf := "not specified"
+	if params.Capability == v4l2.StreamParamTimePerFrame {
+		tpf = "time per frame"
+	}
+	fmt.Printf(template, "Capability", tpf)
+
+	hiqual := "not specified"
+	if params.CaptureMode == v4l2.StreamParamModeHighQuality {
+		hiqual = "high quality"
+	}
+	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))
+	return nil
+}

+ 26 - 0
v4l2/device.go

@@ -41,6 +41,11 @@ func (d *Device) Close() error {
 	return d.file.Close()
 	return d.file.Close()
 }
 }
 
 
+// GetFileDescriptor returns the file descriptor value for the device
+func (d *Device) GetFileDescriptor() uintptr {
+	return d.fd
+}
+
 // GetCapability retrieves device capability info and
 // GetCapability retrieves device capability info and
 // caches it for future capability check.
 // caches it for future capability check.
 func (d *Device) GetCapability() (*Capability, error) {
 func (d *Device) GetCapability() (*Capability, error) {
@@ -108,6 +113,17 @@ func (d *Device) SetPixFormat(pixFmt PixFormat) error {
 	return nil
 	return nil
 }
 }
 
 
+// GetFormatDescription returns a format description for the device at specified format index
+func (d *Device) GetFormatDescription(idx uint32) (FormatDescription, error) {
+	if err := d.assertVideoCaptureSupport(); err != nil {
+		return FormatDescription{}, fmt.Errorf("device: %w", err)
+	}
+
+	return GetFormatDescription(d.fd, idx)
+}
+
+
+// GetFormatDescriptions returns all possible format descriptions for device
 func (d *Device) GetFormatDescriptions() ([]FormatDescription, error) {
 func (d *Device) GetFormatDescriptions() ([]FormatDescription, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 		return nil, fmt.Errorf("device: %w", err)
 		return nil, fmt.Errorf("device: %w", err)
@@ -116,6 +132,7 @@ func (d *Device) GetFormatDescriptions() ([]FormatDescription, error) {
 	return GetAllFormatDescriptions(d.fd)
 	return GetAllFormatDescriptions(d.fd)
 }
 }
 
 
+// GetVideoInputIndex returns current video input index for device
 func (d *Device) GetVideoInputIndex()(int32, error) {
 func (d *Device) GetVideoInputIndex()(int32, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 		return 0, fmt.Errorf("device: %w", err)
 		return 0, fmt.Errorf("device: %w", err)
@@ -123,6 +140,7 @@ func (d *Device) GetVideoInputIndex()(int32, error) {
 	return GetCurrentVideoInputIndex(d.fd)
 	return GetCurrentVideoInputIndex(d.fd)
 }
 }
 
 
+// GetVideoInputInfo returns video input info for device
 func (d *Device) GetVideoInputInfo(index uint32) (InputInfo, error) {
 func (d *Device) GetVideoInputInfo(index uint32) (InputInfo, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 		return InputInfo{}, fmt.Errorf("device: %w", err)
 		return InputInfo{}, fmt.Errorf("device: %w", err)
@@ -130,6 +148,14 @@ func (d *Device) GetVideoInputInfo(index uint32) (InputInfo, error) {
 	return GetVideoInputInfo(d.fd, index)
 	return GetVideoInputInfo(d.fd, index)
 }
 }
 
 
+// GetCaptureParam returns streaming capture parameter information
+func (d *Device) GetCaptureParam() (CaptureParam, error) {
+	if err := d.assertVideoCaptureSupport(); err != nil {
+		return CaptureParam{}, fmt.Errorf("device: %w", err)
+	}
+	return GetStreamCaptureParam(d.fd)
+}
+
 func (d *Device) StartStream(buffSize uint32) error {
 func (d *Device) StartStream(buffSize uint32) error {
 	if d.streaming {
 	if d.streaming {
 		return nil
 		return nil

+ 2 - 2
v4l2/errors.go

@@ -15,9 +15,9 @@ var (
 
 
 func parseErrorType(errno sys.Errno) error {
 func parseErrorType(errno sys.Errno) error {
 	switch errno {
 	switch errno {
-	case sys.EBADF, sys.ENOMEM, sys.ENODEV, sys.EIO, sys.ENXIO: // structural, terminal
+	case sys.EBADF, sys.ENOMEM, sys.ENODEV, sys.EIO, sys.ENXIO, sys.EFAULT: // structural, terminal
 	return ErrorSystem
 	return ErrorSystem
-	case sys.EFAULT, sys.EINVAL: // bad argument, terminal
+	case sys.EINVAL: // bad argument
 	return ErrorBadArgument
 	return ErrorBadArgument
 	case sys.ENOTTY: // unsupported
 	case sys.ENOTTY: // unsupported
 	return ErrorUnsupported
 	return ErrorUnsupported

+ 134 - 47
v4l2/format.go

@@ -46,30 +46,130 @@ var PixelFormats = map[FourCCEncoding]string{
 	PixelFmtMPEG4: "MPEG-4 Part 2 ES",
 	PixelFmtMPEG4: "MPEG-4 Part 2 ES",
 }
 }
 
 
-// YcbcrEncoding (v4l2_ycbcr_encoding)
+// IsPixYUVEncoded returns true if the pixel format is a chrome+luminance YUV format
+func IsPixYUVEncoded(pixFmt FourCCEncoding) bool {
+	switch pixFmt {
+	case
+		PixelFmtYUYV,
+		PixelFmtYYUV,
+		PixelFmtYVYU,
+		PixelFmtUYVY,
+		PixelFmtVYUY:
+		return true
+	default:
+		return false
+	}
+}
+
+// ColorspaceType
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L195
+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
+)
+
+// Colorspaces is a map of colorspace to its respective description
+var Colorspaces = map[ColorspaceType]string{
+	ColorspaceTypeDefault: "Default",
+	ColorspaceREC709:      "Rec. 709",
+	Colorspace470SystemBG: "470 System BG",
+	ColorspaceJPEG:        "JPEG",
+	ColorspaceSRGB:        "sRGB",
+	ColorspaceOPRGB:       "opRGB",
+	ColorspaceBT2020:      "BT.2020",
+	ColorspaceRaw:         "Raw",
+	ColorspaceDCIP3:       "DCI-P3",
+}
+
+// YCbCrEncodingType (v4l2_ycbcr_encoding)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_ycbcr_encoding
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_ycbcr_encoding
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L300
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L300
-type YcbcrEncoding = uint32
+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
+)
+
+var YCbCrEncodings = map[YCbCrEncodingType]string{
+	YCbCrEncodingDefault:        "Default",
+	YCbCrEncoding601:            "ITU-R 601",
+	YCbCrEncoding709:            "Rec. 709",
+	YCbCrEncodingXV601:          "xvYCC 601",
+	YCbCrEncodingXV709:          "xvYCC 709",
+	YCbCrEncodingBT2020:         "BT.2020",
+	YCbCrEncodingBT2020ConstLum: "BT.2020 constant luminance",
+	HSVEncoding180:              "HSV 0-179",
+	HSVEncoding256:              "HSV 0-255",
+}
+
+// ColorspaceToYCbCrEnc is used to get the YCbCrEncoding when only a default YCbCr and the colorspace is known
+func ColorspaceToYCbCrEnc(cs ColorspaceType) YCbCrEncodingType {
+	switch cs {
+	case ColorspaceREC709, ColorspaceDCIP3:
+		return YCbCrEncoding709
+	case ColorspaceBT2020:
+		return YCbCrEncodingBT2020
+	default:
+		return YCbCrEncoding601
+	}
+}
+
+// HSVEncodingType (v4l2_hsv_encoding)
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L352
+type HSVEncodingType = YCbCrEncodingType
 
 
 const (
 const (
-	YcbcrEncDefault YcbcrEncoding = iota // V4L2_YCBCR_ENC_DEFAULT
-	YcbcrEnc601                          // V4L2_YCBCR_ENC_601
-	YcbcrEnc709                          // V4L2_YCBCR_ENC_709
-	YcbcrEncXV601                        // V4L2_YCBCR_ENC_XV601
-	YcbcrEncXV709                        // V4L2_YCBCR_ENC_XV709
+	HSVEncoding180 = HSVEncodingType(128) // V4L2_HSV_ENC_180
+	HSVEncoding256 = HSVEncodingType(129) // V4L2_HSV_ENC_256
 )
 )
 
 
-// Quantization (v4l2_quantization)
+// QuantizationType (v4l2_quantization)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_quantization#c.V4L.v4l2_quantization
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_quantization#c.V4L.v4l2_quantization
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L372
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L372
-type Quantization = uint32
+type QuantizationType = uint32
 
 
 const (
 const (
-	QuantizationDefault   Quantization = iota // V4L2_QUANTIZATION_DEFAULT
-	QuantizationFullRange                     // V4L2_QUANTIZATION_FULL_RANGE
-	QuantizationLimRange                      // V4L2_QUANTIZATION_LIM_RANGE
+	QuantizationDefault      QuantizationType = iota // V4L2_QUANTIZATION_DEFAULT
+	QuantizationFullRange                            // V4L2_QUANTIZATION_FULL_RANGE
+	QuantizationLimitedRange                         // V4L2_QUANTIZATION_LIM_RANGE
 )
 )
 
 
+var Quantizations = map[QuantizationType]string{
+	QuantizationDefault:      "Default",
+	QuantizationFullRange:    "Full range",
+	QuantizationLimitedRange: "Limited range",
+}
+
+func ColorspaceToQuantization(cs ColorspaceType) QuantizationType {
+	// TODO any RGB/HSV pixel formats should also return full-range
+	switch cs {
+	case ColorspaceOPRGB, ColorspaceSRGB, ColorspaceJPEG:
+		return QuantizationFullRange
+	default:
+		return QuantizationLimitedRange
+	}
+}
+
 // XferFunctionType (v4l2_xfer_func)
 // XferFunctionType (v4l2_xfer_func)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_xfer_func#c.V4L.v4l2_xfer_func
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-defs.html?highlight=v4l2_xfer_func#c.V4L.v4l2_xfer_func
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L259
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L259
@@ -97,6 +197,26 @@ var XferFunctions = map[XferFunctionType]string{
 	XferFuncSMPTE2084: "SMPTE 2084",
 	XferFuncSMPTE2084: "SMPTE 2084",
 }
 }
 
 
+// ColorspaceToXferFunc used to get true XferFunc when only colorspace and default XferFuc are known.
+func ColorspaceToXferFunc(cs ColorspaceType) XferFunctionType {
+	switch cs {
+	case ColorspaceOPRGB:
+		return XferFuncOpRGB
+	case ColorspaceSMPTE240M:
+		return XferFuncSMPTE240M
+	case ColorspaceDCIP3:
+		return XferFuncDCIP3
+	case ColorspaceRaw:
+		return XferFuncNone
+	case ColorspaceSRGB:
+		return XferFuncSRGB
+	case ColorspaceJPEG:
+		return XferFuncSRGB
+	default:
+		return XferFunc709
+	}
+}
+
 // FieldType (v4l2_field)
 // FieldType (v4l2_field)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/field-order.html?highlight=v4l2_field#c.v4l2_field
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/field-order.html?highlight=v4l2_field#c.v4l2_field
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L88
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L88
@@ -129,39 +249,6 @@ var Fields = map[FieldType]string{
 	FieldInterlacedBottomTop: "interlaced bottom-top",
 	FieldInterlacedBottomTop: "interlaced bottom-top",
 }
 }
 
 
-// ColorspaceType
-// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L195
-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
-)
-
-// Colorspaces is a map of colorspace to its respective description
-var Colorspaces = map[ColorspaceType]string{
-	ColorspaceTypeDefault: "Default",
-	ColorspaceREC709:      "Rec. 709",
-	Colorspace470SystemBG: "470 System BG",
-	ColorspaceJPEG:        "JPEG",
-	ColorspaceSRGB:        "sRGB",
-	ColorspaceOPRGB:       "opRGB",
-	ColorspaceBT2020:      "BT.2020",
-	ColorspaceRaw:         "Raw",
-	ColorspaceDCIP3:       "DCI-P3",
-}
-
 // PixFormat (v4l2_pix_format)
 // PixFormat (v4l2_pix_format)
 // https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/pixfmt-002.html?highlight=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
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L496
@@ -175,8 +262,8 @@ type PixFormat struct {
 	Colorspace   ColorspaceType
 	Colorspace   ColorspaceType
 	Priv         uint32
 	Priv         uint32
 	Flags        uint32
 	Flags        uint32
-	YcbcrEnc     YcbcrEncoding
-	Quantization Quantization
+	YcbcrEnc     YCbCrEncodingType
+	Quantization QuantizationType
 	XferFunc     XferFunctionType
 	XferFunc     XferFunctionType
 }
 }
 
 

+ 45 - 21
v4l2/format_desc.go

@@ -1,10 +1,9 @@
 package v4l2
 package v4l2
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
 	"unsafe"
 	"unsafe"
-
-	sys "golang.org/x/sys/unix"
 )
 )
 
 
 // FmtDescFlag image format description flags
 // FmtDescFlag image format description flags
@@ -13,18 +12,30 @@ import (
 type FmtDescFlag = uint32
 type FmtDescFlag = uint32
 
 
 const (
 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
-	FmtDescFlagEncCapFrameInterval  FmtDescFlag = 0x0010                 //  V4L2_FMT_FLAG_ENC_CAP_FRAME_INTERVAL
-	FmtDescFlagCscColorspace        FmtDescFlag = 0x0020                 //  V4L2_FMT_FLAG_CSC_COLORSPACE
-	FmtDescFlagCscXferFunc          FmtDescFlag = 0x0040                 // V4L2_FMT_FLAG_CSC_XFER_FUNC
-	FmtDescFlagCscYcbcrEnc          FmtDescFlag = 0x0080                 //  V4L2_FMT_FLAG_CSC_YCBCR_ENC
-	FmtDescFlagCscHsvEnc            FmtDescFlag = FmtDescFlagCscYcbcrEnc // V4L2_FMT_FLAG_CSC_HSV_ENC
-	FmtDescFlagCscQuantization      FmtDescFlag = 0x0100                 // V4L2_FMT_FLAG_CSC_QUANTIZATION
+	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
 )
 )
 
 
+var FormatDescriptionFlags = map[FmtDescFlag]string{
+	FmtDescFlagCompressed:                  "Compressed",
+	FmtDescFlagEmulated:                    "Emulated",
+	FmtDescFlagContinuousBytestream:        "Continuous bytestream",
+	FmtDescFlagDynResolution:               "Dynamic resolution",
+	FmtDescFlagEncodedCaptureFrameInterval: "Encoded capture frame interval",
+	FmtDescFlagConfigColorspace:            "Colorspace update supported",
+	FmtDescFlagConfigXferFunc:              "Transfer func update supported",
+	FmtDescFlagConfigYcbcrEnc:              "YCbCr/HSV update supported",
+	FmtDescFlagConfigQuantization:          "Quantization update supported",
+}
+
 // v4l2FormatDesc  (v4l2_fmtdesc)
 // v4l2FormatDesc  (v4l2_fmtdesc)
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L784
 // 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
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-enum-fmt.html
@@ -75,14 +86,12 @@ func (d FormatDescription) GetBusCode() uint32 {
 	return d.mbusCode
 	return d.mbusCode
 }
 }
 
 
-// GetFrameSize return the supported frame size for the format in description.
-// NOTE: This method must be used on a FormatDescription value that was created
-// with a call to GetFormatDescription or GetAllFormatDescriptions.
-func (d FormatDescription) GetFrameSize() (FrameSize, error) {
+// GetFrameSizes return all supported frame sizes for the format description.
+func (d FormatDescription) GetFrameSizes() ([]FrameSize, error) {
 	if d.fd == 0 {
 	if d.fd == 0 {
-		return FrameSize{}, fmt.Errorf("invalid file descriptor")
+		return nil, fmt.Errorf("invalid file descriptor")
 	}
 	}
-	return GetFormatFrameSize(d.fd, d.index, d.pixelFormat)
+	return GetFormatFrameSizes(d.fd, d.pixelFormat)
 }
 }
 
 
 // GetFormatDescription returns a device format description at index
 // GetFormatDescription returns a device format description at index
@@ -104,13 +113,28 @@ func GetAllFormatDescriptions(fd uintptr) (result []FormatDescription, err error
 	for {
 	for {
 		desc := v4l2FormatDesc{index: index, bufType: BufTypeVideoCapture}
 		desc := v4l2FormatDesc{index: index, bufType: BufTypeVideoCapture}
 		if err = Send(fd, VidiocEnumFmt, uintptr(unsafe.Pointer(&desc))); err != nil {
 		if err = Send(fd, VidiocEnumFmt, uintptr(unsafe.Pointer(&desc))); err != nil {
-			errno := err.(sys.Errno)
-			if errno.Is(sys.EINVAL) && len(result) > 0 {
+			if errors.Is(err, ErrorBadArgument) && len(result) > 0 {
 				break
 				break
 			}
 			}
+			return result, fmt.Errorf("format desc: all: %w", err)
 		}
 		}
 		result = append(result, FormatDescription{fd: fd, v4l2FormatDesc: desc})
 		result = append(result, FormatDescription{fd: fd, v4l2FormatDesc: desc})
 		index++
 		index++
 	}
 	}
-	return result, err
+	return result, nil
 }
 }
+
+// GetFormatDescriptionByEncoding returns a FormatDescription that matches the specified encoded pixel format
+func GetFormatDescriptionByEncoding(fd uintptr, enc FourCCEncoding)(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{
+			return desc, nil
+		}
+	}
+
+	return FormatDescription{}, fmt.Errorf("format desc: driver does not support encoding %d", enc)
+}

+ 37 - 5
v4l2/format_framesizes.go

@@ -1,6 +1,7 @@
 package v4l2
 package v4l2
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
 	"unsafe"
 	"unsafe"
 )
 )
@@ -91,9 +92,15 @@ func (fs v4l2FrameSizeEnum) getFrameSize() FrameSize {
 	case FrameSizeTypeDiscrete:
 	case FrameSizeTypeDiscrete:
 		fsDiscrete := (*FrameSizeDiscrete)(unsafe.Pointer(&fs.frameSize[0]))
 		fsDiscrete := (*FrameSizeDiscrete)(unsafe.Pointer(&fs.frameSize[0]))
 		frameSize.FrameSizeDiscrete = *fsDiscrete
 		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:
 	case FrameSizeTypeStepwise, FrameSizeTypeContinuous:
 		fsStepwise := (*FrameSizeStepwise)(unsafe.Pointer(&fs.frameSize[0]))
 		fsStepwise := (*FrameSizeStepwise)(unsafe.Pointer(&fs.frameSize[0]))
 		frameSize.FrameSizeStepwise = *fsStepwise
 		frameSize.FrameSizeStepwise = *fsStepwise
+		frameSize.FrameSizeDiscrete.Width = frameSize.FrameSizeStepwise.MaxWidth
+		frameSize.FrameSizeDiscrete.Height = frameSize.FrameSizeStepwise.MaxHeight
 	}
 	}
 	return frameSize
 	return frameSize
 }
 }
@@ -107,6 +114,29 @@ func GetFormatFrameSize(fd uintptr, index uint32, encoding FourCCEncoding) (Fram
 	return fsEnum.getFrameSize(), nil
 	return fsEnum.getFrameSize(), nil
 }
 }
 
 
+// GetFormatFrameSizes returns all supported device frame sizes for a specified encoding
+func GetFormatFrameSizes(fd uintptr, encoding FourCCEncoding) (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 {
+			if errors.Is(err, ErrorBadArgument) && len(result) > 0 {
+				break
+			}
+			return result, fmt.Errorf("frame sizes: encoding %s: %w", PixelFormats[encoding], err)
+		}
+
+		// 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 {
+			break
+		}
+		index++
+	}
+	return result, nil
+}
+
 // GetAllFormatFrameSizes returns all supported frame sizes for all supported formats.
 // 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
 // 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.
 // each supported format, it retrieves all supported frame sizes.
@@ -117,12 +147,15 @@ func GetAllFormatFrameSizes(fd uintptr) (result []FrameSize, err error) {
 	}
 	}
 
 
 	// for each supported format, grab frame size
 	// for each supported format, grab frame size
-	for _, fmt := range formats {
+	for _, format := range formats {
 		index := uint32(0)
 		index := uint32(0)
 		for {
 		for {
-			fsEnum := v4l2FrameSizeEnum{index: index, pixelFormat: fmt.GetPixelFormat()}
+			fsEnum := v4l2FrameSizeEnum{index: index, pixelFormat: format.GetPixelFormat()}
 			if err = Send(fd, VidiocEnumFrameSizes, uintptr(unsafe.Pointer(&fsEnum))); err != nil {
 			if err = Send(fd, VidiocEnumFrameSizes, uintptr(unsafe.Pointer(&fsEnum))); err != nil {
-				break
+				if errors.Is(err, ErrorBadArgument) && len(result) > 0 {
+					break
+				}
+				return result, err
 			}
 			}
 
 
 			// At index 0, check the frame type, if not discrete exit loop.
 			// At index 0, check the frame type, if not discrete exit loop.
@@ -131,9 +164,8 @@ func GetAllFormatFrameSizes(fd uintptr) (result []FrameSize, err error) {
 			if index == 0 && fsEnum.frameSizeType != FrameSizeTypeDiscrete {
 			if index == 0 && fsEnum.frameSizeType != FrameSizeTypeDiscrete {
 				break
 				break
 			}
 			}
-
 			index++
 			index++
 		}
 		}
 	}
 	}
-	return result, err
+	return result, nil
 }
 }

+ 1 - 0
v4l2/ioctl.go

@@ -87,6 +87,7 @@ var (
 	VidiocDequeueBuf     = iocEncRW('V', 17, unsafe.Sizeof(BufferInfo{}))        // Represents command VIDIOC_DQBUF
 	VidiocDequeueBuf     = iocEncRW('V', 17, unsafe.Sizeof(BufferInfo{}))        // Represents command VIDIOC_DQBUF
 	VidiocStreamOn       = iocEncW('V', 18, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_STREAMON
 	VidiocStreamOn       = iocEncW('V', 18, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_STREAMON
 	VidiocStreamOff      = iocEncW('V', 19, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_STREAMOFF
 	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
 	VidiocEnumInput      = iocEncRW('V', 26, unsafe.Sizeof(v4l2InputInfo{}))     // Represents command VIDIOC_ENUMINPUT
 	VidiocGetVideoInput  = iocEncR('V', 38, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_G_INPUT
 	VidiocGetVideoInput  = iocEncR('V', 38, unsafe.Sizeof(int32(0)))             // Represents command VIDIOC_G_INPUT
 	VidiocCropCap        = iocEncRW('V', 58, unsafe.Sizeof(CropCapability{}))    // Represents command VIDIOC_CROPCAP
 	VidiocCropCap        = iocEncRW('V', 58, unsafe.Sizeof(CropCapability{}))    // Represents command VIDIOC_CROPCAP

+ 67 - 0
v4l2/stream_param.go

@@ -0,0 +1,67 @@
+package v4l2
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+// Flags for capability and capture mode fields
+// See https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html#parm-flags
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1214
+type StreamParamFlag = uint32
+
+const (
+	StreamParamModeHighQuality StreamParamFlag = 0x0001 // V4L2_MODE_HIGHQUALITY
+	StreamParamTimePerFrame    StreamParamFlag = 0x1000 // V4L2_CAP_TIMEPERFRAME
+)
+
+// CaptureParam (v4l2_captureparam)
+// https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html#c.V4L.v4l2_captureparm
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1205
+type CaptureParam struct {
+	Capability   StreamParamFlag
+	CaptureMode  StreamParamFlag
+	TimePerFrame Fract
+	ExtendedMode uint32
+	ReadBuffers  uint32
+	reserved     [4]uint32
+}
+
+// v4l2StreamParam (v4l2_streamparam)
+// 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
+}
+
+// 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(&param))); err != nil {
+		return CaptureParam{}, fmt.Errorf("stream param: %w", err)
+	}
+	return param.getCaptureParam(), nil
+}