自習室

こもります

Web Audio API の勉強 - グラフィックイコライザーを作る

はじめに

前回の記事で「Web Audio API はシンプルで使いやすいしパフォーマンスも問題なさそう」という予感を得たので、少し勉強してみることにしました。まずは基本的な使い方を勉強して、それから練習問題として、グラフィックイコライザーを作ってみます。

グラフィックイコライザー

iTunes でいうこいつ。ベースを聞きたいから低い周波数を強める(相対的に中高音を抑える)とか、シンバルの響きを感じたいから中高音を上げる(相対的に定温を抑える)とかするのに使うやつ。(俺氏曰く)

f:id:AMANE:20150124182347p:plain

完成品

今回は効果を分かりやすくするために「すべての周波数で同じ強度」となるホワイトノイズを生成し、それに対して周波数ごとの強弱をつけられるようなサンプルになっています。

使い方

  • Play でホワイトノイズが鳴ります(ボリューム注意!)
  • バーを動かすことで、指定周波数周辺を増減させます
  • グラフの下の"Log" "Linear" ボタンを押すと、グラフのx軸(音の周波数)を対数表示に切り替えられます

別ページで開く場合はこちらから > Web Audio Graphical Equalizer

このプログラムは g200kg.com さまの下記記事をベースに改変して作成いたしました。

基礎のお勉強

まずはWeb Audio APIの基本を勉強しました。参考にさせていただいた主なサイトは以下。

Getting Started with Web Audio API - HTML5 Rocks

"Web Audio API" でググるとトップに出るので、まずはここから。Googleのエンジニアによる記事。短い文量でWeb Audio APIの概要をつかむことが出来ました。

(電子書籍) O'Reilly Japan - Web Audio API

上の記事著者の著作、の邦訳。実質上の公式入門書。上の記事を拡張したような内容。この本のサポートページとして作られているサンプル集が非常に良い感じです。

また、上記サンプル集自体が、webaudioapi.com と言うサイトに含まれていて、ここから仕様書だったり、一般の方が作られている作例集に飛べたりします。

Web Audio API 解説 - 01.前説 | g200kg Music & Software

結論としては、このサイトだけで良かったです。Web Audio APIの基本から、細かい各ノードの使い方、それらの背景にあるデジタル音楽の理論まで、一通り学ぶことが出来ます。今回作成したグライコも、ここの記事中のコードをフォークして作らせていただきました。

Web Audio API (日本語訳)

上記サイトの方が、W3C の公式文書を邦訳してくださっています。ありがたすぎました。完璧でした。 Published Versionとしてはいまのところ W3C Working Draft 10 October 2013 が最新のようなので、邦訳も最新、と言うことになります。

Editors Draft としては、2015/1/6 に最新のものが提示されていました。

イコライザの実装

BiquadFilter "peaking" のチェーン

Web Audio API では sourceNode (音の発生源または音声データ)から destinationNode(最終的な出力先。端末のサウンドデバイスなど) までの間に、ゲイン調整や周波数領域でのフィルタリング、パンニングなどの効果をつけるノードを複数挟むことが出来ます。

ターゲット周波数の異なる複数ピーキングフィルタを通すことで、グライコ的な物が作れるだろうと予測をつけて調べて見たところ、似たようなやりとりがされているのを見つけました。これで行けそうな気がします。

操作する周波数は、冒頭にキャプチャを貼った iTunes のイコライザを参考に、 32kHz から 2倍ずつ上がっていく物にしました。

ホワイトノイズの発生と、可視化

この辺りは、参考にさせていただいた元記事様を参考になさってください

グラフのx軸を対数にする

イコライザーのバーは対数で並べているのに対し結果のグラフがリニアで表示されていると、低い周波数の左側の辺りが過密になって見にくかったので、x軸を対数表示と切り替えられるようにしました。

できあがりのルーティング

f:id:AMANE:20150124214154p:plain

(細かいところ)

縦向きのスライダーバーの作り方

さいごに

実装の詳細はコードをご覧下さい。

Web Audio API を使ってリッチな楽器を作っている作例なども勉強中に見かけました。自分もトライしてみたいですが、大変そうな予感。

前回今回試したように、入ってくる音に対してフィルタリングするような用途には、Web Audio APIはかなり答えてくれている印象を持ちました。今回も BiquadFilter を多段で挟んでも、特にパフォーマンスが悪くなったりはしていません。コードもかなりスッキリ書けるので良いですね。

積極的に使っていこうと思います。

マイク入力を WebAudio で加工して、ビデオと合流させた後 WebRTC で使う

はじめに

前の2記事ほど、ウェブカメラの映像を openFrameworks で加工してビデオデバイスに再度流し込むことで、WebRTC などで扱えるようにする方法について調査してきました。

音声も同様に加工して WebRTCで扱えるようにしたいなーと思いました。

完成品

f:id:AMANE:20150118110301p:plain

こちらで動かしています -> http://www.izmiz.me/mediaStreamMerging/

簡単に使い方
  • ウェブカムとマイクを持っている二台のPCで上記サイトにアクセスします
  • カメラとマイクの利用の許諾を問われるので、許可してください。
  • どちらかのPCで、相手の画面に表示されているIDを打ち込んで call すると、ウェブチャットが開始されます
  • Filter on にチェックして、 Frequency や Q値 を動かすと、相手側に伝える音声に ローパスがかかります
  • ハウリング注意。

