GLSLをサーバーサイドでレンダリングして、GPUが非力なkindle fireでみる

mrubyでこんなことやりました！的な記事です。

macOSで動かして試していますが、Linuxでも動くといいなぁと願いながら開発してます。

# GLSLをサーバーサイドでレンダリングして、GPUが非力なkindle fireでみる

# 背景

今年つくったmrbgemのmruby-glslは標準出力にppm形式でレンダリングした画像を出力します。

一方ffmpegが標準入力からの複数の静止画を読み込んで動画にすることができます。

- -f pipe_ppm や -f image2pipe -pix_fmt ppm

さらに、ffmpegはライブ動画の形式のHLS形式での出力もサポートしていることがわかりました。

## HLSとは

Appleが作った規格。なので、iOSでは動く。また、kindle fireやAndroidでも動く。しかし、PCでは冷遇気味。
Edgeがちょっとサポートし始めたが、Chrome、Firefoxともに未サポート。当然macOSのSafariだと動く。

動画を10秒から数分程度のファイルに分け、直近の動画ファイルの数本を索引ファイルに書き込んで、ブラウザは、このファイルを読みんで、再生すべき動画ファイルをDLする、この方式の為、完全にリアルタイムで配信という訳ではないが、まぁ実用的にライブ放送っぽいことは出来る模様。


この応用で、GLSLの編集はkindle fire等のGPUが非力な端末で行い、サーバーサイドでレンダリングを行って、その結果を
HLS形式の動画で端末に返すことで結果を確認できる。結果が動画なのでさらに他の家族も同時にGLSLのレンダリング結果を楽しめるはず。

これで、kindle Fireを持ち歩いて高度なGLSLを家のどこでも書くことができるようになるはず！

```bash
~/work/mruby/glsl/mruby/bin/mruby ~/work/mruby/glsl/mruby-glsl/examples/plazuma.rb |ffmpeg -y \
-f ppm_pipe -r 30 -i - \
-f ssegment -s 320x240 -r 30 -c:v libx264 -preset fast -pix_fmt yuv420p -b:v 1024k -quality good \
-strict experimental \
-segment_list time.m3u8 \
-break_non_keyframes 1 -segment_list_type hls \
-segment_time 10 -segment_list_size 3 -segment_list_flags +live -threads 4 \
out%03d.ts
```

# 全体の構成

![glsl2hlsSystemImage.png](https://qiita-image-store.s3.amazonaws.com/0/3458/8ab48aa2-0006-d0f9-5cc6-d67f97c61560.png)


# redis

- redisをジョブキューとして利用

# WebServer

[mruby-simplehttpserver]()で実装。ただし、後述するSafariのPOST対応の為、独自版を使用。

- 静的ファイルの配信
- なんちゃってWebAPI

# フロントエンド

Fetch　APIが開発当時のMobile Safariで使えず、XMLRequestでテキストフィールドに入力された、
GLSLのバーテックスシェーダーとフラグメントシェーダーのプログラムコードをJSON化して、なんちゃってWebAPIに投げます。

## Link

- http://caniuse.com/#search=fetch

# サーバーサイド

- POSTされたJSONをRedisに格納する(なんちゃってWebAPI)
- Redisに投入されたGLSLプログラムコードをレンダリングして、HLSに

## glsl2hlsの詳細

- RedisにGLSLコードが投入されていないかチェック
- 投入されたGLSLがあれば、レンダリングしてffmpeにPPM形式でパイプ経由で渡す

# ログ周り

ようやく今年になってはじめたfluentdをつかって、やってみようとした。

- https://github.com/y-ken/fluent-logger-mruby

にmrbgemがあったので、ありがたく利用させてもらいました。

ローカル環境では、PostgreSQLに出力するfluentdのPluginを入れて、Postgres.appに出力しています。


# 課題

- 不要なtsファイルの削除
- エラー処理
 - GLSLコードがコンパイルエラーだった時の処理

# 一連の作業で学べたこと

- mruby-simplehttpserverとSafariのPOST
- Cocoaなウィンドウを扱う（内部的にでも扱う）mrbgemsを組み込むとForkしてffmpegが扱えない
- /dev/urandomはOSXでも存在した
- gettimeofdayの使い方


## Simple-http-serverとSafariのPOST問題

macOSのSafariだと、POST時にHTTPのヘッダ部を送信したら必ずflushするみたいで、
オリジナルのSimple-http-serverだとPOST出来なくてハマった。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">SafariでXmlHttpRequestでPOSTしてもmrubyのSimpleHttpServerでPOSTメソッドとして認識されない問題に取り組み中 / “gist:41b92f1abfaeb36fb535995021c2…” <a href="https://t.co/5yCLFs31GB">https://t.co/5yCLFs31GB</a></p>&mdash; kjunichi (@kjunichi) <a href="https://twitter.com/kjunichi/status/767739034428813312">2016年8月22日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">mruby-simplehttpserverが、safariのPOSTに対応出来てないことが分かった。socket#write ヘッダして、フラッシュしてsocket#write ボディ みたいな実装になってるせいで、一気にread出来ない模様。</p>&mdash; kjunichi (@kjunichi) <a href="https://twitter.com/kjunichi/status/767913522579722240">2016年8月23日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">そんなわけで、とりえずSafariのPOSTに対応してみたつもり / “mruby-simplehttpserver/mrb_simplehttpserver.rb at support-safari-post ・ kjunic…” <a href="https://t.co/X7KNuySOqi">https://t.co/X7KNuySOqi</a></p>&mdash; kjunichi (@kjunichi) <a href="https://twitter.com/kjunichi/status/767919405300789248">2016年8月23日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

## ffmpegが使えない？！

当初、webｄサーバーでリクエスト、受けたら、GLSL2HSLするプロセスをforkして実装。しようとしていた。
すると、以下のようなエラー

```txt
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec().
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.
```
mruby-glslを組み込んだ状態でProcess#forkしてffmpegを動かせなかった。mruby-glslとffmpegそれぞれでCocoaのGUI周りの初期化をおこなう
APIを読んでおり、このAPIの一部が同一プロセスで1度だけしか呼び出しを許可していないことに起因している模様。

# 成果物

- https://github.com/kjunichi/glsl2hls

# おわりに

この記事の検証目的も兼ねて、自作PCを刷新して、せめてVGAをNVIDIAのGeForce GTX 1060にアップグレードして試したかったのですが、かないませんでした。
そんな訳で、2012年製のMacBook Pro Retinaで動かしました。

# 参考資料

- http://rubykaigi.org/2016/presentations/elct9620.html
  - GLSLすらRubyで書きた方向けかな

- http://shaky.github.bushong.net/
  - Asciiを手書き風の図にしてくれるツール

# 関連記事

- [node.jsでシェーダーからGIFアニメを生成するツールglsl2imgにプルリク出した件](https://abrakatabura.hatenablog.com/entry/2017/05/22/071141)