SSブログ

aravis解析 その9 [aravis]

aravisの話は今回でひと段落にする。ほんとはSwift化するつもりだったけど、まだSwiftを書き慣れていないのでなかなか進まない。このままでは、また例によってなかったことになりそうなので、その代わりと言ってはなんだけど、aravisのObjective-Cラッパを公開することにした....

5  aravisのswift化

5.1  swift化の方針

ざっくり見たので、逐語的にGObjectからswiftに変換することはできそうな気がしてきた。当然aravisのGObjectはswiftのクラスになって、オブジェクト指向的な操作は何の問題もなくsiwftで書けるものばかりのような気がする。それにGObjectになってない普通のCで書かれた部分はそのままswift関数に置き換えることができる。ダンジョンのようなGObjectをswiftに単純に書き換えるだけでもずいぶん読みやすくなるような気がする。

比較的上位のオブジェクトであるArvCameraやArvDeviceはそれでいいだろうし、逆にレイテンシ制約の厳しいArvStreamのサブクラスなんかでは違った書き方をしにくい(処理の単位の小さな関数の連続とそのループにしかならないので、何で書いても変わらない)だろう。

しかし全部がそれだとやっぱりつまらない。特にGen$<$i$>$CamのXML記述をオブジェクトツリーに展開する部分は、全部のオブジェクトをクラスに書くより簡単な書き方ができそうな気がする(内部状態を持たないオブジェクトがあるし)。それに、全部を逐語的に手動で変換するぐらいなら、それこそGObject->swiftコンバータでも書いたほうが実りあるような気がする。それにswiftの勉強には全然ならないし。やっぱりswift化するならswiftらしい言い回しでもっとシンプルに書けないとつまらない。

例えば、ArvBufferは本来変更されないデータなので、swiftの構造体として書いてもいいかもしれない。payloadTypeごとにenumにすると動的に(受け取ったデータの内容に従って)型を決めているような書き方ができる。でもswiftでは構造体を別の関数に渡すとコピーが作られてしまう。copy-on-writeの規則があるので実際に複製されないように書くことはできるような気はするけど、ちゃんと制御できるかどうかはわからない。大抵の場合サイズが大きいのでコピーが発生すると悲惨である。

payload本体も固定の配列にするかData(NSDataのブリッジ)にするか、もっとUnsafePointer、あるいはUnsafeBufferPointerで用意するか、など考えられる。どれが一番効率がいいんだろう。UnsafePointerで確保したときにちゃんとdeallocするタイミングを決められるだろうか?classにはdeinitがあるけど、構造体にはない(構造体ではUnsafePointerをプロパティに持てない、なんてことはないよな。UnsafePointer自身も構造体なワケだし)。

それよりもArvBufferをクラスに書いて、PayloadTypeごとにサブクラス化するというのが素直な気もする。Objective-Cならそうするはずだし。でもswiftを書いているとクラスがなんだか妙にオーバーヘッドの大きなものに思えてしまって、なんかちょっと違う感がある。できるだけ構造体に書きたいという衝動があるんだけど、そのへんまだ書き慣れてないので直感的に決めることができない。

ということで、swift化はゆっくり考えながらすることにした。

5.2  Objective-Cラッパ

その代わりというかなんというか、Objective-Cのラッパを書くことにした。最初はそんなもの書く気は無い、と言ったんだけど、 Gen<i>Camのカメラは生産設備や開発用のバラックなんかにいっぱい使ってて、それにはほとんどがObjective-Cで書いたアプリケーションが乗っている。

それは僕が昔書いたIIDCレジスタを読み書きする形のドライバを使っている。そのせいでIIDCレジスタがなくて Gen<i>CamのXMLファイルだけしか持っていないカメラや、USB3接続のカメラは使えないでいる。

aravisのObjective-Cラッパを書いて古いIIDCドライバを置き換えれば、最新のカメラが使えるようになるし、古いカメラもそのまま使える。それに難しいGObjectを隠蔽してObjective-Cだけのインターフェイスにすることもできる。

本当はさくさくっとaravisのswift化ができて、書き直しがあったりすると置き換えようと思ってたし当然、新しくカメラアプリケーションを書かないといけなくなったときはObjective-Cではなくswiftで書きたいと思ってるんだけど、慌てないといけない状況ではない。今のところは。

Objective-Cで書くのをこれを最後にして、aravisのObjective-Cラッパを書こう。Objective-Cでなら悩まずにさくっと書けそうな気がするし。ついでにちゃんとframeworkにまとめよう。それなら他の人も使えるかもしれないし。まあそんな必要のある人がどれだけいるのかはわからないけど。

5.3  ということで

githubに置いた(余計な情報が上がるのが怖くて、githubのバージョン管理機能を全然使ってない)。いちおうframeworkの形になってるけど、ビルド済みのframeworkのバイナリは上げていない。それはインストーラのパッケージのような形でframeworkをビルドするとそのバイナリに僕のCodeSignが埋まってしまう(リアルの僕とdecafishが紐づけられてしまう)からである。

今回はまじめにREADME.mdもかなり詳しく書いた。しかも英語で。

それと、aravisを手動でmacOSにインストールする手順も決定版にした。pkg-configに依存するとそれまでにインストールされているpkg-config依存のライブラリに動作が影響されて、Xcodeでのpath設定が増えてしまうため、pathの指定などは全部手動でやることにした。

configureスクリプトは、環境変数のARAVIS_CFLAGSとARAVIS_LIBSの両方を定義するとpkg-configをスキップするようになってる。

なお、libxml2、libzはmacOSに標準でインストールされているものを使った。libffiもmacOSにあるけどこれを指定するとバージョンチェックに失敗したので、こっちはやめた。

