2008年2月アーカイブ

概要: mpg123はコンソールで走るMPEGオーディオデコーダですが、その構成要素にlibmpg123というのがあります。今回はこいつを.NETからプラットフォーム呼び出しして使えるように、Win32 DL...

mpg123はコンソールで走るMPEGオーディオデコーダですが、その構成要素にlibmpg123というのがあります。今回はこいつを.NETからプラットフォーム呼び出しして使えるように、Win32 DLLを作ってみます。と言うよりは、自分で作ってみた時に困ったり躓いた点のメモです。


実は既に用意されている

ダウンロードページにlibmpg123 Win32 DLLっていうのが親切にも置いてある。が、MinGWでの使用を想定して作られたものなので、今回はこれでは不十分。面倒だけど自分で作る。

コンパイラ

結論から言うと、VC++では無理っぽい。やろうとしても、環境特有のヘッダとか定数とか型とか設定とか嫌と言うほど出てきて、やる気にならない、と言うか、無理。お勧め通りMinGWのgccを使う事にする。

MinGWのインストール

MinGWとMSYSをインストールする。MinGW + MSYS による Win32 な OS での開発が参考になる。gccが動くかどうかテストする時、Vistaだとcc1.exeが見つからないって言われるけど、適当にパス通せば良い。例えば、/etc/fstabに

C:/MinGW/libexec/gcc/mingw32/3.4.5 /mingwlib

を追加しておいて、/etc/profileに

export PATH="$PATH:/mingwlib"

を書いておくとか。ここまでしたら、MSYSのシェルでgccがちゃんと動くようになる。

libmpg123のダウンロード

SourceForgeから最新版を落としてくる。ここではそれを解凍して、できたディレクトリの内容を~/mpg123の中にコピーしたとして話を進める。

呼び出し規約の変更

なんだかやっぱりデフォルトの呼び出し規約はstdcallじゃないっぽい(cdeclでもないらしい? 未確認)ので、プラットフォーム呼び出しする関数と、少なくともアンマネージドコードから呼び出されるコールバック関数については__stdcallを付けて回る。具体的には、~/mpg123/src/libmpg123/mpg123.h.inのプロトタイプ宣言と、同ディレクトリのmpg123.cの関数本体の定義を探し回って__stdcallを書き加えていく。mpg123_replace_readerを使う場合は、それの引数(勿論r_readとr_lseekだけ)に加えて、reader.hのreader_data構造体のメンバ、r_read、r_lseek、read、lseek、それとreaders.cのposix_readとposix_lseekもstdcallを使うようにしておく。

隠れた曲者CCALIGN

詳細は後述するが、(MinGW上のgccでのみDLLを利用するのでない限り)CCALIGNを1にしてはいけない。~/mpg123/src/config.hはconfigureさんが作ってしまうので、彼自身を編集する。~/mpg123/configureの、14767行目辺りのccalign="yes"ccalign="no"に変更する。その下のresult: yesもresult: noにしておいた方が気持ちいいかも。修正後はその近辺は次のようになる。

ccalign="unknown"
if test $ccalign = "unknown"; then
  { echo "$as_me:$LINENO: checking __attribute__((aligned(16)))" >&5
echo $ECHO_N "checking __attribute__((aligned(16)))... $ECHO_C" >&6; }
  ccalign="no"
  echo '__attribute__((aligned(16))) float var;' > conftest.c
  if $CC -c -o conftest.o conftest.c; then
    ccalign="no"
    { echo "$as_me:$LINENO: result: no" >&5
echo "${ECHO_T}yes" >&6; }
  else
    { echo "$as_me:$LINENO: result: no" >&5
echo "${ECHO_T}no" >&6; }
  fi
  rm -f conftest.o conftest.c
fi
if test $ccalign = "yes"; then

cat >>confdefs.h <<\_ACEOF
#define CCALIGN 1
_ACEOF

fi

これでconfig.hに#define CCALIGN 1は現れなくなる。

make libmpg123.dll