検討した2手法

仮想サウンドドライバを使ってOSに流し込む方法 (不採用)

ycapture と同じ考え方で、作り上げた音声をOSの音声デバイスに再度流し込む手法を初めは検討しました。ドライバ相当のものを自力で書くのは面倒なので、有り物の組み合わせで何とか行きたいです。

と思って調べていたところ、ニコニコとかで実況をする方々が、自分の音声と音楽やら動画やらの音声を混ぜるのにOSのソフトウェアミキサー(Windowsのステレオミキサー)を使っていることを知りました。さらにその流れで、NETDUETTO というソフトに同梱されている仮想サウンドドライバを使うことで、コンピュータで再生している音声を音声デバイスとして再入力出来ることが分かりました。

となれば、oF なり Pure data なりで音声を加工してこの仕組みで音声デバイスに戻せば、WebRTCでも使えそうです。

f:id:AMANE:20150118104431p:plain

WebAudio と WebRTC を組み合わせる方法 (採用☆)

一方で、WebAudio API を使えば、jsの簡単なコードで音声にエフェクトを掛けられることも分かりました。ブラウザ内で完結するのもgoodです。

  • WebAudio APIの入力をマイクにする
  • WebAudio APIの出力を、mediaStream のビデオに合流させる

この2つが出来れば、目的は果たせます。そのためにちょいと調査をしました

こちらの記事様の時点では、AudioDestinationNode から mediaStream を引っこ抜くのは実装が不完全そう、という結果になっていました。しかし希望を持ったのでもう少し調べて見たところ、w3c の公式で「まさに」な内容が紹介されていました

The following examples illustrate WebRTC integration with the Web Audio API.

とのことです。このページの Example5 が、まさに今回の内容と同じになっています。図示すると下のような感じになります。これでいけそうです。

f:id:AMANE:20150118104440p:plain

今回は

ウェブカム映像へのエフェクトについては、パフォーマンスの観点から別アプリとの連携という形にしましたが、ちょっとWebAudio API を使ってみたところ、シンプルなフィルタリング等ならパフォーマンスに問題はなさそうでした。

システム構成もシンプルに出来るので、音声に関しては後者、ブラウザ内ですべてやる方式でトライしてみます。

音質

NETDUETTO の仮想デバイスをまたいで入ってきた音の音質が悪い印象もありました。これはもしかしたら私の設定の問題かもしれないのでここでは断言はしませんが、今のところ音質良くできている Web Audio API を利用する方向で行きます。

実装

映像のストリームも加える

最終的に「映像・音声共にエフェクトの掛けられるウェブチャットシステム」としたいので、上のExample 5に加えて、カメラ映像もmediaStreamに合流させる必要があります。

試行錯誤した結果、映像・音声で別々の mediaStream を作ったあと、片方をもう片方にマージ出来ることが分かりました。

// videoStream 映像のMediaStream
// audioDestNode WebAudio API の AudioDestinationNode

videoStream.addTrack(audioDestNode.stream.getAudioTracks()[0]);

// できあがったstream を、peerjs で使う

先の構成図にこの処理を加えて、最終的にはこんな感じになります

f:id:AMANE:20150118175140p:plain

完成品

今回は、下記二つのコードを合体させています

これに、上で述べた映像のストリームを追加する方法を加えて、音声加工可能なビデオチャットシステムとしています。

初めにも書きましたが、完成品はこちらで動いています。http://www.izmiz.me/mediaStreamMerging/

SkyWay サービス利用の注意点

上がっているコードには、SkyWayのAPI key が直打ちされています。この API key は私のドメイン(www.izmiz.me)からしか使えない物になっているので、ご自身で試される場合は、ご自身でSkyWayのアカウントを作成し、ご自身のドメインを適用して API key を取得してください。

さいごに

今回はWebRTC との組み合わせにフォーカスしてまとめました。

次はWebAudio API にフォーカスして、エフェクトの作り込みにトライしてみたいです。

CreateJS で背景前景ともに動かしながら曇りガラス的表現をする

はじめに

磨りガラスってこんなかんじ。

f:id:AMANE:20150113153912j:plain

photo by Johnny Jet

友人氏がそういうのをやりたい、と言っていたのを思い出してやってみました。

完成品はこちら

コード

あげてあります

環境

  • EaselJS 0.8.0

参考にしたもの

Cacheについて

DisplayObject.cache() は、オフスクリーンレンダリング用に document.createElement('canvas') して、そこにデータを書き込む操作で、そこに書き込んだ物をなんども使い回したり、できあがった画像全体にポストエフェクトを掛けたりするのに使います。

上記ブログでは、DisplayObject.cacheCanvas でオフスクリーンレンダされている画像にアクセスできることが説明されています。スーパーナイスです。

blurのかけ方、マスクの仕方

上で先に書いちゃいましたが、.cache() 関数でオフスクリーンレンダした結果に対して、ブラーをかけたりできます。

