libjpegをマイコンへポーティングする

# この記事について
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)) をダウンロードして展開する。
- 下記コマンドでビルドしてライブラリを作る

```sh:libjpegビルドコマンド
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)へのエンコード例です。

```cpp:jpetTest.cpp
#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だけです。

```sh
cp <org>\jpeg-9c\*.{c,h} \<project_path>\Libs\libjpeg\.
```

### jconfig.hをコピーする
通常、`jconfig.h`は`configure`コマンドによって、システムに最適なものが生成されます。今回は、先ほどmingwでビルドじに生成されたものをコピーしておきます。

### 余計なファイルを削除する
IDEによりますが、EclipseベースのIDEだとプロジェクト内のソースコードは全てビルド対象になってしまいます。使用しないものを消します。
プラットフォームに応じて使用するメモリ取得関数が変わってきます。今回は基本的な`jmemansi.c`を使うことにして、以下のファイルを削除します。

```sh
rm jmemdos.c  jmemmac.c  jmemname.c jmemnobs.c
```

続いて、JPEGツール用のソースコードとサンプルコードを削除します。

```sh
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"`は不要です)

```c:dummy_systemcalls.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`定義している個所を修正しました。

```c:jmorecfg.h 
前略
#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が減ってる