github.com/blackjack/webcam@v0.0.0-20230509180125-87693b3f29dc/examples/http_mjpeg_streamer/webcam.go (about) 1 // Example program that uses blakjack/webcam library 2 // for working with V4L2 devices. 3 package main 4 5 import ( 6 "bytes" 7 "flag" 8 "fmt" 9 "image" 10 "image/jpeg" 11 "log" 12 "mime/multipart" 13 "net/http" 14 "net/textproto" 15 "os" 16 "sort" 17 "strconv" 18 "time" 19 20 "github.com/blackjack/webcam" 21 ) 22 23 const ( 24 V4L2_PIX_FMT_PJPG = 0x47504A50 25 V4L2_PIX_FMT_YUYV = 0x56595559 26 ) 27 28 type FrameSizes []webcam.FrameSize 29 30 func (slice FrameSizes) Len() int { 31 return len(slice) 32 } 33 34 //For sorting purposes 35 func (slice FrameSizes) Less(i, j int) bool { 36 ls := slice[i].MaxWidth * slice[i].MaxHeight 37 rs := slice[j].MaxWidth * slice[j].MaxHeight 38 return ls < rs 39 } 40 41 //For sorting purposes 42 func (slice FrameSizes) Swap(i, j int) { 43 slice[i], slice[j] = slice[j], slice[i] 44 } 45 46 var supportedFormats = map[webcam.PixelFormat]bool{ 47 V4L2_PIX_FMT_PJPG: true, 48 V4L2_PIX_FMT_YUYV: true, 49 } 50 51 func main() { 52 dev := flag.String("d", "/dev/video0", "video device to use") 53 fmtstr := flag.String("f", "", "video format to use, default first supported") 54 szstr := flag.String("s", "", "frame size to use, default largest one") 55 single := flag.Bool("m", false, "single image http mode, default mjpeg video") 56 addr := flag.String("l", ":8080", "addr to listien") 57 fps := flag.Bool("p", false, "print fps info") 58 flag.Parse() 59 60 cam, err := webcam.Open(*dev) 61 if err != nil { 62 panic(err.Error()) 63 } 64 defer cam.Close() 65 66 // select pixel format 67 format_desc := cam.GetSupportedFormats() 68 69 fmt.Println("Available formats:") 70 for _, s := range format_desc { 71 fmt.Fprintln(os.Stderr, s) 72 } 73 74 var format webcam.PixelFormat 75 FMT: 76 for f, s := range format_desc { 77 if *fmtstr == "" { 78 if supportedFormats[f] { 79 format = f 80 break FMT 81 } 82 83 } else if *fmtstr == s { 84 if !supportedFormats[f] { 85 log.Println(format_desc[f], "format is not supported, exiting") 86 return 87 } 88 format = f 89 break 90 } 91 } 92 if format == 0 { 93 log.Println("No format found, exiting") 94 return 95 } 96 97 // select frame size 98 frames := FrameSizes(cam.GetSupportedFrameSizes(format)) 99 sort.Sort(frames) 100 101 fmt.Fprintln(os.Stderr, "Supported frame sizes for format", format_desc[format]) 102 for _, f := range frames { 103 fmt.Fprintln(os.Stderr, f.GetString()) 104 } 105 var size *webcam.FrameSize 106 if *szstr == "" { 107 size = &frames[len(frames)-1] 108 } else { 109 for _, f := range frames { 110 if *szstr == f.GetString() { 111 size = &f 112 break 113 } 114 } 115 } 116 if size == nil { 117 log.Println("No matching frame size, exiting") 118 return 119 } 120 121 fmt.Fprintln(os.Stderr, "Requesting", format_desc[format], size.GetString()) 122 f, w, h, err := cam.SetImageFormat(format, uint32(size.MaxWidth), uint32(size.MaxHeight)) 123 if err != nil { 124 log.Println("SetImageFormat return error", err) 125 return 126 127 } 128 fmt.Fprintf(os.Stderr, "Resulting image format: %s %dx%d\n", format_desc[f], w, h) 129 130 // start streaming 131 err = cam.StartStreaming() 132 if err != nil { 133 log.Println(err) 134 return 135 } 136 137 var ( 138 li chan *bytes.Buffer = make(chan *bytes.Buffer) 139 fi chan []byte = make(chan []byte) 140 back chan struct{} = make(chan struct{}) 141 ) 142 go encodeToImage(cam, back, fi, li, w, h, f) 143 if *single { 144 go httpImage(*addr, li) 145 } else { 146 go httpVideo(*addr, li) 147 } 148 149 timeout := uint32(5) //5 seconds 150 start := time.Now() 151 var fr time.Duration 152 153 for { 154 err = cam.WaitForFrame(timeout) 155 if err != nil { 156 log.Println(err) 157 return 158 } 159 160 switch err.(type) { 161 case nil: 162 case *webcam.Timeout: 163 log.Println(err) 164 continue 165 default: 166 log.Println(err) 167 return 168 } 169 170 frame, err := cam.ReadFrame() 171 if err != nil { 172 log.Println(err) 173 return 174 } 175 if len(frame) != 0 { 176 177 // print framerate info every 10 seconds 178 fr++ 179 if *fps { 180 if d := time.Since(start); d > time.Second*10 { 181 fmt.Println(float64(fr)/(float64(d)/float64(time.Second)), "fps") 182 start = time.Now() 183 fr = 0 184 } 185 } 186 187 select { 188 case fi <- frame: 189 <-back 190 default: 191 } 192 } 193 } 194 } 195 196 func encodeToImage(wc *webcam.Webcam, back chan struct{}, fi chan []byte, li chan *bytes.Buffer, w, h uint32, format webcam.PixelFormat) { 197 198 var ( 199 frame []byte 200 img image.Image 201 ) 202 for { 203 bframe := <-fi 204 // copy frame 205 if len(frame) < len(bframe) { 206 frame = make([]byte, len(bframe)) 207 } 208 copy(frame, bframe) 209 back <- struct{}{} 210 211 switch format { 212 case V4L2_PIX_FMT_YUYV: 213 yuyv := image.NewYCbCr(image.Rect(0, 0, int(w), int(h)), image.YCbCrSubsampleRatio422) 214 for i := range yuyv.Cb { 215 ii := i * 4 216 yuyv.Y[i*2] = frame[ii] 217 yuyv.Y[i*2+1] = frame[ii+2] 218 yuyv.Cb[i] = frame[ii+1] 219 yuyv.Cr[i] = frame[ii+3] 220 221 } 222 img = yuyv 223 default: 224 log.Fatal("invalid format ?") 225 } 226 //convert to jpeg 227 buf := &bytes.Buffer{} 228 if err := jpeg.Encode(buf, img, nil); err != nil { 229 log.Fatal(err) 230 return 231 } 232 233 const N = 50 234 // broadcast image up to N ready clients 235 nn := 0 236 FOR: 237 for ; nn < N; nn++ { 238 select { 239 case li <- buf: 240 default: 241 break FOR 242 } 243 } 244 if nn == 0 { 245 li <- buf 246 } 247 248 } 249 } 250 251 func httpImage(addr string, li chan *bytes.Buffer) { 252 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 253 log.Println("connect from", r.RemoteAddr, r.URL) 254 if r.URL.Path != "/" { 255 http.NotFound(w, r) 256 return 257 } 258 259 //remove stale image 260 <-li 261 262 img := <-li 263 264 w.Header().Set("Content-Type", "image/jpeg") 265 266 if _, err := w.Write(img.Bytes()); err != nil { 267 log.Println(err) 268 return 269 } 270 271 }) 272 273 log.Fatal(http.ListenAndServe(addr, nil)) 274 } 275 276 func httpVideo(addr string, li chan *bytes.Buffer) { 277 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 278 log.Println("connect from", r.RemoteAddr, r.URL) 279 if r.URL.Path != "/" { 280 http.NotFound(w, r) 281 return 282 } 283 284 //remove stale image 285 <-li 286 const boundary = `frame` 287 w.Header().Set("Content-Type", `multipart/x-mixed-replace;boundary=`+boundary) 288 multipartWriter := multipart.NewWriter(w) 289 multipartWriter.SetBoundary(boundary) 290 for { 291 img := <-li 292 image := img.Bytes() 293 iw, err := multipartWriter.CreatePart(textproto.MIMEHeader{ 294 "Content-type": []string{"image/jpeg"}, 295 "Content-length": []string{strconv.Itoa(len(image))}, 296 }) 297 if err != nil { 298 log.Println(err) 299 return 300 } 301 _, err = iw.Write(image) 302 if err != nil { 303 log.Println(err) 304 return 305 } 306 } 307 }) 308 309 log.Fatal(http.ListenAndServe(addr, nil)) 310 }