main.go 4.1 KB

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