LoginSignup
5
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-01-30

この記事について

libjpeg (http://www.ijg.org/) を自分のプロジェクトに移植する方法について解説します。まず、Windows(MinGW)上でのビルドと簡単なサンプルコードを記載します。その後、ベアメタル(OSなし)環境のマイコンへのポーティング方法を記載します。

ホストPC上で試す

最終的にマイコン上で動かすプロジェクトでも、まずはホストPC上でビルドや簡単な動作確認をしておいたほうが捗ると思います。

環境

  • Windows 10 64bit
  • MSYS2 MinGW 64-bit
  • (LinuxやMACでも同様の手順でいけるはず)

ビルド方法

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)へのエンコード例です。

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だけです。

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

jconfig.hをコピーする

通常、jconfig.hconfigureコマンドによって、システムに最適なものが生成されます。今回は、先ほど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"は不要です)

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

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が減ってる

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6