Building a webcam with Go and go4vl

Vladimir Vivien
go4vl
Published in
4 min readMay 22, 2022

--

In a previous blog post, project go4vl was introduced and discussed as a way to use Go to interact with low level device drivers to capture video frames on a Linux machine. This post looks at how to use the go4vl project to build a functional a webcam.

See project github.com/vladimirvivien/go4vl

The webcam example

The go4vl project comes with an example that demonstrates how to create a functional webcam that can stream images via a webpage (see source). In this example, go4vl is used to stream video buffers from a capture device (camera) and stream the resulting buffers to a web page.

For this post, this example was tested using a Raspberry Pi 3 running 32-bit Linux, with kernel version 5.14, with a cheap USB video camera attached.

Prerequisites

As the go4vl name implies, to get this example working you will need the Linux operating system (32- or 64-bit) and the followings:

  • Linux kernel minimum v5.10.x or higher
  • Go compiler/tools
  • A configured C compiler (i.e. gcc)
  • A video camera (with support for Video for Linux API)

If you are running a system that has not been upgraded in a while, ensure to issue the following commands:

sudo apt update
sudo apt full-upgrade

Build and run code

Before you can run the code, download (or clone) it from its Git repository https://github.com/vladimirvivien/go4vl/ . Next, from within directory examples/webcam, build the source code for the webcam example:

go build -o webcam webcam.go

Once built, you can start the webcam with the following command (and output as shown):

./webcam

2022/05/21 09:04:31 device [/dev/video0] opened
2022/05/21 09:04:31 device info: driver: uvcvideo; card: HDM Webcam USB: HDM Webcam USB; bus info: usb-3f980000.usb-1.5
2022/05/21 09:04:31 Current format: Motion-JPEG [1920x1080]; field=any; bytes per line=0; size image=0; colorspace=Default; YCbCr=Default; Quant=Default; XferFunc=Default
2022/05/21 09:04:31 device capture started, frames available
2022/05/21 09:04:31 starting server on port :9090
2022/05/21 09:04:31 use url path /webcam

Next, point your browser to your machine’s address and shown port (i.e. http://198.162.100.20:9090). If there are no errors, you should see the webpage and the streaming video in your browser as shown below.

Webcam built with Go and go4vl running

How it works

Let us do a quick code walkthrough to examine how simple it is to create programs, such as the webcam, using the go4vl project. Firstly, the main function opens the video device with a set of optional configurations specified (from CLI flags variables not shown):

var frames <-chan []byte

func main() {
...
// create device
dev, err := device.Open(
devName,
device.WithIOType(v4l2.IOTypeMMAP),
device.WithPixFormat(
v4l2.PixFormat{
PixelFormat: getFormatType(format),
Width: uint32(width),
Height: uint32(height),
},
),
device.WithFPS(uint32(frameRate)),
)
}

Next, the code starts the device and make the device’s output stream available via package variable frames:

var frames <-chan []bytefunc main() {
...
ctx, cancel := context.WithCancel(context.TODO())
if err := dev.Start(ctx); err != nil { ... }
defer func() {
cancel()
device.Close()
}()
frames = dev.GetOutput()
}

The last major step, in main(), is to start an HTTP server to serve the video buffers and the page for the webcam:

var frames <-chan []bytefunc main() {
...
// setup http service
http.HandleFunc("/webcam", servePage) // returns an html page
http.HandleFunc("/stream", serveVideoStream) // returns image buff
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatal(err)
}
}

The video stream from the camera is served at endpoint /stream (see source above) which is serviced by HTTP handler function serveVideoStream . That function uses content-type multipart/x-mixed-replace, with a separate content-type boundary for each image buffer, to render a video stream on the browser (see source snippet below).

func serveVideoStream(w http.ResponseWriter, req *http.Request) {
// Start HTTP Response
const boundaryName = "Yt08gcU534c0p4Jqj0p0"
// send multi-part header
w.Header().Set(
"Content-Type",
fmt.Sprintf(
"multipart/x-mixed-replace; boundary=%s",boundaryName,
),
)
w.WriteHeader(http.StatusOK)
for frame := range frames {
// start boundary
io.WriteString(w, fmt.Sprintf("--%s\n", boundaryName))
io.WriteString(w, "Content-Type: image/jpeg\n")
io.WriteString(w, fmt.Sprintf(
"Content-Length: %d\n\n", len(frame)))
if _, err := w.Write(frame); err != nil {
log.Printf("failed to write mjpeg image: %s", err)
return
}

// close boundary
if _, err := io.WriteString(w, "\n"); err != nil {
log.Printf("failed to write boundary: %s", err)
return
}
}
}

You can see the full source code here.

Conclusion

This post explores how to use the go4vl project to create functional programs such as a webcam that streams video images to a web page. The project provides a high-level abstraction to access low-level system functions that interact with hardware drivers in Linux to capture video streams. In future posts, we will continue to explores of go4vl and how to use it to build interesting projects.

--

--