HTML5 Videoの対話的実行ツールを作ってみた。

新しいことを覚えて自分のものにする最も確実な方法は、自分の手を動かして実際に確かめることと思っています。新しいプログラミング言語を覚えるときも、ただ単に本を読んでいるだけではなかなか身につきませんが、実際にキーボードを叩いて結果を確認するだけでも、すごく身近に感じられるようになります。恐らく、実行してすぐ結果を確認できるということが学習効果を高めるのでしょう。

今回、HTML5のビデオ要素について、対話的にJavaScriptコマンドを実行して結果を確認できるようなものを作ってみました。画面イメージは下のようなものです。左側にJavaScriptのコードを入力できるshellもどきの領域があり、右側にビデオとその下にいくつかのボタンがあります。

JavaScriptの入力領域はbashライクなコマンド編集機能をつけました。キーバインドemacsと同じです。Control-A,B,D,E,F,H,J,K,L,N,P,T,U,W,Yが使えます。ブラウザによってはpreventDefault()してもControlキーがそちらに取られてしまうものもあるようです。例えば、ChromeではC-nで別ウィンドウが開いてしまいます。その場合には、代わりに方向キーのDownが使えます。

入力した文字はJavaScriptでeval()され、結果が表示されます。videoという変数に右側に表示しているvideoオブジェクトがバインドされています。videoをいちいち入力しなくても済むように with (video) { eval(input) } がトップレベルになります。

ビデオの再生中の実行例を示します。

js> currentTime                                 // 現在の再生時刻
17.684701
js> pause()                                     // 一時停止
undefined
js> muted = true                                // 音声ミュート
true
js> currentTime = 30                            // 30秒の位置へジャンプ
30
js> src = "http://server/video.ogg"             // 指定したURLのビデオを再生
http://server/video.ogg

上記ではビデオ要素から投げられてくるイベントのログは省略しましたが、イベントも表示されます。

js> currentTime=20
20
Event(13:26:56.322): seeking
Event(13:26:56.894): suspend
Event(13:26:56.959): seeked
js>

currentTimeを20に設定して、先頭から20秒の位置にシークしようとすると、シーク開始にseekingが、シーク終了後にseekedイベントが発行されます。Eventの右の括弧の中の文字はイベント発生のtimeStampです(Firefoxは値がちょっと変)。ネットワークが非常の遅かったりすると、途中にsuspendイベントが複数発行されるようです。


イベントはW3Cでは以下のものが定義されています。一応、これらのイベントは全部拾えるようにしています。

abort              canplay          canplaythrough    cuechange
durationchange     emptied          ended             enter
error              exit             load              loadeddata
loadedmetadata     loadstart        pause             play
playing            progress         ratechange        seeked
seeking            stalled          suspend           timeupdate
volumechange       waiting 

このうち、progressとtimeupdateは再生中に頻繁に発生するため、shell領域には表示しないで、右側の再生時刻表示と、Progressカウンタに表示しています。

ビデオのsrc属性にURLをセットすると、別のビデオを再生することができますが、そのときには以下のようなイベントが発生することがわかります。どのイベントがどのような順番で発生するかはブラウザやビデオのコンテナ/コーデックによって多少異なるようです。

js> src="http://some.server/any/video.webm"
http://some.server/any/video.webm
Event(23:56:02.238): abort
Event(23:56:02.247): emptied
Event(23:56:02.251): loadstart
Event(23:56:02.372): durationchange
Event(23:56:02.372): loadedmetadata
Event(23:56:02.372): loadeddata
Event(23:56:02.372): canplay
Event(23:56:02.372): canplaythrough
Event(23:56:02.372): play
Event(23:56:02.372): playing
js>

一方、load, enter, exit, cuechangeはTextTrack(字幕など)に関するイベントですが、実装しているブラウザはまだないようです。

右下のplay(), pause()などのボタンは、コマンド入力と同じことをしているだけのものです。step()はコマ送りに近い動作をするもので、以下の関数です。

function step(tm) {
  tm = tm || 0.3;
  video.pause()
  var curr = video.currentTime + tm;
  video.currentTime = curr;
  return curr;
}

つまり、ポーズして引数のtm秒(デフォルトは0.3)だけ進めます。

