ソースを参照

Support v4l2_streamparam; refactor Device.Open and Device.StartStream

Vladimir Vivien 3 年 前
コミット
79496a65d6
5 ファイル変更123 行追加67 行削除
  1. 1 1
      examples/device_info/devinfo.go
  2. 2 2
      v4l2/crop.go
  3. 25 11
      v4l2/device/device.go
  4. 52 10
      v4l2/stream_param.go
  5. 43 43
      v4l2/streaming.go

+ 1 - 1
examples/device_info/devinfo.go

@@ -234,7 +234,7 @@ func printCropInfo(dev *device.Device) error {
 }
 }
 
 
 func printCaptureParam(dev *device.Device) error {
 func printCaptureParam(dev *device.Device) error {
-	params, err := dev.GetCaptureParam()
+	params, err := dev.GetStreamParam()
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("streaming capture param: %w", err)
 		return fmt.Errorf("streaming capture param: %w", err)
 	}
 	}

+ 2 - 2
v4l2/crop.go

@@ -21,9 +21,9 @@ type CropCapability struct {
 
 
 // GetCropCapability  retrieves cropping info for specified device
 // GetCropCapability  retrieves cropping info for specified device
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-cropcap.html#ioctl-vidioc-cropcap
 // See https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-cropcap.html#ioctl-vidioc-cropcap
-func GetCropCapability(fd uintptr) (CropCapability, error) {
+func GetCropCapability(fd uintptr, bufType BufType) (CropCapability, error) {
 	var cap C.struct_v4l2_cropcap
 	var cap C.struct_v4l2_cropcap
-	cap._type = C.uint(BufTypeVideoCapture)
+	cap._type = C.uint(bufType)
 
 
 	if err := send(fd, C.VIDIOC_CROPCAP, uintptr(unsafe.Pointer(&cap))); err != nil {
 	if err := send(fd, C.VIDIOC_CROPCAP, uintptr(unsafe.Pointer(&cap))); err != nil {
 		return CropCapability{}, fmt.Errorf("crop capability: %w", err)
 		return CropCapability{}, fmt.Errorf("crop capability: %w", err)

+ 25 - 11
v4l2/device/device.go

@@ -38,7 +38,7 @@ func Open(path string) (*Device, error) {
 		if err := file.Close(); 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: closing after failure: %s", path, err)
 		}
 		}
-		return nil, fmt.Errorf("device %s: capability: %w", path, err)
+		return nil, fmt.Errorf("device open: %s: %w", path, err)
 	}
 	}
 	dev.cap = cap
 	dev.cap = cap
 
 
@@ -49,17 +49,17 @@ func Open(path string) (*Device, error) {
 		dev.bufType = v4l2.BufTypeVideoOutput
 		dev.bufType = v4l2.BufTypeVideoOutput
 	default:
 	default:
 		if err := file.Close(); 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 open: %s: closing after failure: %s", path, err)
 		}
 		}
-		return nil, fmt.Errorf("unsupported device type")
+		return nil, fmt.Errorf("device open: %s: %w", path, v4l2.ErrorUnsupportedFeature)
 	}
 	}
 
 
-	cropCap, err := v4l2.GetCropCapability(file.Fd())
+	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 {
-			return nil, fmt.Errorf("device %s: closing after failure: %s", path, err)
+			return nil, fmt.Errorf("device open: %s: closing after failure: %s", path, err)
 		}
 		}
-		return nil, fmt.Errorf("device %s: crop capability: %w", path, err)
+		return nil, fmt.Errorf("device open: %s: %w", path, err)
 	}
 	}
 	dev.cropCap = cropCap
 	dev.cropCap = cropCap
 
 
@@ -173,12 +173,26 @@ func (d *Device) GetVideoInputInfo(index uint32) (v4l2.InputInfo, error) {
 	return v4l2.GetVideoInputInfo(d.fd, index)
 	return v4l2.GetVideoInputInfo(d.fd, index)
 }
 }
 
 
-// GetCaptureParam returns streaming capture parameter information
-func (d *Device) GetCaptureParam() (v4l2.CaptureParam, error) {
+// GetStreamParam returns streaming parameter information for device
+func (d *Device) GetStreamParam() (v4l2.StreamParam, error) {
+	if !d.cap.IsVideoCaptureSupported() {
+		return v4l2.StreamParam{}, v4l2.ErrorUnsupportedFeature
+	}
+	return v4l2.GetStreamParam(d.fd)
+}
+
+// SetStreamParam saves stream parameters for device
+func (d *Device) SetStreamParam() (v4l2.StreamParam, error) {
 	if !d.cap.IsVideoCaptureSupported() {
 	if !d.cap.IsVideoCaptureSupported() {
-		return v4l2.CaptureParam{}, v4l2.ErrorUnsupportedFeature
+		return v4l2.StreamParam{}, v4l2.ErrorUnsupportedFeature
 	}
 	}
-	return v4l2.GetStreamCaptureParam(d.fd)
+	return v4l2.GetStreamParam(d.fd)
+}
+
+// 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})
 }
 }
 
 
 // GetMediaInfo returns info for a device that supports the Media API
 // GetMediaInfo returns info for a device that supports the Media API