5.3.1  Xcode プロジェクト

githubにあげたXcodeのプロジェクトには
  • framework本体
  • TestViewer
のふたつのターゲットが含まれている。TestViewerはデモとデバグ用で、frameworkをリンクして、カメラが繋がっていればカメラからの絵を表示する簡単なアプリである。frameworkの使い方はこのAppDelegate.mをみればわかると思う。

デモのために
  • カメラのexposure time(シャッタスピード)とgainを切り替える
  • カメラがサポートしているpixel formatと実フレームレートなどを表示する
  • スナップショットをtiffファイルに保存する
  • カメラ内蔵の Gen<i>Cam記述ファイルからカテゴリ分けされたfeatureをOutlineViewに表示する
を追加してある。最近のカメラは機能競争の結果なのか、OutlineViewに表示してみると膨大なfeatureが定義されていることがわかる。

Main Threadに作業が集中しすぎて反応が悪い。NSBitmapImageRepをCALayerに表示してるんだけど、そこでかなり食ってしまっている。NSBitmapImageRepではなくて、CoreVideoCVBufferを使えばもっと軽くできるんだろうけど、CoreVideoは使うのが難しい。

当然、一般的なviewerの機能が実装されているわけではない。僕自身がカラーカメラを使うことがほとんどないので、Bayer-RGB変換などはない。chunkの情報を展開するのもない。必要としている人が実装してくれるとありがたい。

5.3.2  frameworkに埋め込んだライブラリの検索

こないだかなり悩んだframeworkに埋め込んだライブラリの問題が解決できない。しかたないので絨毯爆撃的に試してみてとりあえず動く(リンクできる)方法のひとつにたどり着いた。

どうしたかというと結局install_name_toolでバイナリに埋め込まれたpathを書き換えることにした。しかしそれでも@loader_pathを直接バイナリに書いても解決してくれない。
  1. バイナリには@rpathを埋める
  2. XcodeのBuild Settings- > Linking- > RunPath Search Pathに@loader_path/Frameworks追加。
とするとなぜか解決してくれた。RunPath Search Path、つまりは環境変数のLD_RUNPATH_SEARCH_PATHSに@loader_pathを入れた。
バイナリに埋めるためのスクリプトはよそ様のを参考にさせてもらって、
EXECFILE=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}
LIBPATH=${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}
NEWLIBPATH=@loader_path/Frameworks

cd ${LIBPATH}
TARGETS=(`ls -w *.dylib`)

for TARGET in ${TARGETS[@]}
do
    LIBFILE=${LIBPATH}/${TARGET}
    TARGETID=`otool -DX "${TARGET}"`
    NEWTARGETID=${NEWLIBPATH}/${TARGET}
    install_name_tool -id "${NEWTARGETID}" "${LIBFILE}"
    install_name_tool -change ${TARGETID} ${NEWTARGETID} "${EXECFILE}"
done

for TARGET in ${TARGETS[@]}
do
    for LIB in ${TARGETS[@]}
    do
        if [ ${TARGET} != ${LIB} ]
        then
            REFLIBS=(`otool -LX $TARGET | grep $LIB`)
            if [ ${#REFLIBS[0]% *} -gt 0 ]
            then
                NEWLIBID=@rpath/${LIB}
                install_name_tool -change ${REFLIBS} ${NEWLIBID} ${TARGET}
            fi
        fi
    done
done
みたいな感じ(読みやすくするためにインデントをつけたけど、Xcodeの「Run Script」に書くとインデントは除かれる)。つまり、Frameworkの本体バイナリに埋まった依存ライブラリのpathを
@loader_path/Frameworks
に、依存ライブラリがさらに依存しているライブラリのpathには
@rpath
を埋めた。スクリプトの後半部分のforの入れ子はライブラリがさらに別のライブラリに依存しているのを書き換えるようになっている。そこには@rpathを使っている。

なぜ@rpathのかわりに直接@loader_path/Frameworksを入れるとダメで@rpathを挟むとOKなのか全然わからんし、@rpathにはいろいろなpathが設定されているので、最終的にどの記述が使われているのかもわからん。いろんな設定とは無関係に解決してるのかもしれない、と思うと心配になるんだけど、どうやって調べればいいのかdyldのman pageを見てもよくわからない。

あるライブラリAが依存する別のライブラリBはライブラリAの位置が@loader_pathになってしまうということかもしれないけど、もういい加減うんざりした。勘弁してほしい。

workaroundではなんでいいのかわからんからダメだと言ったけど、もうworkaroundでいいや。商用のアプリを作ってる人たちならやり慣れててよくわかってるんだろうな。できれば教えてほしい。

コンパイルリンク済みのframeworkが欲しい人は、申し訳ないけどhomebrewかなんかでglibなどの必要なライブラリをインストールした上でビルドして欲しい。直接メールを個人的にもらえればお渡しすることも可能である。他所でちゃんと動くかどうかはわからないけど。

ところで、Xcodeからframeworkをarchiveするとなぜか中身が空っぽのフォルダができるだけになる。デフォルトのSchemeではProfileにビルドするとRelease設定のビルドができるので、それをコピーして他のマシン用に使っている。どうすればいいか知ってる人、教えて。

それと、埋め込んだライブラリのCodeSignのチェックがうまく行ったり失敗したりする。/usr/bin/codesignが「unknown error」でexit code 1で終了したり、勝手にクラッシュしてビルドがそこから進まなくなる。Webを見てるとこのエラーはライブラリを埋め込んでいない人でも発生するらしい。僕はこのproject以外で出会ったことはないけど、起こると何をしても治らない。諦めてほったらかしてると、次の時には問題なかったりする。全然わからん。

nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。