Fast/Slow/Normalはそれぞれ、10倍速、0.3倍速、通常再生になります。playbackRate属性にそれぞれの値を代入することで実現できます。ただし、Chrome, Firefox, Operaで確認したところ、実際に再生速度が変わったのはChromeだけでした。

また、videoオブジェクトのプロパティの値を全て表示するdump()という関数も組み込んであります。この出力結果を見ると分かりますが、playedやseekableなどのTimeRangeが実装されているのもChromeだけでした。

どのフォーマットに対応しているかの確認も対話的にできるので便利です。以下はFirefoxの例です。

js> canPlayType("video/webm")
probably                                       // WebMは再生できる
js> canPlayType("video/ogg")
maybe                             // Oggは再生できるかも
js> canPlayType("video/ogg;codecs=theora")
probably                                       // OggでコーデックがTheoraなら再生できる
js> canPlayType("video/mp4")
                                               // MP4は再生できない
js>


実際の動作とソースコードはこちらからどうぞ。

キャプチャなどセキュリティに関するAPIは使っていないので、HTMLファイルとJavaScriptファイルをローカルにダウンロードして適当にビデオファイルを用意すれば、サーバを使わなくてもfile://で利用できます。ボタン[Video 1]〜[Video 5]に対するビデオのURLはJavaScript中で_$video1〜_$video5の変数に代入しているので、そこを変更すればお好みのビデオで動作を確認することができます。

HTML5でビデオのリアルタイムヒストグラムを作ってみた

デジカメを使っていると、撮影直後にヒストグラムを見て黒潰れや白飛びなどをチェックする癖がちょっとは身についたが、デジカメはもちろんのこと、今時のテレビにもヒストグラムをリアルタイムで表示する機能が付いているものもある。今回、HTML5のvideoタグの勉強がてら、再生中のビデオのヒストグラムをリアルタイムで表示するJavaScriptコードを書いてみた。

リアルタイムといってもJavaScriptのパフォーマンスに依存するので全部のフレームがヒストグラム表示されるとは限らない。計算自体は単純だが、画素数が多いとそれなりに負荷がかかる。ちなみに、某社のテレビでヒストグラム表示機能があるので確認したら、ゆったりとした動きだった。

HTML5のビデオからそのヒストグラムを生成する方法はとても簡単だ。まず、再生中のビデオのピクセル情報を取得するため、一旦、canvas上にdrawImage()でビデオフレームを描画する。そして、そのcanvas上のデータをgetImageData()でRGBAのピクセル情報を取得すればよい。このcanvasは画面上に表示する必要はないので、styleのdisplayプロパティをnoneに設定しておく。

その際、canvas上にビデオをdrawImage()するときに縮小しておくのがポイント。その後のJavaScriptによるヒストグラム計算も軽くなる。フルHDビデオのピクセルを全部JavaSccriptでスキャンするのはさすがに重い。縮小したことによるデータ欠損はあるが、ヒストグラムが大きく変わることはないだろう。

RGBそれぞれのヒストグラム表示とモノクロにしたときの輝度のヒストグラムを表示できるようにした。下記はRGBと輝度を同時に重ねて表示したときの様子。ちょと見づらいが、リアルタイムでヒストグラムがうねうね動くのを見るのはなかなか面白い。ヒストグラム自身はvideo上に重ねたcanvasに描画している。

RGBをそれぞれ分離して表示したときは下のようになる。

輝度はRGBからYUV変換のY成分を利用している。RGBからY成分を求めるのはいろいろ方法があるようだが、ここでは以下の式を用いた。

Y = 0.298912 * R + 0.586611 * G + 0.114478 * B

黄色で表示したものが輝度に相当する。上とシーンを合わせればよかったと今思うが面倒なのでこのままにしておく。

下は実際にY成分だけを画像にしてcanvasに表示したもの。これもリアルタイムでビデオに合わせて表示される。


ヒストグラムの表示計算周期は、setInterval()でdurationを1に設定。サンプルで使ったビデオは24fpsなので、必要以上の更新をしないように、ビデオのcurrentTimeと前回計算した時刻を比較して同じならば何もしないようにしている。それなりに高速なPCでは320x240程度の画素ならば軽くフレームレートより高速に計算できるようだ。

CPU使用率をパフォーマンスモニタで見たところ、単純にビデオを再生しているときと比較して、ヒストグラムを表示しているときは、負荷が少しは高くなるが、思ったほどではなかった。

