ESP8266+OLEDにBad Apple(動画)表示

# 概要

ESP8266 に接続された128x64pixel モノクロのOLEDに動画を表示することができたので、その構成、仕組みなどについて説明する。
今回作成したプログラムは、GitHubで公開している <a href="https://github.com/h-nari/HSES_NODE_OLED_Sample_programs">h-nari/HSES_NODE_OLED_Sample_programs</a>にNetOLEDという名前で収められている。

<a href="http://www.youtube.com/watch?v=rIRIXXnOoCE">
<img src="http://img.youtube.com/vi/rIRIXXnOoCE/0.jpg" width="200"></a>
- 動画: <a href="http://www.youtube.com/watch?v=rIRIXXnOoCE">ESP8266+OLEDでBad Apple (youtube)</a>　（音が出ます)

# 背景

ESP8266(ESP-WROOM-02)と<a href="http://www.humblesoft.com/prodocts/HSES-LCD-24/">2.4インチ・カラーLCD</a>を組み合わせた基板や <a href="http://www.humblesoft.com/products/HSES-NODE/">0.96インチOLEDを組み合わせた基板</a>を作成している。こういう基板で、いつかは動画を表示させてみたいと考えていたが2.4インチ・カラーLCDの方はSPI接続でCPU-LCD間の速度ネックで無理。OLEDもSPI接続だが、こちらは画素数が少ないので試してみた。

# 仕組み

![fig170302a1.png](https://qiita-image-store.s3.amazonaws.com/0/144308/2985733e-c7dc-7e9b-5b8d-57b19b101bbd.png)

## ESP8266側

使用しているボードはHSES-NODE-OLED, 回路図も含む技術情報も公開しているので詳しくは<a href="http://www.humblesoft.com/products/HSES-NODE">製品ページ</a>を参照して頂きたい。

ESP8266側では、udpパケットを受信し、含まれる画像データを、そのままOLEDに送るプログラムが動いている。画像データのフォーマットは、OLEDのデータフォーマットそのままである。 1画面分のサイズは 128 x 64 / 8 = 1024byte となり、udpパケット1個で送ることができる。

``` c++:sketch_netOLED.inoの一部

void loop()
{
  if(udp.parsePacket()){
    PacketHeader h;
    int len = udp.read((uint8_t *)&h, sizeof(h));

    if(len < sizeof(h)){
      Serial.printf("Bad length:%d\n",len);
    } else {
      while(udp.available()){
        uint8_t buf[1024];
        int len = udp.read(buf, sizeof(buf));
        oled.writeData(buf, len);
      }
    }
  }
}
```

## PC側

PC側では動画のファイルを読み込み、一定周期でudpパケットを送出する dispImage.pyというpythonのプログラムが動いている。これを以下のように起動する

``` console
$ python dispImage.py -r 30 192.168.0.136 ba_bmp/ba*.bmp
```
 -r 30 は、30fpsで画像を送る指定、 192.168.0.136は esp8266基板のIPアドレス、 ba_bmp/ba*.bmpは表示する画像ファイルで、今回はBad Apple 影絵(<a href="http://www.nicovideo.jp/watch/sm8628149">ニコニコ動画</a>,<a href="https://www.youtube.com/watch?v=FtutLA63Cp8"> YouTube</a>)のファイルをffmpegで1フレームづつbmpファイルに変換したものを使用した。

### Pythonライブラリ

Pythonの画像処理ライブラリPIL(Pillow)のImageオブジェクトから128x64dotモノクロのOLEDのデータフォーマットに変換するライブラリを用意した。Imageのモードの変換やサイズの調整処理を除くと、下のようになる

```py
    def make_img_data(self, im, w = 128, h = 64):
        px = im.load()
        bdata = b''
        for page in range(int(h/8)):
            for x in range(w):
                d = 0
                for i in range(8):
                    b = px[x,page*8+i]
                    if b: 
                        d += 1 << i
                bdata += struct.pack('B',d)
        return bdata 
```

あとは、若干のヘッダーを追加して、udpで送るだけ。

```py
    def send_data(self,bdata,frame):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        with closing(sock):
            msg = b'HSESNODE'
            msg += struct.pack('I', frame)
            msg += bdata
            sock.sendto(msg, (self.host, self.port))

    def send_img(self, img, frame = 0):
        self.send_data(self.make_img_data(img), frame)
```

### 画像ファイルの準備

既に書いた通り、今回は、動画ファイルをffmpegで、フレーム毎にbmpファイルに変換し表示させている。
この時、サイズの変換、縦横比を保存するためのパディングの追加なども行っている。
コマンドラインは、次のようになる

```console
$ ffmpeg -i bad_apple_mono.mp4 -vf pad=768:384:128:0 -s 128x64 -r 30 -y ba_bmp/ba%04d.bmp
```

### 同期処理


WindowsやLinuxなどのマルチタスクOSで、動画の再生のような1定周期で行う処理を、どうやってやれせれば良いのかわからなかったのだが、とにかく処理の遅れが累積にないように下のようなプログラムで試してみたところ、上手くいった。撮影後、編集でBad Appleの音声を合成したのだが、音ズレは全く起こらなかった。

```python:dispImage.pyの一部

for file in args['<img_file>']:
    wait = start + period - time.time();
    start += period
    if verbose:
      print(file,wait)
    if(wait > 0):
        im = Image.open(file)
        oled.send_img(im)
        time.sleep(wait)
```


# 動作動画

表示デモプログラム dispDemo.pyの動画、Bad Apple全体をカメラ固定のまま撮影した動画も作成したので、リンクを張る。

- 動画: <a href="http://www.youtube.com/watch?v=DZ9h0Leanrk">NetOLED dispDemo.py 実行動画  (youtube)</a>
- 動画: <a href="http://www.youtube.com/watch?v=UAhHnJlFdHA">ESP8266 + OLEDでBad Apple フルバージョン   (youtube)</a> (音注意)

<a href="http://www.youtube.com/watch?v=DZ9h0Leanrk">
<img src="http://img.youtube.com/vi/DZ9h0Leanrk/0.jpg" width="200"></a> 
<a href="http://www.youtube.com/watch?v=UAhHnJlFdHA">
<img src="http://img.youtube.com/vi/UAhHnJlFdHA/0.jpg" width="200"></a>

# 最後に

- ESP8266 + OLEDで十分高速に表示できることがわかった。
- pythonで動画再生のような1定周期の処理を行うことが出来た。
- モノクロではあるが、128x64もの解像度があるので、もう少し面白い表示を模索してみたい。