github.com/aamcrae/webcam@v0.0.0-20210915060337-934acc13bdc3/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/aamcrae/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 } 113 } 114 } 115 if size == nil { 116 log.Println("No matching frame size, exiting") 117 return 118 } 119 120 fmt.Fprintln(os.Stderr, "Requesting", format_desc[format], size.GetString()) 121 f, w, h, _, _, err := cam.SetImageFormat(format, uint32(size.MaxWidth), uint32(size.MaxHeight)) 122 if err != nil { 123 log.Println("SetImageFormat return error", err) 124 return 125 126 } 127 fmt.Fprintf(os.Stderr, "Resulting image format: %s %dx%d\n", format_desc[f], w, h) 128 129 // start streaming 130 err = cam.StartStreaming() 131 if err != nil { 132 log.Println(err) 133 return 134 } 135 136 var ( 137 li chan *bytes.Buffer = make(chan *bytes.Buffer) 138 fi chan []byte = make(chan []byte) 139 back chan struct{} = make(chan struct{}) 140 ) 141 go encodeToImage(cam, back, fi, li, w, h, f) 142 if *single { 143 go httpImage(*addr, li) 144 } else { 145 go httpVideo(*addr, li) 146 } 147 148 timeout := uint32(5) //5 seconds 149 start := time.Now() 150 var fr time.Duration 151 152 for { 153 err = cam.WaitForFrame(timeout) 154 if err != nil { 155 log.Println(err) 156 return 157 } 158 159 switch err.(type) { 160 case nil: 161 case *webcam.Timeout: 162 log.Println(err) 163 continue 164 default: 165 log.Println(err) 166 return 167 } 168 169 frame, err := cam.ReadFrame() 170 if err != nil { 171 log.Println(err) 172 return 173 } 174 if len(frame) != 0 { 175 176 // print framerate info every 10 seconds 177 fr++ 178 if *fps { 179 if d := time.Since(start); d > time.Second*10 { 180 fmt.Println(float64(fr)/(float64(d)/float64(time.Second)), "fps") 181 start = time.Now() 182 fr = 0 183 } 184 } 185 186 select { 187 case fi <- frame: 188 <-back 189 default: 190 } 191 } 192 } 193 } 194 195 func encodeToImage(wc *webcam.Webcam, back chan struct{}, fi chan []byte, li chan *bytes.Buffer, w, h uint32, format webcam.PixelFormat) { 196 197 var ( 198 frame []byte 199 img image.Image 200 ) 201 for { 202 bframe := <-fi 203 // copy frame 204 if len(frame) < len(bframe) { 205 frame = make([]byte, len(bframe)) 206 } 207 copy(frame, bframe) 208 back <- struct{}{} 209 210 switch format { 211 case V4L2_PIX_FMT_YUYV: 212 yuyv := image.NewYCbCr(image.Rect(0, 0, int(w), int(h)), image.YCbCrSubsampleRatio422) 213 for i := range yuyv.Cb { 214 ii := i * 4 215 yuyv.Y[i*2] = frame[ii] 216 yuyv.Y[i*2+1] = frame[ii+2] 217 yuyv.Cb[i] = frame[ii+1] 218 yuyv.Cr[i] = frame[ii+3] 219 220 } 221 img = yuyv 222 default: 223 log.Fatal("invalid format ?") 224 } 225 //convert to jpeg 226 buf := &bytes.Buffer{} 227 if err := jpeg.Encode(buf, img, nil); err != nil { 228 log.Fatal(err) 229 return 230 } 231 232 const N = 50 233 // broadcast image up to N ready clients 234 nn := 0 235 FOR: 236 for ; nn < N; nn++ { 237 select { 238 case li <- buf: 239 default: 240 break FOR 241 } 242 } 243 if nn == 0 { 244 li <- buf 245 } 246 247 } 248 } 249 250 func httpImage(addr string, li chan *bytes.Buffer) { 251 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 252 log.Println("connect from", r.RemoteAddr, r.URL) 253 if r.URL.Path != "/" { 254 http.NotFound(w, r) 255 return 256 } 257 258 //remove stale image 259 <-li 260 261 img := <-li 262 263 w.Header().Set("Content-Type", "image/jpeg") 264 265 if _, err := w.Write(img.Bytes()); err != nil { 266 log.Println(err) 267 return 268 } 269 270 }) 271 272 log.Fatal(http.ListenAndServe(addr, nil)) 273 } 274 275 func httpVideo(addr string, li chan *bytes.Buffer) { 276 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 277 log.Println("connect from", r.RemoteAddr, r.URL) 278 if r.URL.Path != "/" { 279 http.NotFound(w, r) 280 return 281 } 282 283 //remove stale image 284 <-li 285 const boundary = `frame` 286 w.Header().Set("Content-Type", `multipart/x-mixed-replace;boundary=`+boundary) 287 multipartWriter := multipart.NewWriter(w) 288 multipartWriter.SetBoundary(boundary) 289 for { 290 img := <-li 291 image := img.Bytes() 292 iw, err := multipartWriter.CreatePart(textproto.MIMEHeader{ 293 "Content-type": []string{"image/jpeg"}, 294 "Content-length": []string{strconv.Itoa(len(image))}, 295 }) 296 if err != nil { 297 log.Println(err) 298 return 299 } 300 _, err = iw.Write(image) 301 if err != nil { 302 log.Println(err) 303 return 304 } 305 } 306 }) 307 308 log.Fatal(http.ListenAndServe(addr, nil)) 309 }