Firefox4.0でビデオだけを単純に再生したときの様子。僕の手元のノートPC(Dynabook RX3; Intel Core i5 2.67GHz)ではだいたい、こんな感じで落ち着いている。

ヒストグラムを表示しているときの様子。多少上がった程度。

Chromeでもだいたい同じだったが、多少Chromeの方がCPU usageが高くなっていた。いずれにしても、CPUが遊んでいるので、実は、ヒストグラムの計算をもっと軽く高速にできるようにWebWorkerを使って実行するコードも書いたけど、ビデオのカクカクして、まともに見れない再生となってしまった。僕の使い方がいけないのだと思うがよくわからない。改めてWebWorkerの振る舞いについて詳細に調査してみる積り。

実際の動作とJavaScriptコードは以下のURLから見ることができます。ビデオはよく知られたクリエティブ・コモンズのも。解像度はSintelは1024x436、Big Buck Bunnyは854x480。どちらもOggフォーマット。Firefox4.0とChrome10.0.648.204でしか動作確認していない。また、遅いPCや環境だと再生までちょっと時間がかかるようだ。YouTubeフルHDがサクサク再生できないようなPCだと厳しいかもしれない。

再生までにかなりの時間がかかるが、もしかしたら、ストリーミング再生に適していないエンコードをしているのかも。再生中にネットワークを切断したところ、FirefoxChromeもかなりのところまで再生が継続した。キャッシュに十分ためないと再生できないということか?しかも、Chromeはエラーがでてタブが死んでしまった。調べてると面白いものがありそうだ。

JavaScriptAPIを使ってvideoやcanvasでビデオのアプリケーションを作るのはそれほど難しいことじゃないけれど、はやり、ビデオのコンテナやコーデックについてちゃんと理解しておいた方がよさそうだ。Sintelのビデオの方には字幕が六ヶ国語も入っている。VLCメディアプレイヤーでは字幕も表示されるが、HTML5のvideoタグでは表示できるのだろうか。複数トラックもどこまで対応しているのか。勉強すべきことはたくさんある。

スーパーフルムーンを手持ちカメラで撮ってみた

正確には1日ばかり過ぎてしまっているが、確かに肉眼で見ても大きいことがわかる。
Nikon D7000とタムロンの18-270mm Di II VC PZDレンズの270mmで三脚を使わずVR(手振れ防止)を有効にして手持ちで撮ってみた。270mmでも解像度が高いため、等倍にするとこんなに大きく写る。手持ちでも殆んどブレていない。VRの威力は絶大だ(シャッター速度が1/800と高速なのもあるか?)。

データ:2011/3/20 21:40, F6.3, ISO 125, 1/800秒; レタッチ:輪郭強調、D-ライティング

Super Full Moon (NASAによる解説)

もう3月だ。

気がつくと、もう2月も終わり。今月はずっと忙しくてブログの更新をしていなかった。欠損月があると「記事一覧」で表示が揃わなくなって気持ち悪く感じるので、とにかく更新。

タムロンのレンズ18-270mm Di II VC PZDを一ヶ月前に買ったけど、まだ2日分くらいしか使っていない。コンパクトで軽くて、値段はそれなりだけど、レンズ交換が不要なのでお散歩カメラとしてはちょうどいい。

HTML5のvideoタグを使ってちょっと遊んでみたけれど、動作が安定していない。デモ程度には使えるけど、本格的に使うにはまだまだといったところか。

天体写真でアナログなネガの頃からコンポジットはあったけど、デジタルではRegiStaxという定番ソフトがあることを知った。アイピースに付けられるレンズのないCCDカメラで撮ったAVIビデオをコンポジットして1枚の絵を生成する。ビデオなので数百枚の写真をコンポジットすることが簡単にできるらしい。「RB星のブログ」にはそんな木星土星のマニアックな写真が沢山ある。一種の超解像だけど、天体写真以外にも使えないだろうか。今度実験してみよう。

あぁ、もうこんな時間だ。寝なくちゃ。

吾妻山公園の菜の花

関東地方はここ1週間、毎日雲ひとつないくらいの晴天が続いた。風は冷たいものの、顔を太陽に向けて目を閉じていると陽射に春の暖かさを感じるほどだ。

