Browserify を利用する vorbis-encoder-js をリリースしたので、 履歴とハマリポイント等を備忘録として書き残していく。

既存のライブラリでは何が足りなかったのか

上記どちらのライブラリでも AudioBuffer から Ogg Vorbis 形式の音楽ファイルを生成し、DLさせる所までは実現した。 しかし、元々想定される用途が Web Audio API を利用して、マイクからの音声データをDLする所がゴールだったようで、 タグ情報を埋め込む事考えられておらず、任意のタグ情報が埋め込めない。

また、モダンなプロジェクトなら Browserifywebpack の事は考慮に入れると思うのだが、そうでもないらしく微妙に使い勝手が悪い。 最初のコミットが1年も2年も前なので仕方ない側面もある。

そういった経緯から要望を満たすライブラリが無かったので、 GitHub で公開されている既存プロジェクトのソースコードを参考に作る事にした。

要件

  • Node.js での利用を想定する
  • JavaScript での利用は Browserify を利用する
  • 任意のタグ情報を設定出来ること
  • ビルド環境の構築は頑張らない

ログ

libvorbis のビルド

libvorbis はC言語で書かれているので、JavaScriptに変換するために Emscripten を経由する必要がある。 もともとC言語で書かれたライブラリを任意の言語に変換して使おうという要望は LLVM というプロジェクトで実現されており、 EmscriptenLLVM が生成した中間コードを JavaScript に変換する機能に特化している。

従って、あれやらそれやらのインストールが必要で、 いつまで経ってもコンパイルが通る環境が作れない!

参考リンク:emscriptenで遊んでみた - kashiの日記

結局 Emscripten に特化したコンテナは Docker Hub で公開されていたので利用することにした。

参考リンク:apiaryio/emcc - Docker Hub

Makefile の作成

ベースとして利用させて頂いている higuma/ogg-vorbis-encoder-js - GitHub のプロジェクトでは、 作者さんが Ruby が好きなのか Rake でソースコードのビルドを行っている。 2年前のプロジェクトなので、 Grunt や Gulp 等の Node.js 製タスクランナーは馴染みが無かったと思われる。

折角なんで Gulp で書いても良かったが、勉強も兼ねて Makefile に移植することにした。 Emscripten は C言語なんでそっちの方が良さそうだ。 結果約2週間ほどかかった。

ハマった主な要因としては、function や foreach を使う手法はバッドノウハウになりがちである事に気が付かなかった。 Makefile の本質はビルド後のファイルを作成することにあるので、素直に依存ファイルを並べていく方が良い事に気がついてからはすんなり実装が完了した。

参考リンク:

encoder.js の作成

Emscripten では生成した JavaScript ファイルに Module という変数を生成して関数一覧を用意し、 module.exports に代入してくれるんで、普通に Node.js のやり方が通用する。

ベースのプロジェクトではpre.jsやpost.jsを利用して、 libvorbisに直接関数を足して対応するという形であったので、普通にrequireして使うように調整。

タグの埋め込み

後は消化試合、テストファイルでも作って終わらせばOK…になるわけが無かった。 Emscripten で出力した関数が文字列を解釈できず大幅に足止め。 結局これも2週間かかった。

emscriptenでC/C++プログラムをwebブラウザから使うまでの難所攻略で紹介されているスライドが勉強になった。

  • emscriptenでは、C/C++の変数をArrayBufferで作った擬似的なメモリ空間に保存している
  • Module.HEAPU8などDataViewでアクセスできる

これのお陰で、Emscripten は単なるトランスパイラではなく、 C言語そのものを JavaScript で表現しているのだという事が理解できた。 その視点でドキュメントやら紹介記事を読み返した事で解決。

参考リンク:

テスト

いつも通り Mocha + Chai + LiveScript というテンプレ構成で実装。 でもNode.jsはAudioBufferないじゃん、テスト作れないじゃんというわけで調査。

依存ライブラリ

普通にnpmを探したら置いてあった。 audio-decodeを組み込んで実装。 しっかりAudioBufferオブジェクトが帰ってくる。

テスト音楽ファイルはaudio-lenaが最適。 ええ声のお姉さんが「お〜れいるひ〜」と12秒間アカペラで歌ってくれた。

参考リンク:

テストの実装

実際の実装はすんなり行かず、大体3日程かかった。 Node.js には Blob が存在しないので Buffer を使って上手くやるようにした。

Node.js のドキュメントをみたところ、 Buffer.concat の項目によれば Uint8Array が詰まった配列でもいいらしい。 楽勝だな。

list List of Buffer or Uint8Array instances to concat

エラーが出まくって結合出来ない。 大嘘じゃねーか!…一応History見ておくか。

History: v8.0.0: The elements of list can now be Uint8Arrays. v0.7.11: Added in: v0.7.11

なるほど、Uint8Array許可はNode.jsのv8からなのね。 一旦は下記で対応。

Buffer.concat(
  this.ogg_buffers.map(function(it){
    return new Buffer(it.buffer);
  })
);

参考リンク:

感想

最初から簡単だとは思わなかったけど相当ハマった。 しかし、時間を掛けた甲斐はあったと思う。

仕事を辞めてPSO2で遊んでいるうちに、ネイティブで動作する言語に対する興味がどんどん湧いてきた。 今回の件でネイティブとWebの技術の橋渡しに関してイメージがかなり掴めたので、 もっと良いアイデアや実装ができそうな気がする。

興味が湧いたらIoTなんかにも手を出してみたい。