2014年10月18日土曜日

ruby入門 ~開発環境構築編~

私は主にC#でWindows GUIアプリケーションを開発する仕事をしている。
サーバーサイドスクリプト言語はPHPを学生の頃に少しと最近はNode.jsを使った仕事もしている。
rubyは構文を一目見てスコープをend句で記述するpascalライクな文法が好きになれず敬遠していた。

が、ちょいと将来的にrubyスキルが必要になってきそうなので導入部分をメモってみる。
対象読者は他の言語を普通に扱えるがrubyには一切触れたことが無い人。


さて、C#というか.NET環境はVisual Studioをインストールするだけで実行環境から優れたエディタ・デバッガがあっという間に構築できる。未だにテキストエディタをドン引きするほど使いこなしてるのも硬派で格好いい気もするし否定はしないけれど初めて触るのに環境構築だけで数日かかりかねない場合、それだけで心が折れかねない。スクリプトなりインストーラーなりyumやaptでさっくりと構築したい。

とりあえず触りだけなのでVisual Studio使いの自分はWindows上に環境を構築することにする。
rubyの統合開発環境を調べるとEclipseまたはそれらの派生品、有償であればRubyMineあたりが候補になりそうだ。
エンバカデロが3rd Rails というものを出しているが現在は開発が止まっているようだ。
Eclipse系もデバッグ環境を整えるまでに相当苦労している、動いたとしても一部機能は動作させれていないような情報が多く目に付く。
有償ではあるがRubyMineが最も手堅そうだ。30日の試用期間もあるようなのでコレに決めた。
ちなみに購入するならパーソナルライセンスで$98のようだ。

Visual Studioのように実行環境まで構築してくれるわけではなくRubyのインストールなどは事前に自分でやっておかなければならないらしい。それくらい自動でやって欲しいものだが・・・

まずRubyInstallerを下記からダウンロードする。
http://rubyinstaller.org/downloads/

最新安定版らしいRuby 2.1.3 (x64)を選択した。特に困ることなくインストール完了。
続いてDevKitが必要らしいのでDevKit-mingw64-64-4.7.2-20130224-1432-sfx.exeをダウンロード。
DevKitのインストールはコマンドプロンプトからinstallスクリプトを走らせる必要があるらしい。
http://www.rubylife.jp/railsinstall/rails/index4.html
を参考にインストールした。

最後にRubyMineをインストール。公式サイトからわかりやすくダウンロードできたしインストールも簡単だった。起動することまで確認。
New Projectを起動してmain.rbというファイルを追加してとりあえずHello World!をやってみる。
puts("Hello World!")
 この一行でとりあえず動いた。
続いてデバッガの動作を確認したいので簡単な計算をさせてみる。
x = 3
y = 4
z = x * y
puts(z)
puts("Hello World!日本語")
3行目あたりにブレークポイントをはってデバッグ実行してみるがデバッグモジュールが足りないという。
モジュールを追加するのはgemというパッケージマネージャーを使う必要があるらしい。
gem
http://rubygems.org/

下記を参考にgemをインストール
http://www.rubylife.jp/railsinstall/rubygems/index1.html

RubyMineのコンソールにはruby-debug-ideというパッケージが見つからないとあったので
> gem install ruby-debug-ide
とするとインストールできた。
一応RubyMineを再起動してデバッグ実行するとブレークポイントでちゃんと止まった。
変数の中身も見れる。右クリックでSet Valueを選ぶと値も書き変えれる。

統合開発環境としての基本動作は問題ないみたい。
エディター部に行番号表示は欲しいのでFile>Settings>Editor>Appearanceに「Show line numbers」にチェックを入れる。
うん、$98くらいの価値は十分にあるんじゃないかな。

2014年8月4日月曜日

RaspberryPiにSubversionのコードをAmazon Elastic Beanstalkにナイトリーデプロイさせる

社内のコード管理はsubversionを使っており
Amazon Elastic Beanstalkにデプロイするアプリもそれで管理している。

Beanstalkは開発環境と本番環境を別々に用意しているが
開発環境にはナイトリービルドならぬナイトリーデプロイさせたくなった。

subversionが動いているLinuxサーバーにシェルスクリプトをcron実行させればいいのだけど
あいにくOSのバージョンが古くデプロイに必要なgitのバージョンをインストールできなかった。
OSのバージョンアップは面倒なのでやりたくないと管理者に言われてしまう。
余っているPCはあるのだけど会社からの節電要求が厳しく24時間365日稼働させると怒られそうだ。
この程度の処理ならRaspberryPiで十分なのでB型を購入した。消費電力3.5Wなら許されると信じたい。

