PHOBOSLAB

Blog Home

MPEG1 Video Decoder in JavaScript

With still no common video format for HTML5 in sight, I decided to implement an MPEG1 decoder in JavaScript. I know there's already an h264 decoder for JavaScript around, but it's huge, compiled with emscripten and quite complicated.

An MPEG1 decoder sounded like a relatively simple and fun weekend project. While the real world use cases for this are of course a bit limited, I still learned a whole lot about video codecs in the process. The size of the source is just around 15kb gzipped and the performance is quite okay-ish - a 320x240 video easily plays with 30fps on the iPhone5.

You browser doesn't support the Canvas Tag. Please use Chrome, Firefox or Safari
Mind Blown - 1.8mb MPEG1, 18 seconds, 570 frames. Click to pause.

For a longer demo see the first few minutes of Big Buck Bunny.

Of course this project still has numerous limitations. First and foremost: no streaming. The video file has to be completely loaded before it can be played back. I'm still waiting for chunked, binary XHR support to arrive in browsers to fix this. Till then, jsmpeg only shows a simple loading animation.

The decoder itself also has some bugs. It currently struggles with packetized MPEG files and stumbles over the packet headers that are randomly thrown in. So in order to play correctly, the MPEG file has to be a raw MPEG1 video stream. Also, B-Frames (those that rely on past and future frames for decoding) are not reordered, so video containing B-Frames will look stuttery. However, I found that most encoders tend to not use B-Frames anyway.

What I learned with this all, is that the MPEG1 file format is a truly ugly one. While I may not understand all the limitations present when it was conceived, I still believe that some parts of it are inane at best, or vicious at worst. Nothing in MPEG1 streams besides the "start codes" is byte aligned, so you're dealing with a raw bitstream.

The width and height for instance is encoded in 12 bits each. The framerate is encoded in 4 bits, but not as a number of frames per second or millisecond delay, but as an index into a pre-defined array of possible frame rates. Want to have a 10fps or 48fps video in MPEG1? Not possible. A 2byte field encoding the real frame rate, not an index, would have allowed far more flexibility - and it only has to be transmitted once anyway.

The MPEG1 format is full of such short sighted decisions that make it quite hard to parse. But then again, it was the first truly usable video format available and I'm not sure if anyone working on it anticipated the success it would have.

The source is available at github.com/phoboslab/jsmpeg.

Tuesday, May 7th 2013
— Dominic Szablewski, @phoboslab