Explorar o código

Adding device configuration options

Vladimir Vivien %!s(int64=3) %!d(string=hai) anos
pai
achega
b2fbce4f08
Modificáronse 5 ficheiros con 97 adicións e 54 borrados
  1. 12 20
      examples/webcam/webcam.go
  2. 50 23
      v4l2/device/device.go
  3. 24 0
      v4l2/device/device_config.go
  4. 4 4
      v4l2/stream_param.go
  5. 7 7
      v4l2/streaming.go

+ 12 - 20
examples/webcam/webcam.go

@@ -54,7 +54,7 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 
 
 	for frame := range frames {
 	for frame := range frames {
-		if len(frame) == 0{
+		if len(frame) == 0 {
 			log.Print("skipping empty frame")
 			log.Print("skipping empty frame")
 			continue
 			continue
 		}
 		}
@@ -133,7 +133,11 @@ func main() {
 	}
 	}
 
 
 	// open device and setup device
 	// open device and setup device
-	device, err := device.Open(devName)
+	device, err := device.Open(devName,
+		device.WithIOType(v4l2.IOTypeMMAP),
+		device.WithPixFormat(v4l2.PixFormat{PixelFormat: getFormatType(format), Width: uint32(width), Height: uint32(height)}),
+	)
+
 	if err != nil {
 	if err != nil {
 		log.Fatalf("failed to open device: %s", err)
 		log.Fatalf("failed to open device: %s", err)
 	}
 	}
@@ -148,15 +152,7 @@ func main() {
 		log.Fatalf("unable to get format: %s", err)
 		log.Fatalf("unable to get format: %s", err)
 	}
 	}
 	log.Printf("Current format: %s", currFmt)
 	log.Printf("Current format: %s", currFmt)
-	if err := device.SetPixFormat(updateFormat(currFmt, format, width, height)); err != nil {
-		log.Fatalf("failed to set format: %s", err)
-	}
-	currFmt, err = device.GetPixFormat()
-	if err != nil {
-		log.Fatalf("unable to get format: %s", err)
-	}
 	pixfmt = currFmt.PixelFormat
 	pixfmt = currFmt.PixelFormat
-	log.Printf("Updated format: %s", currFmt)
 
 
 	// Setup and start stream capture
 	// Setup and start stream capture
 	if err := device.StartStream(2); err != nil {
 	if err := device.StartStream(2); err != nil {
@@ -188,18 +184,14 @@ func main() {
 	}
 	}
 }
 }
 
 
-func updateFormat(pix v4l2.PixFormat, fmtStr string, w, h int) v4l2.PixFormat {
-	pix.Width = uint32(w)
-	pix.Height = uint32(h)
-
+func getFormatType(fmtStr string) v4l2.FourCCType {
 	switch strings.ToLower(fmtStr) {
 	switch strings.ToLower(fmtStr) {
 	case "mjpeg", "jpeg":
 	case "mjpeg", "jpeg":
-		pix.PixelFormat = v4l2.PixelFmtMJPEG
+		return v4l2.PixelFmtMJPEG
 	case "h264", "h.264":
 	case "h264", "h.264":
-		pix.PixelFormat = v4l2.PixelFmtH264
+		return v4l2.PixelFmtH264
 	case "yuyv":
 	case "yuyv":
-		pix.PixelFormat = v4l2.PixelFmtYUYV
+		return v4l2.PixelFmtYUYV
 	}
 	}
-
-	return pix
-}
+	return v4l2.PixelFmtMPEG
+}

+ 50 - 23
v4l2/device/device.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
+	"reflect"
 	sys "syscall"
 	sys "syscall"
 	"time"
 	"time"
 
 
