Browse Source

Adding working minimal prorotype

TC 4 days ago
parent
commit
587d3f8f8e

+ 25 - 14
remdeskd/main.go

@@ -48,44 +48,55 @@ func init() {
 		BaudRate: *usbKVMBaudRate,
 	})
 
+}
+
+func main() {
+	//Start the HID controller
+	err := usbKVM.Connect()
+	if err != nil {
+		log.Fatal(err)
+	}
+
 	// Initiate the video capture device
-	var err error
 	videoCapture, err = usbcapture.NewInstance(&usbcapture.Config{
 		DeviceName: *captureDeviceName,
-		Resolution: &usbcapture.SizeInfo{
-			Width:  1920,
-			Height: 1080,
-			FPS:    []int{30},
-		},
 	})
 
 	if err != nil {
 		log.Fatalf("Failed to create video capture instance: %v", err)
 	}
 
-}
+	//Get device information for debug
+	usbcapture.PrintV4L2FormatInfo(*captureDeviceName)
 
-func main() {
-	//Start the HID controller
-	err := usbKVM.Connect()
+	//Start the video capture device
+	err = videoCapture.StartVideoCapture(&usbcapture.CaptureResolution{
+		Width:  1920,
+		Height: 1080,
+		FPS:    25,
+	})
 	if err != nil {
 		log.Fatal(err)
 	}
 
 	// Handle program exit to close the HID controller
+
 	c := make(chan os.Signal, 1)
 	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 	go func() {
 		<-c
-		log.Println("Shutting down...")
-		usbKVM.Close()
+		log.Println("Shutting down usbKVM...")
+		//usbKVM.Close() //To fix close stuck layer
+		log.Println("Shutting down capture device...")
+		videoCapture.Close()
 		os.Exit(0)
 	}()
 
 	// Start the web server
-	http.HandleFunc("/hid", usbKVM.HIDWebSocketHandler)
 	http.Handle("/", http.FileServer(webfs))
-	addr := ":8080"
+	http.HandleFunc("/hid", usbKVM.HIDWebSocketHandler)
+	http.HandleFunc("/stream", videoCapture.ServeVideoStream)
+	addr := ":9000"
 	log.Printf("Serving on http://localhost%s\n", addr)
 	log.Fatal(http.ListenAndServe(addr, nil))
 

+ 25 - 45
remdeskd/mod/remdeshid/remdeshid.go

@@ -23,8 +23,6 @@ type Controller struct {
 	Config         *Config
 	serialPort     *serial.Port
 	serialRunning  bool
-	readStopChan   chan bool
-	writeStopChan  chan bool
 	writeQueue     chan []byte
 	lastScrollTime int64
 }
@@ -52,58 +50,46 @@ func (c *Controller) Connect() error {
 	}
 
 	c.serialPort = port
-	c.readStopChan = make(chan bool)
 	//Start reading from the serial port
 	go func() {
 		buf := make([]byte, 128)
 		for {
-			select {
-			case <-c.readStopChan:
-				return
-			default:
-				n, err := port.Read(buf)
-				if err != nil {
-					if c.Config.OnReadError != nil {
-						c.Config.OnReadError(err)
-					} else {
-						log.Println(err.Error())
-					}
-					c.readStopChan = nil
-					return
+			n, err := port.Read(buf)
+			if err != nil {
+				if c.Config.OnReadError != nil {
+					c.Config.OnReadError(err)
+				} else {
+					log.Println(err.Error())
 				}
-				if n > 0 {
-					if c.Config.OnDataHandler != nil {
-						c.Config.OnDataHandler(buf[:n], nil)
-					} else {
-						fmt.Print("Received bytes: ")
-						for i := 0; i < n; i++ {
-							fmt.Printf("0x%02X ", buf[i])
-						}
-						fmt.Println()
+				return
+			}
+			if n > 0 {
+				if c.Config.OnDataHandler != nil {
+					c.Config.OnDataHandler(buf[:n], nil)
+				} else {
+					fmt.Print("Received bytes: ")
+					for i := 0; i < n; i++ {
+						fmt.Printf("0x%02X ", buf[i])
 					}
+					fmt.Println()
 				}
 			}
 		}
 	}()
 
 	//Create a loop to write to the serial port
-	c.writeStopChan = make(chan bool)
 	c.writeQueue = make(chan []byte, 1)
 	c.serialRunning = true
 	go func() {
 		for {
-			select {
-			case data := <-c.writeQueue:
-				_, err := port.Write(data)
-				if err != nil {
-					if c.Config.OnWriteError != nil {
-						c.Config.OnWriteError(err)
-					} else {
-						log.Println(err.Error())
-					}
+			data := <-c.writeQueue
+			_, err := port.Write(data)
+			if err != nil {
+				if c.Config.OnWriteError != nil {
+					c.Config.OnWriteError(err)
+				} else {
+					log.Println(err.Error())
 				}
-			case <-c.writeStopChan:
-				c.serialRunning = false
 				return
 			}
 		}
@@ -138,13 +124,7 @@ func (c *Controller) Send(data []byte) error {
 }
 
 func (c *Controller) Close() {
-	if c.readStopChan != nil {
-		c.readStopChan <- true
-	}
-
-	if c.writeStopChan != nil {
-		c.writeStopChan <- true
+	if c.serialPort != nil {
+		c.serialPort.Close()
 	}
-
-	c.serialPort.Close()
 }

+ 24 - 26
remdeskd/mod/usbcapture/usbcapture.go

@@ -16,9 +16,15 @@ import (
 	"github.com/vladimirvivien/go4vl/v4l2"
 )
 
+// The capture resolution to open video device
+type CaptureResolution struct {
+	Width  int
+	Height int
+	FPS    int
+}
+
 type Config struct {
 	DeviceName string
-	Resolution *SizeInfo //The prefered resolution to start the video stream
 }
 
 type Instance struct {
@@ -26,6 +32,7 @@ type Instance struct {
 	SupportedResolutions []FormatInfo //The supported resolutions of the video device
 	Capturing            bool
 	camera               *device.Device
+	cameraStartContext   context.CancelFunc
 	frames_buff          <-chan []byte
 	pixfmt               v4l2.FourCCType
 	width                int
@@ -105,7 +112,7 @@ func (i *Instance) ServeVideoStream(w http.ResponseWriter, req *http.Request) {
 }
 
 // start video capture
-func (i *Instance) StartVideoCapture(selectedFPS int) error {
+func (i *Instance) StartVideoCapture(openWithResolution *CaptureResolution) error {
 	if i.Capturing {
 		return fmt.Errorf("video capture already started")
 	}
@@ -115,26 +122,10 @@ func (i *Instance) StartVideoCapture(selectedFPS int) error {
 	buffSize := 8
 	format := "mjpeg"
 
-	if i.Config.Resolution == nil {
+	if openWithResolution == nil {
 		return fmt.Errorf("resolution not provided")
 	}
 
-	//Check if the selected FPS is valid in the provided Resolutions
-	if i.Config.Resolution != nil {
-		for _, size := range i.Config.Resolution.FPS {
-			if size == selectedFPS {
-				frameRate = size
-				break
-			}
-		}
-
-		if frameRate != selectedFPS {
-			log.Printf("selected FPS %d is not supported, using default %d", selectedFPS, frameRate)
-		}
-	} else {
-		log.Printf("no resolution provided, using default %d", frameRate)
-	}
-
 	//Check if the video device is a capture device
 	isCaptureDev, err := checkVideoCaptureDevice(devName)
 	if err != nil {
@@ -144,13 +135,23 @@ func (i *Instance) StartVideoCapture(selectedFPS int) error {
 		return fmt.Errorf("device %s is not a video capture device", devName)
 	}
 
+	//Check if the selected FPS is valid in the provided Resolutions
+	resolutionIsSupported, err := deviceSupportResolution(i.Config.DeviceName, openWithResolution)
+	if err != nil {
+		return err
+	}
+
+	if !resolutionIsSupported {
+		return errors.New("this device do not support the required resolution settings")
+	}
+
 	//Open the video device
 	camera, err := device.Open(devName,
 		device.WithIOType(v4l2.IOTypeMMAP),
 		device.WithPixFormat(v4l2.PixFormat{
 			PixelFormat: getFormatType(format),
-			Width:       uint32(i.Config.Resolution.Width),
-			Height:      uint32(i.Config.Resolution.Height),
+			Width:       uint32(openWithResolution.Width),
+			Height:      uint32(openWithResolution.Height),
 			Field:       v4l2.FieldAny,
 		}),
 		device.WithFPS(uint32(frameRate)),
@@ -190,10 +191,7 @@ func (i *Instance) StartVideoCapture(selectedFPS int) error {
 	if err := camera.Start(ctx); err != nil {
 		log.Fatalf("stream capture: %s", err)
 	}
-	defer func() {
-		cancel()
-		camera.Close()
-	}()
+	i.cameraStartContext = cancel
 
 	// video stream
 	i.frames_buff = camera.GetOutput()
@@ -216,7 +214,7 @@ func (i *Instance) IsCapturing() bool {
 // StopCapture stops the video capture and closes the camera device
 func (i *Instance) StopCapture() error {
 	if i.camera != nil {
-		i.camera.Stop()
+		i.cameraStartContext()
 		i.camera.Close()
 		i.camera = nil
 	}

+ 27 - 1
remdeskd/mod/usbcapture/video_device.go

@@ -53,6 +53,31 @@ func checkVideoCaptureDevice(device string) (bool, error) {
 	return false, nil
 }
 
+func deviceSupportResolution(devicePath string, resolution *CaptureResolution) (bool, error) {
+	formatInfo, err := GetV4L2FormatInfo(devicePath)
+	if err != nil {
+		return false, err
+	}
+
+	// Yes, this is an O(N^3) operation, but a video decices rarely have supported resolution
+	// more than 20 combinations. The compute time should be fine
+	for _, res := range formatInfo {
+		for _, size := range res.Sizes {
+			//Check if there is a matching resolution
+			if size.Height == resolution.Height && size.Width == resolution.Width {
+				//Matching resolution. Check if the required FPS is supported
+				for _, fps := range size.FPS {
+					if fps == resolution.FPS {
+						return true, nil
+					}
+				}
+			}
+		}
+	}
+
+	return false, nil
+}
+
 func PrintV4L2FormatInfo(devicePath string) {
 	// Check if the device is a video capture device
 	isCapture, err := checkVideoCaptureDevice(devicePath)
@@ -130,8 +155,9 @@ func GetV4L2FormatInfo(devicePath string) ([]FormatInfo, error) {
 			// Match FPS intervals for the current size
 			for scanner.Scan() {
 				line = scanner.Text()
+
 				if fpsMatches := intervalRegex.FindStringSubmatch(line); fpsMatches != nil {
-					fps, _ := strconv.ParseInt(fpsMatches[2], 10, 0)
+					fps, _ := strconv.ParseFloat(fpsMatches[2], 32)
 					sizeInfo.FPS = append(sizeInfo.FPS, int(fps))
 				} else {
 					// Stop parsing FPS intervals when no more matches are found

+ 11 - 1
remdeskd/www/index.html

@@ -13,9 +13,19 @@
     <meta property="og:type" content="website">
     <meta property="og:url" content="http://example.com">
     <meta property="og:image" content="http://example.com/image.jpg">
+    <style>
+        body{
+            margin: 0;
+        }
+        #remoteCapture{
+            width: 100%;
+            pointer-events: none;
+            user-select: none;
+        }
+    </style>
 </head>
 <body>
-    <h1>Hello World</h1>
+    <img id="remoteCapture" src="/stream"></img>
     <p>Click start to start connection to backend and start Capture to start KVM-ing</p>
     <button id="startButton">Start</button>
     <button id="stopButton">Stop</button>