@@ -192,7 +206,7 @@ func (d *Device) StartStream(buffSize uint32) error {
 	}
 	}
 
 
 	// allocate device buffers
 	// allocate device buffers
-	bufReq, err := v4l2.InitBuffers(d.fd, buffSize)
+	bufReq, err := v4l2.RequestBuffersInfo(d.fd, v4l2.IOTypeMMAP, 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)
 	}
 	}

+ 52 - 10
v4l2/stream_param.go

@@ -18,7 +18,16 @@ const (
 	StreamParamTimePerFrame    StreamParamFlag = C.V4L2_CAP_TIMEPERFRAME
 	StreamParamTimePerFrame    StreamParamFlag = C.V4L2_CAP_TIMEPERFRAME
 )
 )
 
 
-// CaptureParam (v4l2_captureparam)
+// StreamParam (v4l2_streamparam)
+// https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html#c.V4L.v4l2_streamparm
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2362
+type StreamParam struct {
+	Type    IOType
+	Capture CaptureParam
+	Output OutputParam
+}
+
+// CaptureParam (v4l2_captureparm)
 // https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html#c.V4L.v4l2_captureparm
 // 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
 // See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1205
 type CaptureParam struct {
 type CaptureParam struct {
@@ -30,16 +39,49 @@ type CaptureParam struct {
 	_            [4]uint32
 	_            [4]uint32
 }
 }
 
 
-// GetStreamCaptureParam returns streaming capture parameter for the driver (v4l2_streamparm).
-// https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html
-// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2347
+// OutputParam (v4l2_outputparm)
+// https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html#c.V4L.v4l2_outputparm
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1228
+type OutputParam struct {
+	Capability   StreamParamFlag
+	CaptureMode  StreamParamFlag
+	TimePerFrame Fract
+	ExtendedMode uint32
+	WriteBuffers  uint32
+	_            [4]uint32
+}
 
 
-func GetStreamCaptureParam(fd uintptr) (CaptureParam, error) {
-	var param C.struct_v4l2_streamparm
-	param._type = C.uint(BufTypeVideoCapture)
+// GetStreamParam returns streaming parameters for the driver (v4l2_streamparm).
+// https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/vidioc-g-parm.html
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L2362
+func GetStreamParam(fd uintptr) (StreamParam, error) {
+	var v4l2Param C.struct_v4l2_streamparm
+	v4l2Param._type = C.uint(BufTypeVideoCapture)
 
 
-	if err := send(fd, C.VIDIOC_G_PARM, uintptr(unsafe.Pointer(&param))); err != nil {
-		return CaptureParam{}, fmt.Errorf("stream param: %w", err)
+	if err := send(fd, C.VIDIOC_G_PARM, uintptr(unsafe.Pointer(&v4l2Param))); err != nil {
+		return StreamParam{}, fmt.Errorf("stream param: %w", err)
 	}
 	}
-	return *(*CaptureParam)(unsafe.Pointer(&param.parm[0])), nil
+
+	capture := *(*CaptureParam)(unsafe.Pointer(&v4l2Param.parm[0]))
+	output := *(*OutputParam)(unsafe.Pointer(uintptr(unsafe.Pointer(&v4l2Param.parm[0])) + unsafe.Sizeof(C.struct_v4l2_captureparm{})))
+
+	return StreamParam{
+		Type:    BufTypeVideoCapture,
+		Capture: capture,
+		Output:  output,
+	}, nil
 }
 }
+
+func SetStreamParam(fd uintptr, param StreamParam) error {
+	var v4l2Param C.struct_v4l2_streamparm
+	v4l2Param._type = C.uint(BufTypeVideoCapture)
+	*(*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(&param.Output))
+
+	if err := send(fd, C.VIDIOC_S_PARM, uintptr(unsafe.Pointer(&v4l2Param))); err != nil {
+		return fmt.Errorf("stream param: %w", err)
+	}
+
+	return nil
+}

+ 43 - 43
v4l2/streaming.go

@@ -26,16 +26,16 @@ const (
 	BufTypeOverlay      BufType = C.V4L2_BUF_TYPE_VIDEO_OVERLAY
 	BufTypeOverlay      BufType = C.V4L2_BUF_TYPE_VIDEO_OVERLAY
 )
 )
 
 