RaspberryPiにRaspbianをインストールする手順までは解説サイトが無数にあるので省略。
主にsubversionとgitとebコマンドラインツール、それらの依存モジュールが必要。
SSHで接続して
$ sudo apt-get update
$ sudo apt-get install python
$ sudo apt-get install ruby
$ sudo apt-get install subversion
$ sudo apt-get install git
$ wget https://s3.amazonaws.com/elasticbeanstalk/cli/AWS-ElasticBeanstalk-CLI-2.6.3.zip
$ unzip AWS-ElasticBeanstalk-CLI-2.6.3.zip
$ sudo mv AWS-ElasticBeanstalk-CLI-2.6.3 /opt/
$ sudo apt-get install vim
$ export PATH=$PATH:/opt/AWS-ElasticBeanstalk-CLI-2.6.3/eb/linux/python2.7/
$ vim .bashrc #上記exportを.bashrcにも書いとく
$ source .bashrc
$ svn checkout svn://hoge.com/piyo /piyo
$ cd piyo
$ git init
$ git add .
$ git --global user.email "hoge@piyo.com"
$ git --global user.name "hogepiyo" 
$ git commit -m "comment"
$ eb init
$ git aws.push

これで行けるかと思いきやpushに失敗する。botoモジュールが無いとか言われる。
$ sudo apt-get install python-pip
$ pip install boto
$ git aws.push
解決しない。

自分でbotoを入れてみる。
$ cd ..
$ git clone git://github.com/boto/boto.git
$ cd boto
$ sudo python setup.py install
$ cd ../piyo
$ git aws.push
上手くいった。botoが何なのかは興味無いので知らん。