先日、TVの天気予報を見ていたら、吾妻山公園の菜の花が満開との話をしていたので、昨日(1/22)、Nikon D7000を持って出かけてきた。起きたのが10時過ぎと寝坊してしまったため、着いたのはちょうどお昼ころ。思った以上に大勢の人々。テレビで紹介するくらいだから、そうりゃそうだろう。あいにく富士山は雲の中で見えなかった。午前中早い時間帯は見えていたらしい。

風もない穏やかな天気で、菜の花を見ながらひなたぼっこするように、のんびりした時間を過ごした。菜の花を近くでじっくりと見ると全身本当に黄色一色だ。

3時半くらいに、もう帰ろうと山を下り、階段のところまできたが、これから登ってくる人もいるため、せっかくだから夕日を見ようと、また引き返した。この公園、5時には閉鎖(入り口にロープが張られる)されるということで、日没時まで居る人は少ない。ほとんどが撮影が目的で残っている。昼間は雲に隠れて見えなかった富士山も見えてきた。

RAWからの現像時にシャドーを持ち上げて、アクティブD-ライティングを多少つけたら、下のようになった。元の画像では菜の花はほとんど見えない。

写真を見ていて、「菜の花畑に 入日薄れ 見渡す山の端 霞深し」の(中島美嘉の)朧月夜のメロディーが浮かんできたが、季節はもっと後のことだろうが、あのような情景的な写真もいつか撮れるようになりたい。

日没を過ぎて、あたりが暗くなってくるとさすがに人もいなくなる。小田原方面の街明かりは見えるが、街灯が一切ないので辺りはすぐに真っ暗闇になるだろう。

懐中電灯も持っていないので、もう帰らないとと三脚をたたんで坂を下り出して、後ろを振り向いたら、いい感じの色の空に木のシュルエット。これは撮らないともったいない、と思い急いで三脚を出して撮ったのが下記。現像時にホワイトバランスで色を変えているが、こういう雰囲気の写真は好きだ。星空であればもっと良い。

次回は桜の季節にまた来ようと思う。

HTML5 LogoをCanvasで描いてみた


かっこ良いんだか悪いんだか、ちょっとわからないけど、HTML5のロゴができたということなので、ちょっと遊んでみた。CCライセンスで提供されている。SVGデータもあるので、中身を見たところ、これならすぐにCavnasでも描けそうだと思い立ったので、帰宅電車の中でコードを書いてみた(はい、お気楽モードです)。


polygonは下記のように頂点の並びpointsと塗りつぶす色を指定しているだけ。

<polygon fill="hsl(13, 77%, 52%)"
         points="107.644,470.877 74.633,100.62 437.367,100.62 404.321,470.819 255.778,512 "/>

pointsを正規表現でパースするのが実装が簡単。一度に全部分解するのもいいけど、gオプションで有効なlastIndex機構を使って前から順番にパースする。以下のようなコードになる。forの使い方にちょっと抵抗を感じる人もいるかもしれない。

var pat = /(-?\d+(\.\d+)?)\s*,\s*(-?\d+(\.\d+)?)/g;
g.beginPath();
for (var p, head = true; (p = pat.exec(points)); ) {
   var x = Number(p[1]);
   var y = Number(p[3]);
   if (head) {
     g.moveTo(x, y);
     head = false;
   ] else {
     g.lineTo(x, y);
   }
}
g.close();
g.fill();


次に、pathは下記のように相対/絶対座標と垂直/水平直線だけを使っている。大文字は絶対座標で、小文字が相対座標。Mは始点(moveTo), Hは水平直線、Vは垂直直線。

<path d="M108.382,0h23.077v22.8h21.11V0h23.078v69.044H152.57v-23.12h-21.11v23.12h-23.077V0z"/>

polygonの正規表現よりちょっと複雑だけど、大したことはない。Canvasには相対位置指定のlineToがないので、現在の(x, y)座標を変数に保持している。以下のようなコードになる。

var pat = /([MLVHZ])(\s*(-?\d+(\.\d+)?)(,|\s+)?(-?\d+(\.\d+)?)?)?/ig;
var x = 0;
var y = 0;
g.beginPath();
for (var p; (p = pat.exec(path)); ) {
  switch (p[1]) {
  case 'M':
    x = Number(p[3]);
    y = Number(p[6]);
    g.moveTo(x, y);
    break;
  case 'v':
    y += Number(p[3]);
    g.lineTo(x, y);
    break;
  case 'H':
    x = Number(p[3]);
    g.lineTo(x, y);
    break;
      :
     (略)
      :
  case 'z':
    g.closePath();
    break;
  }
}
g.fill();



