Recently I’ve been doing a lot of embedded uClinux programming for the Analog Devices Blackfin BF537 processor. Writing drivers, application code, things like that. It’s a lot of fun.
One of the things I’d been battling with was finding a reasonably fast JPEG encoder. Now you need to understand that the BF537 doesn’t have a JPG compression engine or anything like that, so it’ll never be screaming fast. The default uClinux JPEG library is libjpeg. Libjpeg is fairly slow. I wrote a reasonably-optimised DCT function in blackfin assembler for libjpeg & posted it over at http://blackfin.uclinux.org You can find the files, and a description, here at my original posting:
What’s cool is it appears to have been accepted into the mainstream blackfin uClinux distribution (SVN). So pretty soon everyone will be getting this faster libjpeg code by default.
Which is nice, and it helps, but it’s still too slow. My example 752 x 512 RGB image was taking around 260 ms to JPG encode using the “improved” libjpeg function. Too long. So I looked for alternatives.
I had tried FFMPEG. It’s faster, because the Analog Devices folks wrote some optimisations for it. It’s tricky to tell exactly how fast, because FFMPEG will duplicate frames to give you the framerate you request. Just because FFMPEG gives you 25 frames/sec doesn’t mean you’re getting 25 different frames per second. But that aside, the big problem I had with FFMPEG was its tendancy to malloc() and free() chunks of memory constantly. This would eventually result in memory fragmentation & malloc() failing, sometimes in minutes, sometimes in hours. Problems like this I can do without, and it’d be a hard one to fix.
SCS Vision wrote a JPEG encoder for their leanXcam product. Their encoder appears to be based on one written by Nitin Gupta. Based on comments they wrote in their comment block (which I assumed to be correct & did not personally verify), I calculated it would encode my image in around 290 ms. Obviously that’s not the big improvement I’m looking for.
Analog Devices has a very fast optimised JPEG encoder available, right off their Blackfin website, for free. But it only works with their proprietary VDSP+ tools. It’s not compatible with any of the open-source tools (gcc, uClinux, etc) and it’s only distributed in object code form, which makes it impossible to port. Tantalising, but useless.
Then recently I had a break. I stumbled across the Surveyor robot folks. A very bright fellow over there wrote a pretty decent JPG encoder, which took advantage of an Analog Devices optimised DCT function, as well as doing some other smart things in the code. It only handled YUV images, whereas my image is RGB. So before I could try it, I had to hack up a quick RGB24 to YUV422 conversion routine. But once I did, I was encoding my image in 165 ms. That’s clearly a substantial improvement over the libjpeg 260 ms.
With that success, I set to further optimising the code, both theirs and mine. By the time I was finished, my image was JPEG encoding in 96 ms. Go baby go!
Fast JPG encoding seems to be a common question for Blackfin, so I’m posting the final code here. Click on the link below to get to the download page:
Now here’s how to call the function.
If you’re familiar with libjpeg, you’ll find this easier, because there’s nothing to set up or tear down. encode_image() needs a pointer to the raw image in memory, a pointer to a chunk of memory where it can write the resulting JPG image, and a few other parameters. It returns another pointer, which is the next free location in memory where it had just been writing. So that you can work out how large the just-created JPG image is. It’s up to you to write the JPG image to a file (if indeed you even want to do that).
Here’s the function prototype:
UINT8* encode_image (UINT8 *input_ptr, UINT8 *output_ptr, UINT32 quality_factor, UINT32 image_format, UINT32 image_width, UINT32 image_height);
It’s pretty simple. Pointers to the input raw image buffer (YUV or RGB) and an output image (JPG) buffer. A quality factor which ranges from 1 (highest) to 8 (lowest). I use 2. Setting this to 1 results in much longer execution time & much larger filesizes. Image format can be:
- RGB – this is RGB24
- FOUR_TWO_TWO – this is YUV422
FOUR_ZERO_ZERO (monochrome YUV) is reported in the comments below as also working.
Image width & height should be self-explanatory.
To call it, set up your image buffers as you see fit, then do something like this:
jpeg_buff_end = encode_image(text_img_buff, jpeg_buff, 2, RGB, 640, 480);
Your JPG image is of filesize: (jpeg_buff_end – jpeg_buff)
If you want to save your JPG to a file, you can do something like:
jpegfp = fopen ("myimage.jpg", "w");
fwrite (jpeg_buff, 1, jpeg_buff_end-jpeg_buff, jpegfp);
That’s it. Easy to use, and it’s the fastest thing, by far, I’m aware of for JPEG encoding on Blackfin uClinux.