usbcapture.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package usbcapture
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "log"
  7. "mime/multipart"
  8. "net/http"
  9. "net/textproto"
  10. "strings"
  11. "syscall"
  12. "github.com/vladimirvivien/go4vl/device"
  13. "github.com/vladimirvivien/go4vl/v4l2"
  14. )
  15. var (
  16. camera *device.Device
  17. frames <-chan []byte
  18. fps uint32 = 30
  19. /*
  20. 1920 x 1080 60fps = 55Mbps //Edge not support
  21. 1920 x 1080 30fps = 50Mbps
  22. 1920 x 1080 25fps = 40Mbps
  23. 1920 x 1080 20fps = 30Mbps
  24. 1920 x 1080 10fps = 15Mbps
  25. 1360 x 768 60fps = 28Mbps
  26. 1360 x 768 30fps = 25Mbps
  27. 1360 x 768 25fps = 20Mbps
  28. 1360 x 768 20fps = 18Mbps
  29. 1360 x 768 10fps = 10Mbps
  30. */
  31. pixfmt v4l2.FourCCType
  32. width = 1920
  33. height = 1080
  34. streamInfo string
  35. )
  36. // start http service
  37. func serveVideoStream(w http.ResponseWriter, req *http.Request) {
  38. mimeWriter := multipart.NewWriter(w)
  39. w.Header().Set("Content-Type", fmt.Sprintf("multipart/x-mixed-replace; boundary=%s", mimeWriter.Boundary()))
  40. partHeader := make(textproto.MIMEHeader)
  41. partHeader.Add("Content-Type", "image/jpeg")
  42. var frame []byte
  43. for frame = range frames {
  44. if len(frame) == 0 {
  45. log.Print("skipping empty frame")
  46. continue
  47. }
  48. partWriter, err := mimeWriter.CreatePart(partHeader)
  49. if err != nil {
  50. log.Printf("failed to create multi-part writer: %s", err)
  51. return
  52. }
  53. if _, err := partWriter.Write(frame); err != nil {
  54. if errors.Is(err, syscall.EPIPE) {
  55. //broken pipe, the client browser has exited
  56. return
  57. }
  58. log.Printf("failed to write image: %s", err)
  59. }
  60. }
  61. }
  62. // startVideoServer starts the video server
  63. func startVideoServer() {
  64. port := ":9090"
  65. devName := "/dev/video0"
  66. frameRate := int(fps)
  67. buffSize := 8
  68. format := "mjpeg"
  69. //Check if the video device is a capture device
  70. isCaptureDev, err := checkVideoCaptureDevice(devName)
  71. if err != nil {
  72. panic(err)
  73. }
  74. if !isCaptureDev {
  75. panic("target device is not a capture-able device")
  76. }
  77. camera, err := device.Open(devName,
  78. device.WithIOType(v4l2.IOTypeMMAP),
  79. device.WithPixFormat(v4l2.PixFormat{PixelFormat: getFormatType(format), Width: uint32(width), Height: uint32(height), Field: v4l2.FieldAny}),
  80. device.WithFPS(uint32(frameRate)),
  81. device.WithBufferSize(uint32(buffSize)),
  82. )
  83. if err != nil {
  84. log.Fatalf("failed to open device: %s", err)
  85. }
  86. defer camera.Close()
  87. //Get video supported sizes
  88. formatInfo, err := getV4L2FormatInfo(devName)
  89. if err != nil {
  90. log.Fatal(err)
  91. }
  92. for _, format := range formatInfo {
  93. fmt.Printf("Format: %s\n", format.Format)
  94. for _, size := range format.Sizes {
  95. fmt.Printf(" Size: %dx%d\n", size.Width, size.Height)
  96. fmt.Printf(" FPS: %v\n", size.FPS)
  97. }
  98. }
  99. caps := camera.Capability()
  100. log.Printf("device [%s] opened\n", devName)
  101. log.Printf("device info: %s", caps.String())
  102. //2025/03/16 15:45:25 device info: driver: uvcvideo; card: USB Video: USB Video; bus info: usb-0000:00:14.0-2
  103. // set device format
  104. currFmt, err := camera.GetPixFormat()
  105. if err != nil {
  106. log.Fatalf("unable to get format: %s", err)
  107. }
  108. log.Printf("Current format: %s", currFmt)
  109. //2025/03/16 15:45:25 Current format: Motion-JPEG [1920x1080]; field=any; bytes per line=0; size image=0; colorspace=Default; YCbCr=Default; Quant=Default; XferFunc=Default
  110. pixfmt = currFmt.PixelFormat
  111. streamInfo = fmt.Sprintf("%s - %s [%dx%d] %d fps",
  112. caps.Card,
  113. v4l2.PixelFormats[currFmt.PixelFormat],
  114. currFmt.Width, currFmt.Height, frameRate,
  115. )
  116. // start capture
  117. ctx, cancel := context.WithCancel(context.TODO())
  118. if err := camera.Start(ctx); err != nil {
  119. log.Fatalf("stream capture: %s", err)
  120. }
  121. defer func() {
  122. cancel()
  123. camera.Close()
  124. }()
  125. // video stream
  126. frames = camera.GetOutput()
  127. log.Printf("device capture started (buffer size set %d)", camera.BufferCount())
  128. log.Printf("starting server on port %s", port)
  129. log.Println("use url path /webcam")
  130. // setup http service
  131. http.HandleFunc("/stream", serveVideoStream) // returns video feed
  132. if err := http.ListenAndServe(port, nil); err != nil {
  133. log.Fatal(err)
  134. }
  135. }
  136. func getFormatType(fmtStr string) v4l2.FourCCType {
  137. switch strings.ToLower(fmtStr) {
  138. case "jpeg":
  139. return v4l2.PixelFmtJPEG
  140. case "mpeg":
  141. return v4l2.PixelFmtMPEG
  142. case "mjpeg":
  143. return v4l2.PixelFmtMJPEG
  144. case "h264", "h.264":
  145. return v4l2.PixelFmtH264
  146. case "yuyv":
  147. return v4l2.PixelFmtYUYV
  148. case "rgb":
  149. return v4l2.PixelFmtRGB24
  150. }
  151. return v4l2.PixelFmtMPEG
  152. }