このサンプルではDisplayObject.maskcreatejs.Shape のオブジェクトを登録することで、Shapeの形状に切り抜いています。graphics.drawHOGE() で描ける単純な形状で抜きたい場合はこちら。

こちらの AlphaMaskFilter では、 フィルタになるアルファを持った絵を作り(最終的にcacheCanvasを入力に使うので、Shape等にかぎらない)、そのアルファチャネルを使って対象のDisplayObjectにマスクをかけます。アルファを持つ画像等をもとに複雑なマスキングをしたい場合はこちら。

最後に

今回やっていること図示するとこんな感じです。詳しくはコードをご参照ください。

f:id:AMANE:20150117214412p:plain

ブラー元の画像を作って cacheCanvas で引っこ抜くために 1キャッシュ, ブラーをかけてマスクで抜くためにもう1キャッシュ、計2キャッシュ使いそれらを毎フレーム更新するのでやや重いのが問題です。

どうにか改善したいのですが…

html から node.js を介して oF の画像処理をコントロールし、ブラウザ上で閲覧する

はじめに

前回の記事で ycapture を使って、oF の描画内容を Windows のビデオデバイスとして流すことで、ブラウザからも見ることが出来るようにする手法について紹介しました。

前回やったことを図でまとめるとこんな感じ(再掲)

f:id:AMANE:20150111151631p:plain

ブラウザからoFアプリをコントロールする

今回はそれを発展させて、HTML側からカメラエフェクトを変更・調整できるようにしてみます。

できあがりはこんな感じ。ブラウザから、ネイティブアプリ(oF)に指令を送って、エフェクトを調整しています。

完成時のシステムは以下の様になります。

f:id:AMANE:20150112093523p:plain

ブラウザで選んだエフェクトの種類や強度の情報をWebSocket で Node.js にわたし、 Node.js からは OSCでリレーしてエフェクトの種類の情報をoFに渡します

環境とつかったもの

  • Windows 7
  • Visual Studio Express 2012
  • openFrameworks v0.8.4
    • ofxOSC
    • ofxCv
    • ofxOpenCV
    • ycapture.dll
  • node.js v0.10.29
    • node-osc
    • express
    • socket.io

あげてあります

今回のコードは、 node.js(およびhtml) の部分と、 oFの部分で二つに分けてアップしてあります。 oFのプロジェクトは ycapture の存在を前提にしているので、前回の記事を参考に導入してみてください。

ycapture に関しては前回記事参照

ycapture をつかって画像処理の結果を windwos のビデオデバイスとして流すやり方については、前回から変わっていません。今回は、三つの登場人物間で増えた通信部についてメモをしておきます。

html - node.js 側

  • NORMAL(エフェクトなし)
  • CANNY (エッジ抽出)
  • BLUR (ぼかし)

ラジオボタンを選択すると、どれが選ばれたかをNode.js 側に伝えます。

また、CANNY と BLUR については、その効果の効きをレンジスライダーで調整してNode.js側に伝えます。

これらの通信には、エフェクトの効きをリアルタイムに反映させたかったので、WebSocket(Socket.io) を利用しています。Socket.io の使い方については特殊なことはしていないので、ソースコードをご参考下さい。

要素使いこなし

<p><label>THRESH1<input type="range" name="canny_thresh1" min ="1" max="400"></label></p>
// 1~400 の間を、1刻みで選択出来る。

<p><label>SIZE<input type="range" name="blur_size" min="1" max="201" step="2"></label></p>
// 1~201 の間を、2刻みで選択出来る

後者が特徴的です。ofxCv::blur は内部的に cv::GaussianBlur を呼んでいて、ガウシアンカーネルサイズは奇数で無くてはいけません。そこで、上記のような指定にすることで、<input> の出力を奇数に限定しています。

また、<input type="range"> 要素は、value が string となっているため、Socket.io で送り込む際に、number要素に変換しておきます。

document.cv_effect.blur_size.oninput = function() {
  // ドラッグ中も数値を取りたかったら oninputを使う
  // リリース時のみ取りたかったら onchange
  socket.emit('blur size', parseInt(this.value));
}

node.js - oF 側

node.js 側

Socket.io でブラウザから送られてくるメッセージをoscに載せ替えて送り出しています

  socket.on('blur size', function(value){
    oscclient.send('/blursize', value);
  });

oF 側

OSCのメッセージを受け取って、CVのエフェクトを切り替えたり、効果の強さを調整したりします

// ofApp.cpp

while(oscReceiver.hasWaitingMessages()){
  ofxOscMessage m;
  oscReceiver.getNextMessage(&m);

  std::string oscaddress = m.getAddress();

  if(oscaddress == "/changeeffect"){
    std::string typeStr = m.getArgAsString(0);
    if(typeStr == "non") {
      effectType = EffectType::NONE;
    } else if(typeStr == "canny" ) {
      effectType = EffectType::CANNY;
    } else if(typeStr == "blur" ) {
      effectType = EffectType::BLUR;
    } else {

    }
  } /*中略*/ else if(oscaddress == "/blursize") {
    blurSize = m.getArgAsInt32(0);
  }

  // このあと effectType や blurSize を使う
}

追記

本記事の投稿後、 凹先生から助言をいただきました。

