Procházet zdrojové kódy

Merge pull request #11 from vladimirvivien/list-devices

New functionality to query/list device information
Vladimir Vivien před 3 roky
rodič
revize
d82c68647e

+ 3 - 1
TODO.md

@@ -2,7 +2,9 @@
 A general list (of no implied order) of high level tasks for the project.
 > This list is a tool to help guide the project. It does not imply a roadmap or promise of delivery.
 
-* [ ] Add repo documentation
+* [x] Create device package
+* [x] List/query device available
+* [x] Add repo documentation
 * [ ] Document examples 
 * [ ] Support for YUYV conversion
 * [ ] Set and query device controls

+ 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)
 	}

+ 65 - 7
examples/device_info/devinfo.go

@@ -4,18 +4,30 @@ import (
 	"flag"
 	"fmt"
 	"log"
+	"os"
 	"strings"
 
 	"github.com/vladimirvivien/go4vl/v4l2"
+	"github.com/vladimirvivien/go4vl/v4l2/device"
 )
 
 var template = "\t%-24s : %s\n"
 
 func main() {
 	var devName string
+	var devList bool
 	flag.StringVar(&devName, "d", "/dev/video0", "device name (path)")
+	flag.BoolVar(&devList, "l", false, "list all devices")
 	flag.Parse()
-	device, err := v4l2.Open(devName)
+
+	if devList {
+		if err := listDevices(); err != nil{
+			log.Fatal(err)
+		}
+		os.Exit(0)
+	}
+
+	device, err := device.Open(devName)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -42,7 +54,53 @@ func main() {
 	}
 }
 