ここまで来たら、あとはmakeするだけ。~/mpg123/makedll.shを実行すれば、時間はかなり(冗談じゃない程)かかるものの一発でDLLを作成してくれる。デバッグ版を作りたい場合は--enablge-debug=yesをオプションとして付けるといい。makeが終わったら、Hintsにあるようにstrip --strip-unneeded libmpg123-0.dllしておくと軽量化できる。そういう訳で、DLLは~/mpg123/libmpg123-0.dllとして得られるのだけれど……0って何だ。


おまけ : デバッグ版

gccでデバッグ版DLLを作っても、VC++やVisual Studioじゃデバッグできない、それじゃ--enable-debug=yesしても意味無いじゃん。それはそうなんだけど、DEBUGが定義されていると、libmpg123は標準エラー出力に色々と進捗状況を吐き出してくれる。何も無いよりは随分ましだろう。尤もプラットフォーム呼び出しをする場合標準エラー出力はどっかに飛んでいってしまう(のか、どうだか?)ので、そのままではこれも意味が無い。更に、.NETのSystem.Console.SetErrorでリダイレクトっぽい事をしようとしてみても、してくれない。

それじゃあlibmpg123側でリダイレクトしてしまえ、と言う事で、libmpg123.cのmpg123_initの頭でこうやっておく。

#ifdef DEBUG
  freopen( "./stderr.log", "w", stderr );
#endif

律儀にfreopenの戻り値をグローバル変数か何かに取っておいて、mpg123_exitでfcloseしてもいい。ちなみに、--enable-debug=yesオプション付きでmakeしたDLLは、stripするとかなり痩せる。どうせgdbは使えないんだし、痩せさせてしまえ。

そのおまけ : stderrオートフラッシュ

stderrはバッファリングされなくても、再割り当てしたファイルストリームはバッファリングされてしまって、AccessViolationExceptionとかで正常にfcloseされなかった時にstderr.logに何も入ってなかった、じゃ悲しいので、面倒だけど毎回バッファをフラッシュしてやる事にする。幸いlibmpg123のエラー出力は~/mpg123/src/libmpg123/debug.hにまとまっているので、そのマクロ集を次のように変えてやればいい。

#define debug(s) do{ fprintf(stderr, "[" __FILE__ ":%i] debug: " s "\n", __LINE__);\
	fflush( stderr ); }while(0)
#define debug1(s, a) do{ fprintf(stderr, "[" __FILE__ ":%i] debug: " s "\n", __LINE__, a);\
	fflush( stderr ); }while(0)
/* ...... */

fflushを付けてブロックにしただけ。do-whileもくっついてるけど、これはereturnとの付き合いの結果。

CCALIGN?

CCALIGNを1にしておくと、適宜アライメントをしてくれる。gccはアライメントを指定する修飾子に__attribute__((aligned(x)))というのがあるようで、これを使えば変数をxバイト境界に乗っけてくれるようだ。libmpg123のmakeでは、CCALIGNが1と定義されていれば、この修飾子を使ってアライメントが必要な変数の位置を調整する。

そんなものが使われているという事は使わないといけないようなものがあるからで、例えばSSE命令がメモリにアクセスする場合、普通はメモリ上のデータは16バイト境界に乗っていないといけない。らしい。libmpg123も最適化デコーダの一つにSSEデコーダを持っていて、mpg123_newのdecoderに"sse"を渡せば使ってくれるし、NULLを渡した場合でも、利用可能な範囲内でSSEデコーダが一番効率がいいと思えば暗黙裏に使ってくれる。

そんな訳で、gccは__attribute__((aligned(x)))が使えるので、放っておけば良さそうなものだけれど、実は問題がある。どうも、gccで作ったDLLをgccから利用した場合はアライメントは保たれたままのようなのだが、gccで作ったDLLのアライメント情報は、他の環境では理解されないようなのだ。VC++然り、CLR然り。この辺って処理系依存なのか。VC++にも似たような__declspec(align(x))という修飾子があるけど、Microsoft Specificだ。で、運が悪いと、__attribute__((aligned(16)))で修飾されたデータのアドレスが16バイト境界からずれて(実際、AccessViolationExceptionが投げられていた時には4バイトずれていた)、movapsが駄々をこねる……。