ofxLibWebsockets 試してみたところ確かにお手軽に oF 上で WebSocket が扱えて、Node.js が不要になるので良いと思いました!

最後に

WebRTC を使ってビデオチャットにする

今回は扱っていませんが、今回の物に PeerJS (WebRTC)を加えると、画面エフェクトが可能なビデオチャットシステムが完成しますね。

他プラットフォーム

前回ご紹介した内容は、デバイスやOSの仕組みが関わるところだったので Windows 限定でしたが、今回ご紹介した内容は node.js と oFのaddonで出来るので、すべてのプラットフォームで実現可能です。 前回の記事で言う ycapture の仕事を linuxやMac OSX でやる手法についても、前回の記事の最後に調査だけしてあるので、それと組み合わせることで、他のプラットフォームでも同様の機能を実現できるのではと思います。

ycapture で OpenCV での処理結果を Windows のビデオソースとしてブラウザに流し込む

はじめに

WebRTC を使ってビデオチャットアプリを作る際に、ビデオ画像にエフェクトをかけたくなりました。モザイクとか、ブラーとか、漫画カメラとか。

ブラウザ上でJavaScriptでごりごり画像処理をするのはさすがに厳しそうですので、画像処理を行うのは別のアプリケーションで行うことにしました。

最終的に WebRTC で MediaStream として扱うことを考えると、画像処理の結果が通常のWebカメラと同様に navigator.getUserMedia() できれば一番手っ取り早いんじゃないかなーと思っていろいろ手法を探してみました。

便利なソフトを教えていただいた

しかし調べれば調べるほど、Windows上だと DirectShowのお勉強しなきゃとかつらそうな気配が漂っていたのですが、Twitterに泣きついたところ、@jnakano さんが ycapture(わいきゃぷちゃ) の存在を教えて下さいました。

ycapture(わいきゃぷちゃ)

説明から引用

ycaptureとは、WindowsのVideo Capture Sourceのような振る舞いをするDLL(の基本ソース)です。このDLLを使用すると、あたかもビデオキャプチャデバイスのように、任意のプログラムからビデオデータを供給することができます。

これだー!と言うことで、今回試してみました。開発者の谷沢さん、ありがとうございます。

開発の前に

今回やりたいこと

f:id:AMANE:20150111151631p:plain

  1. openFrameworks 上で、OpenCVを使ってカメラ画像を処理し、
  2. ycapture.dll からWindowsのビデオデバイスとして流します。
  3. ブラウザで navigator.getUserMedia() でストリームを取得します

注意

今回のアプローチがベストかどうかはケースバイケースだと思うので、手法の一つとしてご参考にしていただければと思います

環境

ycaptureのビルドは、

の2つの組み合わせで確認しています。openFrameworks に関しては、Visual Studio 版が 2012(v110) にしか対応していないため、今回のシステム全体としては、Visual Studio 2012 を利用しています。

ycapture の準備

公式に載っている利用法に従って下さい。公式ページでは VS2008で、と書いてありますが上記の通りの環境でもビルド & 利用可能でした。以下、ポイントだけメモっておきます。

Release か Debug か。ビルドの種類とパスの設定に注意。

Visual Studio は ライブラリ類のパスを通すのが面倒なので、私は Release ビルドに統一してやっています。Visual Studioのプロパティをいじる際は、毎回 Configuration の表示に気をつけましょう。

f:id:AMANE:20150111092217p:plain

Windows SDK を導入し、DirectShowの baseclasses をビルドして静的ライブラリを作る

Windows SDK アーカイブ – Windows デベロッパー センター

今回はこちらの Windows 7 および .NET Framework 3.5 SP1 用 Windows SDK を使っています。

baseclasses ライブラリのソリューションを Visual Studio 2012(2013) で開こうとすると、変換の際に大量の警告が出ますが、ビルドは出来ます。

配布されている ycapture プロジェクトのビルド準備

ycaptureプロジェクトのプロパティで、インクルードやリンクがうまくいくよう設定を変更します。先述の DirectShow/baseclasses の在処を教えてあげます(配布された状態だと、Windows SDKのバージョンが違ったりしています)

私の場合は以下の通り

  • 追加のインクルードディレクトリ
    C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\multimedia\directshow\baseclasses;

  • 追加のライブラリディレクトリ
    C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\multimedia\directshow\baseclasses\Release;

これでビルドが出来ます。

ycaptureclient と testclienet のビルド

この二つは、配布状態のままでビルド可能です。

testclientは、ソリューション内でycaptureclientに対してプロジェクトの依存関係が組まれているので、特にライブラリの場所とか気にしなくて大丈夫なようです。

ycapture.dll の登録

regsvr32.exe を使ってdllを登録する際は、 powershell/cmd を管理者権限で立ち上げないと怒られます。

>regsvr32.exe ycapture.dll

サンプルを試す

ycapture で配布されている testclient で、以下の流れが確認出来ます。

  1. testclient を起動します。
  2. 受け側として、ブラウザで https://simpl.info/getusermedia/sources/ を開きます。 navigator.getUserMedia のサンプルアプリです。
    受け側はSkypeなどのアプリでももちろん大丈夫です。
  3. 画面に、testclientが流している赤い帯が表示されれば成功です