あとはsvn updateからgit aws.pushまでの流れをシェルスクリプトに書く。
#!/bin/sh
logger "Started nightly deploy"
cd ~/piyo/
svn update
revision=$(svn info svn://hoge.com/piyo | grep "Last Changed Rev: " | cut -c 19-)
sed -i -e "s/revision/$revision\-nightly/" ~/piyo/.ebextensions/version.config
git add .
git commit -m "nightly-$revision"
git aws.push
logger "Exited nightly deploy"

大体こんな感じ。独自の工夫点としては
svn infoの出力からgrepやらcutでリビジョン番号を抽出して
バージョン番号の一部として使用している。
sedコマンドの置換処理の部分は各自のアプリに構造に合わせてね。
バージョン番号にリビジョン番号を入れておくと
不具合を指摘されたときに該当ソースコードの取得が簡単になる。tagsを作成してなくてもよくなる。
このスクリプトをcronに登録して毎日深夜に実行させる。

RaspberryPiのコストはケース・SDカード・充電器・microUSBケーブルを含めれば8000円くらいはかかってる。
この程度の処理だけだと割に合わないので他用途でも活用していきたいね。

2013年12月19日木曜日

MPEG4コンテナのmoov/mvhdボックスについて

boxtype:mvhd
moovボックスの子階層であり必須フィールド。MoVie HeaDerの略。
moovボックスペイロードの先頭にあるべきだがmustではなく
間に何らかのデータがあっても読み込めないといけない。
ここには結構重要な情報が書いてある。

Syntaxは次の通り。
aligned(8) class MovieHeaderBox extends FullBox(‘mvhd’, version, 0) {
    if (version==1) {
        unsigned int(64) creation_time;
        unsigned int(64) modification_time;
        unsigned int(32) timescale;
        unsigned int(64) duration;
    } else { // version==0
        unsigned int(32) creation_time;
        unsigned int(32) modification_time;
        unsigned int(32) timescale;
        unsigned int(32) duration;
    }
    template int(32) rate = 0x00010000; // typically 1.0
    template int(16) volume = 0x0100; // typically, full volume
    const bit(16) reserved = 0;
    const unsigned int(32)[2] reserved = 0;
    template int(32)[9] matrix = { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
    // Unity matrix
    bit(32)[6] pre_defined = 0;
    unsigned int(32) next_track_ID;
}
creation_timeとmodification_timeはどうでもいいがtimescaleとdurationは重要。

続きは後日加筆します。

MPEG4コンテナのmoovボックスについて

boxtype:moov
必須フィールドであり、どんなデータがファイル中にどのように格納されているかを示すMPEG4コンテナ中で最も重要なボックスである。
MPEG2においてPAT→PMT→データと辿るように、MPEG4ではここからデータまで辿ることになる。

mdatボックス(動画データや音声データが格納されるボックス)の説明書のような役割をしておりmdatボックスの後に配置される事が多いようだ。
(データを一旦配置してみて、その後にオフセットを書いていくという処理がエンコーダーにとって都合がよいからだろう。)
仕様としてmdatボックスとの順序が規定されているわけではない。

moovボックスには階層の深い子ボックスがたくさんあり、他に比べれば少し複雑なツリーを構成する。ツリー構造は下記のようになる。
 moov(*)
├─mvhd(*)
├─trak(*)
│  ├─tkhd(*)
│  ├─tref
│  ├─edts
│  │  └─elst
│  └─mdia(*)
│      ├─mdhd(*)
│      ├─hdlr(*)
│      └─minf(*)
│           ├─vmhd
│           ├─smhd
│           ├─hmhd
│           ├─nmhd
│           ├─dinf(*)
│           │   └─dref(*)
│           └─stbl(*)
│                ├─stsd(*)
│                ├─stts(*)
│                ├─ctts
│                ├─stsc
│                ├─stsz
│                ├─stz2
│                ├─stco(*)
│                ├─co64
│                ├─stss
│                ├─stsh
│                ├─padb
│                ├─stdp
│                ├─sdtp
│                ├─sbgp
│                ├─sgpd
│                └─subs
├─mvex
│  ├─mehd
│  └─trex(*)
└─ipmc
trakボックスは複数定義することができる。通常は動画と音声一つずつの2トラックだろうが多国語音声など複数持たせても良い。
(*)を付けているのは必須ボックスである。

このエントリでは子階層ボックスの一つずつまでは説明しきれないが、重要なのはstbl以下の階層である。ここがmdatボックスの構造を定義する部分だからだ。
動画と音声を同期させるための時刻情報もここに含まれている。

moov/mvex/trexが仕様上は必須となっているがTMPGEncで出力したファイルを見ると付いていなかった。少し調べただけでは何故か分からなかったが必須ではないのが正しい気がする。
(親ボックスのmvexが必須とされていないのが説明できないので。見てる仕様書が古いのかな・・・)

moov子階層のボックスの順序はmust指定ではないがstrongly recommendでヘッダ類が最初に来るようにとされてるのでmvhdが先頭に来るだろう。
(mustじゃダメなのかな・・・recommendだと実装が面倒なんだけど)

MPEG4コンテナのftypボックスについて

boxtypeがftypでFile TYPeを意味する。
初めに断っておくが、必須フィールドである割にあまり重要なフィールドではない。
ファイル中に最初に出現するBoxである。
This box must be placed as early as possible in the file.
と定義されているのでBoxとして一番前に配置されるが「ファイルの先頭に来る」とは書いてないので注意。
もしftypボックスの前にゴミデータが付いていても読む側は読み飛ばさなければならない。
(MPEG4に限らずパーサーを作る際にファイル先頭にヘッダがあることを前提に組んじゃう人が結構いる。まあ殆どのケースではファイル先頭にあるんだろけど。)

aligned(8) class FileTypeBox extends Box(‘ftyp’) { 
    unsigned int(32) major_brand; 
    unsigned int(32) minor_version; 
    unsigned int(32) compatible_brands[]; // to end of the box 
}

major_brandは広範囲なMPEG4規格の中で例えばモバイル向けのファイルであること等をprofileとして知らせる識別子である。
別にここにデタラメを書いたところで読み込み側は処理を諦めるべきではないし、処理を切り替える必要も無い。
brandの一覧はこのサイトなどで見ることができるが大手ベンダーの自己アピールが鬱陶しいことが分かるくらいだ。
多くの場合はmp42とでもしておけばいいだろう。

minor_versionはこのフィールドを活用される事自体が無さそうだ。
現状全部0で埋めとけばいいだろう。

compatible_brandsは互換性のあるmajor_brandを複数並べられるが、
どうでもいい情報なので空欄でも構わないだろう。
TMPGEncに出力させた例ではmp42,isom,avc1の3つが定義されていた。

参考までにMP4 Readerでの解析例を載せておく。

MPEG4コンテナのBox構造(Atom)について

MPEG4はBoxと呼ばれる単位を入れ子にした構造で表現される。
QuickTime時代の名残でAtomと呼ばれることもある。

入れ子と言ってもファイル中に親子関係が記述されているわけではなく
解析する側が仕様から各Boxの関係を把握して解析する。
Boxのヘッダー構造は以下であるとISO/IEC 14496-12で定義されている。

aligned(8) class Box (unsigned int(32) boxtype, optional unsigned int(8)[16] extended_type) {
    unsigned int(32) size;
    unsigned int(32) type = boxtype;
    if (size==1) {
        unsigned int(64) largesize;
    } else if (size==0) {
        // box extends to end of file
    }
    if (boxtype==‘uuid’) {
        unsigned int(8)[16] usertype = extended_type;
    }
}
基本的には頭4バイトがサイズ情報(この4バイトも含むバイト数)、次の4バイトにBoxの種類を識別するためのboxtype、ヘッダーの後はペイロード部分となる。
The standard boxes all use compact types
(32-bit) and most boxes will use the compact (32-bit) size.
仕様には上記のように書かれているので基本的には32bit空間で表せるサイズがBoxサイズの上限である。 ただし
 Typically only the Media Data Box(es) need the 64-bit size.
動画データを格納するMedia Dataボックスに限ってはサイズが大きいこともあり64bit空間がサイズ上限となる。64bitに統一しても良かったんじゃ?と思わないでもない。サイズ情報はヘッダーも含んだサイズである。

boxtypeはftypボックスならASCIIコードで直接0x66(f),0x74(t),0x79(y),0x70(p)と表現される。バイナリエディタで見たときに分かりやすい。boxtype=uuidの場合はうんたらと書いてあるがuuidはベンダ固有のオプション定義に使うBoxなので読み飛ばして構わない。中身が独自定義なのだから部外者には把握しようがない。


MP4コンテナの中身を調べる

ISO/IEC 13818-1で定義される動画コンテナとしてのMPEG2はよく普及しており
日本語での資料も充実しているがISO/IEC 14496-14で定義されるMPEG4 Part14 コンテナは
情報が多いとは言えない。私もよく知らない。

そもそもはAppleのQuickTimeフォーマットがベースになっているMPEG4 Part12が
元に拡張したものであるらしい。Part14の仕様書はPart12からの拡張部分しか書いてない。
だからPart14の仕様書の前にPart12の仕様書を見ないと訳が分からない。
そもそもPart14での拡張部分なんぞ大半の人は使用しておらず
Part12互換のファイルを作っているんじゃなかろうか。
(調べ始めたばかりで適当な事言ってるので注意)

動画コンテナとしての役割は大きくは2つあると思う。
ストリーミングに関してはひとまず置いておく。
  1. 映像と音声をズレないよう同期させる
  2. ランダムアクセスを可能にする
 1.に関してMPEG2ではDTS、PTS、PCRといったタイムスタンプ情報が充実しており同期に関してこれを越えるフォーマットは存在しないと思われる。MPEG4ではDT(Decoding Time)、CT(Composition Time)というものがそれらに相当する。

2.に関してMPEG2システムでは上記時刻情報を元にシークするしかない。ファイルのお尻のほうのPCRから全体の時間を求め、ファイルポインタを大雑把に進めてから細かい探索を行う。大した処理ではないが非力な組み込み機器では問題になるかもしれない。MPEG4では基本的にオフセット情報で管理されているため直接目的の位置に飛べそうだ。実際に負荷がどれくらい減るのかは知らないが。

MP4コンテナにおける構成を最も大雑把な単位で書くと次のようになる。
その他のデータは読み飛ばしても問題ないようなゴミデータばかりだが先頭にftypボックスが存在することは必須とされている。
ftypも再生に必要なわけではないが多機能すぎるMPEG4システムの内、どこまでを使ったものなのか知るには意味があるかもしれない。
  • moovボックス(メタデータ)
  • mdatボックス(動画・音声データ)
mdatはMedia Dataを指すが、この中には動画・音声データを比較的自由に格納できる。mdatにどのようにデータを格納したかをmoovボックスに記述することで最終的にはアクセスできるようになる。よって動画データ丸ごとの後に音声データを丸ごと繋げる単純な構造でも良いし、それぞれを細切れにして交互に配置しても構わない。

MPEG4の内部を解析するツールとしてWindowsではMP4 Readerが最も便利だった。
下図はTMPGEnc 5から出力したMPEG4(H.264,LC-AAC)ファイルの解析結果である。

moovボックスの下に2つのtrakボックスがあるのが分かる。それぞれが動画と音声トラックを示している。moovボックス直下にmvhdボックスがあり、図の右側に出ているがここにファイル全体の時間の長さが書いてある。一分の長さのコンテンツなのでduration=60714(ミリ秒)と出ている。こんなに浅い場所を調べるだけで長さが分かるのはMPEG2から比べれば格段に楽だ。MPEG2ではファイルの先頭と最後のタイムスタンプの差分から推測するしかなかった。タイムスタンプは27MHz周期で24時間弱程度で一周してしまう点もやっかいだった。MPEG2-PSはMPEG2-TSとの互換性の問題で長さのフィールドが無いのかもしれないが。

再生にあたって重要な情報はstts/ctts/stsc/stsz/stcoだ。これらがmdatボックスのどの位置に何の情報が書かれているかと時刻情報が書かれている。時刻情報といってもMPEG2のように基準時刻からの経過時間が書かれているのではなく前フレームからのオフセット時間だけが書かれているようだ。sttsがDecode Timeのテーブルになるが、このファイルの場合は「全フレームの間隔は2000」と書いていただけだった。可変フレームレートだともっとややこしい表になるかもしれない。
cttsはフレームの並び替えの問題もあると思うのだがもっと複雑だった。

とりあえず今回はここまで。
機能てんこもりにした挙句に一部しか使われてないから
わざわざMPEG2システムから乗り換えるほどのメリットがあるかは疑問だ。
地デジにしてもAVCHDにしてもMPEG2-TSなのは互換性の問題だけではないだろう。