hz.tools/mjpeg: M-JPEG images in Go
Paul Tagliamonte 2021-02-28 projectStreaming video over the network is pretty annoying. There’s an endless ecosystem of encodings, protocols, and tactics that will deliver 4K video across the globe without hitting a single hiccip. M-JPEG, on the other hand, is designed to be extremely simple to create and display.
Code needed to serve an M-JPEG feed can be found at github.com/hztools/go-mjpeg, and imported by Go source as hz.tools/mjpeg.
M-JPEG Protocol
M-JPEG encodes a video by sending each frame as a JPEG image via an HTTP
connection, using a MIME Multipart stream. If this is a client pulling from
an M-JPEG stream coming from an HTTP server, you’ll get a HTTP response of
Content-Type
of multipart/x-mixed-replace
, and then a set of chunks,
each with a Content-Type
of image/jpeg
.
This means that the server only has to send a stream of JPEG images, which is
very easy for someone without domain expertise in video codecs. From the
client’s perspective, this means that displaying the video feed only requires
storing the last “frame”, as bytes, and sending it as a JPEG. Additionally,
including this in HTML is as easy as adding an HTML img
tag.
<img src="/webm-endpoint.mjpeg" alt="description" />
Example Server
Here, we can simulate some serious nostalgia (for those of us who had grown up fighting their over-the-air TV) by sending out a video stream that looks like the color of television, tuned to a dead channel.
package main
import (
"image"
"math/rand"
"net/http"
"time"
"hz.tools/mjpeg"
)
func main() {
i := image.NewGray(image.Rectangle{
Min: image.Point{X: 0, Y: 0},
Max: image.Point{X: 500, Y: 500},
})
stream := mjpeg.NewStream()
go func() {
for {
for j := range i.Pix {
i.Pix[j] = uint8(rand.Uint32())
}
time.Sleep(time.Second / 30)
stream.Update(i)
}
}()
http.Handle("/", stream)
http.ListenAndServe("0.0.0.0:8888", nil)
}