f:id:AMANE:20150111151857p:plain

testclient が生成するカメラが見つからないときがある

ドライバの読み込み順の関係かキャッシュか正確なことは分かりませんが、作った仮想カメラがクライアントから見つからないことがあります。こういうときは、ページのリロードや、既に刺さっているウェブカムの抜き差しなどをすると、見つかるようになります。

ycapture を oF 内で利用する

ycapture を oF 内で利用する事で、oF での描画結果やCVを使った画像処理の結果を、仮想カメラデバイスとして流すことが出来るようになります。

プロジェクトの準備

ここでは Visual Studio での oF プロジェクトの生成等については割愛いたします。

以下の点をプロジェクトのプロパティに設定していきます

  • baseclasses/ycapture のビルドにあわせて、configuration を Release にする
  • 追加のインクルードディレクトリに、ycaptureとycaptureclient のソースコードがある場所を追加する
<ycaptureをおいた場所>\ycapture-src-0.1.1\ycapture\ycaptureclient
<ycaptureをおいた場所>\ycapture-src-0.1.1\ycapture\ycapture
  • 追加のライブラリディレクトリに、ycapture/ycaptureclientのビルド結果がある場所を追加する
<ycaptureをおいた場所>\ycapture-src-0.1.1\ycapture\Release
  • ライブラリの入力に、以下の物を追加する
ycapture.lib
ycaptureclient.lib

コード

ycapture さんで配布されているtestclient を参考に書いていきます。

// ofApp.h

// 前略
#include "ofxCv.h"
#include "CaptureSender.h"
#include "ycapture.h"

class ofApp : public ofBaseApp {

public:
  // 中略
  ofVideoGrabber cam;
  ofImage raw;
  ofImage blur;
  CaptureSender *sender;  // ycaptureのオブジェクト
};
// ofApp.cpp
int CAM_WIDTH = 640;
int CAM_HEIGHT = 480;
unsigned long avgTimePF = 1000 / 30;    // 30fps ストリームのタイミングを作るのに使うらしいが詳細不明

void ofApp:setup(){
  cam.initGrabber(CAM_WIDTH, CAM_HEIGHT); // カメラの初期化
  sender = new CaptureSender(CS_SHARED_PATH, CS_EVENT_WRITE, CS_EVENT_READ); // ycaptureのセットアップ
  // 後略
}

void ofApp::update(){
  cam.update();

  if(cam.isFrameNew()){
    raw.setFromPixels(cam.getPixels(), CAM_WIDTH, CAM_HEIGHT, OF_IMAGE_COLOR); // カメラ画像をコピー
    raw.mirror(1, 0); // そのまま送るとブラウザ側で垂直方向に反転する
  
    ofxCv::blur(raw, blur, 51); // ブラーをかけてみるテスト
    HRESULT hr = sender->Send(counter * avgTimePF, CAM_WIDTH, CAM_HEIGHT, blur.getPixels() ); //★★ここがポイント★★
    // 後略
  }
}

void ofApp::draw() {
  cam.draw(0, 0);
}

要するに、何かしらの方法で画像を作った後、 RGBの順の unsigned char 配列にして、sender->Send() すれば良いようです。oFやCVには、RGBの順の unsigned char 配列 への変換関数が用意されているので楽ちんです。

さいごに

ウェブブラウザ上で、oFで処理した結果の画像が見られれば完成です。oFのウィンドウに表示されているカメラのスルー画と並べて見ても、遅れは感じられません。良い感じ。

f:id:AMANE:20150111104337j:plain

次は、せっかくブラウザ上でやっているので、ブラウザでのユーザの入力を受け付けて、エフェクトを変更したりする仕組みを作ってみようと思います。

他プラットフォーム

Linuxの場合

余り詳しくないので間違いがあるといけないのですが、、Linux環境だと、gstreamer や V4L2loopback といったモジュールを使うと、割と簡単に動画ストリームをビデオデバイスとして取り扱うことが出来るようです。実際、openFrameworksの addons に arturoc/ofxGstV4L2Sink · GitHub と言う物があって、リポジトリのREADMEによれば、

Uses v4l2sink gst element to create a virtual device which can be used as a camera in any application that supports v4l2

とのことで、今回狙っていることが出来そうです。ただし私は試していません。

Mac OSX の場合

rsodre/ofxFakam · GitHub と言う物があり、これも

ofxFakam is an Open Frameworks addon that streams screen data to a fake camera driver.

ということで、狙っていることが出来そうです。ただし私は試していません。

FFmpegで WebM の半透過動画を作成し、Chromeで再生する

はじめに

f:id:AMANE:20141216172917j:plain

ウェブアプリで画面全体にわたるようなリッチなエフェクトを掛ける手段のひとつとして、デザイナさんに作ってもらう動画(たとえばAfter Effectsで)をそのまま使う、なんてことは出来ないかと考えてトライしてみました。

HTML要素の上に半透過の動画を <video> 要素で置くことで、もとのHTMLを動画で演出、みたいなことができています。

f:id:AMANE:20141216134814p:plain

できあがった物はこちらで確認出来ます

ブラウザは Chrome しか対応しておりません!ご注意。

環境

ちなみに、WebM を透過して表示できるのは、PC/MacChrome だけのようです。悪しからず。

