|
@@ -11,31 +11,65 @@ import (
|
|
)
|
|
)
|
|
|
|
|
|
type Device struct {
|
|
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
|
|
// 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) (*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 {
|
|
if err != nil {
|
|
return nil, fmt.Errorf("device open: %w", err)
|
|
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` .
|
|
// Close closes the underlying device associated with `d` .
|
|
func (d *Device) Close() error {
|
|
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
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -43,47 +77,33 @@ 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 {
|
|
|
|
|
|
+// Name returns the device name (or path)
|
|
|
|
+func (d *Device) Name() uintptr {
|
|
return d.fd
|
|
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
|
|
// SetCropRect crops the video dimension for the device
|
|
func (d *Device) SetCropRect(r v4l2.Rect) error {
|
|
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 {
|
|
if err := v4l2.SetCropRect(d.fd, r); err != nil {
|
|
return fmt.Errorf("device: %w", err)
|
|
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
|
|
// GetPixFormat retrieves pixel format info for device
|
|
func (d *Device) GetPixFormat() (v4l2.PixFormat, error) {
|
|
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)
|
|
pixFmt, err := v4l2.GetPixFormat(d.fd)
|
|
if err != nil {
|
|
if err != nil {
|
|
return v4l2.PixFormat{}, fmt.Errorf("device: %w", err)
|
|
return v4l2.PixFormat{}, fmt.Errorf("device: %w", err)
|
|
}
|
|
}
|
|
|
|
+ d.selectedFormat = pixFmt
|
|
return pixFmt, nil
|
|
return pixFmt, nil
|
|
}
|
|
}
|
|
|
|
|
|
// SetPixFormat sets the pixel format for the associated device.
|
|
// SetPixFormat sets the pixel format for the associated device.
|
|
func (d *Device) SetPixFormat(pixFmt v4l2.PixFormat) error {
|
|
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 {
|
|
if err := v4l2.SetPixFormat(d.fd, pixFmt); err != nil {
|
|
return fmt.Errorf("device: %w", err)
|
|
return fmt.Errorf("device: %w", err)
|
|
}
|
|
}
|
|
|
|
+ d.selectedFormat = pixFmt
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
// GetFormatDescription returns a format description for the device at specified format index
|
|
// GetFormatDescription returns a format description for the device at specified format index
|
|
func (d *Device) GetFormatDescription(idx uint32) (v4l2.FormatDescription, error) {
|
|
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)
|
|
return v4l2.GetFormatDescription(d.fd, idx)
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
// GetFormatDescriptions returns all possible format descriptions for device
|
|
// GetFormatDescriptions returns all possible format descriptions for device
|
|
func (d *Device) GetFormatDescriptions() ([]v4l2.FormatDescription, error) {
|
|
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)
|
|
return v4l2.GetAllFormatDescriptions(d.fd)
|
|
}
|
|
}
|
|
|
|
|
|
// GetVideoInputIndex returns current video input index for device
|
|
// 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)
|
|
return v4l2.GetCurrentVideoInputIndex(d.fd)
|
|
}
|
|
}
|
|
|
|
|
|
// GetVideoInputInfo returns video input info for device
|
|
// GetVideoInputInfo returns video input info for device
|
|
func (d *Device) GetVideoInputInfo(index uint32) (v4l2.InputInfo, error) {
|
|
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)
|
|
return v4l2.GetVideoInputInfo(d.fd, index)
|
|
}
|
|
}
|
|
|
|
|
|
// GetCaptureParam returns streaming capture parameter information
|
|
// GetCaptureParam returns streaming capture parameter information
|
|
func (d *Device) GetCaptureParam() (v4l2.CaptureParam, error) {
|
|
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)
|
|
return v4l2.GetStreamCaptureParam(d.fd)
|
|
}
|
|
}
|
|
@@ -167,9 +190,6 @@ func (d *Device) StartStream(buffSize uint32) error {
|
|
if d.streaming {
|
|
if d.streaming {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
- if err := d.assertVideoStreamSupport(); err != nil {
|
|
|
|
- return fmt.Errorf("device: %w", err)
|
|
|
|
- }
|
|
|
|
|
|
|
|
// allocate device buffers
|
|
// allocate device buffers
|
|
bufReq, err := v4l2.InitBuffers(d.fd, buffSize)
|
|
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
|
|
return dataChan, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (d *Device) StopStream() error{
|
|
|
|
|
|
+func (d *Device) StopStream() error {
|
|
d.streaming = false
|
|
d.streaming = false
|
|
for i := 0; i < len(d.buffers); i++ {
|
|
for i := 0; i < len(d.buffers); i++ {
|
|
if err := v4l2.UnmapMemoryBuffer(d.buffers[i]); err != nil {
|
|
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 fmt.Errorf("device: stop stream: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
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
|
|
|
|
-}
|
|
|
|
|
|
+}
|