水曜日, 11月 04, 2009

JLayerとUbuntu 9.10のOpenJDK+PulseAudioの相性問題

 相変わらず「Tokyo Railwaysコンピュータ版」の障害対応をやってます。
 今日片付けた問題は、先日リリースされたばかりのUbuntu 9.10上で「Tokyo Railwaysコンピュータ版」の音がならなくなるという問題。先日アップした「Ubuntu 9.10にアップグレード」という記事の中でちょっとだけ触れた話です。
 この問題は実は調査した結果、私の環境では起きるけど、「Tokyo Railways コンピュータ版」をダウンロードしてそのままUbuntu 9.10で使っている分にはおそらく起きないだろうという結論になってます。私の環境とダウンロードしてそのままの環境で何が違うのかというと、私のUbuntu環境ではBGMにMP3ファイルを使っているが、ダウンロードしてそのままの環境ならBGMはMIDIファイルを使っているはずという点です。この障害はBGMにMP3を使って、Ubuntu 9.10の環境で「Tokyo Railways コンピュータ版」を実行しない限り発生しないと思います。なぜなら、この現象はMP3ファイルの再生に使っているオープンソースの「JLayer」というライブラリーの内部でBGMの切り替え時に発生してるからです。また、Ubuntu 9.04まではこの現象は発生していなかったので、Ubuntu 9.10とJLayerの相性に起因しているようです。
 調査した結果、問題の現象は以下の2つに分かれます。

 (1)MP3のBGMの切り替えが数秒間遅れることがある。
 (2)MP3のBGMの切り替え時に以後ずっと音がならなくなることがある。

 まず(1)を調べてみると、JLayerフレームワークに含まれているjavazoom.jl.player.Playerというクラスの、isComplete()というメソッドが通常の環境では即時復帰してくれるのに、Ubntu 9.10環境では10秒程度復帰しない場合があるというものでした。実は「Tokyo Railways コンピュータ版」のBGM再生処理では、isComplete()と500ミリ秒のスリープを繰り返しながらループしていて、BGMが完了したかどうかをisComplete()でチェックし、もし完了していたら再度再生することで、エンドレス再生を行ってます。また、isComplete()の直後のタイミングでBGMの停止や切り替えもチェックしています。だからisComplete()で待たされると、BGMの停止や切り替えも遅れるわけです。
 これの対策は比較的容易で、isComplete()を使用せずに自前でフラグを用意して曲の終了をチェックすれば解決です。なぜこれが容易かというと、実はPlayerクラスのplay()というメソッドでMP3を再生しているのですが、このメソッドが演奏が完了するまで復帰してくれないので、その対策として自前でスレッドを作って管理しているので、このスレッドの処理内でplay()の前後でフラグを制御すれば、演奏中かどうかは容易にチェックできるわけです。

 さて、残るは(2)だけなんですが、これの回避策を見つけるのはかなり面倒でした。(2)の問題はエラー出力に出てくるログを見て判断すると、BGMの演奏が終了するタイミングでPlayerクラスのclose()メソッドを使ってBGMを停止しようとすると、Ubuntu 9.10環境ではJLayerがOpenJDK経由でPulseAudioというLinux環境で使われてるサウンドサーバーにサウンドを停止させにいくようなんですが、Ubuntu 9.10環境のPulseAudioではこのタイミングで例外が発生するケースがあるようです。しかもその例外に対するJLayerの処理になにか問題がある様で、なんとまあclose()メソッドが永遠に復帰せずに、以降JLayer内部でなにか排他が開放されなくなる状態となり、結局ハングアップしてしまうようです。
 これは明らかにJLayerのPlayerクラスのclose()メソッドのバグなわけで、問題を回避するにはPlayerクラスのclose()を使わずにBGMを停止する必要があります。で、いろいろと試したのですが、結局うまく言った方法は、Playerクラスのインスタンスを生成する時に、コンストラクターに渡しているMP3ファイルのInputStreamを別途保存しておいて、BGMを止めたいときにはこのInputStreamのclose()を呼んで、MP3ファイルの読み込みを停止してしまえばよいみたいです。この方法で試してみたら、PulseAudioの例外は発生しなくなって、JLayerはハングアップしなくなりました。

 さて、この方法、確かにUbuntu 9.10では有効のようですが、他の環境では何か副作用は出ないのかと言う点がちょっと心配です。たぶん大丈夫だと思うのですが、原因が環境の相性問題だったんで、いろんな環境で一通りテストしてみる必要がありますねえ。
 ただ、いろんなの環境って言っても、いくつあるのだろうか。OSの種類とJavaの種類の組み合わせで考えると、簡単に10種類を越えちゃいますねえ。私が用意できる環境で数えると、11種類もある。うーん、まだまだ完全解決までにはちょっとかかりそうですねえ・・・

0 件のコメント: