SPRESENSEで動画をサポートする（Arduino対応ライブラリ）

# SPRESENSEで動画を記録する

SPRESENSEはカメラでJPEG画像を簡単に取得することができます。しかし、動画を撮るためのライブラリは用意されていません。そこで動画を記録するためのライブラリを作ってみました。このライブラリは、静止画をMotion JPEG (AVI) として保存します（音声はサポートしていません）。

単に動画をとるということだけでなく、タイムラプス記録機能も備えており、フレームレートで再生速度を設定できるようになっています。例えば、もともとのフレームレートが 10fps だった場合、20fps 設定すると倍速の動画になりますし、一方で 5fps と設定すると半分の速度で再生されます。


https://github.com/YoshinoTaro/AviLibrary_Arduino

このライブラリのサンプルコードを３種類用意しました。

- 動画撮影用サンプル（再生速度は実時間）
- タイムラプス撮影用サンプル（再生速度が20fps設定）
- 省電力カメラサンプル（再生速度が5fps設定、ディープスリープ間隔2秒）

タイムラプスの応用として省電力カメラのサンプルも用意しています。2秒毎に電源をオンにして撮影した画像をタイムラプス画像にして保存します。AVIライブラリの使い方は、サンプルコードを用いて解説します。

# AVIライブラリの使い方
AVIライブラリのAPIは、SPRESENSEだけでなくArduinoでも動くように設計しています（ただ試していません）。使い方は非常に簡単です。主要な機能は次の通りです。

- ライブラリを初期化する
- 動画記録／タイムラプスを開始する
- 動画／タイムラプスのフレームを追加する
- 動画／タイムラプスの記録を終了する
- ライブラリを終了する

Motion JPEG として保存するので、記録する画像はJPEGフォーマットのものを必ず使用してください。SPRESENSE の場合は、カメラライブラリでJPEG画像を取得できるので簡単にこのライブラリを利用できます。

# 動画撮影用サンプル
動画撮影用サンプルは次のようになっています。動画記録の場合は startRecording()、addFrame()、stopRecording() を使用します。

```cpp:Spresense_camcoder_sample.ino
void setup() {
  Serial.begin(115200);
  theCamera.begin();
  while (!theSD.begin()) { Serial.println("insert SD card"); }

  // 静止画撮影設定　出力フォーマットは必ずJPEGを設定する
  theCamera.setStillPictureImageFormat(
     CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG);

  theSD.remove(filename);
  aviFile = theSD.open(filename, FILE_WRITE);

  // AVIライブラリを初期化し動画記録を開始
  theAvi.begin(aviFile, CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V);
  theAvi.startRecording();
}

void loop() {
  static uint32_t start_time = millis();
  CamImage img = theCamera.takePicture();

  // フレームを追加
  theAvi.addFrame(img.getImgBuff(), img.getImgSize());

  uint32_t duration = millis() - start_time;
  if (duration > recording_time_in_ms) {
    // 動画記録を停止し終了する
    theAvi.endRecording();
    theAvi.end();
    theCamera.end();
    while (true) {}
  }
}
```

# タイムラプス撮影用サンプル
タイムラプスス撮影用サンプルの場合は startTimelapse()、addTimelapseFrame()、stopTimelapse() を使用します。動画撮影と異なるAPIを使用するのは、動画撮影とタイムラプス撮影で再生時間の処理が異なるためです。

```cpp:Spresense_timelapse_sampe.ino
void setup() {
  Serial.begin(115200);
  theCamera.begin();
  while (!theSD.begin()) { Serial.println("insert SD card"); }

  // 静止画撮影設定　出力フォーマットは必ずJPEGを設定する
  theCamera.setStillPictureImageFormat(
     CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG);

  theSD.remove(filename);
  aviFile = theSD.open(filename, FILE_WRITE);

  // AVIライブラリを初期化し動画記録を開始
  theAvi.begin(aviFile, CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V);
  // タイムラプス動画のFPSを指定して記録開始
  theAvi.startTimelapse(target_fps);
}

void loop() {
  static uint32_t start_time = millis();
  CamImage img = theCamera.takePicture();
  // フレームを追加
  theAvi.addTimelapseFrame(img.getImgBuff(), img.getImgSize());

  uint32_t duration = millis() - start_time;
  if (duration > recording_time_in_ms) {
    // タイムラプス記録を停止し終了する
    theAvi.endTimelapse();
    theAvi.end();
    theCamera.end();
    while (true) {}
  }
}
```