Firefox@Mac だと、アルファチャンネルが捨てられて、全画面カラフルに塗りつぶされます

Safari@Mac だと、そもそも再生されません

元ネタ

今回の記事は、こちらの記事を足がかりに、追加でいろいろ調べてまとめた物になります。

動画の作り方

普段からデザイナさんに After Effects で動画を作ってもらっているので、この記事では After Effects を使ってにアルファ付き WebM 動画を作ってみます。

もしかしたら最新の After Effects では既にアルファ付き WebM の書き出しに対応しているかもしれませんが、私が使える 5.5 では出来なかったので、エンコードには FFmpeg に梱包されている vp8 (libvpx) を利用します。

f:id:AMANE:20141216152454p:plain

After Effectsコンポジション作成

新規プロジェクトから普通にコンポジションを作成し、そこに要素・アニメーションを加えていきます。半透過のテストをしたかったので、アルファが変わったり、淡いグラデーションがかかっているような動画を作ろうと思い、今回はこちらの記事を参考にサンプルを作ってみました

WebM は 60fps にも対応しているので、コンポジションのフレームレート設定を 60fpsにしました。

背景はデフォルトの黒です。

f:id:AMANE:20141216173304j:plain

After Effects でアルファ付き QuickTime Movie 書き出し

FFmpegでWebMにエンコードする元になる、アルファ付きの動画を出力します。AVI と QuickTime形式がアルファ付きに対応しているようですが、AVIだと尋常じゃない容量になるので、QuickTimeで書き出しますことにします。

対象のコンポジションを選択した状態で コンポジション > レンダーキューに追加 を選ぶと、レンダーキューウィンドウにひとつ、レンダリング処理が追加されます。

そのレンダーキューの "出力モジュール" を選択して、下記のような設定にします。ポイントは以下の3点です

  • 形式: QuickTime
  • チャンネル: RGB + アルファ
  • カラー: ストレート(マットなし)

f:id:AMANE:20141216142307j:plain

カラーを「合成チャンネル(マットあり)」 にすると、アルファチャンネルだけで無く、RGBのレイヤーも調整された動画が描き出されます。 出力された MOV ファイル単体を QuickTime で再生するときはこれで良いのですが、WebM にエンコードして他の画面に合成した際に、色味が暗い感じになってしまいます。

ストレートアルファとプリマルチプライド・アルファ(合成チャンネル)の違いは、こちらのページが詳しいです

あとは、出力先で適当に場所を決め、ファイル名を決めてレンダリングを行います。

FFmpeg で WebM にエンコード

インストール

FFmpegは別途インストールしておいてください。 Windowsの場合は、binaryをダウンロードしてきて、どこか適当な場所に解凍し、システム環境変数の path に、ffmpeg/bin のパスを通すと、コマンドプロンプト/PowerShell からコマンドをたたけるようになります。

エンコード

先ほど出力した mov ファイルのあるディレクトリに移動後、以下のコマンドでエンコードを行います。

ffmpeg -i webmtest.mov -crf 10 -b:v 1M webmtest.webm

オプションはいまいちよく理解出来ていないのですが、下記サイト様に説明が載っています。要は、オプション指定しないと結構画質が悪くなるので、VBR(可変ビットレート) の目標値 (1Mbits/s) と、VBRする際の最低品質保障 (4-63 での 10) を指定してね、という感じみたいです。

これでアルファ付きの WebM 動画ファイルが書き出されました。このファイルは、 Chrome にぽいっと投げ込むと、その場で再生してもらえます。が、背景がとくにないので真っ黒に表示され、このままでは透過できているのか分かりません。

f:id:AMANE:20141216173420j:plain

再生

というわけで、背景と適当なHTML要素を加えた上に透過動画をおいてみたのが、一番初めの作例になります。

再掲 → webm transparent test

画質優先で連番.tga を使う場合

AfterEffects から QuickTime(MOV) で書き出した時点で、圧縮率を最低にしたとしても、最終的なWebMにたどり着く前に何かしらの圧縮が入ってしまっていることになります。画質最高とは言えません。

そこで、一度無圧縮のアルファチャネル付き連番画像を書き出し、それから アルファ付き WebM をエンコードする、というパスを取ることもできます。

f:id:AMANE:20141216152504p:plain

連番 TGA 書き出し

新しいレンダリングキューを作成し、出力モジュールを以下の様に設定します。

  • 形式:Targa シーケンス
  • チャンネル: RGB + アルファ
  • カラー: ストレート(マットなし)
  • 形式オプション
    • 解像度 32bits/pixel (アルファチャネル付き)
    • 圧縮はなし!

その上でレンダリングすると、だだーっとTGAファイルが書き出されます。1920x1080、32bit ですと 1枚 8MB ほどもあるのでかなりつらい感じになります。。。

f:id:AMANE:20141216150852j:plain

連番 TGA から WebM にエンコードする

入力を、 MOV から 連番tga に変えるだけです。

ffmpeg -r 60 -i webmtest_%05d.tga -crf 10 -b:v 1M webmtest.webm

tga ファイルのサイズは膨大になりましたが、vp8 としては同じ圧縮を行うので、最終的に出てくる WebM ファイルの容量は、MOV を経由した場合と同程度になります。