-// StreamType (v4l2_memory)
+// IOType (v4l2_memory)
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/mmap.html?highlight=v4l2_memory_mmap
 // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/mmap.html?highlight=v4l2_memory_mmap
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L188
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L188
-type StreamType = uint32
+type IOType = uint32
 
 
 const (
 const (
-	StreamTypeMMAP    StreamType = C.V4L2_MEMORY_MMAP
-	StreamTypeUserPtr StreamType = C.V4L2_MEMORY_USERPTR
-	StreamTypeOverlay StreamType = C.V4L2_MEMORY_OVERLAY
-	StreamTypeDMABuf  StreamType = C.V4L2_MEMORY_DMABUF
+	IOTypeMMAP    IOType = C.V4L2_MEMORY_MMAP
+	IOTypeUserPtr IOType = C.V4L2_MEMORY_USERPTR
+	IOTypeOverlay IOType = C.V4L2_MEMORY_OVERLAY
+	IOTypeDMABuf  IOType = C.V4L2_MEMORY_DMABUF
 )
 )
 
 
 // TODO implement vl42_create_buffers
 // TODO implement vl42_create_buffers
@@ -58,37 +58,37 @@ type RequestBuffers struct {
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1037
 // https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/videodev2.h#L1037
 //
 //
 type Buffer struct {
 type Buffer struct {
-	Index      uint32
-	StreamType uint32
-	BytesUsed  uint32
-	Flags      uint32
-	Field      uint32
-	Timestamp  sys.Timeval
-	Timecode   Timecode
-	Sequence   uint32
-	Memory     uint32
-	Info       BufferInfo // union m
-	Length     uint32
-	Reserved2  uint32
-	RequestFD  int32
+	Index     uint32
+	Type      uint32
+	BytesUsed uint32
+	Flags     uint32
+	Field     uint32
+	Timestamp sys.Timeval
+	Timecode  Timecode
+	Sequence  uint32
+	Memory    uint32
+	Info      BufferInfo // union m
+	Length    uint32
+	Reserved2 uint32
+	RequestFD int32
 }
 }
 
 
 // makeBuffer makes a Buffer value from C.struct_v4l2_buffer
 // makeBuffer makes a Buffer value from C.struct_v4l2_buffer
 func makeBuffer(v4l2Buf C.struct_v4l2_buffer) Buffer {
 func makeBuffer(v4l2Buf C.struct_v4l2_buffer) Buffer {
 	return Buffer{
 	return Buffer{
-		Index:      uint32(v4l2Buf.index),
-		StreamType: uint32(v4l2Buf._type),
-		BytesUsed:  uint32(v4l2Buf.bytesused),
-		Flags:      uint32(v4l2Buf.flags),
-		Field:      uint32(v4l2Buf.field),
-		Timestamp:  *(*sys.Timeval)(unsafe.Pointer(&v4l2Buf.timestamp)),
-		Timecode:   *(*Timecode)(unsafe.Pointer(&v4l2Buf.timecode)),
-		Sequence:   uint32(v4l2Buf.sequence),
-		Memory:     uint32(v4l2Buf.memory),
-		Info:       *(*BufferInfo)(unsafe.Pointer(&v4l2Buf.m[0])),
-		Length:     uint32(v4l2Buf.length),
-		Reserved2:  uint32(v4l2Buf.reserved2),
-		RequestFD:  *(*int32)(unsafe.Pointer(&v4l2Buf.anon0[0])),
+		Index:     uint32(v4l2Buf.index),
+		Type:      uint32(v4l2Buf._type),
+		BytesUsed: uint32(v4l2Buf.bytesused),
+		Flags:     uint32(v4l2Buf.flags),
+		Field:     uint32(v4l2Buf.field),
+		Timestamp: *(*sys.Timeval)(unsafe.Pointer(&v4l2Buf.timestamp)),
+		Timecode:  *(*Timecode)(unsafe.Pointer(&v4l2Buf.timecode)),
+		Sequence:  uint32(v4l2Buf.sequence),
+		Memory:    uint32(v4l2Buf.memory),
+		Info:      *(*BufferInfo)(unsafe.Pointer(&v4l2Buf.m[0])),
+		Length:    uint32(v4l2Buf.length),
+		Reserved2: uint32(v4l2Buf.reserved2),
+		RequestFD: *(*int32)(unsafe.Pointer(&v4l2Buf.anon0[0])),
 	}
 	}
 }
 }
 
 
@@ -144,21 +144,21 @@ func StreamOff(fd uintptr) error {
 	return nil
 	return nil
 }
 }
 
 