いや、そう、実は駄々をこねるのはmovapsだけであって、libmpg123では、他のSSE命令はメモリに直接アクセスしないように書かれている。CCALIGNがオプショナルである事が何よりの証拠。CCALIGNが定義されない場合は、アライメントを行う代わりに、16バイト境界に乗っていなくても文句を言わないmovupsを使ってくれる。パフォーマンスは多少落ちるかも知れないけれど、_alligned_mallocを使って動的に領域をlibmpg123の外から与えたりするのはちょっと暴挙に過ぎるし、何より一番簡単だろう。

概要: 放置しすぎて、このまま放置を続けた方が本来あるべき姿のような気もするけど、折角書く気になったのだから捨て置くのももったいない。という訳で何か最近気づいた事を適当に書いてみる。 最近ようやくやる気が復活...

放置しすぎて、このまま放置を続けた方が本来あるべき姿のような気もするけど、折角書く気になったのだから捨て置くのももったいない。という訳で何か最近気づいた事を適当に書いてみる。

最近ようやくやる気が復活してきたので、例の(?)DirectXのラッパライブラリを見直している。Managed DirectXってなんかどうも捨てられてるっぽい(November 2007以降ではSDKからManaged用のドキュメントが削除されるとか......XNAってのが主流なのか?)し、既存のラッパライブラリをC++ .NETからいじった方が色々と楽そうな気もするけど、無下にするのも忍びないし、ま、一回くらい自力でラッパライブラリ作る経験があったって罰は当たらないだろうという事で継続する事にする。しかし、企画した時から2年以上経っているんだなあ。無為に過ごすと時って凄く速く過ぎ去るもので。少年老いやすく学成りがたしとはよく言ったものだ。


ま、それはともかく。Microsoft.DirectX.Direct3D.Textureのコンストラクタでストリームから画像を読み込むと、とんでもなく時間がかかりませんか?

Device device;
Stream source;

this.texture = new Texture( device, source, Format.Unknown, Pool.Managed );

みたいな事をやると。いや勿論、いつでも遅いという訳では無いのです。ただ、これやると、デバッガでデバッグしようとすると破壊的に遅くなる。物的には何も破壊しないけれど、デバッグ付きで実行しようとする気を決定的なまでに殺いでくれる。このコンストラクタ一回呼び出しただけで数秒かかるんじゃやってられない。

コード見直すに当たって、これも解消できたらいいなあと思って代替手段を探していた時、そう言えばTextureLoaderっていうのもあったなあと思って、それっぽく書き直してみた。

Device device;
Stream source;

// 実際にはFilter.Linearが使えるかどうかCaps見てやんないと駄目だろうけど
this.texture = TextureLoader.FromStream( device, source, 0, 0, 1,
  Usage.None, Format.Unknown, Pool.Managed, Filter.Linear, Filter.Linear, 0 );

あっさり改善されました。

......。

ひょっとしてTextureコンストラクタってMicrosoftの罠?

TextureLoaderは名前空間こそDirect3Dだけど、Direct3DXのアセンブリに入ってるのでどれか一つ参照に追加しないとならない。まあ一番古いのを使えばいいか。


今度はMP3に浮気中。MP3って、Ogg VorbisやFLACみたいにリファレンスエンコーダ/デコーダが無かったから敬遠していたけど、MP3ファイルを一々WAVEやOgg Vorbisに直してから使うの面倒くさいなあ、と思い始めて、いっその事対応する予定と相成りました。問題はどのデコーダを使うか......。エンコーダならLAMEが有名らしい事は知ってるんだけど、デコーダは何が有名なのかな。mpg123? メモリ上のデータをデコードするmpg123_decodeの仕様は気に入らないけど、代わりにread/seekを置き換えるmpg123_replace_reader使えば何とか綺麗にできそうだ。

このアーカイブについて

このページには、2008年2月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2007年7月です。

次のアーカイブは2008年7月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。