main.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package main
  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. func main() {
  63. port := ":9090"
  64. devName := "/dev/video0"
  65. frameRate := int(fps)
  66. buffSize := 8
  67. format := "mjpeg"
  68. //Check if the video device is a capture device
  69. isCaptureDev, err := checkVideoCaptureDevice(devName)
  70. if err != nil {
  71. panic(err)
  72. }
  73. if !isCaptureDev {
  74. panic("target device is not a capture-able device")
  75. }
  76. camera, err := device.Open(devName,
  77. device.WithIOType(v4l2.IOTypeMMAP),
  78. device.WithPixFormat(v4l2.PixFormat{PixelFormat: getFormatType(format), Width: uint32(width), Height: uint32(height), Field: v4l2.FieldAny}),
  79. device.WithFPS(uint32(frameRate)),
  80. device.WithBufferSize(uint32(buffSize)),
  81. )
  82. if err != nil {
  83. log.Fatalf("failed to open device: %s", err)
  84. }
  85. defer camera.Close()
  86. //Get video supported sizes
  87. formatInfo, err := getV4L2FormatInfo(devName)
  88. if err != nil {
  89. log.Fatal(err)
  90. }
  91. for _, format := range formatInfo {
  92. fmt.Printf("Format: %s\n", format.Format)
  93. for _, size := range format.Sizes {
  94. fmt.Printf(" Size: %dx%d\n", size.Width, size.Height)
  95. fmt.Printf(" FPS: %v\n", size.FPS)
  96. }
  97. }
  98. caps := camera.Capability()
  99. log.Printf("device [%s] opened\n", devName)
  100. log.Printf("device info: %s", caps.String())
  101. //2025/03/16 15:45:25 device info: driver: uvcvideo; card: USB Video: USB Video; bus info: usb-0000:00:14.0-2
  102. // set device format
  103. currFmt, err := camera.GetPixFormat()
  104. if err != nil {
  105. log.Fatalf("unable to get format: %s", err)
  106. }
  107. log.Printf("Current format: %s", currFmt)
  108. //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
  109. pixfmt = currFmt.PixelFormat
  110. streamInfo = fmt.Sprintf("%s - %s [%dx%d] %d fps",
  111. caps.Card,
  112. v4l2.PixelFormats[currFmt.PixelFormat],
  113. currFmt.Width, currFmt.Height, frameRate,
  114. )
  115. // start capture
  116. ctx, cancel := context.WithCancel(context.TODO())
  117. if err := camera.Start(ctx); err != nil {
  118. log.Fatalf("stream capture: %s", err)
  119. }
  120. defer func() {
  121. cancel()
  122. camera.Close()
  123. }()
  124. // video stream
  125. frames = camera.GetOutput()
  126. log.Printf("device capture started (buffer size set %d)", camera.BufferCount())
  127. log.Printf("starting server on port %s", port)
  128. log.Println("use url path /webcam")
  129. // setup http service
  130. http.HandleFunc("/stream", serveVideoStream) // returns video feed
  131. if err := http.ListenAndServe(port, nil); err != nil {
  132. log.Fatal(err)
  133. }
  134. }
  135. func getFormatType(fmtStr string) v4l2.FourCCType {
  136. switch strings.ToLower(fmtStr) {
  137. case "jpeg":
  138. return v4l2.PixelFmtJPEG
  139. case "mpeg":
  140. return v4l2.PixelFmtMPEG
  141. case "mjpeg":
  142. return v4l2.PixelFmtMJPEG
  143. case "h264", "h.264":
  144. return v4l2.PixelFmtH264
  145. case "yuyv":
  146. return v4l2.PixelFmtYUYV
  147. case "rgb":
  148. return v4l2.PixelFmtRGB24
  149. }
  150. return v4l2.PixelFmtMPEG
  151. }