2
0
Эх сурвалжийг харах

Update source for device refactor

Vladimir Vivien 3 жил өмнө
parent
commit
c2b767ac88

+ 2 - 1
examples/capture/capture.go

@@ -8,6 +8,7 @@ import (
 	"os"
 
 	"github.com/vladimirvivien/go4vl/v4l2"
+	"github.com/vladimirvivien/go4vl/v4l2/device"
 )
 
 func main() {
@@ -16,7 +17,7 @@ func main() {
 	flag.Parse()
 
 	// open device
-	device, err := v4l2.Open(devName)
+	device, err := device.Open(devName)
 	if err != nil {
 		log.Fatalf("failed to open device: %s", err)
 	}

+ 8 - 7
examples/device_info/devinfo.go

@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/vladimirvivien/go4vl/v4l2"
+	"github.com/vladimirvivien/go4vl/v4l2/device"
 )
 
 var template = "\t%-24s : %s\n"
@@ -15,7 +16,7 @@ func main() {
 	var devName string
 	flag.StringVar(&devName, "d", "/dev/video0", "device name (path)")
 	flag.Parse()
-	device, err := v4l2.Open(devName)
+	device, err := device.Open(devName)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -42,7 +43,7 @@ func main() {
 	}
 }
 
-func printDeviceDriverInfo(dev *v4l2.Device) error {
+func printDeviceDriverInfo(dev *device.Device) error {
 	caps, err := dev.GetCapability()
 	if err != nil {
 		return fmt.Errorf("driver info: %w", err)
@@ -69,7 +70,7 @@ func printDeviceDriverInfo(dev *v4l2.Device) error {
 	return nil
 }
 
-func printVideoInputInfo(dev *v4l2.Device) error {
+func printVideoInputInfo(dev *device.Device) error {
 	// first get current input
 	index, err := dev.GetVideoInputIndex()
 	if err != nil {
@@ -90,7 +91,7 @@ func printVideoInputInfo(dev *v4l2.Device) error {
 	return nil
 }
 
-func printFormatInfo(dev *v4l2.Device) error {
+func printFormatInfo(dev *device.Device) error {
 	pixFmt, err := dev.GetPixFormat()
 	if err != nil {
 		return fmt.Errorf("video capture format: %w", err)
@@ -132,7 +133,7 @@ func printFormatInfo(dev *v4l2.Device) error {
 	return printFormatDesc(dev)
 }
 
-func printFormatDesc(dev *v4l2.Device) error {
+func printFormatDesc(dev *device.Device) error {
 	descs, err := dev.GetFormatDescriptions()
 	if err != nil {
 		return fmt.Errorf("format desc: %w", err)
@@ -153,7 +154,7 @@ func printFormatDesc(dev *v4l2.Device) error {
 	return nil
 }
 
-func printCropInfo(dev *v4l2.Device) error {
+func printCropInfo(dev *device.Device) error {
 	crop, err := dev.GetCropCapability()
 	if err != nil {
 		return fmt.Errorf("crop capability: %w", err)
@@ -180,7 +181,7 @@ func printCropInfo(dev *v4l2.Device) error {
 	return nil
 }
 
-func printCaptureParam(dev *v4l2.Device) error {
+func printCaptureParam(dev *device.Device) error {
 	params, err := dev.GetCaptureParam()
 	if err != nil {
 		return fmt.Errorf("streaming capture param: %w", err)

+ 2 - 1
examples/format/devfmt.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/vladimirvivien/go4vl/v4l2"
+	"github.com/vladimirvivien/go4vl/v4l2/device"
 )
 
 func main() {
@@ -20,7 +21,7 @@ func main() {
 	flag.StringVar(&format, "f", format, "pixel format")
 	flag.Parse()
 
-	device, err := v4l2.Open(devName)
+	device, err := device.Open(devName)
 	if err != nil {
 		log.Fatalf("failed to open device: %s", err)
 	}

+ 3 - 2
examples/webcam/webcam.go

@@ -13,6 +13,7 @@ import (
 
 	"github.com/vladimirvivien/go4vl/imgsupport"
 	"github.com/vladimirvivien/go4vl/v4l2"
+	"github.com/vladimirvivien/go4vl/v4l2/device"
 )
 
 var (
@@ -87,7 +88,7 @@ func serveVideoStream(w http.ResponseWriter, req *http.Request) {
 func main() {
 	port := ":9090"
 	devName := "/dev/video0"
-	defaultDev, err := v4l2.Open(devName)
+	defaultDev, err := device.Open(devName)
 	skipDefault := false
 	if err != nil {
 		skipDefault = true
@@ -125,7 +126,7 @@ func main() {
 	}
 
 	// open device and setup device
-	device, err := v4l2.Open(devName)
+	device, err := device.Open(devName)
 	if err != nil {
 		log.Fatalf("failed to open device: %s", err)
 	}

+ 0 - 307
v4l2/device.go

@@ -1,307 +0,0 @@
-package v4l2
-
-import (
-	"context"
-	"fmt"
-	"os"
-	sys "syscall"
-	"time"
-)
-
-type Device struct {
-	path         string
-	file         *os.File
-	fd           uintptr
-	cap          *Capability
-	cropCap      *CropCapability
-	pixFormat    PixFormat
-	buffers      [][]byte
-	requestedBuf 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)
-	if err != nil {
-		return nil, fmt.Errorf("device open: %w", err)
-	}
-	return &Device{path: path, file: file, fd: file.Fd()}, nil
-}
-
-// Close closes the underlying device associated with `d` .
-func (d *Device) Close() error {
-	if d.streaming{
-		if err := d.StopStream(); err != nil{
-			return err
-		}
-	}
-
-	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
-// caches it for future capability check.
-func (d *Device) GetCapability() (*Capability, error) {
-	if d.cap != nil {
-		return d.cap, nil
-	}
-	cap, err := GetCapability(d.fd)
-	if err != nil {
-		return nil, fmt.Errorf("device: %w", err)
-	}
-	d.cap = &cap
-	return d.cap, nil
-}
-
-// GetCropCapability returns cropping info for device `d`
-// and caches it for future capability check.
-func (d *Device) GetCropCapability() (CropCapability, error) {
-	if d.cropCap != nil {
-		return *d.cropCap, nil
-	}
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return CropCapability{}, fmt.Errorf("device: %w", err)
-	}
-
-	cropCap, err := GetCropCapability(d.fd)
-	if err != nil {
-		return CropCapability{}, fmt.Errorf("device: %w", err)
-	}
-	d.cropCap = &cropCap
-	return cropCap, nil
-}
-
-// SetCropRect crops the video dimension for the device
-func (d *Device) SetCropRect(r Rect) error {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return fmt.Errorf("device: %w", err)
-	}
-	if err := SetCropRect(d.fd, r); err != nil {
-		return fmt.Errorf("device: %w", err)
-	}
-	return nil
-}
-
-// GetPixFormat retrieves pixel format info for device
-func (d *Device) GetPixFormat() (PixFormat, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return PixFormat{}, fmt.Errorf("device: %w", err)
-	}
-	pixFmt, err := GetPixFormat(d.fd)
-	if err != nil {
-		return PixFormat{}, fmt.Errorf("device: %w", err)
-	}
-	return pixFmt, nil
-}
-
-// SetPixFormat sets the pixel format for the associated device.
-func (d *Device) SetPixFormat(pixFmt PixFormat) error {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return fmt.Errorf("device: %w", err)
-	}
-
-	if err := SetPixFormat(d.fd, pixFmt); err != nil {
-		return fmt.Errorf("device: %w", err)
-	}
-	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) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return nil, fmt.Errorf("device: %w", err)
-	}
-
-	return 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)
-	}
-	return GetCurrentVideoInputIndex(d.fd)
-}
-
-// GetVideoInputInfo returns video input info for device
-func (d *Device) GetVideoInputInfo(index uint32) (InputInfo, error) {
-	if err := d.assertVideoCaptureSupport(); err != nil {
-		return InputInfo{}, fmt.Errorf("device: %w", err)
-	}
-	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 {
-	if d.streaming {
-		return nil
-	}
-	if err := d.assertVideoStreamSupport(); err != nil {
-		return fmt.Errorf("device: %w", err)
-	}
-
-	// allocate device buffers
-	bufReq, err := InitBuffers(d.fd, buffSize)
-	if err != nil {
-		return fmt.Errorf("device: start stream: %w", err)
-	}
-	d.requestedBuf = bufReq
-
-	// for each device buff allocated, prepare local mapped buffer
-	bufCount := int(d.requestedBuf.Count)
-	d.buffers = make([][]byte, d.requestedBuf.Count)
-	for i := 0; i < bufCount; i++ {
-		buffer, err := GetBuffer(d.fd, uint32(i))
-		if err != nil {
-			return fmt.Errorf("device start stream: %w", err)
-		}
-
-		offset := buffer.Info.Offset
-		length := buffer.Length
-		mappedBuf, err := MapMemoryBuffer(d.fd, int64(offset), int(length))
-		if err != nil {
-			return fmt.Errorf("device start stream: %w", err)
-		}
-		d.buffers[i] = mappedBuf
-	}
-
-	// Initial enqueue of buffers for capture
-	for i := 0; i < bufCount; i++ {
-		_, err := QueueBuffer(d.fd, uint32(i))
-		if err != nil {
-			return fmt.Errorf("device start stream: %w", err)
-		}
-	}
-
-	// turn on device stream
-	if err := StreamOn(d.fd); err != nil {
-		return fmt.Errorf("device start stream: %w", err)
-	}
-
-	d.streaming = true
-
-	return nil
-}
-
-// Capture captures video buffer from device and emit
-// each buffer on channel.
-func (d *Device) Capture(ctx context.Context, fps uint32) (<-chan []byte, error) {
-	if !d.streaming {
-		return nil, fmt.Errorf("device: capture: streaming not started")
-	}
-	if ctx == nil {
-		return nil, fmt.Errorf("device: context nil")
-	}
-
-	bufCount := int(d.requestedBuf.Count)
-	dataChan := make(chan []byte, bufCount)
-
-	if fps == 0 {
-		fps = 10
-	}
-
-	// delay duration based on frame per second
-	fpsDelay := time.Duration((float64(1) / float64(fps)) * float64(time.Second))
-
-	go func() {
-		defer close(dataChan)
-
-		// capture forever or until signaled to stop
-		for {
-			// capture bufCount frames
-			for i := 0; i < bufCount; i++ {
-				//TODO add better error-handling during capture, for now just panic
-				if err := WaitForDeviceRead(d.fd, 2*time.Second); err != nil {
-					panic(fmt.Errorf("device: capture: %w", err).Error())
-				}
-
-				// dequeue the device buf
-				bufInfo, err := DequeueBuffer(d.fd)
-				if err != nil {
-					panic(fmt.Errorf("device: capture: %w", err).Error())
-				}
-
-				// assert dequeued buffer is in proper range
-				if !(int(bufInfo.Index) < bufCount) {
-					panic(fmt.Errorf("device: capture: unexpected device buffer index: %d", bufInfo.Index).Error())
-				}
-
-				select {
-				case dataChan <- d.buffers[bufInfo.Index][:bufInfo.BytesUsed]:
-				case <-ctx.Done():
-					return
-				}
-				// enqueu used buffer, prepare for next read
-				if _, err := QueueBuffer(d.fd, bufInfo.Index); err != nil {
-					panic(fmt.Errorf("device capture: %w", err).Error())
-				}
-
-				time.Sleep(fpsDelay)
-			}
-		}
-	}()
-
-	return dataChan, nil
-}
-
-func (d *Device) StopStream() error{
-	d.streaming = false
-	for i := 0; i < len(d.buffers); i++ {
-		if err := UnmapMemoryBuffer(d.buffers[i]); err != nil {
-			return fmt.Errorf("device: stop stream: %w", err)
-		}
-	}
-	if err := StreamOff(d.fd); err != nil {
-		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
-}

+ 5 - 5
v4l2/device/device.go

@@ -10,7 +10,6 @@ import (
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 
-// Device represents a v4l2 device
 type Device struct {
 	path         string
 	file         *os.File
@@ -35,8 +34,8 @@ func Open(path string) (*Device, error) {
 
 // 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
 		}
 	}
@@ -125,6 +124,7 @@ func (d *Device) GetFormatDescription(idx uint32) (v4l2.FormatDescription, error
 	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 {
@@ -135,7 +135,7 @@ func (d *Device) GetFormatDescriptions() ([]v4l2.FormatDescription, error) {
 }
 
 // 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 {
 		return 0, fmt.Errorf("device: %w", err)
 	}
@@ -270,7 +270,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 {

+ 25 - 5
v4l2/device/list.go

@@ -13,9 +13,23 @@ var (
 // devPattern is device directory name pattern on Linux
 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
-func IsDevice(devpath string) bool {
-	return devPattern.MatchString(devpath)
+// IsDevice tests whether the path matches a V4L device name and is a device file
+func IsDevice(devpath string) (bool, error) {
+	stat, err := os.Stat(devpath)
+	if err != nil {
+		return false, err
+	}
+	if stat.Mode()&os.ModeSymlink != 0 {
+		target, err := os.Readlink(devpath)
+		if err != nil {
+			return false, err
+		}
+		return IsDevice(target)
+	}
+	if stat.Mode()&os.ModeDevice != 0 {
+		return true, nil
+	}
+	return false, nil
 }
 
 func List() ([]string, error) {
@@ -26,10 +40,16 @@ func List() ([]string, error) {
 	var result []string
 	for _, entry := range entries {
 		dev := fmt.Sprintf("%s/%s", root, entry.Name())
-		if entry.Type() & os.ModeDevice != 0 && IsDevice(dev) {
+		if !devPattern.MatchString(dev) {
+			continue
+		}
+		ok, err := IsDevice(dev)
+		if err != nil {
+			return result, err
+		}
+		if ok {
 			result = append(result, dev)
 		}
 	}
-
 	return result,  nil
 }