Skip to main content

『大恐竜時代へGO!! GO!!』のギズモを作ってみた

· 5 min read

大恐竜時代へGO!! GO!!に登場するギズモを作ってみた。

最終的にはリストバンドにマジックテープを縫い付けて、腕に装着できるようにした。
ギズモ

今回使ったデバイスは 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

mp3wav のどっちがいいのかはわからない。
聴き比べてみたら 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 CodePlatformIO エクステンションを入れて作っていく。

設定手順は省くが、 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();
}
}