# 省電力カメラのサンプル
タイムラプス画像の応用として省電力カメラのサンプルも用意しました。せっかくタイムラプス画像を撮るなら、撮影をしていない時間は電源をオフにしたいもの。SPRESENSEならLowPowerライブラリで実現できます。

他のサンプルよりも少し複雑ですが、ポイントは記録した動画の情報をEEPROMに保存しているところです。記録したフレーム数、動画のサイズ、ファイルサイズを保存し、次の起動時に読み込みます。
ただ、それだけとずっと記録し続けてしまうので、リセットや電源オンされた場合(タイマー起動以外)は、再設定する工夫をいれています。

```cpp:Spresense_lowpower_timelapse.ino
void setup() {
  // LowPowerライブラリを使用する
  LowPower.begin();
  RTC.begin();

  // 起動要因を取得
  bootcause_e bc = LowPower.bootCause();
  
  Serial.begin(115200);
  theCamera.begin();
  while (!theSD.begin()) { Serial.println("insert SD card"); }

  // 静止画撮影設定　出力フォーマットは必ずJPEGを設定する
  theCamera.setStillPictureImageFormat(
     CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG);

  // 起動要因がタイマー起動でない場合は、リセットとみなし記録をやり直し
  if (bc != DEEP_RTC && bc != DEEP_OTHERS) {
    theSD.remove(filename);
    rec_frame = 0;
    movi_size = 0;
    file_size = 0; 
  } else {
    EEPROM.get(rec_frame_address, rec_frame);  // 記録したフレーム数の読込
    EEPROM.get(movi_size_address, movi_size);  // 記録した動画サイズの読込
    EEPROM.get(file_size_address, file_size);  // 記録したファイルサイズの読込
  }

  aviFile = theSD.open(filename, FILE_WRITE);

  // AVIライブラリを初期化し動画記録を開始
  theAvi.begin(aviFile, CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V);
  // EEPROMから読み込んだパラメータを設定
  theAvi.setTotalFrame(rec_frame);
  theAvi.setFileSize(file_size);
  theAvi.setMovieSize(movi_size); 
  // タイムラプス動画のFPSを指定して記録開始
  theAvi.startTimelapse(target_fps);
}

void loop() {
  CamImage img = theCamera.takePicture();
  // フレームを追加
  theAvi.addTimelapseFrame(img.getImgBuff(), img.getImgSize());
  // 記録したたらぐに終了
  theAvi.endTimelapse();
  theAvi.end();
  theCamera.end();

  // パラメータをEEPROMに保存
  EEPROM.put(rec_frame_address, theAvi.getTotalFrame());
  EEPROM.put(movi_size_address, theAvi.getMovieSize());
  EEPROM.put(file_size_address, theAvi.getFileSize());
  
  // 指定したフレーム数を超えたら終了
  if (theAvi.getTotalFrame() > recording_total_frames) {
    while (true) {}
  }
  // sleep_time で指定した時間電源オフ
  LowPower.deepSleep(sleep_time);
}
```

# まとめ
SPRESENSEで動画を記録するためにMotion JPEG(AVI) を扱うライブラリを作成しました。SPRESENSEのLowPowerライブラリと組み合わせ使用することで、省電力カメラも実現できます。一晩中夜空を撮影するカメラや、ドライブやサイクリングの旅程撮影用カメラ、自宅用の監視カメラにも使えると思います。

私はこんな超小型のタイムラプスカメラを作ってみました。
![SPRESENSEタイムラプスカメラ](https://makers-with-myson.c.blog.ss-blog.jp/_images/blog/_786/makers-with-myson/sADSC02027.jpg)

このライブラリはArduinoでも使えるので、OV7670などを使用したカメラシステムにも使えると思います。出来たてほやほやですので、まだまだ問題があると思います。皆さんからのフィードバックをいただけると嬉しいです。

# 関連情報
AVIライブラリを作るきっかけとなった作品群です。参考情報として掲載しておきます。

- [SPRESENSEのカメラのモニターをProcessingで作る](https://makers-with-myson.blog.ss-blog.jp/2019-09-08)
- [SPRESENSEでLowPowerカメラ](https://makers-with-myson.blog.ss-blog.jp/2020-02-10)
- [SPRESENSEでAVI動画をサポートしてみた](https://makers-with-myson.blog.ss-blog.jp/2019-09-30)
- [SPRESENSEでサーマルカメラを作ろう](https://makers-with-myson.blog.ss-blog.jp/2021-04-06)
- [SPRESENSEでタイムラプスカメラを作ろう](https://makers-with-myson.blog.ss-blog.jp/2021-01-31)