SPRESENSE で AVI の動画をサポートしてみた! [SPRESENSE]
SPRESENSEは静止画記録しかサポートしていませんが、せっかくなら動画を記録できるようにしたいなと思い、少し調べてみました。
Motion JPEGであれば対応できるだろうと調べていたら、AVI なら JPG を格納できることが判明!
AVI ファイルの話し
http://landhere.jp/blog/a149.html
紹介されているサイトを巡って調べてみると、音声等も格納できるマルチメディアフォーマットとなっており、真面目に実装しようとするとなかなか工数がかかりそう。まぁ当然だよね。(´・ω・`)
かなり古いフォーマットだし、きっとArduinoで実装している人いるだろ?と探してみたところ、やっぱりいました。オープンソース素晴らしい!
ArduCAM_Mini_Video2SD.ino
https://github.com/ArduCAM/Arduino/blob/master/ArduCAM/examples/mini/ArduCAM_Mini_Video2SD/ArduCAM_Mini_Video2SD.ino
で、このコードを参考にSPRESENSE向けに再実装をしてみたのがこちらです。
https://github.com/YoshinoTaro/Spresense-Simple-AVI-codec
SPRESENSEだと、コードが簡潔にすっきりまとまりました。素晴らしい!
このスケッチで記録した動画を、RIFF Tree Viewer で見ると、このファイルは壊れていると言われてしまうので、まだどこかおかしいようですが、Windows 10 ではきちんと再生できました。
SPRESENSEは、MP3を記録できるのでAVIでオーディオも格納できます。これで音声もサポートできるようになれば、立派なカメラになるのでそのうちチャレンジしてみるかなぁ。
( ´ ▽ ` )ノ
Motion JPEGであれば対応できるだろうと調べていたら、AVI なら JPG を格納できることが判明!
AVI ファイルの話し
http://landhere.jp/blog/a149.html
紹介されているサイトを巡って調べてみると、音声等も格納できるマルチメディアフォーマットとなっており、真面目に実装しようとするとなかなか工数がかかりそう。まぁ当然だよね。(´・ω・`)
かなり古いフォーマットだし、きっとArduinoで実装している人いるだろ?と探してみたところ、やっぱりいました。オープンソース素晴らしい!
ArduCAM_Mini_Video2SD.ino
https://github.com/ArduCAM/Arduino/blob/master/ArduCAM/examples/mini/ArduCAM_Mini_Video2SD/ArduCAM_Mini_Video2SD.ino
で、このコードを参考にSPRESENSE向けに再実装をしてみたのがこちらです。
https://github.com/YoshinoTaro/Spresense-Simple-AVI-codec
#include <Camera.h> #include <SDHCI.h> #include <stdio.h> #include <math.h> SDClass theSD; /* WIDTH == 1280 (0x500) */ #define WIDTH_1 0x00 #define WIDTH_2 0x05 /* HEIGHT == 960 (0x3C0) */ #define HEIGHT_1 0xC0 #define HEIGHT_2 0x03 #define TOTAL_FRAMES 300 #define AVIOFFSET 240 unsigned long movi_size = 0; const char avi_header[AVIOFFSET+1] = { 0x52, 0x49, 0x46, 0x46, 0xD8, 0x01, 0x0E, 0x00, 0x41, 0x56, 0x49, 0x20, 0x4C, 0x49, 0x53, 0x54, 0xD0, 0x00, 0x00, 0x00, 0x68, 0x64, 0x72, 0x6C, 0x61, 0x76, 0x69, 0x68, 0x38, 0x00, 0x00, 0x00, 0xA0, 0x86, 0x01, 0x00, 0x80, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, WIDTH_1, WIDTH_2, 0x00, 0x00, HEIGHT_1, HEIGHT_2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x84, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x6C, 0x73, 0x74, 0x72, 0x68, 0x30, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x73, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x66, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, WIDTH_1, WIDTH_2, 0x00, 0x00, HEIGHT_1, HEIGHT_2, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x84, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x10, 0x00, 0x00, 0x00, 0x6F, 0x64, 0x6D, 0x6C, 0x64, 0x6D, 0x6C, 0x68, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69, 0x00 }; File aviFile; String filename = "movie.avi"; uint32_t start_ms = 0; static void inline uint32_write_to_aviFile(uint32_t v) { char value = v % 0x100; aviFile.write(value); v = v >> 8; value = v % 0x100; aviFile.write(value); v = v >> 8; value = v % 0x100; aviFile.write(value); v = v >> 8; value = v; aviFile.write(value); } void setup() { Serial.begin(115200); theCamera.begin(); theSD.begin(); theSD.remove(filename); aviFile = theSD.open(filename ,FILE_WRITE); aviFile.write(avi_header, AVIOFFSET); Serial.println("Recording..."); theCamera.setStillPictureImageFormat( CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG); start_ms = millis(); digitalWrite(LED0 ,HIGH); } int loopCounter = 0; void loop() { CamImage img = theCamera.takePicture(); if (!img.isAvailable()) { Serial.println("faile to take a picture"); return; } aviFile.write("00dc", 4); uint32_t chunk_top = aviFile.position(); uint32_t jpeg_size = img.getImgSize(); uint32_write_to_aviFile(jpeg_size); aviFile.write(img.getImgBuff() ,jpeg_size); movi_size += jpeg_size; /* Spresense's jpg file is assumed to be 16bits aligned * So, there's no padding operation */ if (++loopCounter == TOTAL_FRAMES) { float duration_sec = (millis() - start_ms) / 1000.0f; float fps_in_float = loopCounter / duration_sec; float us_per_frame_in_float = 1000000.0f / fps_in_float; uint32_t fps = round(fps_in_float); uint32_t us_per_frame = round(us_per_frame_in_float); /* overwrite riff file size */ aviFile.seek(0x04); uint32_t total_size = movi_size + 12*loopCounter + 4; uint32_write_to_aviFile(total_size); /* overwrite hdrl */ /* hdrl.avih.us_per_frame */ aviFile.seek(0x20); uint32_write_to_aviFile(us_per_frame); uint32_t max_bytes_per_sec = movi_size * fps / loopCounter; aviFile.seek(0x24); uint32_write_to_aviFile(max_bytes_per_sec); /* hdrl.avih.tot_frames */ aviFile.seek(0x30); uint32_write_to_aviFile(loopCounter); aviFile.seek(0x84); uint32_write_to_aviFile(fps); /* hdrl.strl.list_odml.frames */ aviFile.seek(0xe0); uint32_write_to_aviFile(loopCounter); aviFile.seek(0xe8); uint32_write_to_aviFile(movi_size); aviFile.close(); Serial.println("Movie saved"); Serial.println(" File size (kB): " + String(total_size)); Serial.println(" Captured Frame: " + String(loopCounter)); Serial.println(" Duration (sec): " + String(duration_sec)); Serial.println(" Frame per sec : " + String(fps)); Serial.println(" Max data rate : " + String(max_bytes_per_sec)); digitalWrite(LED0, LOW); while(1); } }
SPRESENSEだと、コードが簡潔にすっきりまとまりました。素晴らしい!
このスケッチで記録した動画を、RIFF Tree Viewer で見ると、このファイルは壊れていると言われてしまうので、まだどこかおかしいようですが、Windows 10 ではきちんと再生できました。
SPRESENSEは、MP3を記録できるのでAVIでオーディオも格納できます。これで音声もサポートできるようになれば、立派なカメラになるのでそのうちチャレンジしてみるかなぁ。
( ´ ▽ ` )ノ
2019-09-30 07:09
nice!(28)
コメント(2)
AVIだとファイルサイズがかなり大きくなりませんか?
by ぽちの輔 (2019-10-02 07:04)
SPRESENSEはJPEGしか撮れないのでAVI以外の選択肢はとれないのです…
by ys_taro (2019-10-05 05:37)