さいごに

Chrome 限定な上、 WebM ってぶっちゃけそんなに流行っている気配も無いので、この手法もこれといって使えるシーンが無い可能性は大きいと思います。

けどせっかくちゃんと出来るようになったので、まとめておきました。ちゃんちゃん。

(タブレットで) 鏡餅を光らせて新年を迎えましょう | 実装編

はじめに

前回は、おうちハック Advent Calendar 2014 向けとして記事を書きました。そちらでは全体の紹介と、取り組んだ背景みたいなところにフォーカスして書いたので、今回は自分向けに実装で手こずったところ等をメモしていきます。自分用のメモなので雑多でごめんなさい。

目次

  • socket.io でルーム管理。おもち側が閉じたら、繋いでいるコントローラに伝達する仕組みなど
  • 様々なブラウザ・解像度への対応 (軽めに…
  • さくらのVPSに Node.js アプリをデプロイする際のTips
  • そのほか細かい技

おさらいのコード

記事中にコードを展開していくとバカ長くなりそうなので、言及場所にリンクを貼るスタイル。

おさらいの動画

おさらいのシステム図

f:id:AMANE:20141225104949p:plain

socket.io でルーム管理。おもち側が閉じたら、繋いでいるコントローラに伝達する仕組みなど

前回の記事でもちらっと書きましたが、今回の案件、本来なら(おそらく) WebRTC を使ってやるべき案件です。光る端末に対し、リモコンが直接指示を出します。基本は1対1で、始まった後は間にサーバが挟まっている必要はありません。

しかし今回は、自分の不勉強のため短いスパンで WebRTC を使いこなせそうになかったので、使ったことのあるsocket.io を使い、間にサーバが挟まるシステムにしました。そのため、利用者が増えると明らかに不利です。アドベントカレンダー向けのネタアプリですので勘弁してください。

そんな中でも一応こだわって頑張った辺りについてメモしておきます。

room名によるおもちの重複防止

おもちとコントローラの対応付けは、socket.ioが備えているroomの仕組みを使って行っています。roomは一般的なチャットルームを実装するための基本機能なのですが、

  • 存在しないルーム名でjoinしたら、新たにルームが作られる
  • 既存のルーム名でjoinしたら、既存のルームに入る

という動作になっております。

OMOCHI HACK ではこの仕組みを利用しつつ「おもちは必ずオリジナルな名前のルームとして作って、そこにコントローラが名指しでjoinする」仕組みとし、おもちの名前は重複を許さないことにしました。

  • おもち側で存在しないルーム名で入ろうとしたら、joinする → 新たにルームが作られる
  • おもち側で既存のルーム名で入ろうとしたら、既に使われている名前であると警告を出し、再入力を促す

この辺りの処理は、 server.js L62 - L112 あたり にコーディングしてあります。socket.ioのルーム管理とは別に、独自に現存するおもちのリストを管理することで実現しています。

おもち死亡のおしらせをコントローラ側に通知する

通常利用だと、おもち側が閉じるケースとして以下の様な物があります

  • ページをリロードする
  • ブラウザの戻るボタンを利用するなどページ遷移を行う
  • タブを閉じる
  • スマホを放置してしまい画面がロックされる
  • スマホで別のアプリを開く
  • 別の名前のおもちを作り直す

これらを検出したうえで、以下の事をやらなければなりません

  • ルーム(おもち)をリストから削除する(利用された名前がたまってしまわないよう)
  • おもちが消えたことをコントローラに通知する
window.onpagehide を利用する
  • ページをリロードする
  • ブラウザの戻るボタンを利用するなどページ遷移を行う
  • タブを閉じる

この3パタンについては、window.onpagehide で検出できます。はじめ window.onbeforeunload で書いていたのですが、Safari@iOS8 ではこのイベントが使われなくなって居ることを知り、pagehide に書き換えています。

この辺りの処理は、light.js L133 あたり にコーディングしてあります

socket.io の disconnect イベントを検出する
  • スマホを放置してしまい画面がロックされる
  • スマホで別のアプリを開く

この2パタンの場合、私が調べた限りでは ウェブアプリがそれを検出して何かする、というのが出来ませんでした。しかし、socket.io の方で、クライアント(ここではおもち)が不活性な状態を検出してdisconnect イベントとして処理する仕組みがあるようで、それを利用する事にしました。

具体的には、disconnectイベントを送ってきたsocketのidを調べて、それがおもちリスト(コード内では lightlist )に存在するか調べ、していたら削除する、と言う物です

この辺りの処理は server.js L49 - L55 あたりにコーディングしています

イベントを拾えるケース
  • 別の名前のおもちを作り直す

最後のパタンは、サーバ側で処理しています。自分が既におもちとしてroom名を持っていて、それと同じ名前で作ろうとした場合は、何もせず既存のroomを利用します。新しい名前を指定した場合は、さっきまで使っていたroomを破棄し、新しいroomを作ります。

この辺りの処理は、 server.js L105 - L109 あたりでコーディングしています。

おもちが消えたことをコントローラに通知する

上記パタンでおもちが消えたとき、コントローラ側は、カラーコントロールのUIがdisableされ、再度おもちの名前入力を要求される画面になります。これで、接続が切れたことをコントローラ側からも認識出来ます。この辺りの処理は sever.js L138controller.js L123 にコーディングしてあります。

f:id:AMANE:20141226202130p:plain

様々なブラウザ・解像度への対応 (軽めに…

ちまたのウェブデザイナ、ウェブコーダ様方にあたられましては、日々多様化するディスプレイの解像度や、ブラウザごとの微妙に異なるCSSの解釈に日々頭を悩ませていることと存じ上げますが、私も今回のアプリを作り上げるに当たりまして、「出来る範囲で・時間が許す範囲で」広めに対応を取ってみたところです

動作確認しているのは以下の環境

です。すべて最新の物です。

レスポンシブ的CSS切り分け

  • @media screen and (min-width: 320px) and (max-width: 480px) 的な物を使う

vendor prefix

-webkit-transform -moz-transform とか。

ブラウザごとの対応状況を調べられる。おなじ webkit でも、Chromeではつかえて、 safari@iOS じゃ使えない、とかかなりよくある。

Chrome for Androidborder-radius が微妙に効かない件

コントローラの円形のカラーピッカーを描くのに <div> に対して四隅50% でradius をかけようとしたが、Androidでだけうまくいかない。

f:id:AMANE:20141225220038j:plain

ここを参考に修正

.selector {
/* だめ */
border-radius: 50%;

/* だめ */
border-top-left-radius: 50%;
border-top-right-radius: 50%;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;

/* OK */
border-top-left-radius: 50%;
border-top-right-radius: 50%;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 49.9%; /* ここがポイント */

/* これでもOK */
border-radius: 50% 50% 50% 49.9%;
}

この記事では Galaxy S4 で、 と言っている。私の場合は Nexus7 (2013)。機種依存かもしれない。

さくらのVPSに Node.js アプリをデプロイする際のTips

基本

forever さまさまです。

この記事では /etc/nginx/conf.d/ 内にリバースプロキシの設定を書いていますが、

  1. /etc/nginx/sites-available/ 内に今回のアプリ用の新しい設定ファイルを作っておき
  2. /etc/nginx/sites-enabled/シンボリックリンク しておくのがいまどき、なはず

linux - What is the different usages for sites-available vs the conf.d directory for nginx - Server Fault

自分が作ったサイトの設定ファイルはこんな感じ

# /etc/nginx/sites-available/omochi_hack

upstream omochi {
    server localhost:3000;
}

server {
    listen 80; 
    server_name omochi.izmiz.me;

    proxy_redirect                          off;
    proxy_set_header Host                   $host;
    proxy_set_header X-Real-IP              $remote_addr;
    proxy_set_header X-Forwarded-Host       $host;
    proxy_set_header X-Forwarded-Server     $host;
    proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://omochi/;
    }
}

VPSのポート設定

そういえば以前iptablesで厳しくポートを絞っていたのを忘れていました。express が ポート3000番でサーバをあげているので、あけてあげなくちゃいけません。

sudo ufw allow 3000/tcp

おなまえ.com のDNS設定

リバースプロキシに設定した omochi.izmiz.me がちゃんと解決出来るよう、DNSに覚えていただきます

おなまえ.com > ログイン > 今回のドメイン > (タブ)ドメイン設定 > DNS関連機能設定 > 今回のドメインを選んで「次へ進む」 > DNSレコード設定

ログインした状態で直リンク > https://www.onamae.com/domain/navi/dns_controll/input

普通にたどるととんでもない階層にいます。ほとんどここしか使わないのに…!

ここに、 nginx の sites-availabe に設定した server_name を登録します。

以上

以上で、 http://omochi.izmiz.me にアクセスすると、 さくらVPS内の expressが立てている 3000 番ポートで動いている OMOCHI HACK アプリにアクセス出来るようになりました。

そのほか細かい技

今回のアプリで参考にさせていただいたその他のノウハウをまとめておきます

Canvas + image でカラーピッカー

letter-spacing したテキストをセンタリングする

index.html L25 - L32

letter-spacing: 1em すると、文末にも1文字スペースが空いてしまうので、その分文末に負のマージンを与えるか、文の頭に正のマージンを与える

ブラウザの「ホーム画面に追加」を利用する

これをしてホーム画面からアイコンタップで起動すると、全画面でアプリが開きます。ここで登録されるアイコンについての覚え書き

log4js でログ取り

ロギングのセッティングを json で外部化出来るのが素敵。

内で1行のテキストを垂直方向にセンタリング

.selector {
  height: 100px;
  line-height: 100px; // heightline-height に同じ値を指定する
}

JavaScriptHSV -> RGB 変換

jQuery.delay() が便利

jQuery .val() で form の値を取得

jQueryセレクターを取得

$something.selector で取得 出来るのだけど、 deprecated. どうしましょう。今回使ってしまいました。

カラーホイールの絵の描き方

さいごに

今回、Advent Calendar締切ドリブンで、UI、通信、サーバサイド、サーバへのアプリのデプロイ と1人で通貫でやってみました。できあがった物はまぁしょぼいのですが、個人的には非常に大変でした。良い経験になりました。