-// InitBuffers sends buffer allocation request to initialize buffer IO
-// for video capture when using either mem map, user pointer, or DMA buffers.
+// RequestBuffersInfo sends buffer allocation request to initialize buffer IO
+// 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 InitBuffers(fd uintptr, buffSize uint32) (RequestBuffers, error) {
+func RequestBuffersInfo(fd uintptr, ioType IOType, bufType BufType, buffSize uint32) (RequestBuffers, error) {
+	if ioType != IOTypeMMAP && ioType != IOTypeDMABuf {
+		return RequestBuffers{}, fmt.Errorf("request buffers: %w", ErrorUnsupported)
+	}
 	var req C.struct_v4l2_requestbuffers
 	var req C.struct_v4l2_requestbuffers
 	req.count = C.uint(buffSize)
 	req.count = C.uint(buffSize)
-	req._type = C.uint(BufTypeVideoCapture)
-	req.memory = C.uint(StreamTypeMMAP)
+	req._type = C.uint(bufType)
+	req.memory = C.uint(ioType)
 
 
 	if err := send(fd, C.VIDIOC_REQBUFS, uintptr(unsafe.Pointer(&req))); err != nil {
 	if err := send(fd, C.VIDIOC_REQBUFS, uintptr(unsafe.Pointer(&req))); err != nil {
 		return RequestBuffers{}, fmt.Errorf("request buffers: %w", err)
 		return RequestBuffers{}, fmt.Errorf("request buffers: %w", err)
 	}
 	}
-	if req.count < 2 {
-		return RequestBuffers{}, errors.New("request buffers: insufficient memory on device")
-	}
 
 
 	return *(*RequestBuffers)(unsafe.Pointer(&req)), nil
 	return *(*RequestBuffers)(unsafe.Pointer(&req)), nil
 }
 }
@@ -168,7 +168,7 @@ func InitBuffers(fd uintptr, buffSize uint32) (RequestBuffers, error) {
 func GetBuffer(fd uintptr, index uint32) (Buffer, error) {
 func GetBuffer(fd uintptr, index uint32) (Buffer, error) {
 	var v4l2Buf C.struct_v4l2_buffer
 	var v4l2Buf C.struct_v4l2_buffer
 	v4l2Buf._type = C.uint(BufTypeVideoCapture)
 	v4l2Buf._type = C.uint(BufTypeVideoCapture)
-	v4l2Buf.memory = C.uint(StreamTypeMMAP)
+	v4l2Buf.memory = C.uint(IOTypeMMAP)
 	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 {
@@ -202,7 +202,7 @@ func UnmapMemoryBuffer(buf []byte) error {
 func QueueBuffer(fd uintptr, index uint32) (Buffer, error) {
 func QueueBuffer(fd uintptr, index uint32) (Buffer, error) {
 	var v4l2Buf C.struct_v4l2_buffer
 	var v4l2Buf C.struct_v4l2_buffer
 	v4l2Buf._type = C.uint(BufTypeVideoCapture)
 	v4l2Buf._type = C.uint(BufTypeVideoCapture)
-	v4l2Buf.memory = C.uint(StreamTypeMMAP)
+	v4l2Buf.memory = C.uint(IOTypeMMAP)
 	v4l2Buf.index = C.uint(index)
 	v4l2Buf.index = C.uint(index)
 
 
 	if err := send(fd, C.VIDIOC_QBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
 	if err := send(fd, C.VIDIOC_QBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
@@ -219,7 +219,7 @@ func QueueBuffer(fd uintptr, index uint32) (Buffer, error) {
 func DequeueBuffer(fd uintptr) (Buffer, error) {
 func DequeueBuffer(fd uintptr) (Buffer, error) {
 	var v4l2Buf C.struct_v4l2_buffer
 	var v4l2Buf C.struct_v4l2_buffer
 	v4l2Buf._type = C.uint(BufTypeVideoCapture)
 	v4l2Buf._type = C.uint(BufTypeVideoCapture)
-	v4l2Buf.memory = C.uint(StreamTypeMMAP)
+	v4l2Buf.memory = C.uint(IOTypeMMAP)
 
 
 	if err := send(fd, C.VIDIOC_DQBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
 	if err := send(fd, C.VIDIOC_DQBUF, uintptr(unsafe.Pointer(&v4l2Buf))); err != nil {
 		return Buffer{}, fmt.Errorf("buffer dequeue: %w", err)
 		return Buffer{}, fmt.Errorf("buffer dequeue: %w", err)