こうしてできたのが、左の画面。単に表示してもつまらないので、くるくる回転伸縮アニメーションをつけた。

コードと実際の動作は、こちらからどうぞ:

気晴らしにSVGそのものからCanvasに描画するコードを書くのも面白いかもしれない。

Have fun!

Nikon D7000で撮った写真をいくつか

昨年末に買ったD7000であるが、これまで1000枚程撮っているがまだまだ使いこなせていない。とりあえず、何枚か晒します。写真はリンクになっているので、辿っていけばオリジナルサイズ(4928×3264)の写真にアクセスできます。念のため言っておきますが、素人がテキトーに撮った写真なのでD7000購入の参考などにはしないでください。

まずは、久しぶりにレインボーブリッジを歩いて渡って、お台場に行ったので、そのときの写真を。

ここは、芝浦側。カメラスポットだと思うのだが、人はほとんどいない。ここで夜景を撮ったことないので、今度試してみよう。この写真、ブラケットの-2.0EVの1枚。0.0EVは白とびしていた。このくらいの空が好み。ここの場所、CLIF EDGEの「SA・YO・NA・RA 〜君を忘れないよ〜」のPVでも使われている。最初見たとき外国のどっかだと思った。

レインボーブリッジの上から。今回は初めて北側を歩く。北側は陽が射さないので暗くて寒いけど、順光なので景色がクリアに見える。写真もモアモア感なく、なかなかシャッキリしている。このくらいなら満足。東京スカイツリーもよく写っている。解像度が高いので、実倍サイズでみると、ビルの看板やトラックの文字なども読める。数年後には1枚の手持ちカメラで撮った写真だけでスーパーズームが可能になるのだろうか。

レインボーブリッジを渡りきり、お台場公園への道の途中から。波がなければ、鏡のような反射になったのだが、ちょと残念。

自由の女神像とフジテレビ。あまりこのアングルで撮る人はいないので。この球体の中、展望室になっていて誰でも入れるみたいだけど、まだ入ったことない。一人で行ってもつまんないか。


どう見てもキリンだよね。コンテナ船のコンテナ用のクレーン。近くで見ると凄い大きいけど、遠くから夕日にたたずむ姿は郷愁さえ感じるよ。

この時期だけ虹色にライトアップされているレインボーブリッジと自由の女神と東京タワー。それにしても、中国語がバンバン耳に飛び込んでくるんだけど、上海の夜景の方がよっぽど綺麗だと思うよ。


場所は突然変わって、鎌倉へ。

映画やドラマのロケーションによく登場する江ノ電鎌倉高校前駅。ホームの真ん前が海という絶好の場所であり、カメラを持った人を数多く見かけるところ。一眼レフを首からぶら下げて、ホームのベンチに座ってぼっと海を見ていると、「シャッター押してもらってもいいですか?」とよく頼まれる。カップルでなく女の子同士の場合などは、とびきりの笑顔でポーズしたりするから、こちらもつい口元が緩んでしまう。

駅前の道路には、派手目の車やバイクが走ったり、ツーリグをする人、ジョギングする人などさまざまで、また、海ではサーフィンする人が見えるので眺めているだけで楽しい。この写真、実は、GIMPで合成したもの。と言っても、場所は同じで、標識部分を切り取って合成した。


駅の前の海岸。薄っすらと富士山が見える。JUJUの「Hello, Again 〜昔からある場所〜」のPVにも登場する海岸。それにしても、このPVは何度見ても泣ける。誰も写っていない海だけの写真の意味が最後にさりげなく明かされるという演出も憎い。写真には撮ったその時の気持ちまでもが記録されるものなのですね。



最後は、横浜みなとみらいの夜景。D7000の夜景モードのサンプルにも使われている。ただ、夜景モードはちょっと明るすぎるように感じるので、それは利用していない。この写真は大桟橋からだけど、D7000のサンプルは汽車道付近からと思われるので次回はそこから撮ってみよう。



擬似HDRの原理もだいたい分かったので、CanvasJavaScriptでなんちゃってHDRのコードを書くのも面白いかもしれない。

あとはレンズだ。