『大恐竜時代へGO!! GO!!』のギズモを作ってみた
大恐竜時代へGO!! GO!!に登場するギズモを作ってみた。
『大恐竜時代へGO!! GO!!』のギズモを作ってみた。
— Ken (@teaplanet) December 5, 2021
リストバンドに縫い付けて完成。 pic.twitter.com/7JUnfDvFt4
最終的にはリストバンドにマジックテープを縫い付けて、腕に装着できるようにした。
今回使ったデバイスは M5Stack Basic V2.6
という製品。
https://www.switch-science.com/catalog/7362/
続きは作る過程の話。
素材の準備
動画の準備
まずは画像と音を切り出す動画 ( full_movie.mp4
) を用意する。
動画全体はいらないので、必要そうな場所だけを抜き出しておく。
ffmpeg -ss 1:23 -to 4:56 -i full_movie.mp4 -c copy movie.mp4
音の準備
次は音を取り出す。
ffmpeg -i movie.mp4 -vn -acodec copy bgm.mp3
音声だけmp3形式で取り出せるんだけど、M5Stackでちょうどよい感じの音質にまで劣化させておく。
ffmpeg -i bgm.mp3 -ar 8000 -ac 1 -acodec pcm_u8 bgm.wav
mp3
と wav
のどっちがいいのかはわからない。
聴き比べてみたら wav
の方が聴きやすかったから今回は wav
を採用。
画像の準備
音の次は画像を用意する。
下のコマンドを実行すると、1秒間から10枚の画像を切り出してくれる。
ffmpeg -i movie.mp4 -r 10 -vcodec mjpeg image_%03d.jpg
音声も同様にコマンドで抜き出す。
ffmpeg -i movie.mp4 -vn -acodec copy bgm.mp3
組み込む
素材が揃ったところで M5Stack に組み込んでいくんだけど、素材はあらかじめSDカードに入れて M5Stack に刺しておく。
IDEの準備
今回は Visual Studio Code
に PlatformIO
エクステンションを入れて作っていく。
設定手順は省くが、 platformio.ini
はライブラリとバージョンの情報が役立ちそうなので載せておく。
[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
lib_deps =
m5stack/M5Stack@^0.3.9
gianbacchio/ESP8266Spiram@^1.0
earlephilhower/ESP8266Audio@1.9.3
upload_port = /dev/cu.wchusbserial52D2026711
monitor_port = /dev/cu.wchusbserial52D2026711
monitor_speed = 115200
ここで重要なのが ESP8266Audio
のバージョン。
最新の 1.9.4
では音が鳴らず、 1.9.3
では音が鳴った。
ESP8266Audio@1.9.4
で表示されていたエラーはこんな感じ。
AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid RIFF header, got: 76797574
AudioGeneratorWAV::begin: failed during ReadWAVInfo
ライブラリの情報は PlatformIO で設定すれば反映されるが、下の3行は platformio.ini
を直接追記した。
upload_port = /dev/cu.wchusbserial52D2026711
monitor_port = /dev/cu.wchusbserial52D2026711
monitor_speed = 115200
実装
実装準備が整ったので実装。
音を流したまま画像を切り替えようと、 wav->begin(file, out);
の直後に画像表示を行うと音の再生が止まった。
画像表示が終わってから wav->isRunning()
のあたりを実行すると引き続き流れるので、今回は画像表示 → 音再生 の順序にしている。
#include <M5Stack.h>
#include <WiFi.h>
#include "AudioFileSourceSD.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"
#define IMAGE_GIZMO "/gizmo.jpg"
void playWav(const char path[]){
Serial.println("#playWav");
AudioFileSourceSD *file = new AudioFileSourceSD(path);
AudioOutputI2S *out = new AudioOutputI2S(0, 1);
out->SetOutputModeMono(true);
out->SetGain(0.6);
AudioGeneratorWAV *wav = new AudioGeneratorWAV();
wav->begin(file, out);
while(wav->isRunning()){
if (!wav->loop()) wav->stop();
}
}
void drawJpeg(char* path) {
M5.Lcd.drawJpgFile(SD, path);
}
void drawPlayWav(char* jpgPath, const char wavPath[]) {
drawJpeg(jpgPath);
playWav(wavPath);
}
void drawGizmo() {
Serial.println("#drawGizmo");
drawJpeg((char*)IMAGE_GIZMO);
}
void playCheck() {
Serial.println("#playCheck");
drawPlayWav((char*)"/check/01_check_vest.jpg", "/check/01_check_vest.wav");
drawPlayWav((char*)"/check/02_check_gizmo.jpg", "/check/02_check_gizmo.wav");
drawPlayWav((char*)"/check/03_check_hat.jpg", "/check/03_check_hat.wav");
drawPlayWav((char*)"/check/04_check_bag.jpg", "/check/04_check_bag.wav");
}
void playGo() {
Serial.println("#playCheck");
drawPlayWav((char*)"/go/01_go.jpg", "/go/01_go.wav");
drawPlayWav((char*)"/go/02_reverse.jpg", "/go/02_reverse.wav");
drawPlayWav((char*)"/go/03_warp.jpg", "/go/03_warp.wav");
drawPlayWav((char*)"/go/04_arrive.jpg", "/go/04_arrive.wav");
}
void playSmatt() {
Serial.println("#SoftwareSerial");
drawPlayWav((char*)"/smatt/smatt.jpg", "/smatt/smatt.wav");
}
void setup() {
M5.begin();
M5.Power.begin();
M5.Lcd.setBrightness(15);
WiFi.mode(WIFI_OFF);
delay(500);
Serial.println("#setup");
drawGizmo();
}
void loop() {
M5.update();
if (M5.BtnA.wasPressed()) {
playCheck();
delay(2000);
drawGizmo();
} else if (M5.BtnB.wasPressed()) {
playGo();
delay(2000);
drawGizmo();
} else if (M5.BtnC.wasPressed()) {
playSmatt();
}
}