この記事について
libjpeg (http://www.ijg.org/) を自分のプロジェクトに移植する方法について解説します。まず、Windows(MinGW)上でのビルドと簡単なサンプルコードを記載します。その後、ベアメタル(OSなし)環境のマイコンへのポーティング方法を記載します。
ホストPC上で試す
最終的にマイコン上で動かすプロジェクトでも、まずはホストPC上でビルドや簡単な動作確認をしておいたほうが捗ると思います。
環境
- Windows 10 64bit
- MSYS2 MinGW 64-bit
- (LinuxやMACでも同様の手順でいけるはず)
ビルド方法
- libjpeg (jpegsr9c.zip (http://www.ijg.org/files/jpegsr9c.zip)) をダウンロードして展開する。
- 下記コマンドでビルドしてライブラリを作る
cd jpeg-9c/
mkdir build
cd build
sh ../configure
make
libjpegを使う
作成されたライブラリ(DLLまたはso)と必要なヘッダファイルを、開発を行うフォルダにコピー
- libjpeg-9.dll
- jpeglib.h
- jconfig.h
- jmorecfg.h
サンプルコード
メモリ(RGB) → メモリ(JPEG)へのエンコードと、メモリ(RGB) → ファイル(JPEG)へのエンコード例です。
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "jpeglib.h"
// RGB 24bit per pixel
#define IMAGE_WIDTH 1280
#define IMAGE_HEIGHT 720
#define BYTE_PER_PIXEL 3
void drawColorBar(uint8_t *imgRGB, uint32_t width, uint32_t height)
{
for (uint32_t y = 0; y < height / 2; y++) {
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0xFF; *imgRGB++ = 0x00; *imgRGB++ = 0x00;}
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0x00; *imgRGB++ = 0xFF; *imgRGB++ = 0x00;}
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0x00; *imgRGB++ = 0x00; *imgRGB++ = 0xFF;}
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0xFF; *imgRGB++ = 0xFF; *imgRGB++ = 0xFF;}
}
for (uint32_t y = 0; y < height / 2; y++) {
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0xFF; *imgRGB++ = 0xFF; *imgRGB++ = 0xFF;}
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0x88; *imgRGB++ = 0x88; *imgRGB++ = 0x88;}
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0x44; *imgRGB++ = 0x44; *imgRGB++ = 0x44;}
for (uint32_t x = 0; x < width / 4; x++) { *imgRGB++ = 0x00; *imgRGB++ = 0x00; *imgRGB++ = 0x00;}
}
}
void fileout(const char* filename, uint8_t *buf, uint32_t size)
{
FILE *fd = fopen(filename, "wb");
fwrite(buf, 1, size, fd);
fclose(fd);
}
void encodeJpegFile(const char* filename, uint8_t *imgRGB, uint32_t width, uint32_t height)
{
JSAMPROW lineBuffer[1] = {0};
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
FILE *fd = fopen(filename, "wb");
jpeg_stdio_dest(&cinfo, fd);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = BYTE_PER_PIXEL;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
// jpeg_set_quality(&cinfo, 10, TRUE);
jpeg_start_compress(&cinfo, TRUE);
for (uint32_t y = 0; y < height; y++) {
lineBuffer[0] = imgRGB;
jpeg_write_scanlines(&cinfo, lineBuffer, 1);
imgRGB += width * BYTE_PER_PIXEL;
}
jpeg_finish_compress(&cinfo);
fclose(fd);
jpeg_destroy_compress(&cinfo);
}
int encodeJpegMem(uint8_t *imgJpeg, uint8_t *imgRGB, uint32_t width, uint32_t height, uint32_t imgJpegSize)
{
JSAMPROW lineBuffer[1] = {0};
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
unsigned long writtenSize = imgJpegSize;
jpeg_mem_dest(&cinfo, &imgJpeg, &writtenSize);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = BYTE_PER_PIXEL;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
// jpeg_set_quality(&cinfo, 10, TRUE);
jpeg_start_compress(&cinfo, TRUE);
for (uint32_t y = 0; y < height; y++) {
lineBuffer[0] = imgRGB;
imgRGB += width * BYTE_PER_PIXEL;
jpeg_write_scanlines(&cinfo, lineBuffer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return (imgJpegSize - cinfo.dest->free_in_buffer);
}
int main()
{
printf("Hello \n");
/* 元画像 (1280 x 720 x RGB(24bit))を用意してカラーバーを描画する */
static uint8_t s_imageRGB[IMAGE_WIDTH * IMAGE_HEIGHT * BYTE_PER_PIXEL];
drawColorBar(s_imageRGB, IMAGE_WIDTH, IMAGE_HEIGHT);
/* JPEGファイル出力のテスト */
encodeJpegFile("test.jpg", s_imageRGB, IMAGE_WIDTH, IMAGE_HEIGHT);
/* JPEGメモリ出力のテスト */
static uint8_t s_imageJEPG[IMAGE_WIDTH * IMAGE_HEIGHT / 2];
uint32_t jpegSize;
jpegSize = encodeJpegMem(s_imageJEPG, s_imageRGB, IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_WIDTH * IMAGE_HEIGHT / 2);
fileout("testmem.jpg", s_imageJEPG, jpegSize);
return 0;
}
実行する
g++ jpegTest.cpp -I./ -L./ -llibjpeg-9
./a.out
ターゲットマイコン用にポーティングする
環境
下記環境をターゲットにポーティングします。
- Target: ZYBO(Zynq-7000, Cortex-A9 )
- IDE: Xilinx SDK
- Platform: baremetal (OSなし)
ファイルシステム無し、一部のシステムコール無し、という環境です。STM32などの他のマイコンでも同じ手順で行けると思います。
ポーティングする
まず、libjpeg (jpegsr9c.zip (http://www.ijg.org/files/jpegsr9c.zip)) をダウンロード、展開して、プロジェクト内にコピーしておきます。
\<project_path>\Libs\libjpeg
みたいな構成が良いと思います。
コピーするのは、.cと.hだけです。
cp <org>\jpeg-9c\*.{c,h} \<project_path>\Libs\libjpeg\.
jconfig.hをコピーする
通常、jconfig.h
はconfigure
コマンドによって、システムに最適なものが生成されます。今回は、先ほどmingwでビルドじに生成されたものをコピーしておきます。
余計なファイルを削除する
IDEによりますが、EclipseベースのIDEだとプロジェクト内のソースコードは全てビルド対象になってしまいます。使用しないものを消します。
プラットフォームに応じて使用するメモリ取得関数が変わってきます。今回は基本的なjmemansi.c
を使うことにして、以下のファイルを削除します。
rm jmemdos.c jmemmac.c jmemname.c jmemnobs.c
続いて、JPEGツール用のソースコードとサンプルコードを削除します。
rm cderror.h cdjpeg.h transupp.h cdjpeg.c cjpeg.c ckconfig.c djpeg.c example.c rdbmp.c rdcolmap.c rdgif.c rdjpgcom.c rdppm.c rdrle.c rdswitch.c rdtarga.c transupp.c wrbmp.c wrgif.c wrjpgcom.c wrppm.c wrrle.c wrtarga.c jpegtran.c
足りていないシステムコールのダミーを作る
自分のプロジェクトで使用する気はなくても、libjpegはファイル操作系のシステムコールをリンクします。当然、素のマイコンで使用しているライブラリにはありません。例えば、以下のようなエラーが出ます。
unlinkr.c:(.text+0x1c): undefined reference to `_unlink'
ちなみに、fopen,fwriteなどはリンクエラーは発生しませんでした。呼んだら死ぬけど、リンクはOKみたい。
大体の場合は、--specs=nosys.specs
オプションをつけることで、空のシステムコールをリンクしてくれます。
僕の環境の場合、これが動かなかったので、リンクエラーの発生したシステムコールを自分で用意することにしました。
以下のようなソースコードを用意して、プロジェクトに追加します。この中で、リンクエラーが発生したシステムコールを作成します。当然、呼んでも何もしませんが、リンクは通ります。(gccを使用している場合は、extern "C"
は不要です)
#ifdef __cplusplus
extern "C" {
#endif
int _unlink (const char *path)
{
return -1;
}
#ifdef __cplusplus
}
#endif
C++対応 (必要なら)
libjpeg内部でboolean
という型をenumで定義しています。これは、libjpegはCなのでbool
型の代わりです。
libjpegは全部Cコードですが、Xilinx SDKでC++アプリケーションとしてプロジェクトを作ると、Cコードにもg++が使用されているように見えます。
結果として、一部コードで型の不整合が発生して、ビルドエラーが起きました。
対策として、boolean
定義している個所を修正しました。
前略
#ifdef __cplusplus
typedef bool boolean;
#define FALSE false
#define TRUE true
#else
typedef enum { FALSE = 0, TRUE = 1 } boolean;
#endif
後略
その他
僕の環境では、上述のカスタマイズだけでポーティングできました。使用するシステム、コンパイラ(ツールチェイン)、または、使用可能なメモリサイズに応じて、下記ファイルをカスタマイズしてください。
- jmorecfg.h
- jconfig.h
- jinclude.h
- jmemansi.c
- dummy_systemcalls.c
今回は運よくファイル系のシステムコールは入っていました。ない場合は、dummy_systemcalls.cに追加するか、jinclude.hでのJFREAD
の定義を適切に変える必要があります。
さらに、malloc/freeもない場合には、jmemansi.c
内の実装を変える必要があります。(静的に確保したstatic変数から割り当てるなど。)。さすがに、malloc/freeは使えると思いますが。。。
リンカスクリプトの設定
上述したように、libjpegではmallocによってメモリ確保を行っています。結構使うので、ヒープサイズを増やしておいてください。
使用上の注意
ファイル操作(fopen、fread等)をサポートするシステム(OS)でない場合、ファイルアクセスが絡む関数を呼ぶことはできません。先ほどのサンプルコードでいうと、encodeJpegMem()
はOSなしのマイコンでも呼べますが、encodeJpegFile
は使えません。
ファイル一式
面倒な場合は、https://github.com/take-iwiw/libjpeg_porting_microcontroller 内のLibsフォルダを自分のプロジェクトのコピペしたら、大体の場合はそのまま動くと思います。
ノート
ちなみに、STM32 (sw4stm32)の場合は、専用のlibjpegを設定一つで追加できます。
高速化メモ
neonオプションを付けたり、最適化オプションを色々変えたけど、あまり高速化はされなかった。やはり、libjpeg-turboを使わないとダメっぽい。(Zynq使ってるなら、ハードウェア化しろよという話ですが。。。)
1280 x 720 x RGB画像のエンコード時間 = 約150 msec @ Cortex-A9, 650Mhz
追記
cinfo.dct_method = JDCT_FLOAT;
のとき、
NEON無効だと約150msec
NEON有効(-mcpu=cortex-a9 -mfloat-abi=hard -mfpu=neon -O3
)だと約130msec
最速設定は、cinfo.dct_method = JDCT_IFAST;
、cinfo.do_fancy_downsampling = FALSE;
のときに、
約120msec
コードサイズメモ
libjpegによるバイナリサイズ増加量 [Byte]
jmorecfg.h
で、全Capability options(XXX_SUPPORTED)をコメントアウトすると、バイナリサイズ(Text領域)は110KByte削減できます。
オプション | text | data | bss | トータル |
---|---|---|---|---|
デフォルトオプション | 295,010 | 4 | -16 | 294,998 |
全Capability options無効 | 185,454 | 4 | -16 | 185,442 |
なぜかbssが減ってる