-func printDeviceDriverInfo(dev *v4l2.Device) error {
+func listDevices() error {
+	paths, err := device.GetAllDevicePaths()
+	if err != nil {
+		return err
+	}
+	for _, path := range paths {
+		dev, err := device.Open(path)
+		if err != nil {
+			log.Print(err)
+			continue
+		}
+
+		var busInfo, card string
+		cap, err := dev.GetCapability()
+		if err != nil {
+			// is a media device?
+			if mdi, err := dev.GetMediaInfo(); err == nil {
+				if mdi.BusInfo != "" {
+					busInfo = mdi.BusInfo
+				}else{
+					busInfo = "platform: " + mdi.Driver
+				}
+				if mdi.Model != "" {
+					card = mdi.Model
+				}else{
+					card = mdi.Driver
+				}
+			}
+		}else{
+			busInfo = cap.BusInfo
+			card = cap.Card
+		}
+
+		// close device
+		if err := dev.Close(); err != nil {
+			log.Print(err)
+			continue
+		}
+
+		fmt.Printf("Device [%s]: %s: %s\n", path, card, busInfo)
+
+
+	}
+	return nil
+}
+
+func printDeviceDriverInfo(dev *device.Device) error {
 	caps, err := dev.GetCapability()
 	if err != nil {
 		return fmt.Errorf("driver info: %w", err)
@@ -69,7 +127,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 +148,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 +190,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 +211,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 +238,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)
 	}

+ 48 - 41
v4l2/device.go → v4l2/device/device.go

@@ -1,4 +1,4 @@
-package v4l2
+package device
 
 import (
 	"context"
@@ -6,17 +6,19 @@ import (
 	"os"
 	sys "syscall"
 	"time"
+
+	"github.com/vladimirvivien/go4vl/v4l2"
 )
 
 type Device struct {
 	path         string
 	file         *os.File
 	fd           uintptr
-	cap          *Capability
-	cropCap      *CropCapability
-	pixFormat    PixFormat
+	cap          *v4l2.Capability
+	cropCap      *v4l2.CropCapability
+	pixFormat    v4l2.PixFormat
 	buffers      [][]byte
-	requestedBuf RequestBuffers
+	requestedBuf v4l2.RequestBuffers
 	streaming    bool
 }
 
@@ -48,11 +50,11 @@ func (d *Device) GetFileDescriptor() uintptr {
 
 // GetCapability retrieves device capability info and
 // caches it for future capability check.
-func (d *Device) GetCapability() (*Capability, error) {
+func (d *Device) GetCapability() (*v4l2.Capability, error) {
 	if d.cap != nil {
 		return d.cap, nil
 	}
-	cap, err := GetCapability(d.fd)
+	cap, err := v4l2.GetCapability(d.fd)
 	if err != nil {
 		return nil, fmt.Errorf("device: %w", err)
 	}
@@ -62,74 +64,74 @@ func (d *Device) GetCapability() (*Capability, error) {
 
 // GetCropCapability returns cropping info for device `d`
 // and caches it for future capability check.
-func (d *Device) GetCropCapability() (CropCapability, error) {
+func (d *Device) GetCropCapability() (v4l2.CropCapability, error) {
 	if d.cropCap != nil {
 		return *d.cropCap, nil
 	}
 	if err := d.assertVideoCaptureSupport(); err != nil {
-		return CropCapability{}, fmt.Errorf("device: %w", err)
+		return v4l2.CropCapability{}, fmt.Errorf("device: %w", err)
 	}
 
-	cropCap, err := GetCropCapability(d.fd)
+	cropCap, err := v4l2.GetCropCapability(d.fd)
 	if err != nil {
-		return CropCapability{}, fmt.Errorf("device: %w", err)
+		return v4l2.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 {
+func (d *Device) SetCropRect(r v4l2.Rect) error {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 		return fmt.Errorf("device: %w", err)
 	}
-	if err := SetCropRect(d.fd, r); err != nil {
+	if err := v4l2.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) {
+func (d *Device) GetPixFormat() (v4l2.PixFormat, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
-		return PixFormat{}, fmt.Errorf("device: %w", err)
+		return v4l2.PixFormat{}, fmt.Errorf("device: %w", err)
 	}
-	pixFmt, err := GetPixFormat(d.fd)
+	pixFmt, err := v4l2.GetPixFormat(d.fd)
 	if err != nil {
-		return PixFormat{}, fmt.Errorf("device: %w", err)
+		return v4l2.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 {
+func (d *Device) SetPixFormat(pixFmt v4l2.PixFormat) error {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 		return fmt.Errorf("device: %w", err)
 	}
 
-	if err := SetPixFormat(d.fd, pixFmt); err != nil {
+	if err := v4l2.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) {
+func (d *Device) GetFormatDescription(idx uint32) (v4l2.FormatDescription, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
-		return FormatDescription{}, fmt.Errorf("device: %w", err)
+		return v4l2.FormatDescription{}, fmt.Errorf("device: %w", err)
 	}
 
-	return GetFormatDescription(d.fd, idx)
+	return v4l2.GetFormatDescription(d.fd, idx)
 }
 
 
 // GetFormatDescriptions returns all possible format descriptions for device
-func (d *Device) GetFormatDescriptions() ([]FormatDescription, error) {
+func (d *Device) GetFormatDescriptions() ([]v4l2.FormatDescription, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 		return nil, fmt.Errorf("device: %w", err)
 	}
 
-	return GetAllFormatDescriptions(d.fd)
+	return v4l2.GetAllFormatDescriptions(d.fd)
 }
 
 // GetVideoInputIndex returns current video input index for device
@@ -137,23 +139,28 @@ func (d *Device) GetVideoInputIndex()(int32, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
 		return 0, fmt.Errorf("device: %w", err)
 	}
-	return GetCurrentVideoInputIndex(d.fd)
+	return v4l2.GetCurrentVideoInputIndex(d.fd)
 }
 
 // GetVideoInputInfo returns video input info for device
-func (d *Device) GetVideoInputInfo(index uint32) (InputInfo, error) {
+func (d *Device) GetVideoInputInfo(index uint32) (v4l2.InputInfo, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
-		return InputInfo{}, fmt.Errorf("device: %w", err)
+		return v4l2.InputInfo{}, fmt.Errorf("device: %w", err)
 	}
-	return GetVideoInputInfo(d.fd, index)
+	return v4l2.GetVideoInputInfo(d.fd, index)
 }
 
 // GetCaptureParam returns streaming capture parameter information
-func (d *Device) GetCaptureParam() (CaptureParam, error) {
+func (d *Device) GetCaptureParam() (v4l2.CaptureParam, error) {
 	if err := d.assertVideoCaptureSupport(); err != nil {
-		return CaptureParam{}, fmt.Errorf("device: %w", err)
+		return v4l2.CaptureParam{}, fmt.Errorf("device: %w", err)
 	}
-	return GetStreamCaptureParam(d.fd)
+	return v4l2.GetStreamCaptureParam(d.fd)
+}
+
+// GetMediaInfo returns info for a device that supports the Media API
+func (d *Device) GetMediaInfo() (v4l2.MediaDeviceInfo, error) {
+	return v4l2.GetMediaDeviceInfo(d.fd)
 }
 
 func (d *Device) StartStream(buffSize uint32) error {
@@ -165,7 +172,7 @@ func (d *Device) StartStream(buffSize uint32) error {
 	}
 
 	// allocate device buffers
-	bufReq, err := InitBuffers(d.fd, buffSize)
+	bufReq, err := v4l2.InitBuffers(d.fd, buffSize)
 	if err != nil {
 		return fmt.Errorf("device: start stream: %w", err)
 	}
@@ -175,14 +182,14 @@ func (d *Device) StartStream(buffSize uint32) error {
 	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))
+		buffer, err := v4l2.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))
+		mappedBuf, err := v4l2.MapMemoryBuffer(d.fd, int64(offset), int(length))
 		if err != nil {
 			return fmt.Errorf("device start stream: %w", err)
 		}
@@ -191,14 +198,14 @@ func (d *Device) StartStream(buffSize uint32) error {
 
 	// Initial enqueue of buffers for capture
 	for i := 0; i < bufCount; i++ {
-		_, err := QueueBuffer(d.fd, uint32(i))
+		_, err := v4l2.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 {
+	if err := v4l2.StreamOn(d.fd); err != nil {
 		return fmt.Errorf("device start stream: %w", err)
 	}
 
@@ -235,12 +242,12 @@ func (d *Device) Capture(ctx context.Context, fps uint32) (<-chan []byte, error)
 			// 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 {
+				if err := v4l2.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)
+				bufInfo, err := v4l2.DequeueBuffer(d.fd)
 				if err != nil {
 					panic(fmt.Errorf("device: capture: %w", err).Error())
 				}
@@ -256,7 +263,7 @@ func (d *Device) Capture(ctx context.Context, fps uint32) (<-chan []byte, error)
 					return
 				}
 				// enqueu used buffer, prepare for next read
-				if _, err := QueueBuffer(d.fd, bufInfo.Index); err != nil {
+				if _, err := v4l2.QueueBuffer(d.fd, bufInfo.Index); err != nil {
 					panic(fmt.Errorf("device capture: %w", err).Error())
 				}
 
@@ -271,11 +278,11 @@ func (d *Device) Capture(ctx context.Context, fps uint32) (<-chan []byte, error)
 func (d *Device) StopStream() error{
 	d.streaming = false
 	for i := 0; i < len(d.buffers); i++ {
-		if err := UnmapMemoryBuffer(d.buffers[i]); err != nil {
+		if err := v4l2.UnmapMemoryBuffer(d.buffers[i]); err != nil {
 			return fmt.Errorf("device: stop stream: %w", err)
 		}
 	}
-	if err := StreamOff(d.fd); err != nil {
+	if err := v4l2.StreamOff(d.fd); err != nil {
 		return fmt.Errorf("device: stop stream: %w", err)
 	}
 	return nil

+ 56 - 0
v4l2/device/list.go

@@ -0,0 +1,56 @@
+package device
+
+import (
+	"fmt"
+	"os"
+	"regexp"
+)
+
+var (
+	root = "/dev"
+)
+
+// 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 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
+}
+
+// GetAllDevicePaths return a slice of all mounted v4l2 devices
+func GetAllDevicePaths() ([]string, error) {
+	entries, err := os.ReadDir(root)
+	if err != nil {
+		return nil, err
+	}
+	var result []string
+	for _, entry := range entries {
+		dev := fmt.Sprintf("%s/%s", root, entry.Name())
+		if !devPattern.MatchString(dev) {
+			continue
+		}
+		ok, err := IsDevice(dev)
+		if err != nil {
+			return result, err
+		}
+		if ok {
+			result = append(result, dev)
+		}
+	}
+	return result,  nil
+}

+ 13 - 0
v4l2/device/list_test.go

@@ -0,0 +1,13 @@
+package device
+
+import (
+	"testing"
+)
+
+func TestList(t *testing.T){
+	devices, err := GetAllDevicePaths()
+	if err != nil {
+		t.Error(err)
+	}
+	t.Logf("devices: %#v", devices)
+}

+ 38 - 0
v4l2/media.go

@@ -0,0 +1,38 @@
+package v4l2
+
+// #include <linux/media.h>
+import "C"
+import (
+	"fmt"
+	"unsafe"
+)
+
+// MediaDeviceInfo (media_device_info)
+// See https://www.kernel.org/doc/html/latest/userspace-api/media/mediactl/media-ioc-device-info.html
+// See https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/media.h#L29
+type MediaDeviceInfo struct {
+	Driver           string
+	Model            string
+	Serial           string
+	BusInfo          string
+	MediaVersion     VersionInfo
+	HardwareRevision uint32
+	DriverVersion    VersionInfo
+}
+
+// GetMediaDeviceInfo retrieves media information for specified device, if supported.
+func GetMediaDeviceInfo(fd uintptr) (MediaDeviceInfo, error) {
+	var mdi C.struct_media_device_info
+	if err := send(fd, C.MEDIA_IOC_DEVICE_INFO, uintptr(unsafe.Pointer(&mdi))); err != nil {
+		return MediaDeviceInfo{}, fmt.Errorf("media device info: %w", err)
+	}
+	return MediaDeviceInfo{
+		Driver:           C.GoString((*C.char)(&mdi.driver[0])),
+		Model:            C.GoString((*C.char)(&mdi.model[0])),
+		Serial:           C.GoString((*C.char)(&mdi.serial[0])),
+		BusInfo:          C.GoString((*C.char)(&mdi.bus_info[0])),
+		MediaVersion:     VersionInfo{value: uint32(mdi.media_version)},
+		HardwareRevision: uint32(mdi.hw_revision),
+		DriverVersion:    VersionInfo{value: uint32(mdi.driver_version)},
+	}, nil
+}

+ 5 - 0
v4l2/types.go

@@ -20,6 +20,11 @@ func (v VersionInfo) Patch() uint32{
 	return v.value&0xff
 }
 
+// Value returns the raw numeric version value
+func (v VersionInfo) Value() uint32 {
+	return v.value
+}
+
 func (v VersionInfo) String() string {
 	return fmt.Sprintf("v%d.%d.%d", v.Major(), v.Minor(), v.Patch())
 }