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  }