@@ -14,11 +15,10 @@ type Device struct {
 	path             string
 	path             string
 	file             *os.File
 	file             *os.File
 	fd               uintptr
 	fd               uintptr
+	config           Config
 	bufType          v4l2.BufType
 	bufType          v4l2.BufType
 	cap              v4l2.Capability
 	cap              v4l2.Capability
 	cropCap          v4l2.CropCapability
 	cropCap          v4l2.CropCapability
-	selectedFormat   v4l2.PixFormat
-	supportedFormats []v4l2.PixFormat
 	buffers          [][]byte
 	buffers          [][]byte
 	requestedBuf     v4l2.RequestBuffers
 	requestedBuf     v4l2.RequestBuffers
 	streaming        bool
 	streaming        bool
@@ -26,13 +26,26 @@ type Device struct {
 
 
 // Open creates opens the underlying device at specified path
 // Open creates opens the underlying device at specified path
 // and returns a *Device or an error if unable to open device.
 // and returns a *Device or an error if unable to open device.
-func Open(path string) (*Device, error) {
+func Open(path string, options ...Option) (*Device, error) {
 	file, err := os.OpenFile(path, sys.O_RDWR|sys.O_NONBLOCK, 0644)
 	file, err := os.OpenFile(path, sys.O_RDWR|sys.O_NONBLOCK, 0644)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("device open: %w", err)
 		return nil, fmt.Errorf("device open: %w", err)
 	}
 	}
-	dev := &Device{path: path, file: file, fd: file.Fd()}
+	dev := &Device{path: path, file: file, fd: file.Fd(), config: Config{}}
 
 
+	// apply options
+	if len(options) > 0 {
+		for _, o := range options {
+			o(&dev.config)
+		}
+	}
+
+	// ensures IOType is set
+	if reflect.ValueOf(dev.config.ioType).IsZero() {
+		dev.config.ioType = v4l2.IOTypeMMAP
+	}
+
+	// set capability
 	cap, err := v4l2.GetCapability(file.Fd())
 	cap, err := v4l2.GetCapability(file.Fd())
 	if err != nil {
 	if err != nil {
 		if err := file.Close(); err != nil {
 		if err := file.Close(); err != nil {
@@ -54,6 +67,7 @@ func Open(path string) (*Device, error) {
 		return nil, fmt.Errorf("device open: %s: %w", path, v4l2.ErrorUnsupportedFeature)
 		return nil, fmt.Errorf("device open: %s: %w", path, v4l2.ErrorUnsupportedFeature)
 	}
 	}
 
 
+	// set crop
 	cropCap, err := v4l2.GetCropCapability(file.Fd(), dev.bufType)
 	cropCap, err := v4l2.GetCropCapability(file.Fd(), dev.bufType)
 	if err != nil {
 	if err != nil {
 		if err := file.Close(); err != nil {
 		if err := file.Close(); err != nil {
@@ -63,6 +77,15 @@ func Open(path string) (*Device, error) {
 	}
 	}
 	dev.cropCap = cropCap
 	dev.cropCap = cropCap
 
 
+	// set pix format
+	if reflect.ValueOf(dev.config.pixFormat).IsZero() {
+		pixFmt, err :=  v4l2.GetPixFormat(file.Fd())
+		if err != nil {
+			fmt.Errorf("device open: %s: set format: %w", path, err)
+		}
+		dev.config.pixFormat = pixFmt
+	}
+
 	return dev, nil
 	return dev, nil
 }
 }
 
 
@@ -116,12 +139,16 @@ func (d *Device) GetPixFormat() (v4l2.PixFormat, error) {
 	if !d.cap.IsVideoCaptureSupported() {
 	if !d.cap.IsVideoCaptureSupported() {
 		return v4l2.PixFormat{}, v4l2.ErrorUnsupportedFeature
 		return v4l2.PixFormat{}, v4l2.ErrorUnsupportedFeature
 	}
 	}
-	pixFmt, err := v4l2.GetPixFormat(d.fd)
-	if err != nil {
-		return v4l2.PixFormat{}, fmt.Errorf("device: %w", err)
+
+	if reflect.ValueOf(d.config.pixFormat).IsZero() {
+		pixFmt, err := v4l2.GetPixFormat(d.fd)
+		if err != nil {
+			return v4l2.PixFormat{}, fmt.Errorf("device: %w", err)
+		}
+		d.config.pixFormat = pixFmt
 	}
 	}
-	d.selectedFormat = pixFmt
-	return pixFmt, nil
+
+	return d.config.pixFormat, nil
 }
 }
 
 
 // SetPixFormat sets the pixel format for the associated device.
 // SetPixFormat sets the pixel format for the associated device.
@@ -133,7 +160,7 @@ func (d *Device) SetPixFormat(pixFmt v4l2.PixFormat) error {
 	if err := v4l2.SetPixFormat(d.fd, pixFmt); err != nil {
 	if err := v4l2.SetPixFormat(d.fd, pixFmt); err != nil {
 		return fmt.Errorf("device: %w", err)
 		return fmt.Errorf("device: %w", err)
 	}
 	}
-	d.selectedFormat = pixFmt
+	d.config.pixFormat = pixFmt
 	return nil
 	return nil
 }
 }
 
 
@@ -175,24 +202,24 @@ func (d *Device) GetVideoInputInfo(index uint32) (v4l2.InputInfo, error) {
 
 
 // GetStreamParam returns streaming parameter information for device
 // GetStreamParam returns streaming parameter information for device
 func (d *Device) GetStreamParam() (v4l2.StreamParam, error) {
 func (d *Device) GetStreamParam() (v4l2.StreamParam, error) {
-	if !d.cap.IsVideoCaptureSupported() {
+	if !d.cap.IsVideoCaptureSupported() && d.cap.IsVideoOutputSupported() {
 		return v4l2.StreamParam{}, v4l2.ErrorUnsupportedFeature
 		return v4l2.StreamParam{}, v4l2.ErrorUnsupportedFeature
 	}
 	}
-	return v4l2.GetStreamParam(d.fd)
+	return v4l2.GetStreamParam(d.fd, d.bufType)
 }
 }
 
 
 // SetStreamParam saves stream parameters for device
 // SetStreamParam saves stream parameters for device
-func (d *Device) SetStreamParam() (v4l2.StreamParam, error) {
-	if !d.cap.IsVideoCaptureSupported() {
-		return v4l2.StreamParam{}, v4l2.ErrorUnsupportedFeature
+func (d *Device) SetStreamParam(param v4l2.StreamParam) error {
+	if !d.cap.IsVideoCaptureSupported() && d.cap.IsVideoOutputSupported() {
+		return v4l2.ErrorUnsupportedFeature
 	}
 	}
-	return v4l2.GetStreamParam(d.fd)
+	return v4l2.SetStreamParam(d.fd, d.bufType, param)
 }
 }
 
 
-// SetCaptureFramerate sets the video capture FPS value of the device
-func (d *Device) SetCaptureFramerate (fps uint32) error {
-	param := v4l2.CaptureParam{TimePerFrame: v4l2.Fract{Numerator:1,Denominator: fps}}
-	return v4l2.SetStreamParam(d.fd, v4l2.StreamParam{Capture: param})
+// SetCaptureFPS sets the video capture FPS value of the device
+func (d *Device) SetCaptureFPS(fps uint32) error {
+	capture := v4l2.CaptureParam{TimePerFrame: v4l2.Fract{Numerator: 1, Denominator: fps}}
+	return d.SetStreamParam(v4l2.StreamParam{Capture: capture})
 }
 }
 
 
 // GetMediaInfo returns info for a device that supports the Media API
 // GetMediaInfo returns info for a device that supports the Media API
@@ -206,7 +233,7 @@ func (d *Device) StartStream(buffSize uint32) error {
 	}
 	}
 
 
 	// allocate device buffers
 	// allocate device buffers
-	bufReq, err := v4l2.RequestBuffersInfo(d.fd, v4l2.IOTypeMMAP, d.bufType, buffSize)
+	bufReq, err := v4l2.InitBuffers(d.fd, d.config.ioType, d.bufType, buffSize)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("device: start stream: %w", err)
 		return fmt.Errorf("device: start stream: %w", err)
 	}
 	}
@@ -216,7 +243,7 @@ func (d *Device) StartStream(buffSize uint32) error {
 	bufCount := int(d.requestedBuf.Count)
 	bufCount := int(d.requestedBuf.Count)
 	d.buffers = make([][]byte, d.requestedBuf.Count)
 	d.buffers = make([][]byte, d.requestedBuf.Count)
 	for i := 0; i < bufCount; i++ {
 	for i := 0; i < bufCount; i++ {
-		buffer, err := v4l2.GetBuffer(d.fd, uint32(i))
+		buffer, err := v4l2.GetBuffer(d.fd, v4l2.IOTypeMMAP, d.bufType, uint32(i))
 		if err != nil {
 		if err != nil {
 			return fmt.Errorf("device start stream: %w", err)
 			return fmt.Errorf("device start stream: %w", err)
 		}
 		}
@@ -320,4 +347,4 @@ func (d *Device) StopStream() error {
 		return fmt.Errorf("device: stop stream: %w", err)
 		return fmt.Errorf("device: stop stream: %w", err)
 	}
 	}
 	return nil
 	return nil
-}
+}

+ 24 - 0
v4l2/device/device_config.go

@@ -0,0 +1,24 @@
+package device
+
+import (
+	"github.com/vladimirvivien/go4vl/v4l2"
+)
+
+type Config struct {
+	ioType v4l2.IOType
+	pixFormat v4l2.PixFormat
+}
+
+type Option func(*Config)
+
+func WithIOType(ioType v4l2.IOType) Option {
+	return func(o *Config) {
+		o.ioType = ioType
+	}
+}
+
+func WithPixFormat(pixFmt v4l2.PixFormat) Option {
+	return func(o *Config) {
+		o.pixFormat = pixFmt
+	}
+}

+ 4 - 4
v4l2/stream_param.go

@@ -54,9 +54,9 @@ type OutputParam struct {
 // GetStreamParam returns streaming parameters for the driver (v4l2_streamparm).
 // GetStreamParam returns streaming parameters for the driver (v4l2_streamparm).
 // https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html
 // 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#L2362
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2362
-func GetStreamParam(fd uintptr) (StreamParam, error) {
+func GetStreamParam(fd uintptr, bufType BufType) (StreamParam, error) {
 	var v4l2Param C.struct_v4l2_streamparm
 	var v4l2Param C.struct_v4l2_streamparm
-	v4l2Param._type = C.uint(BufTypeVideoCapture)
+	v4l2Param._type = C.uint(bufType)
 
 
 	if err := send(fd, C.VIDIOC_G_PARM, uintptr(unsafe.Pointer(&v4l2Param))); err != nil {
 	if err := send(fd, C.VIDIOC_G_PARM, uintptr(unsafe.Pointer(&v4l2Param))); err != nil {
 		return StreamParam{}, fmt.Errorf("stream param: %w", err)
 		return StreamParam{}, fmt.Errorf("stream param: %w", err)
@@ -72,9 +72,9 @@ func GetStreamParam(fd uintptr) (StreamParam, error) {
 	}, nil
 	}, nil
 }
 }
 
 
-func SetStreamParam(fd uintptr, param StreamParam) error {
+func SetStreamParam(fd uintptr, bufType BufType, param StreamParam) error {
 	var v4l2Param C.struct_v4l2_streamparm
 	var v4l2Param C.struct_v4l2_streamparm
-	v4l2Param._type = C.uint(BufTypeVideoCapture)
+	v4l2Param._type = C.uint(bufType)
 	*(*C.struct_v4l2_captureparm)(unsafe.Pointer(&v4l2Param.parm[0])) = *(*C.struct_v4l2_captureparm)(unsafe.Pointer(&param.Capture))
 	*(*C.struct_v4l2_captureparm)(unsafe.Pointer(&v4l2Param.parm[0])) = *(*C.struct_v4l2_captureparm)(unsafe.Pointer(&param.Capture))
 	*(*C.struct_v4l2_outputparm)(unsafe.Pointer(uintptr(unsafe.Pointer(&v4l2Param.parm[0])) + unsafe.Sizeof(C.struct_v4l2_captureparam{}))) =
 	*(*C.struct_v4l2_outputparm)(unsafe.Pointer(uintptr(unsafe.Pointer(&v4l2Param.parm[0])) + unsafe.Sizeof(C.struct_v4l2_captureparam{}))) =
 		*(*C.struct_v4l2_outputparm)(unsafe.Pointer(&param.Output))
 		*(*C.struct_v4l2_outputparm)(unsafe.Pointer(&param.Output))

+ 7 - 7
v4l2/streaming.go

@@ -144,10 +144,10 @@ func StreamOff(fd uintptr) error {
 	return nil
 	return nil
 }
 }
 
 
-// RequestBuffersInfo sends buffer allocation request to initialize buffer IO
+// InitBuffers sends buffer allocation request to initialize buffer IO
 // for video capture or video output when using either mem map, user pointer, or DMA buffers.
 // for video capture or video output when using either mem map, user pointer, or DMA buffers.
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-reqbufs.html#vidioc-reqbufs
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-reqbufs.html#vidioc-reqbufs
-func RequestBuffersInfo(fd uintptr, ioType IOType, bufType BufType, buffSize uint32) (RequestBuffers, error) {
+func InitBuffers(fd uintptr, ioType IOType, bufType BufType, buffSize uint32) (RequestBuffers, error) {
 	if ioType != IOTypeMMAP && ioType != IOTypeDMABuf {
 	if ioType != IOTypeMMAP && ioType != IOTypeDMABuf {
 		return RequestBuffers{}, fmt.Errorf("request buffers: %w", ErrorUnsupported)
 		return RequestBuffers{}, fmt.Errorf("request buffers: %w", ErrorUnsupported)
 	}
 	}
@@ -163,12 +163,12 @@ func RequestBuffersInfo(fd uintptr, ioType IOType, bufType BufType, buffSize uin
 	return *(*RequestBuffers)(unsafe.Pointer(&req)), nil
 	return *(*RequestBuffers)(unsafe.Pointer(&req)), nil
 }
 }
 
 
-// GetBuffer retrieves bunffer info for allocated buffers at provided index.
-// This call should take place after buffers are allocated (for mmap for instance).
-func GetBuffer(fd uintptr, index uint32) (Buffer, error) {
+// GetBuffer retrieves buffer info for allocated buffers at provided index.
+// This call should take place after buffers are allocated with RequestBuffers (for mmap for instance).
+func GetBuffer(fd uintptr, ioType IOType, bufType BufType, index uint32) (Buffer, error) {
 	var v4l2Buf C.struct_v4l2_buffer
 	var v4l2Buf C.struct_v4l2_buffer
-	v4l2Buf._type = C.uint(BufTypeVideoCapture)
-	v4l2Buf.memory = C.uint(IOTypeMMAP)
+	v4l2Buf._type = C.uint(bufType)
+	v4l2Buf.memory = C.uint(ioType)
 	v4l2Buf.index = C.uint(index)
 	v4l2Buf.index = C.uint(index)
 
 
 	if err := send(fd, C.VIDIOC_QUERYBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
 	if err := send(fd, C.VIDIOC_QUERYBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {