SPRESENSE Arduino で libjpeg を使いたい


SPRESENSE Arduino で libjpeg を使いたいと思っています。SDK ではライブラリとして使えるように思えるのですが、Arduino
環境ではどうやったら使えるのでしょうか？サブコアで libjpeg を動かしたいと思っています。




こちら、メインコアですが、試す必要があってやってみました。この内容はサブコアにも応用できると思います。実際にやってみたところ、いくつかポイントがあったので備忘録として記載しておきます。（思った以上に大変でした）

  1. SpresenseSDKのインストール  
<https://developer.sony.com/develop/spresense/docs/sdk_set_up_ja.html>

  2. spresense-arduino-compatibleをクローン  
`$ git clone --recursive https://github.com/sonydevworld/spresense-arduino-
compatible.git`

  3. spresense-arduino-compatible/tools/configs/spresense.confの書き換え  
次の内容を加えます。(subcoreの場合は、spresense_sub.conf）

`feature/libjpeg`  
`+ARCH_SETJMP_H=y`

  4. spresense/sdk/tools/mksdkexport.shの書き換え  
135行目 `${SDK_DIR}/../externals/libjpeg/libjpeg.a\`  
113行目(2行追加)  
`mkdir -p ${TMP_DIR}/${SDK_EXP_SDK}/externals/include/libjpeg`  
`cp -a ${SDK_DIR}/../externals/libjpeg/*.h \
${TMP_DIR}/${SDK_EXP_SDK}/externals/include/libjpeg/`

  5. borad.txt の書き換え  
`spresense-arduino-
comaptible/Arduino15/packages/SPRESENSE/hardware/spresense/1.0.0/board.txt`
を編集  
100行目 `spresense,menu.Core.Main.build.libs` に `"{build.libpath}/libjpeg.a"`
を追加

  6. Arduino の boolean の定義を変更  
libjpegでもbooleanを定義しています。libjpeg の
booleanの定義を変更すると容量計算が狂ってしまい、デコード時にエラーが出てしまいます。そのため、Arduinoのbooleanの定義を変更しなければなりません。次のファイルで定義されています。  
`spresense-arudino-
comaptible/Arduino15/packages/SPRESENSE/hardware/spresense/1.0.0/cores/spresense/WCharacter.h`

46行目の`typedef bool boolean`を`typedef int boolean`に変更

  7. Arduino Library Packageを生成/コピー  
`$ cd spresense-arduino-compatible`  
`$ ./tools/prepare_arduino.sh -S ~/spresense -c spresense -d disable -p`  
`$ ./tools/prepare_arduino.sh -S ~/spresense -c spresense -d enable -p`  
`$ ./tools/prepare_arduino.sh -S ~/spresense -c spresense_sub -d disable -p`  
`$ ./tools/prepare_arduino.sh -S ~/spresense -c spresense_sub -d enable -p`  
`$ make`  
`$ cp ./out/package_spresense_local_index.json
/home/<user>/snap/arduino/61/.arduino15`  
`$ cp -r ./out/staging /home/<user>/snap/arduino/61/.arduino15`

  8. Arduino IDE でボードマネージャーに追加  
環境設定で「追加のボードマネージャのURL」に`file:///home/<user>/snap/arduino/61/.arduino15/package_spresense_local_index.json`を設定（Linuxの場合）  
ボードマネージャーを使って「Spresense local Board」をインストール

  9. libjpeg のテスト用スケッチを使って動作確認  
確認には、小さめのJPEGを用意してください。私は添付のものを使用しました。注意する点としては、setjmp/longjmp
はlibc.aに含まれますので`extern "C"` で囲わないとリンクエラーが出ます。

[![テスト用画像](https://i.stack.imgur.com/nwROe.jpg)](https://i.stack.imgur.com/nwROe.jpg)

[code]

    #define HAVE_BOOLEAN
    
    #include <nuttx/config.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <libjpeg/jpeglib.h>
    extern "C" {
    #include <setjmp.h>
    }
    
    extern JSAMPLE* image_buffer;
    
    struct my_error_mgr {
      struct jpeg_error_mgr pub;    /* "public" fields */
      jmp_buf setjmp_buffer;        /* for return to caller */
    };
    
    typedef struct my_error_mgr* my_error_ptr;
    
    
    char filename[16] = "/mnt/sd0/in.jpg";
    char outfile[17] = "/mnt/sd0/out.rgb";
    
    
    METHODDEF(void) my_error_exit(j_common_ptr cinfo) {
      my_error_ptr myerr = (my_error_ptr) cinfo->err;
      (*cinfo->err->output_message) (cinfo);
      longjmp(myerr->setjmp_buffer, 1);
    }
    
    
    void setup() {
      static struct jpeg_decompress_struct cinfo;
      struct my_error_mgr jerr;
      FILE * infile;                /* source file */
      FILE* out;                    /* output file */
      JSAMPARRAY buffer;            /* Output row buffer */
      int row_stride;               /* physical row width in output buffer */
    
      struct stat buf;
      for (;;) {
        int ret = stat("/mnt/sd0", &buf);
        if (ret) {
          printf("Please insert formatted SD Card.\n");
          sleep(1);
        } else {
          break;
        }
      }
    
      if ((infile = fopen(filename, "rb")) == NULL) {
        fprintf(stderr, "can't open %s\n", filename);
        return 0;
      }
      printf("open %s\n", filename);
    
      if ((out = fopen(outfile, "wb")) == NULL) {
        fprintf(stderr, "can't open %s\n", outfile);
        return 0;    
      }
      printf("create %s\n", outfile);
    
      /* Step 1: allocate and initialize JPEG decompression object */
      /* We set up the normal JPEG error routines, then override error_exit. */
      cinfo.err = jpeg_std_error(&jerr.pub);
      jerr.pub.error_exit = my_error_exit;
      /* Establish the setjmp return context for my_error_exit to use. */
      if (setjmp(jerr.setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error.
         * We need to clean up the JPEG object, close the input file, and return.
         */
        jpeg_destroy_decompress(&cinfo);
        fprintf(stderr, "can't make decompress object\n");
        fclose(infile);
        return;
      }
      /* Now we can initialize the JPEG decompression object. */
      jpeg_create_decompress(&cinfo);
      printf("Step1: create decompress object\n");
    
      /* Step 2: specify data source (eg, a file) */
      jpeg_stdio_src(&cinfo, infile);
      printf("Step2: set the data source\n");
    
      /* Step 3: read file parameters with jpeg_read_header() */
      (void)jpeg_read_header(&cinfo, TRUE);
      printf("Step3: read the data header\n");
      /* We can ignore the return value from jpeg_read_header since
       *   (a) suspension is not possible with the stdio data source, and
       *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
       * See libjpeg.txt for more info.
       */
    
      /* Step 4: set parameters for decompression */
      cinfo.out_color_space = JCS_RGB;
      printf("Step4: set the output parameter as JCS_RGB\n");
    
      /* Step 5: Start decompressor */
      (void) jpeg_start_decompress(&cinfo);
      printf("Step5: start decompression\n");
      /* JSAMPLEs per row in output buffer */
      /* Modified for Spresense by Sony Semiconductor Solutions.
       * CbYCrY format has 16bpp. So, stride is width * 2 bytes.
       */
      /* row_stride = cinfo.output_width * cinfo.output_components; */
      //row_stride = cinfo.output_width * 2; // in case of YCbCr
      row_stride = cinfo.output_width * 3; // in case of RGB
      /* Make a one-row-high sample array that will go away when done with image */
      buffer = (*cinfo.mem->alloc_sarray)
                    ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
    
      /* Step 6: while (scan lines remain to be read) */
      printf("Step6: read scanlines\n");
      int n = 0;
      while (cinfo.output_scanline < cinfo.output_height) {
        /* jpeg_read_scanlines expects an array of pointers to scanlines.
         * Here the array is only one element long, but you could ask for
         * more than one scanline at a time if that's more convenient.
         */
        (void) jpeg_read_scanlines(&cinfo, buffer, 1);
        if (row_stride != fwrite(buffer[0], 1, row_stride, out)) {
          printf("fwrite error : %d\n", errno);
        }
        ++n;
      }
      printf("read lines: %d\n", n);
      printf("close output file\n");
      fclose(out);
    
      /* Step 7: Finish decompression */
      printf("Step7: finish decompression\n");  
      (void) jpeg_finish_decompress(&cinfo);
    
      /* Step 8: Release JPEG decompression object */
      printf("Step8: destroy decompress object\n");  
      jpeg_destroy_decompress(&cinfo);
      printf("Step9: close infile\n");
      fclose(infile);
    }
    
    void loop() {}
    
[/code]

【補足】  
Spresense SDK
はデフォルトの状態では解凍だけサポートしており圧縮はサポートしていません。圧縮も有効にしたい場合は、`spresense/sdk/externals/libjpeg/Makefile`
に次のファイルを追記してコンパイル対象にする必要があります。

[code]

    CSRCS += jcapimin.c jcapistd.c jcarith.c jccoefct.c jccolor.c
    CSRCS += jcdctmgr.c jchuff.c jcinit.c jcmainct.c jcmarker.c jcmaster.c
    CSRCS += jcparam.c jcprepct.c jcsample.c jctrans.c jdatadst.c
    CSRCS += jfdctflt.c jfdctfst.c jfdctint.c
    
[/code]

