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  }